diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 00000000000..73302834b68
--- /dev/null
+++ b/.github/codecov.yml
@@ -0,0 +1,8 @@
+coverage:
+ status:
+ project:
+ default:
+ target: 75%
+ patch:
+ default:
+ target: 75%
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 00000000000..cef046bf7b1
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,30 @@
+name: MarkBind Action
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install Graphviz
+ run: sudo apt-get install graphviz
+ - name: Install Java
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ - name: Build & Deploy MarkBind site
+ uses: MarkBind/markbind-action@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ rootDirectory: './docs'
+ baseUrl: '/tp' # assuming your repo name is tp
+ version: '^5.2.0'
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v4.0.1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ slug: AY2324S2-CS2103T-F12-1/tp
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 6ff220b5196..a3107a816e2 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -23,7 +23,7 @@ jobs:
- name: Run repository-wide tests
if: runner.os == 'Linux'
- working-directory: ${{ github.workspace }}/.github
+ working-directory: ${{ github.workspace }}/.github
run: ./run-checks.sh
- name: Validate Gradle Wrapper
@@ -39,7 +39,7 @@ jobs:
run: ./gradlew check coverage
- name: Upload coverage reports to Codecov
- if: runner.os == 'Linux'
- uses: codecov/codecov-action@v3
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ uses: codecov/codecov-action@v4.0.1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ slug: AY2324S2-CS2103T-F12-1/tp
diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..f2ea41f188e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@
/build/
src/main/resources/docs/
+# Other build/bin files
+/bin/
+
# IDEA files
/.idea/
/out/
@@ -21,3 +24,10 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+docs/_markbind/logs/
+
+# VSCode configuration files
+.vscode/
+
+# NetConnect exported CSV files
+*.csv
diff --git a/README.md b/README.md
index 13f5c77403f..8b8a2d99765 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,55 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![Java CI](https://github.com/AY2324S2-CS2103T-F12-1/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S2-CS2103T-F12-1/tp/actions/workflows/gradle.yml/badge.svg)
+[![codecov](https://codecov.io/gh/AY2324S2-CS2103T-F12-1/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2324S2-CS2103T-F12-1/tp)
+
+# NetConnect
+
+## Introduction
+
+NetConnect is a desktop app for food business managers managing contacts in SMEs, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). It enables managers to efficiently manage their employees, clients, as well as suppliers, **all in one place** ☝🏻.
![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)**.
+## Features
+
+1. Profile Management
+ * Add a new profile: Easily create a new profile for an employee, client, or supplier. You can choose to tag them with a custom tag.
+ * Delete or Edit a profile: Remove a profile from the list or Edit their information via their ID.
+
+
+2. Category Management
+ * Roles: Categorize your profiles into *Employees, Clients, or Suppliers*.
+ * Tailored profile information: Each of the roles have their own set of information to be filled in, e.g. Employees have the field `Department`, while Supplier have `Product`.
+
+3. Search. The searches can be stacked and allows you to find a profile based on the following criteria:
+ * Search by name: Find a profile by their name.
+ * Search by contact: Find a profile by their contact number.
+ * Search by tag: Find a profile by their tag.
+ * Search by role: Find a profile by their role.
+ * Search by remark: Find a profile by their remark.
+
+4. Related Profiles
+ * Relate profiles: Link profiles together to show their relationship.
+ * Unrelate profiles: Remove the relationship between two profiles.
+ * View related profiles: View all profiles related to a specific profile.
+
+5. Export
+ * Export profiles: Export all profiles to a CSV file.
+
+## User-Friendly Commands
+
+NetConnect uses simple CLI commands to make it easy for you to manage your contacts.
+
+## Warning System
+
+NetConnect will warn you if you are about to carry out a destructive action, and it requires your confirmation before execution. This ensures that you do not accidentally delete important information.
+
+## Error Handling
+
+With a robust error handling system, clear error and resolution messages will guide you through the process of managing your contacts seamlessly.
+
+## Getting Started
+To begin your journey with NetConnect, simply download the latest release from [here](https://github.com/AY2324S2-CS2103T-F12-1/tp/releases) and refer to the [User Guide](docs/UserGuide.md)
+for detailed guidance in using the application.
+
+For the detailed documentation of this project, see the **[NetConnect Website](https://ay2324s2-cs2103t-f12-1.github.io/tp/)**.
* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
diff --git a/build.gradle b/build.gradle
index a2951cc709e..d2897d194dc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,7 +66,11 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'netconnect.jar'
+}
+
+run {
+ enableAssertions = true
}
defaultTasks 'clean', 'test'
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index eb761a9b9a7..ddd9f168508 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -310,7 +310,7 @@
+
+
+
## Build automation
@@ -73,7 +76,7 @@ Any warnings or errors will be printed out to the console.
Here are the steps to create a new release.
-1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java).
+1. Update the version number in [`MainApp.java`](https://github.com/AY2324S2-CS2103T-F12-1/tp/tree/master/src/main/java/seedu/address/MainApp.java).
1. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`).
1. Tag the repo with the version number. e.g. `v0.1`
1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created.
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 1b56bb5d31b..afad5f707ac 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -1,15 +1,13 @@
---
-layout: page
-title: Developer Guide
+ layout: default.md
+ title: "Developer Guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
---------------------------------------------------------------------------------------------------------------------
-
-## **Acknowledgements**
+# NetConnect Developer Guide
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+
+
--------------------------------------------------------------------------------------------------------------------
@@ -21,14 +19,9 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
## **Design**
-
-
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
-
-
### Architecture
-
+
The ***Architecture Diagram*** given above explains the high-level design of the App.
@@ -36,7 +29,7 @@ Given below is a quick overview of main components and how they interact with ea
**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.
+**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/netconnect-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/netconnect-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.
@@ -51,9 +44,9 @@ The bulk of the app's work is done by the following four 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 i/1`.
-
+
Each of the four main components (also shown in the diagram above),
@@ -62,19 +55,19 @@ Each of the four main components (also shown in the diagram above),
For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
-
+
The sections below give more details of each component.
### UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/netconnect-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
-![Structure of the UI Component](images/UiClassDiagram.png)
+
The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
-The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/netconnect-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/netconnect-level3/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
@@ -85,22 +78,24 @@ The `UI` component,
### Logic component
-**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+**API** : [`Logic.java`](https://github.com/se-edu/netconnect-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
-
+
+
+The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete i/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)
+
-
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
-
+**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
+
How the `Logic` component works:
-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. When `Logic` is called upon to execute a command, it is passed to an `NetConnectParser` 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.
@@ -108,46 +103,58 @@ How the `Logic` component works:
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
-
+
How the parsing works:
-* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
+* When called upon to parse a user command, the `NetConnectParser` 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 `NetConnectParser` 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/netconnect-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+
+Here's a (partial) class diagram of the `Model` component:
+
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 the netconnect 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. This filtered list is further filtered by the Filter classes.
* 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.
+
-
+**Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `NetConnect`, which `Person` references. This allows `NetConnect` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
-
+
+
+
+Here are the other classes in `Model` (omitted from the class diagram above) that are used for filtering the list that is displayed to users:
+
+
+
+How the filtering works:
+* `ModelManager` stores only one instance of `Filter` at any one time. The stored `Filter` instance in turn stores all the `XYZPredicate` objects currently applied to the filtered view.
+* `FilteredList#setPredicate(...)` is called with the `Filter` instance, and only shows all `Person` objects that satisfy **all** predicates in `Filter`.
### Storage component
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+**API** : [`Storage.java`](https://github.com/se-edu/netconnect-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
-
+
The `Storage` component,
-* can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
-* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
+* can save both netconnect data and user preference data in JSON format, and read them back into corresponding objects.
+* it can also save the state of the command box in a file called `state.txt` in the data folder.
+* inherits from both `NetConnectStorage`, `UserPrefStorage` as well as `StateStorage`, 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.addressbook.commons` package.
+Classes used by multiple components are in the `seedu.netconnect.commons` package.
--------------------------------------------------------------------------------------------------------------------
@@ -155,95 +162,445 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
+### Export Feature
+
+#### Expected Behaviour
+
+The `export` command allows users to export contact data from NetConnect into a CSV file. Users have the option to specify a file name. If no file path is provided, the CSV file is saved as a default name. The command exhibits the following exceptional behaviors:
+
+* If the file name provided is invalid or inaccessible, an error message is displayed to the user.
+
+The successful execution of the `export` command results in the creation of a CSV file as the specified or default name at default location, containing all the contact data available in NetConnect.
+
+#### Current Implementation
+
+A `ExportCommand` instance is created in the `ExportCommandParser#parse(...)` method when the user issues the `export` command. The process involves the following steps:
+
+1. Parsing the command input to extract the file nqm3 if provided.
+2. If no filename is provided, setting the filename to a default value.
+3. Utilizing the `CsvExporter` component to write the data to the CSV file.
+
+The sequence diagram below illustrates the execution of a `ExportCommand`:
+
+
+
+The sequence diagram below illustrates the creation and execution of a `CsvExporter`:
+
+
+
+#### Design Considerations
+
+**Aspect: Handling of file paths in the `export` command:**
+
+* **Alternative 1 (Current Choice):** Always save the CSV file to a fixed, pre-defined location without user input.
+ * Pros: Simplifies the command's implementation by removing the need to parse and validate user-provided file paths.
+ * Cons: Reduces user flexibility in determining where the CSV file should be saved.
+* **Alternative 2:** Allow users to specify a file path, defaulting to a pre-defined location if not specified.
+ * Pros: Provides flexibility for users to save the CSV file wherever they prefer.
+ * Cons: Additional error handling is required to manage invalid or inaccessible file paths.
+
+### Person Roles Feature
+
+#### Overview
+
+The person can be categorized into three roles: `Client`, `Supplier`, and `Employee`. These classes extend the base `Person` class and encapsulate various role-specific functionalities and attributes, improving the application's ability to cater to a diverse range of user interactions.
+
+* **Client**: Represents a customer, associated with products and preferences.
+* **Supplier**: Represents a vendor, associated with products and terms of service.
+* **Employee**: Represents an employee, associated with a department, job title, and skills.
+
+#### Expected Behaviour
+
+The `Person` class is extended by three other classes, each with their own additional attributes:
+
+* `Client` Subclass contains `products` attribute of type `Products`, representing the products associated with the client, and contains `preferences` as a `String`, detailing client-specific preferences.
+
+* `Supplier` Subclass contains `products` attribute of type `Products`, which lists the items supplied, and holds `termsOfService` of type `TermsOfService`, outlining the agreement with the supplier.
+
+* `Employee` Subclass includes a `department` attribute of type `Department`, signifying the department the employee belongs to, has a `jobTitle` attribute of type `JobTitle`, representing the employee's official title, and features `skills` of type `Skills`, indicating the competencies of the employee.
+
+#### Implementation
+
+##### Class Additions
+
+* `Client`, `Supplier`, and `Employee` classes have been added, extending the `Person` class to include role-specific fields.
+* New classes `Department`, `JobTitle`, `Products`, `Skills`, and `TermsOfService` are introduced to encapsulate relevant attributes.
+
+##### Data Storage
+
+* The `JsonAdaptedPerson` class has been implemented to support the conversion to and from the new role-based classes, ensuring compatibility with the enhanced JSON schema.
+
+##### User Interface Enhancements
+
+* The UI has been enhanced to dynamically display optional fields based on the person's role, offering a tailored user experience.
+
+##### Command and Parser Modifications
+
+* Commands and parsers have been updated to recognize and process additional arguments related to the new person types.
+* Modified commands handle logic specific to each role, ensuring correct operation based on person type.
+
+#### Usage Scenario
+
+1. An administrator decides to add a new `Employee` to the system.
+2. The administrator inputs the employee's details, including department, job title, and skills.
+3. The system processes the input, updates the data storage, and the UI reflects the new employee's information, displaying department and job title.
+
+#### Design Considerations
+
+**Aspect: How to distinguish the person role:**
+
+* **Alternative 1 (current choice)**: Creation of subclasses `Supplier`, `Employee`, and `Client` that inherit from the `Person` class.
+ * **Pros**:
+ 1. Specialization: Allows for clear role-specific methods and properties, ensuring that objects have only the attributes and behaviors pertinent to their role.
+ 1. Extensibility: Easier to add new roles by creating additional subclasses.
+ 1. Polymorphism: Enables the use of a single interface to represent any person type, which simplifies code that interacts with these objects.
+ * **Cons**:
+ 1. Complexity: More complex class hierarchy which can become difficult to manage with a large number of roles.
+ 1. Redundancy: Potential for redundant code in subclasses if there is significant overlap in behavior or data.
+ 1. Rigid Hierarchy: Changing the class hierarchy can be challenging if the differentiation between roles changes over time.
+
+* **Alternative 2**: Introducing a `type` field within the `Person` class to indicate the person's role and including all possible fields for all types.
+ * **Pros**:
+ 1. Simplicity: A flat structure with a single `Person` class could simplify the system.
+ 1. Flexibility: Easy to change a person’s role by updating the type field without the need to instantiate a new object.
+ 1. Single Table Storage: All person records can be stored in a single database table, which can simplify CRUD operations.
+ * **Cons**:
+ 1. Data Sparseness: The `Person` object will have redundant fields that are not applicable to all types, leading to wasted storage space and potential confusion.
+ 1. Increased Conditionals: The code will require conditional logic to handle behavior specific to each role, which can make the code more complex and harder to maintain.
+ 1. Loss of Type Safety: Without distinct classes, it's easier to mistakenly assign the wrong attributes or behaviors to a person object.
+
+
+
+### Save state feature
+
+The save state feature is implemented using the `TextStateStorage` which implements `StateStorage` interface. It is responsible for saving the state of the command box. The state is saved in a file called `state.txt` in the data folder. The state is updated at each change in the input. Additionally, it implements the following operations:
+
+* `TextStateStorage#saveState(String state)` — Saves the current state of the command box into file.
+* `TextStateStorage#readState()` — Reads the saved state of the command box from the file and returns the string.
+* `TextStateStorage#clearState()` — Clears the file storing the states.
+* `TextStateStorage#isStateStorageExists()` — Checks if the file storing the states exists.
+* `TextStateStorage#deleteStateStorage()` — Deletes the file storing the states.
+* `TextStateStorage#getStateStorageFilePath()` — Returns the file path of the file storing the states.
+* `TextStateStorage#getFilePathString()` — Returns the file path of the file storing the states as a string.
+* `TextStateStorage#getDirectoryPath()` — Returns the directory path of the file storing the states.
+
+Given below is an example usage scenario and how the save state feature behaves at each step.
+
+Step 1. The user launches the application. During set up, the presence of the state storage file is checked. If absent, a new storage file is created. When the command box is instantiated, it calls the `TextStateStorage#readState()` method to get the last command that was present in the command box before it was last closed. The text in the file is retrieved and loaded into the command box using `TextField#setText`.
+
+**Note:** If the storage file is not found a new empty file is created.
+
+Step 2. The user changes the input in the command box. The `TextStateStorage#saveState(String state)` method is called to save the current state of the command box into the file.
+
+
+
+
+#### Design considerations:
+
+**Aspect: How save state executes:**
+
+* **Alternative 1 (current choice):** Update the storage file at every change in input.
+ * Pros: Lower risk of data loss.
+ * Cons: Constantly updating the storage file with every change in input may introduce performance overhead.
+
+* **Alternative 2:** Update the storage file only when the application is closed.
+ * Pros: Reduces the number of writes to the storage file, reducing performance overhead.
+ * Cons: Does not save the state of the command box in case of a crash.
+
+* **Alternative 3:** Update the storage file when there is a pause in typing.
+ * Pros: Reduces the number of writes to the storage file, reducing performance overhead.
+ * Cons: May not save the state of the command box in case of a crash.
+
+### Relate feature
+
+#### Expected behaviour
+
+The `relate` command allows users to relate two persons via their unique `ID`. Exceptional behavior:
+* If the `ID` provided by user does not exist, an error message is displayed.
+* If the `ID` provided is not an integer value that is more than 0, an error message is displayed.
+
+
+
+#### Current implementation
+Given a command `relate i/1 i/2`, the `NetConnectParser` recognises the `relate` command and first instantiates a `RelateCommandParser` object. It then passes the command string into `RelateCommandParser#parse(...)`, where the input `i/1` and `i/2` is validated for its format. Following which, `RelateCommandParser` instantiates a `RelateCommand` object.
+
+The`RelateCommand` object extends the `Command` interface, and hence contains a method called `execute(...)`, which takes in a `model`. A model can be thought of as a container for the application's data, and it also controls the exact contact list that the user will see. In the `execute(...)` command, we validate that NetConnect has both IDs `1` and `2` and, does not already have a relation. We add it to our RelatedList storage if both are true. To change our view and to ratify the successful command, we will have to change the view the user sees by instantiating a predicate called `IdContainsDigitsPredicate`. We will then pass it into the `model#stackFilters(predicate)` method in the `model` object to update the filtered list of persons to only include the two people with ID `1` and `2`.
+
+Recalling that we also have a message box to inform the result of the actions taken (in prose form), the `RelateCommand#execute(...)` method will also return a `CommandResult` object, which contains the summary of the number of people listed.
+
+#### Design considerations
+
+**Aspect: How to store relations between contacts**
+
+* **Alternative 1 (current choice)**: Store the Related List within the NetConnect model as a JSON file. Each time a relate command is done, we will just have to save the NetConnect model.
+ * Pros: This approach requires a single command for saving and loading and does not violate encapsulation of classes. Saving and loading does not have to be exposed to wider classes and done within the NetConnect model interface.
+ * Cons: JSON files can be difficult to amend and maintain.
+
+* **Alternative 2 (previously implemented)**: Store the Related List as a .txt file.
+ * Pros: Easier to edit, and implement.
+ * Cons: Harder to maintain as there will be multiple files to be used by our application. Save and load implementation is also exposed outside the NetConnect model.
+
+* **Alternative 3**: Store Relations as another field in every person. A relate command would add the opposing contact to both persons provided.
+ * Pros: Easy to understand as a user. Querying of contacts will also be fast as the relations are stored within the same contact.
+ * Cons: Unnecessary to user, and complicates UI. Also has a higher potential for bugs given that the entire contact list has to be searched and updated each time a relation is added and subsequently removed.
+
+### ShowRelated feature
+
+#### Expected behaviour
+
+The `showrelated` command allows users to view all persons related to a specific person via their unique `ID`. Exceptional behavior:
+* If there are multiple `ID` provided by user, an error message is displayed.
+* If the `ID` provided is not an integer value that is more than 0, an error message is displayed.
+
+
+
+#### Current implementation
+Given a command `showrelated i/1`, the `NetConnectParser` recognises the `showrelated` command and first instantiates a `ShowRelatedCommandParser` object. It then passes the command string into `ShowRelatedCommandParser#parse(...)`, where the input `i/1` is validated for its format. Following which, `ShowRelatedCommandParser` instantiates a `ShowRelatedCommand` object.
+
+The`ShowRelatedCommand` object extends the `Command` interface, and hence contains a method called `execute(...)`, which takes in a `model`. A model can be thought of as a container for the application's data, and it also controls the exact contact list that the user will see. In the `execute(...)` command, we extract all the tuples that contain ID `1` and use it to instantiate a predicate called `IdContainsDigitsPredicate` which extracts all the `ID` of the related profiles (excluding itself of ID `1`). We will then pass it into the `model#stackFilters(predicate)` method in the `model` object to update the filtered list of persons to only include persons related to the person with ID of `1`.
+
+Recalling that we also have a message box to inform the result of the actions taken (in prose form), the `ShowRelatedCommand#execute(...)` method will also return a `CommandResult` object, which contains the summary of the number of people listed.
+
+#### Design considerations
+
+**Aspect: How to extract the IDs of the related profiles**
+
+* **Alternative 1 (current choice)**: Extract all the tuples from storage and use regular expressions to check if either of the `ID` in the tuple is the specified `ID`, then extract the `ID` of the other related profile.
+ * Pros: This approach is straightforward and uses existing methods to extract the related profiles.
+ * Cons: Regular expressions can be difficult to understand and maintain.
+
+* **Alternative 2**: Extract only tuples that contain the specified `ID`, then extract the `ID` of the other related profile.
+ * Pros: Smaller chunk of data is extracted, reducing the amount of data to be processed as data is filtered out during read from storage.
+ * Cons: More complex implementation as the data is filtered out during read from storage, requiring filtering with String data instead of as IdTuple.
+
+### Unrelate feature
+
+#### Expected behaviour
+
+The `unrelate` command allows users to unrelate two persons via their unique `ID`. Exceptional behavior:
+* If the `ID` provided by user does not exist, an error message is displayed.
+* If the `ID` provided is not an integer value that is more than 0, an error message is displayed.
+
+
+
+#### Current implementation
+Given a command `unrelate i/1 i/2`, the `NetConnectParser` recognises the `unrelate` command and first instantiates an `UnrelateCommandParser` object. It then passes the command string into `UnrelateCommandParser#parse(...)`, where the input `i/1` and `i/2` is validated for its format. Following which, `UnrelateCommandParser` instantiates an `UnrelateCommand` object.
+
+The`UnrelateCommand` object extends the `Command` interface, and hence contains a method called `execute(...)`, which takes in a `model`. A model can be thought of as a container for the application's data, and it also controls the exact contact list that the user will see. In the `execute(...)` command, we validate that NetConnect has both IDs `1` and `2` and already has a relation. We remove it from our RelatedList stored in NetConnect if both are true. To change our view and to ratify the successful user command, we will have to change the view the user sees by instantiating a predicate called `IdContainsDigitsPredicate`. We will then pass it into the `model#stackFilters(predicate)` method in the `model` object to update the filtered list of persons to only show the two people who have just been unrelated with the ID `1` and `2`.
+
+Recalling that we also have a message box to inform the result of the actions taken (in prose form), the `UnrelateCommand#execute(...)` method will also return a `CommandResult` object, which contains the id of the two people, in a 1relates2 format..
+
+#### Design considerations
+
+**Aspect: What kind of unrelation will be done using the two IDs**
+
+* **Alternative 1 (current choice)**: The two IDs given will be used to remove that specific relation.
+ * Pros: This approach would allow the user the greatest flexibility in removing relations between contacts.
+ * Cons: User may think that it would delete all relations from each contact.
+
+* **Alternative 2**: The two IDs given will be used to remove all relations for the two contacts.
+ * Pros: Can help with resetting of all relations for the contact quickly.
+ * Cons: Since the showrelated feature can show the user which relations for the contact already exists, manual and more precise unrelation has more value. This multi-field input for unrelate may confuse users based on the existing relate command usage.
+
+### Delete feature
+
+#### Expected behaviour
+
+The `delete` command allows users to delete `Person` from NetConnect using either the target `Person`'s `Id` or `Name`. `Id` allows users to directly and accurately delete the `Person` if the `Id` is known, while `Name` provides the flexibility to delete using name if `Id` is not known. Exceptional behaviour:
+* If both `Id` and `Name` is provided, an error message is shown.
+* If there are no `Person`s with the given `Name` or `Id`, the display is updated to show all `Person`s, and an error message is shown.
+
+
+
+* If `Name` is provided but there are more than one `Person` with the specified `Name` in NetConnect, all `Person`s with the matching `Name` will be displayed, and user will be prompted to re-input the `delete` command using `Id` instead.
+
+
+
+`delete` can be done regardless of whether the target `Person` is in the current displayed list. If the `Person` is in the current displayed list, the display view does not change upon successful delete. If the `Person` is in not in the current displayed list, the display view is changed to display all `Person`s upon successful delete.
+
+
+
+#### Current implementation
+
+A `DeleteCommand` instance is instantiated in `DeleteCommandParser#parse(...)` by the factory methods `DeleteCommand#byId(Id)` or `DeleteComamnd#byName(Name)`. The sequence diagram below shows the creation of a `DeleteCommand` with `Id`. The process is similar for `DeleteCommand` with `Name`.
+
+
+
+
+
+**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
+
+
+Deletion of `Person` from NetConnect is facilitated by `Model#getPersonById(Id)` or `Model#getPersonByName(Name)`, and `Model#deletePerson(Person))`. The sequence diagram below shows the execution of a `DeleteCommand` with `Id`. The process is similar for `DeleteCommand` with `Name`.
+
+
+
+#### Design considerations
+
+**Aspect: How delete command executes:**
+
+* **Alternative 1 (current choice):** A single `Model#deletePerson(Person)` method.
+ * Pros:
+ 1. Implementation of `Model#getPersonById(Id)` and `Model#getPersonByName(Name)` can be reused for other purposes.
+ 1. Simple implementation of `Model#deletePerson(Person)`.
+ * Cons: Need to ensure `Person` with matching `Id` or `Name` exists in the list before `Model#getPersonById(Id)`, `Model#getPersonByName(Name)` and `Model#deletePerson(Person)` are called.
+
+* **Alternative 2:** Separate methods of `Model#deletePersonById(Id)` and `Model#deletePersonByName(Name)`.
+ * Pros: Simple implementation of `DeleteCommand#execute(...)`.
+ * Cons:
+ 1. Presence checks required in `Model#deletePersonById(Id)` and `Model#deletePersonByName(Name)`.
+ 1. More boilerplate code since `Model#deletePersonById(Id)` and `Model#deletePersonByName(Name)` are very similar.
+
+### Unique `Id` of `Person`
+
+The unique id of `Person` is stored as a private field `Id` instance in `Person`. `Id` value is enforced to be unique between each `Person` by keeping the constructors of the `Id` class private, and by using a private static field `nextId`. The `Id` class provides 3 factory methods to instantiate `Id`:
+
+* `Id#generateNextId()` — instantiates an `Id` object with the next available value given by `nextId`.
+* `Id#generateId(int)` — instantiates an `Id` object with the given value, and updates nextId to be the given value + 1. This method is necessary to update `nextId` while keeping the `Id` value of each `Person` the same between different runs of the application.
+* `Id#generateTempId(int)` — instantiates an `Id` object with the given value, without changing the value of `nextId`. This method is used for non-`add` NetConnect commands that accepts id as an argument. This method is necessary as using `Id#generateId(int)` in non-`add` commands will cause `nextId` to be updated, even if there are no persons in the NetConnect using the previous `Id`. Example:
+ 1. NetConnect has persons with `Id` 1 to 5.
+ 1. User inputs `delete i/1000` command, where `Id#generateId(int)` is used.
+ 1. `nextId` is updated to be 1001.
+ 1. On the next `Id#generateNextId()`, `Id` value 1001 will be used although values 6 to 1000 are not used.
+
+
+
+Operations with `Id` on `Person` in NetConnect is facilitated through `Model#hasId(Id)` and `Model#getPersonById(Id)`.
+
+### `Find` feature
+
+#### Expected behaviour
+The `find` command allows users to filter the display to show `Person`s from NetConnect with fields matching certain values. The command allows finding by name, phone number, tag, role, remark. Parameters provided are subjected to its respective validity checks, and mentions of these checks will be omitted in this section.
+
+#### Current implementation
+
+The execution of `find` is facilitated by `Model#clearFilter()` and `Model#stackFilters(NetConnectPredicate)`. `Model` uses the `Filter` classes in the `Model` component to facilitate the implementation. `find` by each field uses its respective `XYZPredicate`:
+* `find` by name uses `NameContainsKeywordsPredicate`
+* `find` by tag uses `TagsContainsKeywordsPredicate`
+* `find` by phone number uses `PhoneMatchesDigitsPredicate`
+* `find` by role uses `RoleMatchesKeywordsPredicate`
+* `find` by remark uses `RemarkContainsKeywordsPredicate`
+
+The sequence diagram below shows the parsing of a `find n/John` command. The process is similar for `find` command with other parameters.
+
+
+
+
+
+**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
+
+
+The sequence diagram below shows the execution of the `find` command created from a `find n/John` command.
+
+
+
+The `list` command, or any other command that alters the displayed view can be used to clear all existing filters. In these cases, the stored `Filter` object in `ModelManager` will be set to a `Filter` containing no predicates.
+
+#### Design considerations
+
+**Aspect: How the view of the displayed list is filtered**
+
+* **Alternative 1 (current choice)**: Create a new `Filter` class to store all the predicates.
+ * Pros: Allows retrieval of current predicates, and can be displayed to user.
+ * Cons: Require an additional private field in `ModelManager`.
+
+* **Alternative 2**: Stack the predicates using `FilteredList#setPredicate(...)`, `FilteredList#getPredicate()` and `Predicate#and(...), without explicitly storing the predicates.
+ * Pros: Simple implementation without additional classes or fields.
+ * Cons: Unable to retrieve the current predicates applied and hence unable to display to users the current filters applied.
+
### \[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 `VersionedNetConnect`. It extends `NetConnect` with an undo/redo history, stored internally as an `netConnectStateList` 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.
+* `VersionedNetConnect#commit()` — Saves the current netconnect state in its history.
+* `VersionedNetConnect#undo()` — Restores the previous netconnect state from its history.
+* `VersionedNetConnect#redo()` — Restores a previously undone netconnect 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#commitNetConnect()`, `Model#undoNetConnect()` and `Model#redoNetConnect()` 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 `VersionedNetConnect` will be initialized with the initial netconnect state, and the `currentStatePointer` pointing to that single netconnect state.
+
+
+
+Step 2. The user executes `delete 5` command to delete the 5th person in the netconnect. The `delete` command calls `Model#commitNetConnect()`, causing the modified state of the netconnect after the `delete 5` command executes to be saved in the `netConnectStateList`, and the `currentStatePointer` is shifted to the newly inserted netconnect state.
+
+
-![UndoRedoState0](images/UndoRedoState0.png)
+Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitNetConnect()`, causing another modified netconnect state to be saved into the `netConnectStateList`.
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+
-![UndoRedoState1](images/UndoRedoState1.png)
+
-Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+**Note:** If a command fails its execution, it will not call `Model#commitNetConnect()`, so the netconnect state will not be saved into the `netConnectStateList`.
-![UndoRedoState2](images/UndoRedoState2.png)
+
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+Step 4. The 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#undoNetConnect()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous netconnect state, and restores the netconnect to that state.
-
+
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
-![UndoRedoState3](images/UndoRedoState3.png)
+
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
+**Note:** If the `currentStatePointer` is at index 0, pointing to the initial NetConnect state, then there are no previous NetConnect states to restore. The `undo` command uses `Model#canUndoNetConnect()` to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
-
+
The following sequence diagram shows how an undo operation goes through the `Logic` component:
-![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png)
+
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
-
+**Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+
Similarly, how an undo operation goes through the `Model` component is shown below:
-![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png)
+
+
+The `redo` command does the opposite — it calls `Model#redoNetConnect()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the netconnect 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.
+**Note:** If the `currentStatePointer` is at index `netConnectStateList.size() - 1`, pointing to the latest netconnect state, then there are no undone NetConnect states to restore. The `redo` command uses `Model#canRedoNetConnect()` 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 netconnect, such as `list`, will usually not call `Model#commitNetConnect()`, `Model#undoNetConnect()` or `Model#redoNetConnect()`. Thus, the `netConnectStateList` 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#commitNetConnect()`. Since the `currentStatePointer` is not pointing at the end of the `netConnectStateList`, all netconnect states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
-![UndoRedoState5](images/UndoRedoState5.png)
+
The following activity diagram summarizes what happens when a user executes a new command:
-
+
-#### Design considerations:
+#### Design considerations
**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 netconnect.
+ * Pros: Easy to implement.
+ * Cons: May have performance issues in terms of memory usage.
* **Alternative 2:** Individual command knows how to undo/redo by
itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+ * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
+ * Cons: We must ensure that the implementation of each individual command are correct.
_{more aspects and alternatives to be added}_
-### \[Proposed\] Data archiving
-
-_{Explain here how the data archiving feature will be implemented}_
-
-
--------------------------------------------------------------------------------------------------------------------
## **Documentation, logging, testing, configuration, dev-ops**
@@ -256,77 +613,407 @@ _{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
+## **Appendix: Planned Enhancements**
+
+Team size: 5
+
+1. **Make `showrelated` command output a specific error message when an `ID` that does not exist is provided:** Currently, when input is `showrelated i/10` but there is no `Person` with `ID` 10, the command succeeds and outputs the message "0 persons listed!". This is not the expected result, as the command should fail with an appropriate error message. We plan to handle this erroneous input, and show the error message: "There is no person with id 10" for the above command.
+1. **Make `relate` command success message more specific:** The current message for a successful `relate i/1 i/2` command only shows "2 persons listed!", which is too general and might not catch user's typos or mistakes if they wanted to do `relate i/1 i/3 instead`. We plan to make the success message output the two persons that are involved in the `relate` command: "The relation between these two persons have been created: `Alex Yeoh, Phone: 87438807, Email:alexyeoh@example.com` and `Bernice Yu, Phone: 99272758, Email: berniceyu@example.com`".
+1. **Fix input validation for `relate` and `unrelate` command:** Currently, the commands `relate i/ 1 i/ 2 `, `relate i/1 2 i/3`, `relate i/1 i/2 tag/friends`, or `relate i/a 1 i/2` will fail with the following error message: "Invalid command format! ...". This is not consistent with the other commands that ignores additional whitespaces provided to arguments, invalid prefixes provided, or invalid id. We plan to accept values with trailing and leading whitespaces in `relate` and `unrelate` command, and output specific error message for invalid prefixes and invalid id.
+1. **Enhance UI to wrap long text:** Current implementation of NetConnect GUI is only able to accommodate input of approximately 120 characters for the profile fields (name, tags, etc.) in fullscreen mode. Additional text are represented by ellipsis. We plan to enhance the UI to wrap text responsively according to the screen size in the GUI and input length to prevent truncation of text.
+1. **Make `edit` command output a specific error message when the given values of fields are the same as existing ones:** Currently, when input is `edit i/1 n/Alex` but `Person` with `ID` 1 already has the name `Alex`, the command succeeds and outputs the message: "Edited Person: ..." although no values were actually changed since the same value is provided. We plan to handle this case where same values are given, making the command fail and show the error message: "There are no changes to values provided".
+1. **Add soft delete and data recovery feature:** Currently, our id is used in a strictly increasing order, i.e. after deletion of person with `ID` 6, there will be no other persons allowed to have `ID` 6 in the same run of the app. The rationale for this implementation is to facilitate the feature of soft delete and data recovery. Soft delete means that we will not completely remove the deleted `Person` and the details from NetConnect, but only mark it as deleted and not display it or use in any other commands. Soft delete will facilitate the data recovery feature, for user to recover the details of a deleted `Person` in the event of accidental deletion.
+1. **Make `unrelate` command success message more user-friendly:** The current success message for the command `unrelate i/1 i/2` shows "Unrelated the following persons: 1relates2", which is not very user friendly. We plan to change the command success message to show: "Unrelated the following persons: `Alex Yeoh, Phone: 87438807, Email:alexyeoh@example.com` and `Bernice Yu, Phone: 99272758, Email: berniceyu@example.com`"
+
## **Appendix: Requirements**
### Product scope
**Target user profile**:
-* has a need to manage a significant number of contacts
+Experienced managers who:
+* oversees contact relations with suppliers, customers and employees
+* has to manage a significant number of contacts
+* works independently in a supervisory role
+* has average IT skills
* prefer desktop apps over other types
* can type fast
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: provides managers a platform to manage employee, client and partner contact information
+easily, and to keep track of past interactions.
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| Priority | As a … | I want to … | So that I can… |
+|----------|------------------|----------------------------------------------|----------------------------------------------------------------------|
+| `* * *` | occasional user | add a new person | |
+| `* * *` | occasional user | delete a person | remove entries that I no longer need |
+| `* * *` | occasional user | find a person by name | quickly access their contact details |
+| `* * *` | occasional user | find a person by contact number | see who contacted me |
+| `* * *` | occasional user | find a person by remark | view all people with a particular remark |
+| `* * *` | occasional user | find a person by role | view all roles separately |
+| `* * *` | occasional user | tag roles to members | see distinctions and manage using roles |
+| `* *` | occasional user | go back to the state from where i left off | avoid going back to the same page/state when I close the application |
+| `* *` | occasional user | edit person information | refer to accurate personal information in the future |
+| `* *` | experienced user | export contact lists to a CSV file | create backups or use the data in other applications |
+| `* *` | experienced user | relate two profiles together | connect two contacts together |
+| `* *` | experienced user | view which contacts are related to a profile | assign tasks to my employees |
+| `* *` | experienced user | unrelate two profiles | disconnect two contacts |
+| `* *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
+| `* *` | new user | clear all existing contacts | populate with my actual contacts |
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `NetConnect` and the **Actor** is the `user`, unless specified otherwise)
-**Use case: Delete a person**
+**Use case: UC01 - List All Persons**
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to list all persons.
+2. NetConnect shows the list of all persons.
Use case ends.
**Extensions**
-* 2a. The list is empty.
+* 2a. The list of all persons is empty.
Use case ends.
-* 3a. The given index is invalid.
+**Use case: UC02 - Find a List of Persons by Name**
+
+**MSS**
+
+1. User requests for the list of persons matching a name.
+2. NetConnect shows a list of persons with matching name.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. There are no persons with a matching name.
+
+ Use case ends.
+
+**Use case: UC03 - Find a Specific Person by Contact Number**
+
+**MSS**
+
+1. User requests for the person with the matching contact number.
+2. NetConnect shows the person with matching contact number.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. There is no person with a matching number.
+
+ Use case ends.
+
+**Use case: UC04 - Find a Specific Person by Role**
+
+**MSS**
- * 3a1. AddressBook shows an error message.
+1. User requests for the person with the matching role.
+2. NetConnect shows the person with matching role.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. There is no person with a matching role.
+
+ Use case ends.
+
+**Use case: UC05 - Find a Specific Person by Remark**
+
+**MSS**
+
+1. User requests for the person with the matching remark.
+2. NetConnect shows the person with matching remark.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. There is no person with a matching number.
+
+ Use case ends.
+
+**Use case: UC06 - Add a New Person**
+
+**MSS**
+
+1. User requests to add a new person with given information.
+2. NetConnect adds a new person to the list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Some given arguments are invalid.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+**Use case: UC07 - Delete a Person by UID**
+
+**MSS**
+
+1. User requests to delete a specific person by UID.
+2. NetConnect deletes the person.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. There is no person with the provided UID.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+**Use case: UC08 - Delete a Person by Name**
+
+**MSS**
+
+1. User requests to delete a specific person by name.
+2. NetConnect deletes the person.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. There are no persons with the provided name.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. There are more than one person with the provided name.
+
+ * 1b1. NetConnect !!list the persons with matching name (UC2)!!.
+ * 1b2. User selects an UID from the list.
+
+1. User requests for the list of persons matching a name.
+2. NetConnect shows a list of persons with matching name.
+
+**Use case: UC09 - Tag a Person by UID with Custom Tag**
+
+**MSS**
+
+1. User requests to tag a specific person by UID with a custom tag.
+2. NetConnect tags the person with given custom tag.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. There is no person with the provided UID.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+**Use case: UC10 - Tag a Person by Name with Custom Tag**
+
+**MSS**
+
+1. User requests to tag a specific person by name with a custom tag.
+2. NetConnect tags the person with given custom tag.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. There are no persons with the provided name.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. There are more than one person with the provided name.
+
+ * 1b1. NetConnect !!list the persons with matching name (UC2)!!.
+ * 1b2. User selects an UID from the list.
Use case resumes at step 2.
-*{More to be added}*
+**Use case: UC11 - Edit Person Information by UID**
-### Non-Functional Requirements
+**MSS**
+
+1. User requests to edit the information of a specific person by UID.
+2. NetConnect edit the person information.
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+ Use case ends.
-*{More to be added}*
+**Extensions**
+
+* 1a. There is no person with the provided UID.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. Some given arguments are invalid.
+
+ * 1b1. NetConnect shows an error message.
+
+ Use case ends.
+
+**Use case: UC12 - Export Contact List to CSV File**
+
+**MSS**
+
+1. User requests to export contact list to CSV file with a given filename.
+2. NetConnect creates a CSV file with the contact list data.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given filename is invalid.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. The contact list is empty.
+
+ * 1b1. NetConnect shows an error message.
+
+ Use case ends.
+
+**Use case: UC13 - Relate two contacts together**
+
+**MSS**
+
+1. User requests to relate Contact A with Contact B.
+2. NetConnect adds a relation between the two contacts.
+
+**Extensions**
+
+* 1a. The given ID does not exist.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. The contact list is empty.
+
+ * 1b1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1c. There is an ambiguous command.
+
+ * 1c1. NetConnect shows an error message requesting to fix ambiguity.
+
+ Use case ends.
+
+* 1c. User relates contact to the same contact.
+
+ * 1c1. NetConnect shows an error message.
+
+ Use case ends.
+
+ Use case ends.
+
+**Use case: UC14 - View all contacts related to a single contact**
+
+**MSS**
+
+1. User requests to see all contacts related to Contact A.
+2. NetConnect shows the list of related contacts.
+
+**Extensions**
+
+* 1a. The given ID does not exist.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. The contact list is empty.
+
+ * 1b1. NetConnect shows an error message.
+
+ Use case ends.
+
+ Use case ends.
+
+**Use case: UC15 - Unrelate two contacts**
+
+**MSS**
+
+1. User requests to unrelate Contact A with Contact B.
+2. NetConnect removes the relation between the two contacts.
+
+**Extensions**
+
+* 1a. The given ID does not exist.
+
+ * 1a1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1b. The contact list is empty.
+
+ * 1b1. NetConnect shows an error message.
+
+ Use case ends.
+
+* 1c. There is an ambiguous command.
+
+ * 1c1. NetConnect shows an error message requesting to fix ambiguity.
+
+ Use case ends.
+
+* 1c. User unrelates contact to the same contact.
+
+ * 1c1. NetConnect shows an error message.
+
+ Use case ends.
+
+ Use case ends.
+
+**Use case: UC16 - Clear all contact list**
+
+**MSS**
+
+1. User requests to clear all contacts.
+2. NetConnect requests confirmation from user.
+3. User confirms the request.
+4. NetConnect deletes the entire contact list.
+
+ Use case ends.
+
+### Non-Functional Requirements
+
+1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+2. Should be able to hold up to 500 _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. All user operations (excluding export to CSV) should complete within 200 milliseconds.
+5. The codebase should follow a [given set of coding style](https://se-education.org/guides/conventions/java/intermediate.html) and should be well documented.
+6. Should provide friendlier syntax and command _aliases_ for advanced users to complete tasks quicker.
+7. Should display clear error messages for invalid inputs and failed operations, stating the correct command format or inputs required.
### Glossary
* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Persons**: Any person in the contact list. Includes all employees, clients and partners.
+* **State**: The page that displays the results from the last command given.
+* **UID**: A unique numerical identifier for any person added in NetConnect.
+* **CSV file**: A plain text file format that uses commas to separate values, and newlines to separate records.
+* **Alias**: A shortcut name/format for commands.
--------------------------------------------------------------------------------------------------------------------
@@ -334,10 +1021,12 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Given below are instructions to test the app manually.
-
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
+
+
+**Note:** These instructions only provide a starting point for testers to work on;
testers are expected to do more *exploratory* testing.
-
+
### Launch and shutdown
@@ -354,29 +1043,43 @@ testers are expected to do more *exploratory* testing.
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 … }_
-
### Deleting a person
1. Deleting a person while all persons are being shown
1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Test case: `delete i/1`
+ Expected: Contact with id 1 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 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: No person is deleted. Error details shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect delete commands to try: `delete`, `delete i/x`, `...` (where no persons have id x)
+ Expected: Similar to previous.
+
+ 1. Guarantees: Person with the specified id will be deleted from NetConnect and removed from the displayed list.
+
+1. Deleting a person while displayed list is filtered
- 1. Test case: `delete 0`
+ 1. Prerequisites: Filter displayed list using `find` command or one of its variants. Partial contact list displayed.
+
+ 1. Test case: `delete i/x` (where x is the id of person displayed in the list)
+ Expected: Contact with id x 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 i/y` (where y is the id of a person **not** displayed in the list)
+ Expected: Contact with id x is deleted from NetConnect, and displayed list displays the full list of persons in NetConnect (without the person with id y. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+
+ 1. Test case: `delete 1`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Other incorrect delete commands to try: `delete`, `delete i/z`, `...` (where no persons have id z)
Expected: Similar to previous.
-1. _{ more test cases … }_
+ 1. Guarantees: Person with the specified id will be deleted from NetConnect and removed from the displayed list. Full unfiltered list of persons will be displayed (similar to when `list` command is entered).
### Saving data
1. Dealing with missing/corrupted data files
1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-
-1. _{ more test cases … }_
diff --git a/docs/Documentation.md b/docs/Documentation.md
index 3e68ea364e7..082e652d947 100644
--- a/docs/Documentation.md
+++ b/docs/Documentation.md
@@ -1,29 +1,21 @@
---
-layout: page
-title: Documentation guide
+ layout: default.md
+ title: "Documentation guide"
+ pageNav: 3
---
-**Setting up and maintaining the project website:**
-
-* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation.
-* The `docs/` folder is used for documentation.
-* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html).
-* Note these points when adapting the documentation to a different project/product:
- * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar.
- * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format).
-* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping)
+# Documentation Guide
+* We use [**MarkBind**](https://markbind.org/) to manage documentation.
+* The `docs/` folder contains the source files for the documentation website.
+* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation.
**Style guidance:**
* Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style).
+* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html).
-* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html)
-
-**Diagrams:**
-
-* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html)
-**Converting a document to the PDF format:**
+**Converting to PDF**
-* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html)
+* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html).
diff --git a/docs/Gemfile b/docs/Gemfile
deleted file mode 100644
index c8385d85874..00000000000
--- a/docs/Gemfile
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
-
-gem 'jekyll'
-gem 'github-pages', group: :jekyll_plugins
-gem 'wdm', '~> 0.1.0' if Gem.win_platform?
-gem 'webrick'
diff --git a/docs/Logging.md b/docs/Logging.md
index 5e4fb9bc217..589644ad5c6 100644
--- a/docs/Logging.md
+++ b/docs/Logging.md
@@ -1,8 +1,10 @@
---
-layout: page
-title: Logging guide
+ layout: default.md
+ title: "Logging guide"
---
+# Logging guide
+
* We are using `java.util.logging` package for logging.
* The `LogsCenter` class is used to manage the logging levels and logging destinations.
* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..f68fc28f6b7 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -1,27 +1,33 @@
---
-layout: page
-title: Setting up and getting started
+ layout: default.md
+ title: "Setting up and getting started"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Setting up and getting started
+
+
--------------------------------------------------------------------------------------------------------------------
## Setting up the project in your computer
-
:exclamation: **Caution:**
+
+**Caution:**
Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps.
-
+
First, **fork** this repo, and **clone** the fork into your computer.
If you plan to use Intellij IDEA (highly recommended):
+
1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**.
-1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
+1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
+
+ Note: Importing a Gradle project is slightly different from importing a normal Java project.
+
1. **Verify the setup**:
1. Run the `seedu.address.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
@@ -34,10 +40,11 @@ If you plan to use Intellij IDEA (highly recommended):
If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours.
-
:bulb: **Tip:**
+
+ **Tip:**
Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
-
+
1. **Set up CI**
@@ -45,7 +52,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Learn the design**
- When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture).
+ When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [NetConnect architecture](DeveloperGuide.md#architecture).
1. **Do the tutorials**
These tutorials will help you get acquainted with the codebase.
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..78ddc57e670 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -1,12 +1,15 @@
---
-layout: page
-title: Testing guide
+ layout: default.md
+ title: "Testing guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Testing guide
---------------------------------------------------------------------------------------------------------------------
+
+
+
+
## Running tests
@@ -19,8 +22,10 @@ There are two ways to run tests.
* **Method 2: Using Gradle**
* Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`)
-
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
-
+
+
+**Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
+
--------------------------------------------------------------------------------------------------------------------
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 7abd1984218..67d9bb1b15f 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,55 +1,80 @@
---
-layout: page
-title: User Guide
+ layout: default.md
+ title: "User Guide"
+ pageNav: 3
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+# NetConnect User Guide
-* Table of Contents
-{:toc}
+NetConnect is a desktop app for managing contacts in SMEs, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). It enables HR and relations managers to efficiently manage their employees, clients, as well as suppliers, **all in one place** ☝🏻.
---------------------------------------------------------------------------------------------------------------------
+The inspiration behind NetConnect lies in solving a specific set of challenges faced by the food business managers demographic. Specifically, we aim to address the challenge of managing various contact types: clients, suppliers, and employees.
-## Quick start
+If you relate to this problem we identified, then NetConnect might be just right for you. This user guide will accompany you in maximising the capabilities of this product, freeing time for more pressing issues.
-1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+# Table of Contents
+- [NetConnect User Guide](#netconnect-user-guide)
+- [Table of Contents](#table-of-contents)
+- [Quick start](#quick-start)
+- [Features](#features)
+ - [Data Constraints](#data-constraints)
+ - [Viewing help : `help`](#viewing-help-help)
+ - [Adding a person: `add`](#adding-a-person-add)
+ - [Deleting a person : `delete`](#deleting-a-person-delete)
+ - [Listing all contacts : `list`](#listing-all-contacts-list)
+ - [Editing a person : `edit`](#editing-a-person-edit)
+ - [Locating Contacts : `find`](#locating-contacts-find)
+ - [Clearing all entries : `clear`](#clearing-all-entries-clear)
+ - [Create Relations between Profiles : `relate`](#create-relations-between-profiles-relate)
+ - [Remove Relations between Profiles : `unrelate`](#remove-relations-between-profiles-unrelate)
+ - [Show Relations Associated to a Person : `showrelated`](#show-relations-associated-to-a-person-showrelated)
+ - [Open on Last State](#open-on-last-state)
+ - [Export view to CSV File : `export`](#export-view-to-csv-file-export)
+ - [Exiting the program : `exit`](#exiting-the-program-exit)
+ - [Saving the data](#saving-the-data)
+ - [Editing the data file](#editing-the-data-file)
+- [Planned Enhancements](#planned-enhancements)
+- [FAQ](#faq)
+- [Known issues](#known-issues)
+- [Command summary](#command-summary)
-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.
- Some example commands you can try:
+# Quick start
- * `list` : Lists all contacts.
+1. Ensure you have Java `11` or above installed in your Computer.
- * `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.
+1. Download the latest `netconnect.jar` from [here](https://github.com/AY2324S2-CS2103T-F12-1/tp/releases).
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+1. Copy the file to the folder you want to use as the _home folder_ for your NetConnect.
- * `clear` : Deletes all contacts.
+1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar netconnect.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)
- * `exit` : Exits the app.
+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.
1. Refer to the [Features](#features) below for details of each command.
--------------------------------------------------------------------------------------------------------------------
+
-## Features
+# Features
-
+
-**:information_source: Notes about the command format:**
+**Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+ e.g. `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
* Items with `…` after them can be used multiple times including zero times.
e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
@@ -61,138 +86,352 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
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.
-
+
-### Viewing help : `help`
+
-Shows a message explaning how to access the help page.
+## Data Constraints
-![help message](images/helpMessage.png)
+**Constraints:**
+Here are the constraints for each field in the application:
+
+* `NAME`: Names should only contain alphanumeric characters and spaces, and it should not be blank.
+* `PHONE_NUMBER`: Phone numbers should only contain numbers, and it should be at least 3 digits long to accommodate staff extensions.
+* `EMAIL`: Emails should be of the format `local-part@domain`. NetConnect does not check for the validity of the domain part, hence extra attention should be put into ensuring no typos are present in the domain part of the email.
+* `ADDRESS`: Addresses can take any format, and it should not be blank.
+* `ROLE`: Roles can only be `client`, `supplier`, or `employee`.
+* `REMARK`: Remark can take any format.
+* `TAG`: Tags should only contain alphanumeric characters and spaces.
+* `DEPARTMENT`: Department names should only contain alphanumeric characters and spaces.
+* `JOB`: Job titles should only contain alphanumeric characters and spaces.
+* `SKILLS`: Skills should only contain alphanumeric characters and spaces.
+* `PREFERENCES`: Preferences can take any format.
+* `TERMS OF SERVICE`: Terms of service can take any format.
+* `PRODUCTS`: Product names should only contain alphanumeric characters and spaces.
+
+
+
+## Viewing help : `help`
+
+Shows a message explaining how to access the help page.
Format: `help`
+![help message](images/helpMessage.png)
+
+
+
+
+## Adding a person: `add`
+
+Adds a person (Client, Supplier or Employee) to the address book. Note that each role (e.g. Client, Supplier, Employee) has its own specific set of fields that can be added. The input for all fields should adhere to the [Data Constraints](#data-constraints).
+
+Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/ROLE [r/remark] [t/TAG]…` (other fields specific to the role)
+
+**Client:**
+* Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/Client [r/remark] [t/TAG]... [pref/PREFERENCES] [prod/PRODUCT]...`
+
+* Example: `add n/Benson Mayer p/87728933 e/mayerb@example.com a/311, Clementi Ave 2, #02-25 role/Client pref/Dairy-free prod/Sourdough bread prod/Raisin Bread`
+
+**Employee:**
+* Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/Employee [r/remark] [t/TAG]... [dept/DEPARTMENT] [job/JOBTITLE] [skills/SKILL]...`
+
+* Example: `add n/Bob Ye p/8928732 e/boby@example.com a/Blk 11, Clementi Ave 1, #03-32 t/friends t/coreTeam r/requires follow up on pay raise role/employee dept/HR job/Manager skills/Java`
+
+**Supplier:**
+* Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/Supplier [r/remark] [t/TAG]... [tos/TERMS OF SERVICE] [prod/PRODUCT]...`
-### Adding a person: `add`
+* Example: `add n/Fiona Kunz p/94824272 e/lydia@example.com a/little tokyo role/Supplier tos/Delivery within 2 weeks prod/Office Supplies prod/Furniture`
-Adds a person to the address book.
+![Add Command Result](images/addExample.png)
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+
+Info: NetConnect checks for unique profiles by its NAME, PHONE NUMBER and EMAIL. It does not allow you to create two profiles with identical name, phone number and email.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+
+
+## Deleting a person : `delete`
+
+Deletes the specified person from the address book.
+
+Format: `delete [n/NAME] [i/ID]`
+
+* Deletes the person with the specified `NAME` or `ID`.
+* If there are more than one person with the same specified `NAME`, `ID` has to be used.
+* `ID` refers to the unique identification number assigned to each person when first added to the list.
+* `ID` **must refer to a person that exist within NetConnect**.
+* Full name must be provided for `NAME`.
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`
+* `delete n/Benson Mayer` deletes the person with the name Benson Mayer (if no one else have the same name).
+* `delete i/2` deletes the person with an ID of 2 in the address book.
+![Add Command Result](images/deleteExample.png)
+
+
-### Listing all persons : `list`
+Warnings: Due to the destructive nature of this action, NetConnect will require a confirmation from the user before it is executed.
+![Delete Warning](images/deletewarning.png)
+
-Shows a list of all persons in the address book.
+
+
+## Listing all contacts : `list`
+
+Shows a list of all contacts in the address book.
Format: `list`
+![list](images/list.png)
+
+
-### Editing a person : `edit`
+## Editing a person : `edit`
Edits an existing person in the address book.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Format: `edit i/ID [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [r/remark] [t/TAG]…` (other fields specific to the role)
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* Edits the person with the specified `ID`. `ID` refers to the unique identification number assigned to each person when first added to the list.
+* `ID` **must refer to a person that exist within NetConnect**.
+* At least one of the optional fields `[..]` must be provided.
+* When editing multiple-value fields, all existing values of that field will be removed and replaced with the new values, i.e., adding tags, products, skills is not cumulative.
+* You can remove the value of the optional fields by typing the respective field flag without specifying any value. For example, `edit i/6 t/` will clear all the tags in contact ID 6.
+* You cannot edit a field that is invalid for the current person type.
+
+Please refer to the [Data Constraints](#data-constraints) for valid input.
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `edit i/4 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the person with ID of 1 to be `91234567` and `johndoe@example.com` respectively.
+![EditResultExample](images/editExample.png)
+* `edit i/2 n/Betsy Crower t/` Edits the name of the person with ID of 2 to be `Betsy Crower` and clears all existing tags.
-### Locating persons by name: `find`
+
-Finds persons whose names contain any of the given keywords.
+## Locating Contacts : `find`
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Finds persons whose information matches any of the specified parameters. You can find persons by names, phone numbers, tags, roles, and remarks. To search via different fields, you can stack multiple `find`-type commands to narrow down your 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`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+Format: `find [n/NAME]... [t/TAG]... [p/PHONE_NUMBER]... [role/ROLE]... [r/REMARK]...`
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* Only one type of field is allowed for each `find` command.
+* Multiple parameters of the same field can be provided, showing persons who match any of the field in that command, e.g. `find n/alex n/david` will show all persons with either `alex` or `david` in their names.
+* __`list` is required to remove the stacked filters.__
+* Searches are case-insensitive, e.g. `hans` will match `Hans`.
+* Partial matches are allowed for names, e.g. `Ha` will match `Hans`.
+* Find by remark requires full word match that is contained in the remark sentence, e.g. `find r/marketing` will match `r/marketing IC`, `find r/has dog` will match `r/he has a dog`, `find r/market` will **not** match `r/marketing`.
+* Find by remark requires all words given to be contained in the remark sentence, e.g. `find r/has cute dog` will **not** match ` r/he has a dog`.
+* Find by remark allows unordered search, e.g. `find r/dog has` will match `r/he has a dog`.
+* `find r/` will search for contacts with an empty remark.
+* For phone numbers, tags and role, only exact matches are allowed, e.g. `83647382` or `8364` will not match `83641001`, `find t/fri` will not match contacts with tag `friends`, `find role/clie` will not match contacts with role `client`.
-### Deleting a person : `delete`
+Find by name example:
+* `find n/John` returns `john` and `John Doe`.
+* `find n/megan n/roy` returns `Megan Lim`, `Megan Ho`,`Ng Royton`, `Roy Chua`.
+![result for 'find megan roy'](images/findMeganRoy.png)
-Deletes the specified person from the address book.
+Find by tag example:
+* `find t/friends` returns all persons who have the tag `friends`.
-Format: `delete INDEX`
+Find by phone number example:
+* `find p/98765432` returns `John Doe` who has the phone number `98765432`.
-* 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, …
+Find by role example:
+* `find role/client` returns all persons who have the role `client`.
+* `find role/supplier role/client` returns all persons who have the role `supplier` or `client`.
-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.
+Find by remark example:
+* `find r/` returns all persons who have an empty remark.
+* `find r/has a dog` returns all persons who have the remark containing all these words ['has', 'a', 'dog']
+
+Stacking find by role and tag example
+* `find role/Employee` returns all persons who have the role `Employee`.
+
+* Followed by `find t/friends`, returns all persons who are `Employee` and have the tag `friends`.
+
+![result for 'find employee friends'](images/stackEmployeeFriends.png)
-### Clearing all entries : `clear`
+
+
+## Clearing all entries : `clear`
Clears all entries from the address book.
Format: `clear`
-### Exiting the program : `exit`
+
+
+Warnings: Due to the destructive nature of this action, NetConnect will require a confirmation from the user before it is executed.
+
+![result for 'clear warning'](images/ClearWarning.png)
+
+
+
+
+## Create Relations between Profiles : `relate`
+
+Creates a relation between two profiles in the address book.
+
+Format: `relate i/ID i/ID`
+
+Example: `relate i/1 i/3` creates a relation between the profiles with ID of 1 and 3.
+
+![result for 'relate result'](images/relateResult.png)
+
+
+
+## Remove Relations between Profiles : `unrelate`
+
+Removes a relation between two profiles in the address book.
+
+Format: `unrelate i/ID i/ID`
+
+Example: `unrelate i/1 i/23` removes a relation between the profiles with ID of 1 and 3.
+
+![result for 'unrelate result'](images/unrelateResult.png)
+
+
+
+## Show Relations Associated to a Person : `showrelated`
+
+Shows all the relations associated to a person in the address book.
+
+Format: `showrelated i/ID`
+
+Example: `showrelated i/1` shows all relations between the profile with ID 1 and all other contacts.
+
+
+Info: If there are no persons related to the provided ID, the interface will show "0 persons listed".
+
+
+![result for 'showrelated result'](images/showrelatedResult.png)
+
+
+
+## Open on Last State
+With every change to the command input, NetConnect saves and updates the command input in a separate file. When the app closes and is opened again, the last command present before closure will be retrieved from the separate file and input into the command field (if any). This way, you never have to worry about losing progress!
+
+
+
+## Export view to CSV File : `export`
+Retrieve information on a group of profiles at once with this function! This can be useful for consolidating all the emails or contact number at once, or to share information with third parties.
+
+**To export _all_ profiles in the address book to a CSV file:**
+
+Format:
+
+Step 1: `list`
+
+Step 2: `export [filename]`
+
+* The `list` command in the first step is to pull all profiles into the current view.
+* Following `export` in Step 2, you can choose the filename to save the file as. The filename must end with `.csv`. If you do not specify a filename, it will be automatically named as `contact.csv`
+
+Example:
+
+Step 1: `list` first returns the list of all contacts
+
+Step 2: `export` a CSV file containing all contacts to a folder on your laptop that is located within the same directory as the NetConnect application.
+
+**To export a _specific_ group of profiles to a CSV file:**
+
+Format:
+
+Step 1: `find [keyword]`
+
+Step 2: `export [filename]`
+
+* The first step is to filter the profiles you want to export into the current view.
+
+Example:
+
+Step 1: `find role/Client`
+![result for 'CSV file'](images/findClient.png)
+Step 2: `export client.csv`
+![result for 'export current view'](images/exportview.png)
+The CSV file named clients.csv containing all client contacts is exported to a folder on your laptop located within the same directory as the NetConnect application.
+![result for 'CSV file'](images/csvfile.png)
+
+
+
+## Exiting the program : `exit`
Exits the program.
Format: `exit`
-### Saving the data
+
+
+## Saving the data
+
+NetConnect data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
-AddressBook 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
-### Editing the data file
+NetConnect data are saved automatically as a JSON file `[JAR file location]/data/netconnect.json`. Advanced users are welcome to update data directly by editing that data file.
+
+**Caution:**
+If your changes to the data file makes its format invalid, NetConnect 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 NetConnect to behave in unexpected ways (e.g., if a value entered is outside the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
+
+--------------------------------------------------------------------------------------------------------------------
-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.
+
-
: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.
-
+# Planned Enhancements
+The NetConnect team is working on new features and fixes for you, but they are unfortunately unavailable in this current implementation. We intend to have future fixes for these occurences below!
-### Archiving data files `[coming in v2.0]`
+1. Improved error messages for `showrelated` command
+1. Improved command success messages for `relate` and `unrelate` commands
+1. Improved validity checks for `relate` and `unrelate` commands
+1. Wrap text instead of truncate in GUI to accommodate long text fields
+1. Handle situations where input entered is the same as the current value for `edit` command
+1. Data recovery
-_Details coming soon ..._
+Further details on the planned enhancements can be found in the Developer Guide.
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+
+
+# 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 NetConnect home folder.
+
+**Q**: How do I know if Java 11 is already installed on my computer?
+**A**: Open the Command Prompt (Windows) or the Terminal (MacOS) and run the `java -version` command. The output should contain Java 11 if it is installed.
+
+**Q**: What if I have newer versions of Java already installed on my computer?
+**A**: You will still need Java 11 to run NetConnect, though multiple versions should be fine. You may check this using the `java -version` command. The output should contain Java 11 if it is installed.
+
+**Q**: What operating systems can I use NetConnect on?
+**A**: NetConnect can be run on Linux, Windows, and macOS, provided that Java 11 is installed.
+
+**Q**: Do I require the internet to run the application?
+**A**: No, you do not need the internet to access our application or its features.
--------------------------------------------------------------------------------------------------------------------
-## Known issues
+# 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.
--------------------------------------------------------------------------------------------------------------------
-## 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`
+
+
+# Command summary
+| Action | Format | Examples |
+|--------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Help** | `help` | `help` |
+| **Add (Employee)** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/Employee [r/REMARK] [t/TAG]... [dept/DEPARTMENT] [job/JOB] [skills/SKILL]... ` | `add n/Bob Ye p/8928732 e/boby@example.com a/Blk 11, Clementi Ave 1, #03-32 t/friends t/coreTeam r/requires follow up on pay raise role/employee dept/HR job/Manager skills/Java` |
+| **Add (Client)** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/Client [r/REMARK] [t/TAG]... [pref/PREFERENCES] [prod/PRODUCT]...` | `add n/Benson Mayer p/87728933 e/mayerb@example.com a/311, Clementi Ave 2, #02-25 role/Client pref/Dairy-free prod/Sourdough bread prod/Raisin Bread` |
+| **Add (Supplier)** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS role/Supplier [r/REMARK] [t/TAG].. [tos/TERMS OF SERVICE] [prod/PRODUCT]...` | `add n/Fiona Kunz p/94824272 e/lydia@example.com a/little tokyo role/Supplier tos/Delivery within 2 weeks prod/Office Supplies prod/Furniture` |
+| **List** | `list` | `list` |
+| **Delete** | `delete [i/ID] [n/NAME]` | `delete i/123`, `delete n/John Doe` |
+| **Edit** | `edit i/ID [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [r/REMARK] [t/TAG]…` (other fields specific to the role) | `edit i/123 n/James Lee e/jameslee@example.com` |
+| **Find** | `find [n/NAME] [t/TAG] [p/PHONE_NUMBER] [role/ROLE] [r/REMARK]` | `find role/employee` , followed by `find n/Bob` to stack filters |
+| **Relate Profiles** | `relate i/ID i/ID` | `relate i/1 i/2` |
+| **Unrelate Profiles** | `unrelate i/ID i/ID` | `unrelate i/1 i/2` |
+| **Show related Profile** | `showrelated i/ID` | `showrelated i/2` |
+| **Export** | `export [filename]` | `export ClientInfo.csv` |
+| **Clear** | `clear` | `clear` |
+| **Exit** | `exit` | `exit` |
diff --git a/docs/_config.yml b/docs/_config.yml
deleted file mode 100644
index 6bd245d8f4e..00000000000
--- a/docs/_config.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-title: "AB-3"
-theme: minima
-
-header_pages:
- - UserGuide.md
- - DeveloperGuide.md
- - AboutUs.md
-
-markdown: kramdown
-
-repository: "se-edu/addressbook-level3"
-github_icon: "images/github-icon.png"
-
-plugins:
- - jemoji
diff --git a/docs/_data/projects.yml b/docs/_data/projects.yml
deleted file mode 100644
index 8f3e50cb601..00000000000
--- a/docs/_data/projects.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-- name: "AB-1"
- url: https://se-edu.github.io/addressbook-level1
-
-- name: "AB-2"
- url: https://se-edu.github.io/addressbook-level2
-
-- name: "AB-3"
- url: https://se-edu.github.io/addressbook-level3
-
-- name: "AB-4"
- url: https://se-edu.github.io/addressbook-level4
-
-- name: "Duke"
- url: https://se-edu.github.io/duke
-
-- name: "Collate"
- url: https://se-edu.github.io/collate
-
-- name: "Book"
- url: https://se-edu.github.io/se-book
-
-- name: "Resources"
- url: https://se-edu.github.io/resources
diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html
deleted file mode 100644
index 8559a67ffad..00000000000
--- a/docs/_includes/custom-head.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{% comment %}
- Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons:
-
- 1. Head over to https://realfavicongenerator.net/ to add your own favicons.
- 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet.
-{% endcomment %}
diff --git a/docs/_includes/head.html b/docs/_includes/head.html
deleted file mode 100644
index 83ac5326933..00000000000
--- a/docs/_includes/head.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
- {%- include custom-head.html -%}
-
- {{page.title}}
-
-
diff --git a/docs/_includes/header.html b/docs/_includes/header.html
deleted file mode 100644
index 33badcd4f99..00000000000
--- a/docs/_includes/header.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
+
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
+Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
-
+
Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
-
## Updating Storage
-AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedPerson` to work with our new `Person`!
+NetConnect stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedPerson` to work with our new `Person`!
While the changes to code may be minimal, the test data will have to be updated as well.
-
+
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
+You must delete NetConnect’s storage file located at `/data/netconnect.json` before running it! Not doing so will cause NetConnect to default to an empty address book!
-
+
Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
to see what the changes entail.
@@ -308,7 +308,7 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c
**`PersonCard.java`:**
-``` java
+```java
public PersonCard(Person person, int displayedIndex) {
//...
remark.setText(person.getRemark().value);
@@ -328,7 +328,7 @@ save it with `Model#setPerson()`.
**`RemarkCommand.java`:**
-``` java
+```java
//...
public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s";
public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s";
@@ -396,4 +396,4 @@ You should end up with a test that looks something like [this](https://github.co
## Conclusion
-This concludes the tutorial for adding a new `Command` to AddressBook.
+This concludes the tutorial for adding a new `Command` to NetConnect.
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..d100e6b31a0 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -1,26 +1,28 @@
---
-layout: page
-title: "Tutorial: Removing Fields"
+ layout: default.md
+ title: "Tutorial: Removing Fields"
+ pageNav: 3
---
+# Tutorial: Removing Fields
+
> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
>
-> — Antoine de Saint-Exupery
+> — Antoine de Saint-Exupery
When working on an existing code base, you will most likely find that some features that are no longer necessary.
This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class.
-
+
-**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.
+**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.
However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.**
-
-
+
-* Table of Contents
-{:toc}
+
+
## Safely deleting `Address`
@@ -29,6 +31,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re
### Assisted refactoring
The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
+
* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences`
![Usages detected](../images/remove/UnsafeDelete.png)
@@ -50,10 +53,10 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`.
1. Remove the usages of `address` and select `Do refactor` when you are done.
-
+
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
-
+ **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
+
1. Repeat the steps for the remaining usages of `Address`
@@ -71,7 +74,7 @@ A quick look at the `PersonCard` class and its `fxml` file quickly reveals why i
**`PersonCard.java`**
-``` java
+```java
...
@FXML
private Label address;
@@ -80,7 +83,7 @@ private Label address;
**`PersonCard.fxml`**
-``` xml
+```xml
...
@@ -96,16 +99,18 @@ At this point, your application is working as intended and all your tests are pa
In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
-**`invalidPersonAddressBook.json`:**
+**`invalidPersonNetConnect.json`:**
```json
{
- "persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
- } ]
+ "persons": [
+ {
+ "name": "Person with invalid name field: Ha!ns Mu@ster",
+ "phone": "9482424",
+ "email": "hans@example.com",
+ "address": "4th street"
+ }
+ ]
}
```
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..cd6c5e100a6 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -1,30 +1,34 @@
---
-layout: page
-title: "Tutorial: Tracing code"
+ layout: default.md
+ title: "Tutorial: Tracing code"
+ pageNav: 3
---
+# Tutorial: Tracing code
+
> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …\[Therefore,\] making it easy to read makes it easier to write.
>
-> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
+> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command.
-* Table of Contents
-{:toc}
+
+
## Before we start
Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components.
-![ArchitectureDiagram](../images/ArchitectureDiagram.png)
+
It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App.
-
+
Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works.
Before we proceed, ensure that you have done the following:
+
1. Read the [*Architecture* section of the DG](../DeveloperGuide.md#architecture)
1. Set up the project in Intellij IDEA
1. Learn basic debugging features of Intellij IDEA
@@ -37,16 +41,16 @@ As you know, the first step of debugging is to put in a breakpoint where you wan
In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to the `Logic` component.
-
+
According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
-
+
-:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-
+**Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
+
A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
@@ -67,30 +71,30 @@ public interface Logic {
But apparently, this is an interface, not a concrete implementation.
That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells us that components interact through interfaces. Here's the relevant diagram:
-
+
Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`.
-
+
-:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
-
+**Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
+
![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png)
Bingo\! `MainWindow#executeCommand()` seems to be exactly what we’re looking for\!
Now let’s set the breakpoint. First, double-click the item to reach the corresponding code. Once there, click on the left gutter to set a breakpoint, as shown below.
- ![LeftGutter](../images/tracing/LeftGutter.png)
+![LeftGutter](../images/tracing/LeftGutter.png)
## Tracing the execution path
Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
-
+
-:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
-
+**Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
+
1. To start the debugging session, simply `Run` \> `Debug Main`
@@ -104,13 +108,13 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
`CommandResult commandResult = logic.execute(commandText);` is the line that you end up at (i.e., the place where we put the breakpoint).
1. We are interested in the `logic.execute(commandText)` portion of that line so let’s _Step in_ into that method call:
- ![StepInto](../images/tracing/StepInto.png)
+ ![StepInto](../images/tracing/StepInto.png)
1. We end up in `LogicManager#execute()` (not `Logic#execute` -- but this is expected because we know the `execute()` method in the `Logic` interface is actually implemented by the `LogicManager` class). Let’s take a look at the body of the method. Given below is the same code, with additional explanatory comments.
**LogicManager\#execute().**
- ``` java
+ ```java
@Override
public CommandResult execute(String commandText)
throws CommandException, ParseException {
@@ -120,14 +124,14 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
CommandResult commandResult;
//Parse user input from String to a Command
- Command command = addressBookParser.parseCommand(commandText);
+ Command command = netConnectParser.parseCommand(commandText);
//Executes the Command and stores the result
commandResult = command.execute(model);
try {
//We can deduce that the previous line of code modifies model in some way
// since it's being stored here.
- storage.saveAddressBook(model.getAddressBook());
+ storage.saveNetConnect(model.getNetConnect());
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
@@ -141,8 +145,9 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. _Step over_ the logging code since it is of no interest to us now.
![StepOver](../images/tracing/StepOver.png)
-1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below):
- ``` java
+1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `NetConnectParser#parseCommand()` method (partial code given below):
+
+ ```java
public Command parseCommand(String userInput) throws ParseException {
...
final String commandWord = matcher.group("commandWord");
@@ -151,23 +156,25 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
```
1. _Step over_ the statements in that method until you reach the `switch` statement. The 'Variables' window now shows the value of both `commandWord` and `arguments`:
- ![Variables](../images/tracing/Variables.png)
+ ![Variables](../images/tracing/Variables.png)
1. We see that the value of `commandWord` is now `edit` but `arguments` is still not processed in any meaningful way.
1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command).
- ``` java
- ...
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
- ...
- ```
+ ```java
+ ...
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parse(arguments);
+ ...
+ ```
1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`.
-
:bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
-
+
+
+ **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
+
1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
@@ -175,17 +182,18 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
![EditCommand](../images/tracing/EditCommand.png)
1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component.
-
+
1. Let’s continue stepping through until we return to `LogicManager#execute()`.
- The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png)
+ The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
+
1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below):
**`EditCommand#execute()`:**
- ``` java
+
+ ```java
@Override
public CommandResult execute(Model model) throws CommandException {
...
@@ -201,67 +209,73 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
```
1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically,
+
* it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data.
* it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked.
-
+
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component)
1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component:
-
+
+
* :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component)
1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component.
-
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
-
+
-1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
+ **Intellij Tip:** When trying to step into a statement such as `storage.saveNetConnect(model.getNetConnect())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
+
- **`JsonSerializableAddressBook` constructor:**
- ``` java
- /**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
- *
- * @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()));
- }
- ```
+1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonNetConnect#saveNetConnect()` method which calls the `JsonSerializableNetConnect` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
-1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`.
+ **`JsonSerializableNetConnect` constructor:**
+
+ ```java
+ /**
+ * Converts a given {@code ReadOnlyNetConnect} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created
+ * {@code JsonSerializableNetConnect}.
+ */
+ public JsonSerializableNetConnect(ReadOnlyNetConnect source) {
+ persons.addAll(
+ source.getPersonList()
+ .stream()
+ .map(JsonAdaptedPerson::new)
+ .collect(Collectors.toList()));
+ }
+ ```
+
+1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableNetConnect`.
This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format.
1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
-
+
+
* :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component)
1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint).
1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in:
- **`ResultDisplay#setFeedbackToUser()`**
- ``` java
- public void setFeedbackToUser(String feedbackToUser) {
- requireNonNull(feedbackToUser);
- resultDisplay.setText(feedbackToUser);
- }
- ```
+ **`ResultDisplay#setFeedbackToUser()`**
+
+ ```java
+ public void setFeedbackToUser(String feedbackToUser) {
+ requireNonNull(feedbackToUser);
+ resultDisplay.setText(feedbackToUser);
+ }
+ ```
1. Finally, you can step through until you reach the end of`MainWindow#executeCommand()`.
:bulb: This may be a good time to read through the [`UI` component section of the DG](../DeveloperGuide.html#ui-component)
-
## Conclusion
In this tutorial, we traced a valid edit command from raw user input to the result being displayed to the user. From this tutorial, you learned more about how the various components work together to produce a response to a user command.
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java
index ec1b7958746..005165c89c6 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/seedu/address/Main.java
@@ -7,22 +7,22 @@
/**
* The main entry point to the application.
- *
+ *
* This is a workaround for the following error when MainApp is made the
* entry point of the application:
- *
- * Error: JavaFX runtime components are missing, and are required to run this application
- *
+ *
+ * Error: JavaFX runtime components are missing, and are required to run this application
+ *
* The reason is that MainApp extends Application. In that case, the
* LauncherHelper will check for the javafx.graphics module to be present
* as a named module. We don't use JavaFX via the module system so it can't
* find the javafx.graphics module, and so the launch is aborted.
- *
+ *
* By having a separate main class (Main) that doesn't extend Application
* to be the entry point of the application, we avoid this issue.
*/
public class Main {
- private static Logger logger = LogsCenter.getLogger(Main.class);
+ private static final Logger logger = LogsCenter.getLogger(Main.class);
public static void main(String[] args) {
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 3d6bd06d5af..7ee2a77a45f 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -15,18 +15,20 @@
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.Logic;
import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.NetConnect;
+import seedu.address.model.ReadOnlyNetConnect;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonNetConnectStorage;
import seedu.address.storage.JsonUserPrefsStorage;
+import seedu.address.storage.NetConnectStorage;
+import seedu.address.storage.StateStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
+import seedu.address.storage.TextStateStorage;
import seedu.address.storage.UserPrefsStorage;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
@@ -36,7 +38,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(1, 3, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -48,7 +50,7 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing NetConnect ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -57,8 +59,9 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ NetConnectStorage netConnectStorage = new JsonNetConnectStorage(userPrefs.getNetConnectFilePath());
+ StateStorage stateStorage = new TextStateStorage();
+ storage = new StorageManager(netConnectStorage, userPrefsStorage, stateStorage);
model = initModelManager(storage, userPrefs);
@@ -68,26 +71,29 @@ public void init() throws Exception {
}
/**
- * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found,
- * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
+ * Returns a {@code ModelManager} with the data from {@code storage}'s address
+ * book and {@code userPrefs}.
+ * The data from the sample address book will be used instead if
+ * {@code storage}'s address book is not found,
+ * or an empty address book will be used instead if errors occur when reading
+ * {@code storage}'s address book.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- logger.info("Using data file : " + storage.getAddressBookFilePath());
+ logger.info("Using data file : " + storage.getNetConnectFilePath());
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional netConnectOptional;
+ ReadOnlyNetConnect initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Creating a new data file " + storage.getAddressBookFilePath()
- + " populated with a sample AddressBook.");
+ netConnectOptional = storage.readNetConnect();
+ if (!netConnectOptional.isPresent()) {
+ logger.info("Creating a new data file " + storage.getNetConnectFilePath()
+ + " populated with a sample NetConnect.");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialData = netConnectOptional.orElseGet(SampleDataUtil::getSampleNetConnect);
} catch (DataLoadingException e) {
- logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded."
- + " Will be starting with an empty AddressBook.");
- initialData = new AddressBook();
+ logger.warning("Data file at " + storage.getNetConnectFilePath() + " could not be loaded."
+ + " Will be starting with an empty NetConnect.");
+ initialData = new NetConnect();
}
return new ModelManager(initialData, userPrefs);
@@ -127,7 +133,8 @@ protected Config initConfig(Path configFilePath) {
initializedConfig = new Config();
}
- //Update config file in case it was missing to begin with or there are new/unused fields
+ // Update config file in case it was missing to begin with or there are
+ // new/unused fields
try {
ConfigUtil.saveConfig(initializedConfig, configFilePathUsed);
} catch (IOException e) {
@@ -137,7 +144,8 @@ protected Config initConfig(Path configFilePath) {
}
/**
- * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path,
+ * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs
+ * file path,
* or a new {@code UserPrefs} with default configuration if errors occur when
* reading from the file.
*/
@@ -158,7 +166,8 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
initializedPrefs = new UserPrefs();
}
- //Update prefs file in case it was missing to begin with or there are new/unused fields
+ // Update prefs file in case it was missing to begin with or there are
+ // new/unused fields
try {
storage.saveUserPrefs(initializedPrefs);
} catch (IOException e) {
@@ -170,7 +179,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting NetConnect " + MainApp.VERSION);
ui.start(primaryStage);
}
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java
index 8cf8e15a0f0..c1d5e8992ba 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/seedu/address/commons/core/LogsCenter.java
@@ -14,13 +14,13 @@
* Configures and manages loggers and handlers, including their logging level
* Named {@link Logger}s can be obtained from this class
* These loggers have been configured to output messages to the console and a {@code .log} file by default,
- * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log
- * file reaches 5MB big, up to a maximum of 5 files.
+ * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log
+ * file reaches 5MB big, up to a maximum of 5 files.
*/
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "netconnect.log";
private static final Logger logger; // logger for this class
private static Logger baseLogger; // to be used as the parent of all other loggers created by this class.
private static Level currentLogLevel = Level.INFO;
@@ -75,11 +75,11 @@ private static void removeHandlers(Logger logger) {
}
/**
- * Creates a logger named 'ab3', containing a {@code ConsoleHandler} and a {@code FileHandler}.
+ * Creates a logger named 'netconnect', containing a {@code ConsoleHandler} and a {@code FileHandler}.
* Sets it as the {@code baseLogger}, to be used as the parent logger of all other loggers.
*/
private static void setBaseLogger() {
- baseLogger = Logger.getLogger("ab3");
+ baseLogger = Logger.getLogger("netconnect");
baseLogger.setUseParentHandlers(false);
removeHandlers(baseLogger);
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java
index 491d24559b4..7162ba1b8a5 100644
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ b/src/main/java/seedu/address/commons/core/Version.java
@@ -50,6 +50,7 @@ public boolean isEarlyAccess() {
/**
* Parses a version number string in the format V1.2.3.
+ *
* @param versionString version number string
* @return a Version object
*/
@@ -64,7 +65,7 @@ public static Version fromString(String versionString) throws IllegalArgumentExc
return new Version(Integer.parseInt(versionMatcher.group(1)),
Integer.parseInt(versionMatcher.group(2)),
Integer.parseInt(versionMatcher.group(3)),
- versionMatcher.group(4) == null ? false : true);
+ versionMatcher.group(4) != null);
}
@JsonValue
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java
deleted file mode 100644
index dd170d8b68d..00000000000
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package seedu.address.commons.core.index;
-
-import seedu.address.commons.util.ToStringBuilder;
-
-/**
- * Represents a zero-based or one-based index.
- *
- * {@code Index} should be used right from the start (when parsing in a new user input), so that if the current
- * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what
- * base the other component is using for its index. However, after receiving the {@code Index}, that component can
- * convert it back to an int if the index will not be passed to a different component again.
- */
-public class Index {
- private int zeroBasedIndex;
-
- /**
- * Index can only be created by calling {@link Index#fromZeroBased(int)} or
- * {@link Index#fromOneBased(int)}.
- */
- private Index(int zeroBasedIndex) {
- if (zeroBasedIndex < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- this.zeroBasedIndex = zeroBasedIndex;
- }
-
- public int getZeroBased() {
- return zeroBasedIndex;
- }
-
- public int getOneBased() {
- return zeroBasedIndex + 1;
- }
-
- /**
- * Creates a new {@code Index} using a zero-based index.
- */
- public static Index fromZeroBased(int zeroBasedIndex) {
- return new Index(zeroBasedIndex);
- }
-
- /**
- * Creates a new {@code Index} using a one-based index.
- */
- public static Index fromOneBased(int oneBasedIndex) {
- return new Index(oneBasedIndex - 1);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Index)) {
- return false;
- }
-
- Index otherIndex = (Index) other;
- return zeroBasedIndex == otherIndex.zeroBasedIndex;
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this).add("zeroBasedIndex", zeroBasedIndex).toString();
- }
-}
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
index 19124db485c..651ce290208 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
@@ -13,7 +13,7 @@ public IllegalValueException(String message) {
/**
* @param message should contain relevant information on the failed constraint(s)
- * @param cause of the main exception
+ * @param cause of the main exception
*/
public IllegalValueException(String message, Throwable cause) {
super(message, cause);
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java
index eafe4dfd681..942edda97d7 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java
@@ -12,7 +12,9 @@
*/
public class CollectionUtil {
- /** @see #requireAllNonNull(Collection) */
+ /**
+ * @see #requireAllNonNull(Collection)
+ */
public static void requireAllNonNull(Object... items) {
requireNonNull(items);
Stream.of(items).forEach(Objects::requireNonNull);
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java
index b1e2767cdd9..f811215474c 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/seedu/address/commons/util/FileUtil.java
@@ -20,6 +20,7 @@ public static boolean isFileExists(Path file) {
/**
* Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)},
* otherwise returns false.
+ *
* @param path A string representing the file path. Cannot be null.
*/
public static boolean isValidPath(String path) {
@@ -33,6 +34,7 @@ public static boolean isValidPath(String path) {
/**
* Creates a file if it does not exist along with its missing parent directories.
+ *
* @throws IOException if the file or directory cannot be created.
*/
public static void createIfMissing(Path file) throws IOException {
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java
index 100cb16c395..57d795f9412 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/seedu/address/commons/util/JsonUtil.java
@@ -30,7 +30,7 @@ public class JsonUtil {
private static final Logger logger = LogsCenter.getLogger(JsonUtil.class);
- private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules()
+ private static final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
@@ -52,7 +52,7 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject
* Returns the JSON object from the given file or {@code Optional.empty()} object if the file is not found.
* If any values are missing from the file, default values will be used, as long as the file is a valid JSON file.
*
- * @param filePath cannot be null.
+ * @param filePath cannot be null.
* @param classOfObjectToDeserialize JSON file has to correspond to the structure in the class given here.
* @throws DataLoadingException if loading of the JSON file failed.
*/
@@ -80,6 +80,7 @@ public static Optional readJsonFile(
/**
* Saves the Json object to the specified file.
* Overwrites existing file if it exists, creates a new file if it doesn't.
+ *
* @param jsonFile cannot be null
* @param filePath cannot be null
* @throws IOException if there was an error during writing to the file
@@ -94,6 +95,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio
/**
* Converts a given string representation of a JSON data to instance of a class
+ *
* @param The generic type to create an instance of
* @return The instance of T with the specified values in the JSON string
*/
@@ -103,8 +105,9 @@ public static T fromJsonString(String json, Class instanceClass) throws I
/**
* Converts a given instance of a class into its JSON data string representation
+ *
* @param instance The T object to be converted into the JSON string
- * @param The generic type to create an instance of
+ * @param The generic type to create an instance of
* @return JSON data representation of the given class instance, in string
*/
public static String toJsonString(T instance) throws JsonProcessingException {
@@ -129,7 +132,6 @@ protected Level _deserialize(String value, DeserializationContext ctxt) {
* Gets the logging level that matches loggingLevelString
*
* Returns null if there are no matches
- *
*/
private Level getLoggingLevel(String loggingLevelString) {
return Level.parse(loggingLevelString);
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..d233f930d88 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -6,6 +6,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
+import java.util.regex.Pattern;
/**
* Helper functions for handling strings.
@@ -14,14 +15,15 @@ public class StringUtil {
/**
* Returns true if the {@code sentence} contains the {@code word}.
- * Ignores case, but a full word match is required.
- * examples:
+ * Ignores case, but a full word match is required.
+ * examples:
* containsWordIgnoreCase("ABc def", "abc") == true
* containsWordIgnoreCase("ABc def", "DEF") == true
* containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
*
+ *
* @param sentence cannot be null
- * @param word cannot be null, cannot be empty, must be a single word
+ * @param word cannot be null, cannot be empty, must be a single word
*/
public static boolean containsWordIgnoreCase(String sentence, String word) {
requireNonNull(sentence);
@@ -38,6 +40,27 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
.anyMatch(preppedWord::equalsIgnoreCase);
}
+ /**
+ * Returns true if the {@code source} contains the {@code target}.
+ * Ignores case, and a partial match returns true.
+ * examples:
+ *
+ * @param target cannot be null, cannot be empty
+ * @param source cannot be null,
+ */
+ public static boolean hasPartialMatchIgnoreCase(String target, String source) {
+ requireNonNull(target);
+ requireNonNull(source);
+ checkArgument(!target.trim().isEmpty(), "Target cannot be empty");
+
+ return Pattern.compile(Pattern.quote(target), Pattern.CASE_INSENSITIVE).matcher(source).find();
+ }
+
/**
* Returns a detailed message of the t, including the stack trace.
*/
@@ -45,7 +68,7 @@ public static String getDetails(Throwable t) {
requireNonNull(t);
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
- return t.getMessage() + "\n" + sw.toString();
+ return t.getMessage() + "\n" + sw;
}
/**
@@ -53,6 +76,7 @@ public static String getDetails(Throwable t) {
* e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input
* e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters)
+ *
* @throws NullPointerException if {@code s} is null.
*/
public static boolean isNonZeroUnsignedInteger(String s) {
diff --git a/src/main/java/seedu/address/commons/util/ToStringBuilder.java b/src/main/java/seedu/address/commons/util/ToStringBuilder.java
index d979b926734..3d9c6d5e0aa 100644
--- a/src/main/java/seedu/address/commons/util/ToStringBuilder.java
+++ b/src/main/java/seedu/address/commons/util/ToStringBuilder.java
@@ -30,7 +30,7 @@ public ToStringBuilder(Object object) {
/**
* Adds a field name/value pair to the output string.
*
- * @param fieldName The name of the field.
+ * @param fieldName The name of the field.
* @param fieldValue The value of the field.
* @return A reference to this {@code ToStringBuilder} object, allowing method calls to be chained.
*/
@@ -48,6 +48,6 @@ public ToStringBuilder add(String fieldName, Object fieldValue) {
*/
@Override
public String toString() {
- return stringBuilder.toString() + OBJECT_SUFFIX;
+ return stringBuilder + OBJECT_SUFFIX;
}
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..f3f3baca824 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -7,7 +7,7 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyNetConnect;
import seedu.address.model.person.Person;
/**
@@ -16,27 +16,30 @@
public interface Logic {
/**
* Executes the command and returns the result.
+ *
* @param commandText The command as entered by the user.
* @return the result of the command execution.
* @throws CommandException If an error occurs during command execution.
- * @throws ParseException If an error occurs during parsing.
+ * @throws ParseException If an error occurs during parsing.
*/
CommandResult execute(String commandText) throws CommandException, ParseException;
/**
- * Returns the AddressBook.
+ * Returns the NetConnect.
*
- * @see seedu.address.model.Model#getAddressBook()
+ * @see seedu.address.model.Model#getNetConnect()
*/
- ReadOnlyAddressBook getAddressBook();
+ ReadOnlyNetConnect getNetConnect();
- /** Returns an unmodifiable view of the filtered list of persons */
+ /**
+ * Returns an unmodifiable view of the filtered list of persons
+ */
ObservableList getFilteredPersonList();
/**
* Returns the user prefs' address book file path.
*/
- Path getAddressBookFilePath();
+ Path getNetConnectFilePath();
/**
* Returns the user prefs' GUI settings.
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 5aa3b91c7d0..7d0ef402e12 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -11,10 +11,10 @@
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.AddressBookParser;
+import seedu.address.logic.parser.NetConnectParser;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyNetConnect;
import seedu.address.model.person.Person;
import seedu.address.storage.Storage;
@@ -24,22 +24,23 @@
public class LogicManager implements Logic {
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 FILE_OPS_PERMISSION_ERROR_FORMAT = "Could not save data to file %s due to insufficient"
+ + "permissions to write to the file or the folder.";
private final Logger logger = LogsCenter.getLogger(LogicManager.class);
private final Model model;
private final Storage storage;
- private final AddressBookParser addressBookParser;
+ private final NetConnectParser netConnectParser;
/**
- * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
+ * Constructs a {@code LogicManager} with the given {@code Model} and
+ * {@code Storage}.
*/
public LogicManager(Model model, Storage storage) {
this.model = model;
this.storage = storage;
- addressBookParser = new AddressBookParser();
+ netConnectParser = new NetConnectParser();
}
@Override
@@ -47,11 +48,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
logger.info("----------------[USER COMMAND][" + commandText + "]");
CommandResult commandResult;
- Command command = addressBookParser.parseCommand(commandText);
+ Command command = netConnectParser.parseCommand(commandText);
commandResult = command.execute(model);
try {
- storage.saveAddressBook(model.getAddressBook());
+ storage.saveNetConnect(model.getNetConnect());
} catch (AccessDeniedException e) {
throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
} catch (IOException ioe) {
@@ -62,8 +63,8 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
}
@Override
- public ReadOnlyAddressBook getAddressBook() {
- return model.getAddressBook();
+ public ReadOnlyNetConnect getNetConnect() {
+ return model.getNetConnect();
}
@Override
@@ -72,8 +73,8 @@ public ObservableList getFilteredPersonList() {
}
@Override
- public Path getAddressBookFilePath() {
- return model.getAddressBookFilePath();
+ public Path getNetConnectFilePath() {
+ return model.getNetConnectFilePath();
}
@Override
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..1ddfe55c7c8 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -5,7 +5,10 @@
import java.util.stream.Stream;
import seedu.address.logic.parser.Prefix;
+import seedu.address.model.person.Client;
+import seedu.address.model.person.Employee;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Supplier;
/**
* Container for user visible messages.
@@ -14,10 +17,24 @@ 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_CANNOT_RELATE_ITSELF = "Cannot relate a person to his/herself.";
+ public static final String MESSAGE_RELATION_EXISTS = "This relation already exists.";
+ public static final String MESSAGE_RELATION_NOT_EXISTS = "This relation does not exist.";
+ public static final String MESSAGE_CANNOT_UNRELATE_ITSELF = "Cannot unrelate a person from his/herself.";
+ public static final String MESSAGE_UNRELATION_SUCCESS = "Unrelated the following persons: %1$s";
+ public static final String MESSAGE_INVALID_PERSON_ID = "There is no person with id %1$d";
+ public static final String MESSAGE_INVALID_PERSON_NAME = "There is no person with name %1$s";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
public static final String MESSAGE_DUPLICATE_FIELDS =
- "Multiple values specified for the following single-valued field(s): ";
+ "Multiple values specified for the following single-valued field(s): ";
+ public static final String MESSAGE_NON_UNIQUE_FIELDS =
+ "Only one type of field is allowed for this command at any one time";
+ public static final String MESSAGE_INVALID_CLIENT_PROPERTY = "Client cannot have department, job title, skills, "
+ + "and terms of service fields";
+ public static final String MESSAGE_INVALID_EMPLOYEE_PROPERTY = "Employee cannot have preferences, products, "
+ + "and terms of service fields";
+ public static final String MESSAGE_INVALID_SUPPLIER_PROPERTY = "Supplier cannot have department, job title, "
+ + "skills, and preferences fields";
/**
* Returns an error message indicating the duplicate prefixes.
@@ -37,6 +54,8 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref
public static String format(Person person) {
final StringBuilder builder = new StringBuilder();
builder.append(person.getName())
+ .append("; Id: ")
+ .append(person.getId())
.append("; Phone: ")
.append(person.getPhone())
.append("; Email: ")
@@ -45,6 +64,18 @@ public static String format(Person person) {
.append(person.getAddress())
.append("; Tags: ");
person.getTags().forEach(builder::append);
+ builder.append("; Remark: ").append(person.getRemark());
+ if (person instanceof Client) {
+ builder.append("; Products: ").append(((Client) person).getProducts())
+ .append("; Preferences: ").append(((Client) person).getPreferences());
+ } else if (person instanceof Employee) {
+ builder.append("; Department: ").append(((Employee) person).getDepartment())
+ .append("; Job Title: ").append(((Employee) person).getJobTitle())
+ .append("; Skills: ").append(((Employee) person).getSkills());
+ } else if (person instanceof Supplier) {
+ builder.append("; Products: ").append(((Supplier) person).getProducts())
+ .append("; Terms of Service: ").append(((Supplier) person).getTermsOfService());
+ }
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..a17229a0edb 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -2,10 +2,18 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBTITLE;
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_PREFERENCES;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCTS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SKILLS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TERMSOFSERVICE;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
@@ -26,14 +34,27 @@ public class AddCommand extends Command {
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + PREFIX_ROLE + "ROLE "
+ + "[" + PREFIX_TAG + "TAG] "
+ + "[" + PREFIX_REMARK + "REMARK] "
+ + "[" + PREFIX_DEPARTMENT + "DEPARTMENT] "
+ + "[" + PREFIX_JOBTITLE + "JOBTITLE] "
+ + "[" + PREFIX_PREFERENCES + "PREFERENCES] "
+ + "[" + PREFIX_TERMSOFSERVICE + "TERMSOFSERVICE] "
+ + "[" + PREFIX_PRODUCTS + "PRODUCTS] "
+ + "[" + PREFIX_SKILLS + "SKILLS]\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
+ PREFIX_EMAIL + "johnd@example.com "
+ PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
+ + PREFIX_TAG + "owesMoney "
+ + PREFIX_REMARK + "owes money "
+ + PREFIX_ROLE + "employee "
+ + PREFIX_DEPARTMENT + "HR "
+ + PREFIX_JOBTITLE + "Manager "
+ + PREFIX_SKILLS + "Java";
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";
@@ -81,4 +102,5 @@ public String toString() {
.add("toAdd", toAdd)
.toString();
}
+
}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..98e187caf4a 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -1,9 +1,10 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.ui.MainWindow.handleDestructiveCommands;
-import seedu.address.model.AddressBook;
import seedu.address.model.Model;
+import seedu.address.model.NetConnect;
/**
* Clears the address book.
@@ -11,13 +12,30 @@
public class ClearCommand extends Command {
public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
-
+ public static final String CLEAR_SUCCESS_MESSAGE = "Address book has been cleared!";
+ public static final String CLEAR_CANCELLED_MESSAGE = "Clear command cancelled";
+ private static boolean doNotSkipConfirmation = true;
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.setAddressBook(new AddressBook());
- return new CommandResult(MESSAGE_SUCCESS);
+
+ if (doNotSkipConfirmation) {
+ boolean isConfirmed = handleDestructiveCommands(false, true);
+ if (!isConfirmed) {
+ return new CommandResult(CLEAR_CANCELLED_MESSAGE, false, false);
+ }
+ }
+
+ model.setNetConnect(new NetConnect());
+ return new CommandResult(CLEAR_SUCCESS_MESSAGE, false, false);
+ }
+
+ public static void setUpForTesting() {
+ doNotSkipConfirmation = false;
+ }
+
+ public static void cleanUpAfterTesting() {
+ doNotSkipConfirmation = true;
}
}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 249b6072d0d..dddbcfad29e 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -13,10 +13,14 @@ public class CommandResult {
private final String feedbackToUser;
- /** Help information should be shown to the user. */
+ /**
+ * Help information should be shown to the user.
+ */
private final boolean showHelp;
- /** The application should exit. */
+ /**
+ * The application should exit.
+ */
private final boolean exit;
/**
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..c279f2034f2 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -1,15 +1,20 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ID;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.ui.MainWindow.handleDestructiveCommands;
-import java.util.List;
+import java.util.Objects;
-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.Id;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.person.filter.NetConnectPredicate;
/**
* Deletes a person identified using it's displayed index from the address book.
@@ -19,32 +24,103 @@ public class DeleteCommand extends Command {
public static final String COMMAND_WORD = "delete";
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
+ + ": Deletes the person with the specified id or name\n"
+ + "Either only id or name must be provided\n"
+ + "Name has to be an exact match, but is case-insensitive\n"
+ + "Parameters: "
+ + PREFIX_ID + "[ID] "
+ + PREFIX_NAME + "[NAME]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_ID + "1 "
+ + "or " + COMMAND_WORD + " n/John";
+ public static final String MESSAGE_DUPLICATE_NAME_USAGE = "%1d %2$s found!\n"
+ + "Use id to delete instead\n"
+ + COMMAND_WORD + ": Deletes the person with the specified id\n"
+ + "Parameters: "
+ + PREFIX_ID + "[ID]\n"
+ + "Example: " + COMMAND_WORD + " i/1";
public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_DELETE_CANCELLED = "Delete cancelled";
+ private static boolean doNotSkipConfirmation = true;
+ private final Id targetId;
+ private final Name targetName;
+
+ private DeleteCommand(Id targetId, Name name) {
+ this.targetId = targetId;
+ this.targetName = name;
+ }
- private final Index targetIndex;
+ /**
+ * Factory method to create a {@code DeleteCommand} that deletes a Person
+ * by the given id.
+ *
+ * @param id The id of the {@code Person} to be deleted
+ * @return {@code DeleteCommand} to delete by id
+ */
+ public static DeleteCommand byId(Id id) {
+ requireNonNull(id);
+ return new DeleteCommand(id, null);
+ }
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
+ /**
+ * Factory method to create a {@code DeleteCommand} that deletes a Person
+ * by the given name.
+ *
+ * @param name The name of the {@code Person} to be deleted
+ * @return {@code DeleteCommand} to delete by name
+ */
+ public static DeleteCommand byName(Name name) {
+ requireNonNull(name);
+ return new DeleteCommand(null, name);
}
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ assert targetId != null ^ targetName != null;
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ if (doNotSkipConfirmation) {
+ boolean isConfirmed = handleDestructiveCommands(true, false);
+ if (!isConfirmed) {
+ return new CommandResult(MESSAGE_DELETE_CANCELLED, false, false);
+ }
}
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+ Person personToDelete = getPersonToDelete(model);
+
+ boolean showList = !model.getFilteredPersonList().contains(personToDelete);
model.deletePerson(personToDelete);
+ if (showList) {
+ model.clearFilter();
+ }
return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
}
+ private Person getPersonToDelete(Model model) throws CommandException {
+ assert targetId != null ^ targetName != null;
+
+ if (targetId != null) {
+ if (!model.hasId(targetId)) {
+ model.clearFilter();
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_ID, targetId.value));
+ }
+ return model.getPersonById(targetId);
+ } else {
+ int count = model.countPersonsWithName(targetName);
+ if (count < 1) {
+ model.clearFilter();
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_NAME, targetName.fullName));
+ }
+ if (count > 1) {
+ model.updateFilteredList(NetConnectPredicate.box(
+ p -> p.getName().fullName.equalsIgnoreCase(targetName.fullName)));
+ throw new CommandException(String.format(MESSAGE_DUPLICATE_NAME_USAGE, count, targetName.fullName));
+ }
+ return model.getPersonByName(targetName);
+ }
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -57,13 +133,23 @@ public boolean equals(Object other) {
}
DeleteCommand otherDeleteCommand = (DeleteCommand) other;
- return targetIndex.equals(otherDeleteCommand.targetIndex);
+ return Objects.equals(targetId, otherDeleteCommand.targetId)
+ && Objects.equals(targetName, otherDeleteCommand.targetName);
+ }
+
+ public static void setUpForTesting() {
+ doNotSkipConfirmation = false;
+ }
+
+ public static void cleanUpAfterTesting() {
+ doNotSkipConfirmation = true;
}
@Override
public String toString() {
return new ToStringBuilder(this)
- .add("targetIndex", targetIndex)
+ .add("targetId", targetId)
+ .add("targetName", targetName)
.toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..2b01b3aece9 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -2,30 +2,45 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ID;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBTITLE;
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_PREFERENCES;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCTS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SKILLS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TERMSOFSERVICE;
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;
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.Address;
+import seedu.address.model.person.Client;
+import seedu.address.model.person.Department;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Employee;
+import seedu.address.model.person.Id;
+import seedu.address.model.person.JobTitle;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Products;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Skills;
+import seedu.address.model.person.Supplier;
+import seedu.address.model.person.TermsOfService;
import seedu.address.model.tag.Tag;
/**
@@ -35,16 +50,25 @@ public class EditCommand extends Command {
public static final String COMMAND_WORD = "edit";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Edits the details of the person with the specified id. "
+ "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
+ + "Parameters: "
+ + PREFIX_ID + "ID "
+ "[" + PREFIX_NAME + "NAME] "
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " 1 "
+ + "[" + PREFIX_TAG + "TAG]..."
+ + "[" + PREFIX_REMARK + "REMARK] "
+ + "[" + PREFIX_DEPARTMENT + "DEPARTMENT] "
+ + "[" + PREFIX_JOBTITLE + "JOBTITLE] "
+ + "[" + PREFIX_SKILLS + "SKILLS] "
+ + "[" + PREFIX_PRODUCTS + "PRODUCTS] "
+ + "[" + PREFIX_TERMSOFSERVICE + "TERMSOFSERVICE] "
+ + "[" + PREFIX_PREFERENCES + "PREFERENCES]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_ID + "1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
@@ -52,31 +76,30 @@ public class EditCommand extends Command {
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.";
- private final Index index;
+ private final Id id;
private final EditPersonDescriptor editPersonDescriptor;
/**
- * @param index of the person in the filtered person list to edit
+ * @param id of the person in the NetConnect to edit
* @param editPersonDescriptor details to edit the person with
*/
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
+ public EditCommand(Id id, EditPersonDescriptor editPersonDescriptor) {
+ requireNonNull(id);
requireNonNull(editPersonDescriptor);
- this.index = index;
+ this.id = id;
this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
}
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ if (!model.hasId(id)) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_ID, id.value));
}
- Person personToEdit = lastShownList.get(index.getZeroBased());
+ Person personToEdit = model.getPersonById(id);
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
@@ -84,7 +107,8 @@ public CommandResult execute(Model model) throws CommandException {
}
model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.clearFilter();
+
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
}
@@ -92,16 +116,58 @@ public CommandResult execute(Model model) throws CommandException {
* Creates and returns a {@code Person} with the details of {@code personToEdit}
* edited with {@code editPersonDescriptor}.
*/
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
+ static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor)
+ throws CommandException {
assert personToEdit != null;
+ Id idOfPerson = personToEdit.getId();
Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Remark updatedRemark = editPersonDescriptor.getRemark().orElse(personToEdit.getRemark());
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ if (personToEdit instanceof Client) {
+ if (editPersonDescriptor.getDepartment().isPresent()
+ || editPersonDescriptor.getJobTitle().isPresent()
+ || editPersonDescriptor.getSkills().isPresent()
+ || editPersonDescriptor.getTermsOfService().isPresent()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CLIENT_PROPERTY);
+ }
+ String updatedPreferences = editPersonDescriptor.getPreferences()
+ .orElse(((Client) personToEdit).getPreferences());
+ Products updatedProducts = editPersonDescriptor.getProducts().orElse(((Client) personToEdit).getProducts());
+ return new Client(idOfPerson, updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark,
+ updatedTags, updatedProducts, updatedPreferences);
+ } else if (personToEdit instanceof Employee) {
+ if (editPersonDescriptor.getPreferences().isPresent() || editPersonDescriptor.getProducts().isPresent()
+ || editPersonDescriptor.getTermsOfService().isPresent()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EMPLOYEE_PROPERTY);
+ }
+ Department updatedDepartment = editPersonDescriptor.getDepartment()
+ .orElse(((Employee) personToEdit).getDepartment());
+ JobTitle updatedJobTitle = editPersonDescriptor.getJobTitle()
+ .orElse(((Employee) personToEdit).getJobTitle());
+ Skills updatedSkills = editPersonDescriptor.getSkills().orElse(((Employee) personToEdit).getSkills());
+ return new Employee(idOfPerson, updatedName, updatedPhone, updatedEmail, updatedAddress,
+ updatedRemark, updatedTags, updatedDepartment, updatedJobTitle, updatedSkills);
+ } else if (personToEdit instanceof Supplier) {
+ if (editPersonDescriptor.getDepartment().isPresent()
+ || editPersonDescriptor.getJobTitle().isPresent()
+ || editPersonDescriptor.getSkills().isPresent()
+ || editPersonDescriptor.getPreferences().isPresent()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_SUPPLIER_PROPERTY);
+ }
+ TermsOfService updatedTermsOfService = editPersonDescriptor.getTermsOfService()
+ .orElse(((Supplier) personToEdit).getTermsOfService());
+ Products updatedProducts = editPersonDescriptor.getProducts()
+ .orElse(((Supplier) personToEdit).getProducts());
+ return new Supplier(idOfPerson, updatedName, updatedPhone, updatedEmail, updatedAddress,
+ updatedRemark, updatedTags, updatedProducts, updatedTermsOfService);
+ }
+
+ return personToEdit;
}
@Override
@@ -116,14 +182,14 @@ public boolean equals(Object other) {
}
EditCommand otherEditCommand = (EditCommand) other;
- return index.equals(otherEditCommand.index)
+ return id.equals(otherEditCommand.id)
&& editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor);
}
@Override
public String toString() {
return new ToStringBuilder(this)
- .add("index", index)
+ .add("id", id)
.add("editPersonDescriptor", editPersonDescriptor)
.toString();
}
@@ -138,6 +204,13 @@ public static class EditPersonDescriptor {
private Email email;
private Address address;
private Set tags;
+ private Remark remark;
+ private Department department;
+ private JobTitle jobTitle;
+ private Skills skills;
+ private Products products;
+ private TermsOfService termsOfService;
+ private String preferences;
public EditPersonDescriptor() {}
@@ -151,13 +224,21 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setEmail(toCopy.email);
setAddress(toCopy.address);
setTags(toCopy.tags);
+ setRemark(toCopy.remark);
+ setDepartment(toCopy.department);
+ setJobTitle(toCopy.jobTitle);
+ setSkills(toCopy.skills);
+ setProducts(toCopy.products);
+ setTermsOfService(toCopy.termsOfService);
+ setPreferences(toCopy.preferences);
}
/**
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, tags, remark, department, jobTitle, skills,
+ products, termsOfService, preferences);
}
public void setName(Name name) {
@@ -209,6 +290,62 @@ public Optional> getTags() {
return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
+ public void setRemark(Remark remark) {
+ this.remark = remark;
+ }
+
+ public Optional getRemark() {
+ return Optional.ofNullable(remark);
+ }
+
+ public void setDepartment(Department department) {
+ this.department = department;
+ }
+
+ public Optional getDepartment() {
+ return Optional.ofNullable(department);
+ }
+
+ public void setJobTitle(JobTitle jobTitle) {
+ this.jobTitle = jobTitle;
+ }
+
+ public Optional getJobTitle() {
+ return Optional.ofNullable(jobTitle);
+ }
+
+ public void setSkills(Skills skills) {
+ this.skills = skills;
+ }
+
+ public Optional getSkills() {
+ return Optional.ofNullable(skills);
+ }
+
+ public void setProducts(Products products) {
+ this.products = products;
+ }
+
+ public Optional getProducts() {
+ return Optional.ofNullable(products);
+ }
+
+ public void setTermsOfService(TermsOfService termsOfService) {
+ this.termsOfService = termsOfService;
+ }
+
+ public Optional getTermsOfService() {
+ return Optional.ofNullable(termsOfService);
+ }
+
+ public void setPreferences(String preferences) {
+ this.preferences = preferences;
+ }
+
+ public Optional getPreferences() {
+ return Optional.ofNullable(preferences);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -225,7 +362,14 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(tags, otherEditPersonDescriptor.tags)
+ && Objects.equals(remark, otherEditPersonDescriptor.remark)
+ && Objects.equals(department, otherEditPersonDescriptor.department)
+ && Objects.equals(jobTitle, otherEditPersonDescriptor.jobTitle)
+ && Objects.equals(skills, otherEditPersonDescriptor.skills)
+ && Objects.equals(products, otherEditPersonDescriptor.products)
+ && Objects.equals(termsOfService, otherEditPersonDescriptor.termsOfService)
+ && Objects.equals(preferences, otherEditPersonDescriptor.preferences);
}
@Override
@@ -236,6 +380,13 @@ public String toString() {
.add("email", email)
.add("address", address)
.add("tags", tags)
+ .add("remark", remark)
+ .add("department", department)
+ .add("jobTitle", jobTitle)
+ .add("skills", skills)
+ .add("products", products)
+ .add("termsOfService", termsOfService)
+ .add("preferences", preferences)
.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..1d47bb6261b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ExportCommand.java
@@ -0,0 +1,78 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Export the address book data as a CSV file.
+ */
+public class ExportCommand extends Command {
+
+ public static final String COMMAND_WORD = "export";
+
+ public static final String MESSAGE_SUCCESS = "Success! Your contact has been exported as ";
+ public static final String MESSAGE_FAILURE_EMPTY_LIST = "Failed to export contacts. The contact list is empty.";
+ public static final String MESSAGE_FAILURE_FILE_WRITE = "Failed to export contacts due to file write error.";
+
+ public static final String MESSAGE_USAGE = "Usage: export [FILENAME]\n"
+ + "Exports the contact list as a CSV file with the specified filename.\n"
+ + "If no filename is provided, the default filename 'contacts.csv' will be used.\n"
+ + "Example: export my_contacts.csv";
+
+ public final String filename;
+
+ /**
+ * Creates an ExportCommand to export contact as CV file
+ */
+ public ExportCommand() {
+ this.filename = "contacts.csv";
+ }
+
+ public ExportCommand(String filename) {
+ this.filename = filename;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.getFilteredPersonList().isEmpty()) {
+ throw new CommandException(MESSAGE_FAILURE_EMPTY_LIST);
+ }
+
+ boolean isSuccessful = model.exportCsv(filename);
+
+ if (!isSuccessful) {
+ throw new CommandException(MESSAGE_FAILURE_FILE_WRITE);
+ }
+
+ return new CommandResult(MESSAGE_SUCCESS + filename);
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof seedu.address.logic.commands.ExportCommand)) {
+ return false;
+ }
+
+ ExportCommand otherAddCommand = (ExportCommand) other;
+ return filename.equals(otherAddCommand.filename);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("filename", filename)
+ .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..14efdc9f8fb 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -1,37 +1,58 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+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_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.filter.NetConnectPredicate;
/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
+ * Finds and lists all persons in NetConnect whose information matches any of the given arguments.
+ * Keyword matching is case-insensitive.
+ * Find command supports finding by: name, tag, role, and remark.
*/
public class FindCommand extends Command {
public static final String COMMAND_WORD = "find";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Finds all persons whose information matches any of the given arguments.\n"
+ + "Only one type of argument can be given per " + COMMAND_WORD + " command.\n"
+ + "Name, phone, tag and role cannot be empty. "
+ + "Remark can be empty to find persons with no remarks.\n"
+ + "Parameters: "
+ + "[" + PREFIX_NAME + "NAME]... "
+ + "[" + PREFIX_PHONE + "PHONE]..."
+ + "[" + PREFIX_TAG + "TAG]... "
+ + "[" + PREFIX_ROLE + "ROLE]... "
+ + "[" + PREFIX_REMARK + "REMARK]... \n"
+ + "Examples: \n"
+ + COMMAND_WORD + " n/alice n/bob n/charlie\n"
+ + COMMAND_WORD + " p/91278539 p/83489532\n"
+ + COMMAND_WORD + " t/friends t/colleagues\n"
+ + COMMAND_WORD + " role/client\n"
+ + COMMAND_WORD + " r/owes money r/quarterly report";
- private final NameContainsKeywordsPredicate predicate;
+ private final NetConnectPredicate predicate;
- public FindCommand(NameContainsKeywordsPredicate predicate) {
+ public FindCommand(NetConnectPredicate predicate) {
this.predicate = predicate;
}
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ model.stackFilters(predicate);
+ String output = String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())
+ + "\n" + model.printFilters();
+ return new CommandResult(output);
}
@Override
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..735a91aa075 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -1,7 +1,6 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import seedu.address.model.Model;
@@ -18,7 +17,7 @@ public class ListCommand extends Command {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.clearFilter();
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/RelateCommand.java b/src/main/java/seedu/address/logic/commands/RelateCommand.java
new file mode 100644
index 00000000000..cefb6119778
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RelateCommand.java
@@ -0,0 +1,94 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+
+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.Id;
+import seedu.address.model.person.filter.IdContainsDigitsPredicate;
+import seedu.address.model.util.IdTuple;
+
+/**
+ * Finds and lists all persons in address book whose ID contains any of the argument IDs.
+ */
+public class RelateCommand extends Command {
+
+ public static final String COMMAND_WORD = "relate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": relates strictly two existing persons in NetConnect "
+ + "using their unique id.\n"
+ + "Both unique IDs must exist.\n"
+ + "Parameters: i/ID_1 i/ID_2\n"
+ + "Example: " + COMMAND_WORD + " i/4 i/12";
+
+ private final Id firstPersonId;
+ private final Id secondPersonId;
+
+ /**
+ * Creates a RelateCommand to relate the two specified {@code IdContainsDigitsPredicate}
+ */
+ public RelateCommand(Id firstPersonId, Id secondPersonId) {
+ requireAllNonNull(firstPersonId, secondPersonId);
+
+ this.firstPersonId = firstPersonId;
+ this.secondPersonId = secondPersonId;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (firstPersonId.equals(secondPersonId)) {
+ model.clearFilter();
+ throw new CommandException(Messages.MESSAGE_CANNOT_RELATE_ITSELF);
+ }
+ // actual execution occurs here
+ if (!model.hasId(firstPersonId)) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_ID, firstPersonId.value));
+ } else if (!model.hasId(secondPersonId)) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_ID, secondPersonId.value));
+ }
+
+ IdTuple tuple = new IdTuple(firstPersonId, secondPersonId);
+ if (model.hasRelatedIdTuple(tuple)) {
+ throw new CommandException(Messages.MESSAGE_RELATION_EXISTS);
+ }
+
+ model.addRelatedIdTuple(tuple);
+ model.updateFilteredList(new IdContainsDigitsPredicate(List.of(firstPersonId.value, secondPersonId.value)));
+
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof RelateCommand)) {
+ return false;
+ }
+
+ RelateCommand otherFindNumCommand = (RelateCommand) other;
+ return (this.firstPersonId.equals(otherFindNumCommand.firstPersonId)
+ && this.secondPersonId.equals(otherFindNumCommand.secondPersonId))
+ || (this.firstPersonId.equals(otherFindNumCommand.secondPersonId)
+ && this.secondPersonId.equals(otherFindNumCommand.firstPersonId));
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("first id", firstPersonId)
+ .add("second id", secondPersonId)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ShowRelatedCommand.java b/src/main/java/seedu/address/logic/commands/ShowRelatedCommand.java
new file mode 100644
index 00000000000..aac2bf1c86c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ShowRelatedCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ID;
+
+import java.util.List;
+
+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.Id;
+import seedu.address.model.person.filter.IdContainsDigitsPredicate;
+import seedu.address.model.util.RelatedList;
+
+/**
+ * Finds and lists all persons in address book related to the person with the specified id.
+ */
+public class ShowRelatedCommand extends Command {
+
+ public static final String COMMAND_WORD = "showrelated";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Finds all persons related to person with the specified id.\n"
+ + "Parameters: i/ID\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_ID + "1";
+
+ private final Id id;
+
+ public ShowRelatedCommand(Id id) {
+ this.id = id;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ RelatedList relatedList = model.getRelatedIdTuples();
+
+ List relatedIds = relatedList.getAllRelatedIds(relatedList, id);
+ IdContainsDigitsPredicate predicate = new IdContainsDigitsPredicate(relatedIds);
+
+ model.updateFilteredList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ShowRelatedCommand)) {
+ return false;
+ }
+
+ ShowRelatedCommand otherCommand = (ShowRelatedCommand) other;
+ return id.equals(otherCommand.id);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("id", id)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnrelateCommand.java b/src/main/java/seedu/address/logic/commands/UnrelateCommand.java
new file mode 100644
index 00000000000..e879cd134b1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnrelateCommand.java
@@ -0,0 +1,97 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+
+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.Id;
+import seedu.address.model.person.filter.IdContainsDigitsPredicate;
+import seedu.address.model.util.IdTuple;
+
+/**
+ * Represents a command to unrelate two persons in NetConnect using either their unique id or name.
+ * The unique IDs or names provided must exist.
+ * Parameters: [i/ID_1] [i/ID_2]
+ * Example: unrelate i/4 i/12
+ */
+public class UnrelateCommand extends Command {
+ public static final String COMMAND_WORD = "unrelate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Unrelates the two specified persons in NetConnect using either their unique id, OR, name.\n"
+ + "The unique IDs or names provided must exist.\n"
+ + "Parameters: i/ID_1 i/ID_2\n"
+ + "Example: " + COMMAND_WORD + " i/4 i/12";
+
+ private final Id firstPersonId;
+ private final Id secondPersonId;
+
+ /**
+ * Creates a UnrelateCommand to unrelate the two specified {@code IdContainsDigitsPredicate}
+ */
+ public UnrelateCommand(Id firstPersonId, Id secondPersonId) {
+ requireAllNonNull(firstPersonId, secondPersonId);
+
+ this.firstPersonId = firstPersonId;
+ this.secondPersonId = secondPersonId;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (firstPersonId.equals(secondPersonId)) {
+ model.clearFilter();
+ throw new CommandException(Messages.MESSAGE_CANNOT_UNRELATE_ITSELF);
+ }
+ // actual execution occurs here
+ if (!model.hasId(firstPersonId)) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_ID, firstPersonId.value));
+ } else if (!model.hasId(secondPersonId)) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_PERSON_ID, secondPersonId.value));
+ }
+
+ IdTuple tuple = new IdTuple(firstPersonId, secondPersonId);
+
+ if (!model.hasRelatedIdTuple(tuple)) {
+ throw new CommandException(Messages.MESSAGE_RELATION_NOT_EXISTS);
+ } else {
+ model.removeRelatedIdTuple(tuple);
+ }
+
+ model.updateFilteredList(new IdContainsDigitsPredicate(List.of(firstPersonId.value, secondPersonId.value)));
+
+ return new CommandResult(String.format(Messages.MESSAGE_UNRELATION_SUCCESS, tuple));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UnrelateCommand)) {
+ return false;
+ }
+
+ UnrelateCommand otherFindNumCommand = (UnrelateCommand) other;
+ return (this.firstPersonId.equals(otherFindNumCommand.firstPersonId)
+ && this.secondPersonId.equals(otherFindNumCommand.secondPersonId))
+ || (this.firstPersonId.equals(otherFindNumCommand.secondPersonId)
+ && this.secondPersonId.equals(otherFindNumCommand.firstPersonId));
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("first id", firstPersonId)
+ .add("second id", secondPersonId)
+ .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..23ab1443b4e 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -2,21 +2,39 @@
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_DEPARTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBTITLE;
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_PREFERENCES;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCTS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SKILLS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TERMSOFSERVICE;
import java.util.Set;
import java.util.stream.Stream;
+import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Client;
+import seedu.address.model.person.Department;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Employee;
+import seedu.address.model.person.JobTitle;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Products;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Skills;
+import seedu.address.model.person.Supplier;
+import seedu.address.model.person.TermsOfService;
import seedu.address.model.tag.Tag;
/**
@@ -27,13 +45,15 @@ public class AddCommandParser implements Parser {
/**
* Parses the given {@code String} of arguments in the context of the AddCommand
* and returns an AddCommand object for execution.
+ *
* @throws ParseException if the user input does not conform the expected format
*/
public AddCommand parse(String args) throws ParseException {
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_TAG, PREFIX_REMARK, PREFIX_ROLE, PREFIX_PREFERENCES, PREFIX_PRODUCTS,
+ PREFIX_DEPARTMENT, PREFIX_JOBTITLE, PREFIX_TERMSOFSERVICE, PREFIX_SKILLS);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ROLE)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
@@ -43,11 +63,14 @@ public AddCommand parse(String args) throws ParseException {
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ Remark remark = argMultimap.getValue(PREFIX_REMARK).isPresent()
+ ? ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).get())
+ : new Remark("");
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ String role = ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get());
- Person person = new Person(name, phone, email, address, tagList);
-
- return new AddCommand(person);
+ return new AddCommand(
+ createPerson(role, name, phone, email, address, remark, tagList, argMultimap));
}
/**
@@ -58,4 +81,66 @@ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Pre
return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
}
+ /**
+ * Returns true if any of the prefixes are present in the given {@code ArgumentMultimap}.
+ */
+ private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ private static Person createPerson(
+ String role, Name name, Phone phone, Email email,
+ Address address, Remark remark, Set tagList, ArgumentMultimap argMultimap)
+ throws ParseException {
+ switch (role.toLowerCase()) {
+ case "client":
+ if (anyPrefixesPresent(
+ argMultimap, PREFIX_DEPARTMENT, PREFIX_JOBTITLE, PREFIX_SKILLS, PREFIX_TERMSOFSERVICE)) {
+ throw new ParseException(Messages.MESSAGE_INVALID_CLIENT_PROPERTY);
+ }
+ String preferences = "";
+ Products products = new Products();
+ if (argMultimap.getValue(PREFIX_PREFERENCES).isPresent()) {
+ preferences = ParserUtil.parsePreferences(argMultimap.getValue(PREFIX_PREFERENCES).get());
+ }
+ if (argMultimap.getValue(PREFIX_PRODUCTS).isPresent()) {
+ products = ParserUtil.parseProducts(argMultimap.getAllValues(PREFIX_PRODUCTS));
+ }
+ return new Client(name, phone, email, address, remark, tagList, products, preferences);
+ case "employee":
+ if (anyPrefixesPresent(argMultimap, PREFIX_PRODUCTS, PREFIX_PREFERENCES, PREFIX_TERMSOFSERVICE)) {
+ throw new ParseException(Messages.MESSAGE_INVALID_EMPLOYEE_PROPERTY);
+ }
+ Department department = new Department();
+ JobTitle jobTitle = new JobTitle();
+ Skills skills = new Skills();
+ if (argMultimap.getValue(PREFIX_DEPARTMENT).isPresent()) {
+ department = ParserUtil.parseDepartment(argMultimap.getValue(PREFIX_DEPARTMENT).get());
+ }
+ if (argMultimap.getValue(PREFIX_JOBTITLE).isPresent()) {
+ jobTitle = ParserUtil.parseJobTitle(argMultimap.getValue(PREFIX_JOBTITLE).get());
+ }
+ if (argMultimap.getValue(PREFIX_SKILLS).isPresent()) {
+ skills = ParserUtil.parseSkills(argMultimap.getAllValues(PREFIX_SKILLS));
+ }
+ return new Employee(name, phone, email, address, remark, tagList, department, jobTitle, skills);
+ case "supplier":
+ if (anyPrefixesPresent(
+ argMultimap, PREFIX_DEPARTMENT, PREFIX_JOBTITLE, PREFIX_SKILLS, PREFIX_PREFERENCES)) {
+ throw new ParseException(Messages.MESSAGE_INVALID_SUPPLIER_PROPERTY);
+ }
+ TermsOfService termsOfService = new TermsOfService();
+ Products supplierProducts = new Products();
+ if (argMultimap.getValue(PREFIX_TERMSOFSERVICE).isPresent()) {
+ termsOfService = ParserUtil.parseTermsOfService(argMultimap.getValue(PREFIX_TERMSOFSERVICE).get());
+ }
+ if (argMultimap.getValue(PREFIX_PRODUCTS).isPresent()) {
+ supplierProducts = ParserUtil.parseProducts(argMultimap.getAllValues(PREFIX_PRODUCTS));
+ }
+ return new Supplier(name, phone, email, address, remark, tagList, supplierProducts, termsOfService);
+ default:
+ throw new ParseException("Invalid role specified. Must be one of: client, employee, supplier.");
+ }
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
index 21e26887a83..ec4ea8559ee 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
@@ -19,7 +19,9 @@
*/
public class ArgumentMultimap {
- /** Prefixes mapped to their respective arguments**/
+ /**
+ * Prefixes mapped to their respective arguments
+ **/
private final Map> argMultimap = new HashMap<>();
/**
@@ -75,4 +77,14 @@ public void verifyNoDuplicatePrefixesFor(Prefix... prefixes) throws ParseExcepti
throw new ParseException(Messages.getErrorMessageForDuplicatePrefixes(duplicatedPrefixes));
}
}
+
+ /**
+ * Throws a {@code ParseException} if more than one prefix is given in the arguments.
+ */
+ public void verifyOnlyOnePrefix() throws ParseException {
+ // check more than 2 since preamble is added into the multimap
+ if (argMultimap.values().size() > 2) {
+ throw new ParseException(Messages.MESSAGE_NON_UNIQUE_FIELDS);
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..a9047a42136 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
@@ -7,11 +7,11 @@
/**
* Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
* 2. Leading and trailing whitespaces of an argument value will be discarded.
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/}
- * in the above example.
+ * in the above example.
*/
public class ArgumentTokenizer {
@@ -21,7 +21,7 @@ public class ArgumentTokenizer {
*
* @param argsString Arguments string of the form: {@code preamble value value ...}
* @param prefixes Prefixes to tokenize the arguments string with
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @return ArgumentMultimap object that maps prefixes to their arguments
*/
public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
List positions = findAllPrefixPositions(argsString, prefixes);
@@ -33,7 +33,7 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
*
* @param argsString Arguments string of the form: {@code preamble value value ...}
* @param prefixes Prefixes to find in the arguments string
- * @return List of zero-based prefix positions in the given arguments string
+ * @return List of zero-based prefix positions in the given arguments string
*/
private static List findAllPrefixPositions(String argsString, Prefix... prefixes) {
return Arrays.stream(prefixes)
@@ -62,7 +62,7 @@ private static List findPrefixPositions(String argsString, Prefi
* {@code argsString} starting from index {@code fromIndex}. An occurrence
* is valid if there is a whitespace before {@code prefix}. Returns -1 if no
* such occurrence can be found.
- *
+ *
* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and
* {@code fromIndex} = 0, this method returns -1 as there are no valid
* occurrences of "p/" with whitespace before it. However, if
@@ -82,7 +82,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from
*
* @param argsString Arguments string of the form: {@code preamble value value ...}
* @param prefixPositions Zero-based positions of all prefixes in {@code argsString}
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @return ArgumentMultimap object that maps prefixes to their arguments
*/
private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) {
@@ -114,8 +114,8 @@ private static ArgumentMultimap extractArguments(String argsString, List {
/**
* 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
+ *
+ * @throws ParseException if the user input does not conform to the expected format
*/
public DeleteCommand parse(String args) throws ParseException {
- try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
- } catch (ParseException pe) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
- }
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ID, PREFIX_NAME);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_ID, PREFIX_NAME);
+
+ return createDeleteCommand(argMultimap);
}
+ /**
+ * Creates a {@code DeleteCommand} from the values in the given {@code ArgumentMultimap}.
+ * @param argMultimap The {@code ArgumentMultimap} containing the users given values
+ * @throws ParseException if the user input does not conform to the expected format
+ */
+ private static DeleteCommand createDeleteCommand(ArgumentMultimap argMultimap) throws ParseException {
+ boolean hasId = argMultimap.getValue(PREFIX_ID).isPresent();
+ boolean hasName = argMultimap.getValue(PREFIX_NAME).isPresent();
+
+ if (hasId && hasName) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ } else if (hasId) {
+ Id id = ParserUtil.parseId(argMultimap.getValue(PREFIX_ID).get());
+ return DeleteCommand.byId(id);
+ } else if (hasName) {
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ return DeleteCommand.byName(name);
+ } else {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..138d1f98021 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -3,20 +3,29 @@
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_DEPARTMENT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ID;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_JOBTITLE;
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_PREFERENCES;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCTS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SKILLS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TERMSOFSERVICE;
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.person.Id;
import seedu.address.model.tag.Tag;
/**
@@ -25,27 +34,49 @@
public class EditCommandParser implements Parser {
/**
- * Parses the given {@code String} of arguments in the context of the EditCommand
+ * Parses the given {@code String} of arguments in the context of the
+ * EditCommand
* and returns an EditCommand object for execution.
+ *
* @throws ParseException if the user input does not conform the expected format
*/
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ID, PREFIX_NAME, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, PREFIX_PREFERENCES, PREFIX_PRODUCTS, PREFIX_DEPARTMENT,
+ PREFIX_JOBTITLE, PREFIX_SKILLS, PREFIX_ROLE, PREFIX_TERMSOFSERVICE, PREFIX_REMARK);
- Index index;
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_ID, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ if (argMultimap.getValue(PREFIX_ROLE).isPresent()) {
+ throw new ParseException("Role cannot be edited");
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ if (argMultimap.getValue(PREFIX_ID).isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE));
+ }
+
+ Id id = ParserUtil.parseId(argMultimap.getValue(PREFIX_ID).get());
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ parseCommonFields(argMultimap, editPersonDescriptor);
+
+ parseOptionalFields(argMultimap, editPersonDescriptor);
+
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
+
+ if (!editPersonDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditCommand(id, editPersonDescriptor);
+ }
+
+ private void parseCommonFields(ArgumentMultimap argMultimap, EditPersonDescriptor editPersonDescriptor)
+ throws ParseException {
+ // Parsing common fields that are applicable to all roles
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
@@ -58,18 +89,41 @@ public EditCommand parse(String args) throws ParseException {
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()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ if (argMultimap.getValue(PREFIX_REMARK).isPresent()) {
+ editPersonDescriptor.setRemark(ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).get()));
}
+ }
- return new EditCommand(index, editPersonDescriptor);
+ void parseOptionalFields(ArgumentMultimap argMultimap, EditPersonDescriptor editPersonDescriptor)
+ throws ParseException {
+ if (argMultimap.getValue(PREFIX_PREFERENCES).isPresent()) {
+ editPersonDescriptor
+ .setPreferences(ParserUtil.parsePreferences(argMultimap.getValue(PREFIX_PREFERENCES).get()));
+ }
+ if (argMultimap.getValue(PREFIX_PRODUCTS).isPresent()) {
+ editPersonDescriptor.setProducts(ParserUtil.parseProducts(argMultimap.getAllValues(PREFIX_PRODUCTS)));
+ }
+ if (argMultimap.getValue(PREFIX_DEPARTMENT).isPresent()) {
+ editPersonDescriptor
+ .setDepartment(ParserUtil.parseDepartment(argMultimap.getValue(PREFIX_DEPARTMENT).get()));
+ }
+ if (argMultimap.getValue(PREFIX_JOBTITLE).isPresent()) {
+ editPersonDescriptor.setJobTitle(ParserUtil.parseJobTitle(argMultimap.getValue(PREFIX_JOBTITLE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_SKILLS).isPresent()) {
+ editPersonDescriptor.setSkills(ParserUtil.parseSkills(argMultimap.getAllValues(PREFIX_SKILLS)));
+ }
+ if (argMultimap.getValue(PREFIX_TERMSOFSERVICE).isPresent()) {
+ editPersonDescriptor.setTermsOfService(
+ ParserUtil.parseTermsOfService(argMultimap.getValue(PREFIX_TERMSOFSERVICE).get()));
+ }
}
/**
- * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
- * If {@code tags} contain only one element which is an empty string, it will be parsed into a
+ * Parses {@code Collection tags} into a {@code Set} if
+ * {@code tags} is non-empty.
+ * If {@code tags} contain only one element which is an empty string, it will be
+ * parsed into a
* {@code Set} containing zero tags.
*/
private Optional> parseTagsForEdit(Collection tags) throws ParseException {
diff --git a/src/main/java/seedu/address/logic/parser/ExportCommandParser.java b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java
new file mode 100644
index 00000000000..42c2d16a995
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java
@@ -0,0 +1,83 @@
+package seedu.address.logic.parser;
+
+import seedu.address.logic.commands.ExportCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ExportCommand object
+ */
+public class ExportCommandParser implements Parser {
+ public static final String MESSAGE_NO_EXTENSION = "Error: Invalid filename. "
+ + "Please provide a valid filename with the .csv extension.";
+
+ public static final String MESSAGE_SPACE_DETECTED = "Error: Invalid filename. "
+ + "Please ensure the filename does not contain leading, trailing spaces, or end with a period.";
+
+ public static final String MESSAGE_NO_SPECIAL_CHARACTER = "Error: Invalid filename. "
+ + "Please ensure the filename only contain alphanumeric, underscores, periods and hyphens";
+
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ExportCommand
+ * and returns an ExportCommand object for execution.
+ *
+ * @param args The user input string containing the filename for export.
+ * @return An ExportCommand object representing the parsed command.
+ * @throws ParseException if the user input does not conform to the expected format
+ * or if the filename is invalid.
+ */
+ public ExportCommand parse(String args) throws ParseException {
+ if (args.isEmpty()) {
+ return new ExportCommand();
+ }
+ String trimmedArgs = args.trim();
+ if (!isValidFilename(trimmedArgs)) {
+ throw new ParseException(getErrorMessage(trimmedArgs));
+ }
+ return new ExportCommand(trimmedArgs);
+ }
+
+ /**
+ * The isValidFilename function in Java checks if a given filename ends with ".csv", does not start or end with
+ * spaces or a period, and only contains alphanumeric characters, underscores, hyphens, and periods.
+ * @param filename The `isValidFilename` method checks if a given filename is valid based on the following
+ * criteria:
+ * @return The method `isValidFilename` returns a boolean value indicating whether the input filename is
+ * considered valid based on the specified criteria. It returns `true` if the filename ends with ".csv", does
+ * not start or end with spaces or end with a period, and only contains alphanumeric characters, underscores,
+ * hyphens, and periods. If any of these conditions are not met, it returns `false`.
+ */
+ public boolean isValidFilename(String filename) {
+ // Check if filename ends with ".csv"
+ if (!filename.endsWith(".csv")) {
+ return false;
+ }
+
+ if (filename.startsWith(" ") || filename.endsWith(" ") || filename.endsWith(".")) {
+ return false;
+ }
+
+ if (!filename.matches("[a-zA-Z0-9_\\-.]*")) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public String getErrorMessage(String filename) {
+ if (!filename.endsWith(".csv")) {
+ return MESSAGE_NO_EXTENSION;
+ }
+
+ if (filename.startsWith(" ") || filename.endsWith(" ") || filename.endsWith(".")) {
+ return MESSAGE_SPACE_DETECTED;
+ }
+
+ if (!filename.matches("[a-zA-Z0-9_\\-.]*")) {
+ return MESSAGE_NO_SPECIAL_CHARACTER;
+ }
+
+ return null;
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 2867bde857b..6497742af2b 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -1,12 +1,29 @@
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_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.ParserUtil.isContainSlash;
-import java.util.Arrays;
+import java.util.List;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.filter.NameContainsKeywordsPredicate;
+import seedu.address.model.person.filter.NetConnectPredicate;
+import seedu.address.model.person.filter.PhoneMatchesDigitsPredicate;
+import seedu.address.model.person.filter.RemarkContainsKeywordsPredicate;
+import seedu.address.model.person.filter.RoleMatchesKeywordsPredicate;
+import seedu.address.model.person.filter.TagsContainsKeywordsPredicate;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.util.TestCommandFormatUtil;
/**
* Parses input arguments and creates a new FindCommand object
@@ -16,18 +33,69 @@ public class FindCommandParser implements Parser {
/**
* Parses the given {@code String} of arguments in the context of the FindCommand
* and returns a FindCommand object for execution.
+ *
* @throws ParseException if the user input does not conform the expected format
*/
public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer
+ .tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_TAG, PREFIX_ROLE, PREFIX_REMARK);
+ argMultimap.verifyOnlyOnePrefix();
+
+ return new FindCommand(createPredicate(argMultimap));
+ }
+
+ /**
+ * Creates the matching NetConnectPredicate based on the provided values in
+ * the given {@code argMultimap}.
+ *
+ * @throws ParseException if the given values are not valid
+ */
+ private static NetConnectPredicate createPredicate(
+ ArgumentMultimap argMultimap) throws ParseException {
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ List names = argMultimap.getAllValues(PREFIX_NAME);
+ if (isContainSlash(names)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ if (!names.stream().allMatch(Name::isValidName)) {
+ throw new ParseException(Name.MESSAGE_CONSTRAINTS);
+ }
+ return new NameContainsKeywordsPredicate(names);
+ } else if (argMultimap.getValue(PREFIX_TAG).isPresent()) {
+ List tags = argMultimap.getAllValues(PREFIX_TAG);
+ if (isContainSlash(tags)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ if (!tags.stream().allMatch(Tag::isValidTagName)) {
+ throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
+ }
+ return new TagsContainsKeywordsPredicate(tags);
+ } else if (argMultimap.getValue(PREFIX_ROLE).isPresent()) {
+ List roles = argMultimap.getAllValues(PREFIX_ROLE);
+ if (isContainSlash(roles)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ if (!roles.stream().allMatch(Person::isValidRole)) {
+ throw new ParseException(Person.MESSAGE_ROLE_CONSTRAINTS);
+ }
+ return new RoleMatchesKeywordsPredicate(roles);
+ } else if (argMultimap.getValue(PREFIX_REMARK).isPresent()) {
+ List remarks = argMultimap.getAllValues(PREFIX_REMARK);
+ return new RemarkContainsKeywordsPredicate(remarks);
+ } else if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ List phones = argMultimap.getAllValues(PREFIX_PHONE);
+ if (isContainSlash(phones)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ if (!phones.stream().allMatch(Phone::isValidPhone)) {
+ throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
+ }
+ return new PhoneMatchesDigitsPredicate(phones);
+ } else {
+ // no field provided
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
-
- String[] nameKeywords = trimmedArgs.split("\\s+");
-
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
}
-
}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/NetConnectParser.java
similarity index 78%
rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java
rename to src/main/java/seedu/address/logic/parser/NetConnectParser.java
index 3149ee07e0b..61d4fe1f08a 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/NetConnectParser.java
@@ -14,21 +14,25 @@
import seedu.address.logic.commands.DeleteCommand;
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.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.RelateCommand;
+import seedu.address.logic.commands.ShowRelatedCommand;
+import seedu.address.logic.commands.UnrelateCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
* Parses user input.
*/
-public class AddressBookParser {
+public class NetConnectParser {
/**
* Used for initial separation of command word and args.
*/
private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
- private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class);
+ private static final Logger logger = LogsCenter.getLogger(NetConnectParser.class);
/**
* Parses user input into command for execution.
@@ -46,7 +50,8 @@ public Command parseCommand(String userInput) throws ParseException {
final String commandWord = matcher.group("commandWord");
final String arguments = matcher.group("arguments");
- // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower)
+ // Note to developers: Change the log level in config.json to enable lower level
+ // (i.e., FINE, FINER and lower)
// log messages such as the one below.
// Lower level log messages are used sparingly to minimize noise in the code.
logger.fine("Command word: " + commandWord + "; Arguments: " + arguments);
@@ -68,6 +73,15 @@ public Command parseCommand(String userInput) throws ParseException {
case FindCommand.COMMAND_WORD:
return new FindCommandParser().parse(arguments);
+ case RelateCommand.COMMAND_WORD:
+ return new RelateCommandParser().parse(arguments);
+
+ case UnrelateCommand.COMMAND_WORD:
+ return new UnrelateCommandParser().parse(arguments);
+
+ case ShowRelatedCommand.COMMAND_WORD:
+ return new ShowRelatedCommandParser().parse(arguments);
+
case ListCommand.COMMAND_WORD:
return new ListCommand();
@@ -77,6 +91,9 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case ExportCommand.COMMAND_WORD:
+ return new ExportCommandParser().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/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java
index d6551ad8e3f..ce644a9c6fd 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/seedu/address/logic/parser/Parser.java
@@ -10,6 +10,7 @@ public interface Parser {
/**
* Parses {@code userInput} into a command and returns it.
+ *
* @throws ParseException if {@code userInput} does not conform the expected format
*/
T parse(String userInput) throws ParseException;
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..3f0bd659da4 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -4,35 +4,49 @@
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
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.person.Address;
+import seedu.address.model.person.Department;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Id;
+import seedu.address.model.person.JobTitle;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Products;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Skills;
+import seedu.address.model.person.TermsOfService;
import seedu.address.model.tag.Tag;
+import seedu.address.model.util.TestCommandFormatUtil;
/**
- * Contains utility methods used for parsing strings in the various *Parser classes.
+ * Contains utility methods used for parsing strings in the various *Parser
+ * classes.
*/
public class ParserUtil {
- public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
-
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
* trimmed.
+ *
* @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
*/
- public static Index parseIndex(String oneBasedIndex) throws ParseException {
- String trimmedIndex = oneBasedIndex.trim();
- if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
- throw new ParseException(MESSAGE_INVALID_INDEX);
+ public static Id parseId(String id) throws ParseException {
+ String trimmedId = id.trim();
+ if (isContainSlash(trimmedId)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!(StringUtil.isNonZeroUnsignedInteger(trimmedId))) {
+ throw new ParseException(Id.MESSAGE_CONSTRAINTS);
+ }
+ int parsedId = Integer.parseInt(trimmedId);
+ if (!Id.isValidId(parsedId)) {
+ throw new ParseException(Id.MESSAGE_CONSTRAINTS);
}
- return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ return Id.generateTempId(parsedId);
}
/**
@@ -44,7 +58,9 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
public static Name parseName(String name) throws ParseException {
requireNonNull(name);
String trimmedName = name.trim();
- if (!Name.isValidName(trimmedName)) {
+ if (isContainSlash(trimmedName)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Name.isValidName(trimmedName)) {
throw new ParseException(Name.MESSAGE_CONSTRAINTS);
}
return new Name(trimmedName);
@@ -59,7 +75,9 @@ public static Name parseName(String name) throws ParseException {
public static Phone parsePhone(String phone) throws ParseException {
requireNonNull(phone);
String trimmedPhone = phone.trim();
- if (!Phone.isValidPhone(trimmedPhone)) {
+ if (isContainSlash(trimmedPhone)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Phone.isValidPhone(trimmedPhone)) {
throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
}
return new Phone(trimmedPhone);
@@ -89,7 +107,9 @@ public static Address parseAddress(String address) throws ParseException {
public static Email parseEmail(String email) throws ParseException {
requireNonNull(email);
String trimmedEmail = email.trim();
- if (!Email.isValidEmail(trimmedEmail)) {
+ if (isContainSlash(trimmedEmail)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Email.isValidEmail(trimmedEmail)) {
throw new ParseException(Email.MESSAGE_CONSTRAINTS);
}
return new Email(trimmedEmail);
@@ -104,12 +124,23 @@ public static Email parseEmail(String email) throws ParseException {
public static Tag parseTag(String tag) throws ParseException {
requireNonNull(tag);
String trimmedTag = tag.trim();
- if (!Tag.isValidTagName(trimmedTag)) {
+ if (isContainSlash(trimmedTag)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Tag.isValidTagName(trimmedTag)) {
throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
}
return new Tag(trimmedTag);
}
+ /**
+ * Parses a {@code String remark} into a {@code Remark}.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static Remark parseRemark(String remark) {
+ requireNonNull(remark);
+ return new Remark(remark);
+ }
+
/**
* Parses {@code Collection tags} into a {@code Set}.
*/
@@ -121,4 +152,157 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses a {@code String role} into a {@code Role}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code role} is invalid.
+ */
+ public static String parseRole(String role) throws ParseException {
+ requireNonNull(role);
+ String trimmedRole = role.trim();
+ if (isContainSlash(trimmedRole)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ return trimmedRole;
+ }
+
+ /**
+ * Parses a {@code String department} into a {@code Department}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code department} is invalid.
+ */
+ public static Department parseDepartment(String department) throws ParseException {
+ requireNonNull(department);
+ String trimmedDepartment = department.trim();
+ if (isContainSlash(trimmedDepartment)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Department.isValidDepartment(trimmedDepartment)) {
+ throw new ParseException(Department.MESSAGE_CONSTRAINTS);
+ }
+ return new Department(trimmedDepartment);
+ }
+
+ /**
+ * Parses a {@code String jobTitle} into a {@code JobTitle}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code jobTitle} is invalid.
+ */
+ public static JobTitle parseJobTitle(String jobTitle) throws ParseException {
+ requireNonNull(jobTitle);
+ String trimmedJobTitle = jobTitle.trim();
+ if (isContainSlash(trimmedJobTitle)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!JobTitle.isValidJobTitle(trimmedJobTitle)) {
+ throw new ParseException(JobTitle.MESSAGE_CONSTRAINTS);
+ }
+ return new JobTitle(trimmedJobTitle);
+ }
+
+ /**
+ * Parses a {@code String products} into a {@code Products}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code products} is invalid.
+ */
+ public static Products parseProducts(List products) throws ParseException {
+ requireNonNull(products);
+ if (isContainSlash(products)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Products.isValidProducts(products)) {
+ throw new ParseException(Products.MESSAGE_CONSTRAINTS);
+ }
+ return new Products(products);
+ }
+
+ /**
+ * Represents a set of skills.
+ */
+ public static Skills parseSkills(Collection skills) throws ParseException {
+ requireNonNull(skills);
+ final Set skillsSet = new HashSet<>();
+ for (String skill : skills) {
+ String trimmedSkills = skill.trim();
+ if (isContainSlash(trimmedSkills)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Skills.isValidSkills(trimmedSkills)) {
+ throw new ParseException(Skills.MESSAGE_CONSTRAINTS);
+ }
+ skillsSet.add(trimmedSkills);
+ }
+ return new Skills(skillsSet);
+ }
+
+ /**
+ * Parses a {@code String preferences} into a {@code String}.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static Skills parseSkills(List skills) throws ParseException {
+ requireNonNull(skills);
+ final Set skillsSet = new HashSet<>();
+ for (String skill : skills) {
+ String trimmedSkills = skill.trim();
+ if (isContainSlash(trimmedSkills)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ } else if (!Skills.isValidSkills(trimmedSkills)) {
+ throw new ParseException(Skills.MESSAGE_CONSTRAINTS);
+ }
+ skillsSet.add(trimmedSkills);
+ }
+ return new Skills(skillsSet);
+ }
+
+ /**
+ * Parses a {@code String preferences} into a {@code String}.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static String parsePreferences(String preferences) throws ParseException {
+ requireNonNull(preferences);
+ if (isContainSlash(preferences)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ return preferences;
+ }
+
+ /**
+ * Parses the terms of service.
+ *
+ * @param termsOfService The terms of service to be parsed.
+ * @return The parsed terms of service.
+ */
+ public static TermsOfService parseTermsOfService(String termsOfService) throws ParseException {
+ requireNonNull(termsOfService);
+ if (isContainSlash(termsOfService)) {
+ throw new ParseException(TestCommandFormatUtil.MESSAGE_CONSTRAINTS);
+ }
+ return new TermsOfService(termsOfService);
+ }
+
+ /**
+ * Checks if the given input string contains a slash ("/").
+ *
+ * @param input the input string to check
+ * @return true if the input string contains a slash, false otherwise
+ */
+ public static Boolean isContainSlash(String input) {
+ return input.contains("/");
+ }
+
+ /**
+ * Checks if any string in the given list contains a slash.
+ *
+ * @param input the list of strings to check
+ * @return true if any string in the list contains a slash, false otherwise
+ */
+ public static Boolean isContainSlash(List input) {
+ for (String s : input) {
+ if (isContainSlash(s)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/RelateCommandParser.java b/src/main/java/seedu/address/logic/parser/RelateCommandParser.java
new file mode 100644
index 00000000000..3beffea90b8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/RelateCommandParser.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.RelateCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Id;
+
+/**
+ * Parses input arguments and creates a new RelateCommand object
+ */
+public class RelateCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the RelateCommand
+ * and returns a RelateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public RelateCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, RelateCommand.MESSAGE_USAGE));
+ }
+
+ String[] providedIds = trimmedArgs.split("\\s+");
+
+ if (providedIds.length != 2) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, RelateCommand.MESSAGE_USAGE));
+ }
+
+ Id[] ids = new Id[2];
+
+ for (int i = 0; i < providedIds.length; i++) {
+ String[] flagAndId = providedIds[i].split("/");
+ // find and validate flag
+ if (flagAndId[0].equals("i")) {
+ ids[i] = ParserUtil.parseId(flagAndId[1]);
+ } else {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, RelateCommand.MESSAGE_USAGE));
+ }
+ }
+
+ return new RelateCommand(ids[0], ids[1]);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ShowRelatedCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowRelatedCommandParser.java
new file mode 100644
index 00000000000..2b97e898e3b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ShowRelatedCommandParser.java
@@ -0,0 +1,40 @@
+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_ID;
+
+import seedu.address.logic.commands.ShowRelatedCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Id;
+
+/**
+ * Parses input arguments and creates a new ShowRelatedCommand object
+ */
+public class ShowRelatedCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ShowRelatedCommand
+ * and returns a ShowRelatedCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ShowRelatedCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ID);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_ID);
+
+ if (argMultimap.getValue(PREFIX_ID).isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowRelatedCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Id id = ParserUtil.parseId(argMultimap.getValue(PREFIX_ID).get());
+ return new ShowRelatedCommand(id);
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(
+ MESSAGE_INVALID_COMMAND_FORMAT, ShowRelatedCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/UnrelateCommandParser.java b/src/main/java/seedu/address/logic/parser/UnrelateCommandParser.java
new file mode 100644
index 00000000000..c87843df1ab
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnrelateCommandParser.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.UnrelateCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Id;
+
+/**
+ * Parses input arguments and creates a new RelateCommand object
+ */
+public class UnrelateCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the RelateCommand
+ * and returns a RelateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnrelateCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnrelateCommand.MESSAGE_USAGE));
+ }
+
+ String[] providedIds = trimmedArgs.split("\\s+");
+
+ if (providedIds.length != 2) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnrelateCommand.MESSAGE_USAGE));
+ }
+
+ Id[] ids = new Id[2];
+
+ for (int i = 0; i < providedIds.length; i++) {
+ String[] flagAndId = providedIds[i].split("/");
+ // find and validate flag
+ if (flagAndId[0].equals("i")) {
+ ids[i] = ParserUtil.parseId(flagAndId[1]);
+ } else {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnrelateCommand.MESSAGE_USAGE));
+ }
+ }
+
+ return new UnrelateCommand(ids[0], ids[1]);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/utils/CsvExporter.java b/src/main/java/seedu/address/logic/utils/CsvExporter.java
new file mode 100644
index 00000000000..34e2783e183
--- /dev/null
+++ b/src/main/java/seedu/address/logic/utils/CsvExporter.java
@@ -0,0 +1,131 @@
+package seedu.address.logic.utils;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.collections.transformation.FilteredList;
+import seedu.address.model.person.Client;
+import seedu.address.model.person.Employee;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Supplier;
+
+/**
+ * A utility class for exporting data from an address book to a CSV file.
+ * It provides methods to create a CSV file containing the data of persons in the address book.
+ */
+public class CsvExporter {
+
+ private final String filename;
+
+ private boolean isSuccessful = false;
+
+ private final FilteredList persons;
+
+ /**
+ * Constructs a CSVExporter object with the specified list of persons and filename.
+ *
+ * @param persons The list of persons to be exported to CSV.
+ * @param filename The filename for the CSV file to be created.
+ */
+ public CsvExporter(FilteredList persons, String filename) {
+ this.persons = persons;
+ this.filename = filename;
+ }
+
+ /**
+ * Executes the CSV export process by creating a CSV file with the data from the list of contacts.
+ * If the export process is successful, sets the {@code isSuccessful} flag to true; otherwise, sets it to false.
+ */
+ public void execute() {
+ List data = createDataList();
+ try (Writer writer = new FileWriter(getJarDirectory() + File.separator + filename)) {
+ for (String[] row : data) {
+ writer.write(String.join(",", row) + System.lineSeparator());
+ }
+ isSuccessful = true;
+ } catch (IOException e) {
+ isSuccessful = false;
+ }
+ }
+
+ /**
+ * Creates a list of string arrays representing the data to be exported to a CSV file.
+ * Each string array represents a row in the CSV file, with the first array containing field names.
+ *
+ * @return A list of string arrays representing the data to be exported.
+ */
+ protected List createDataList() {
+ List dataList = new ArrayList<>();
+ String[] fieldNames = {"ID", "Name", "Phone", "Email", "Address", "Remark", "Tags", "Department",
+ "Job Title", "Skills", "Products", "Preferences", "Terms of Service"};
+ dataList.add(fieldNames);
+
+ for (Person person : this.persons) {
+ String[] personStringArray = convertPersonToStringArray(person);
+ dataList.add(personStringArray);
+ }
+
+ return dataList;
+ }
+
+ /**
+ * Converts a Person object to a string array representing its data.
+ *
+ * @param person The Person object to be converted.
+ * @return A string array representing the data of the Person object.
+ */
+ public String[] convertPersonToStringArray(Person person) {
+ String[] personStringArray = new String[13];
+
+ personStringArray[0] = person.getId().toString();
+ personStringArray[1] = person.getName().toString();
+ personStringArray[2] = person.getPhone().toString();
+ personStringArray[3] = person.getEmail().toString();
+ personStringArray[4] = "\"" + person.getAddress().toString() + "\"";
+ personStringArray[5] = (person.getRemark() != null) ? person.getRemark().toString() : "";
+ personStringArray[6] = person.getTagsAsString();
+ if (person instanceof Employee) {
+ Employee employee = (Employee) person;
+ personStringArray[7] = employee.getDepartment().toString();
+ personStringArray[8] = employee.getJobTitle().toString();
+ personStringArray[9] = employee.getSkillsAsString();
+ personStringArray[10] = "";
+ personStringArray[11] = "";
+ personStringArray[12] = "";
+ } else if (person instanceof Client) {
+ Client client = (Client) person;
+ personStringArray[7] = "";
+ personStringArray[8] = "";
+ personStringArray[9] = "";
+ personStringArray[10] = client.getProductsAsString();
+ personStringArray[11] = client.getPreferences();
+ personStringArray[12] = "";
+ } else if (person instanceof Supplier) {
+ Supplier supplier = (Supplier) person;
+ personStringArray[7] = "";
+ personStringArray[8] = "";
+ personStringArray[9] = "";
+ personStringArray[10] = supplier.getProductsAsString();
+ personStringArray[11] = "";
+ personStringArray[12] = supplier.getTermsOfService().toString();
+ }
+
+ return personStringArray;
+ }
+
+ public boolean getIsSuccessful() {
+ return isSuccessful;
+ }
+
+ private String getJarDirectory() {
+ try {
+ return new File(CsvExporter.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
deleted file mode 100644
index 73397161e84..00000000000
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
-
-/**
- * Wraps all data at the address-book level
- * Duplicates are not allowed (by .isSamePerson comparison)
- */
-public class AddressBook implements ReadOnlyAddressBook {
-
- private final UniquePersonList persons;
-
- /*
- * 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();
- }
-
- public AddressBook() {}
-
- /**
- * Creates an AddressBook using the Persons in the {@code toBeCopied}
- */
- public AddressBook(ReadOnlyAddressBook toBeCopied) {
- this();
- resetData(toBeCopied);
- }
-
- //// list overwrite operations
-
- /**
- * Replaces the contents of the person list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- this.persons.setPersons(persons);
- }
-
- /**
- * Resets the existing data of this {@code AddressBook} with {@code newData}.
- */
- public void resetData(ReadOnlyAddressBook newData) {
- requireNonNull(newData);
-
- setPersons(newData.getPersonList());
- }
-
- //// person-level operations
-
- /**
- * 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);
- }
-
- /**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
- */
- public void addPerson(Person p) {
- persons.add(p);
- }
-
- /**
- * 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.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
-
- persons.setPerson(target, editedPerson);
- }
-
- /**
- * Removes {@code key} from this {@code AddressBook}.
- * {@code key} must exist in the address book.
- */
- public void removePerson(Person key) {
- persons.remove(key);
- }
-
- //// util methods
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("persons", persons)
- .toString();
- }
-
- @Override
- public ObservableList getPersonList() {
- return persons.asUnmodifiableObservableList();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof AddressBook)) {
- return false;
- }
-
- AddressBook otherAddressBook = (AddressBook) other;
- return persons.equals(otherAddressBook.persons);
- }
-
- @Override
- public int hashCode() {
- return persons.hashCode();
- }
-}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..2af52a9f7fc 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,18 +1,20 @@
package seedu.address.model;
import java.nio.file.Path;
-import java.util.function.Predicate;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.person.Id;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.person.filter.NetConnectPredicate;
+import seedu.address.model.util.IdTuple;
+import seedu.address.model.util.RelatedList;
/**
* 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}.
@@ -35,30 +37,54 @@ public interface Model {
void setGuiSettings(GuiSettings guiSettings);
/**
- * Returns the user prefs' address book file path.
+ * Returns the user prefs' netconnect file path.
*/
- Path getAddressBookFilePath();
+ Path getNetConnectFilePath();
/**
- * Sets the user prefs' address book file path.
+ * Sets the user prefs' netconnect file path.
*/
- void setAddressBookFilePath(Path addressBookFilePath);
+ void setNetConnectFilePath(Path netConnectFilePath);
/**
- * Replaces address book data with the data in {@code addressBook}.
+ * Replaces netconnect data with the data in {@code netConnect}.
*/
- void setAddressBook(ReadOnlyAddressBook addressBook);
+ void setNetConnect(ReadOnlyNetConnect netConnect);
- /** Returns the AddressBook */
- ReadOnlyAddressBook getAddressBook();
+ /**
+ * Returns the NetConnect
+ */
+ ReadOnlyNetConnect getNetConnect();
/**
- * 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 netconnect.
*/
boolean hasPerson(Person person);
/**
- * Deletes the given person.
+ * Returns true if a person with the specified id exists in the NetConnect.
+ */
+ boolean hasId(Id id);
+
+ /**
+ * Returns the {@code Person} with the specified id.
+ */
+ Person getPersonById(Id id);
+
+ /**
+ * Returns the number of {@code Person}s in the NetConnect with
+ * the specified name.
+ */
+ int countPersonsWithName(Name name);
+
+ /**
+ * Returns the {@code Person} with the specified name.
+ */
+ Person getPersonByName(Name name);
+
+ /**
+ * Deletes the given person including any of its relations.
* The person must exist in the address book.
*/
void deletePerson(Person target);
@@ -72,16 +98,54 @@ public interface Model {
/**
* Replaces the given person {@code target} 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 NetConnect.
+ * {@code target} and {@code editedPerson} must have the same id.
*/
void setPerson(Person target, Person editedPerson);
- /** Returns an unmodifiable view of the filtered person list */
+ /**
+ * 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}.
+ * Clears all filters of the filtered person list. Displays all persons.
+ *
+ */
+ void clearFilter();
+
+ /**
+ * Updates the existing filter of the filtered person list with an
+ * additional filter by the given {@code predicate}.
+ *
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void stackFilters(NetConnectPredicate predicate);
+
+ /**
+ * Updates the existing view of the filtered person list with the given {@code predicate}.
+ *
* @throws NullPointerException if {@code predicate} is null.
*/
- void updateFilteredPersonList(Predicate predicate);
+ void updateFilteredList(NetConnectPredicate predicate);
+
+ /**
+ * Returns the current filters applied in a user readable format.
+ */
+ String printFilters();
+
+ /**
+ * Exports the data from the address book as a CSV file with the specified filename.
+ * Returns {@code true} if the export operation is successful, {@code false} otherwise.
+ */
+ boolean exportCsv(String filename);
+
+ boolean hasRelatedIdTuple(IdTuple idTuple);
+
+ void addRelatedIdTuple(IdTuple idTuple);
+
+ boolean removeRelatedIdTuple(IdTuple idTuple);
+
+ RelatedList getRelatedIdTuples();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..6b173dc0883 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -4,14 +4,21 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
-import java.util.function.Predicate;
+import java.util.Collections;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.utils.CsvExporter;
+import seedu.address.model.person.Id;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.person.filter.Filter;
+import seedu.address.model.person.filter.NetConnectPredicate;
+import seedu.address.model.util.IdTuple;
+import seedu.address.model.util.RelatedList;
/**
* Represents the in-memory model of the address book data.
@@ -19,28 +26,32 @@
public class ModelManager implements Model {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
- private final AddressBook addressBook;
+ private final NetConnect netConnect;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private Filter filter = Filter.noFilter();
/**
- * Initializes a ModelManager with the given addressBook and userPrefs.
+ * Initializes a ModelManager with the given netConnect and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
+ public ModelManager(ReadOnlyNetConnect netConnect, ReadOnlyUserPrefs userPrefs) {
+ requireAllNonNull(netConnect, userPrefs);
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
+ logger.fine("Initializing with address book: "
+ + netConnect + " , user prefs "
+ + userPrefs);
- this.addressBook = new AddressBook(addressBook);
+ this.netConnect = new NetConnect(netConnect);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredPersons = new FilteredList<>(this.netConnect.getPersonList());
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new NetConnect(), new UserPrefs());
}
- //=========== UserPrefs ==================================================================================
+ // =========== UserPrefs
+ // ==================================================================================
@Override
public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
@@ -65,57 +76,117 @@ public void setGuiSettings(GuiSettings guiSettings) {
}
@Override
- public Path getAddressBookFilePath() {
- return userPrefs.getAddressBookFilePath();
+ public Path getNetConnectFilePath() {
+ return userPrefs.getNetConnectFilePath();
}
@Override
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- userPrefs.setAddressBookFilePath(addressBookFilePath);
+ public void setNetConnectFilePath(Path netConnectFilePath) {
+ requireNonNull(netConnectFilePath);
+ userPrefs.setNetConnectFilePath(netConnectFilePath);
}
- //=========== AddressBook ================================================================================
+ // =========== NetConnect
+ // ================================================================================
@Override
- public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
+ public void setNetConnect(ReadOnlyNetConnect netConnect) {
+ this.netConnect.resetData(netConnect);
}
@Override
- public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
+ public ReadOnlyNetConnect getNetConnect() {
+ return netConnect;
}
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
- return addressBook.hasPerson(person);
+ return netConnect.hasPerson(person);
+ }
+
+ @Override
+ public boolean hasId(Id id) {
+ requireNonNull(id);
+ return netConnect.hasId(id);
+ }
+
+ @Override
+ public Person getPersonById(Id id) {
+ requireNonNull(id);
+ return netConnect.getPersonById(id);
+ }
+
+ @Override
+ public int countPersonsWithName(Name name) {
+ requireNonNull(name);
+ return netConnect.countPersonsWithName(name);
+ }
+
+ @Override
+ public Person getPersonByName(Name name) {
+ requireNonNull(name);
+ return netConnect.getPersonByName(name);
}
@Override
public void deletePerson(Person target) {
- addressBook.removePerson(target);
+ netConnect.removePerson(target);
}
@Override
public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ netConnect.addPerson(person);
+ clearFilter();
}
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
- addressBook.setPerson(target, editedPerson);
+ netConnect.setPerson(target, editedPerson);
+ }
+
+ @Override
+ public boolean exportCsv(String filename) {
+ requireNonNull(filename);
+ CsvExporter exporter = new CsvExporter(filteredPersons, filename);
+ exporter.execute();
+ return exporter.getIsSuccessful();
+ }
+
+ // =========== Related List Accessors
+ // =============================================================
+
+ @Override
+ public boolean hasRelatedIdTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ return netConnect.hasRelatedId(idTuple);
+ }
+
+ @Override
+ public void addRelatedIdTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ netConnect.allowAddIdTuple(idTuple);
+ }
+
+ @Override
+ public boolean removeRelatedIdTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ return netConnect.removeRelatedIdTuple(idTuple);
+ }
+
+ @Override
+ public RelatedList getRelatedIdTuples() {
+ return netConnect.getRelatedList();
}
- //=========== Filtered Person List Accessors =============================================================
+ // =========== Filtered Person List Accessors
+ // =============================================================
/**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
- * {@code versionedAddressBook}
+ * Returns an unmodifiable view of the list of {@code Person} backed by the
+ * internal list of {@code versionedNetConnect}
*/
@Override
public ObservableList getFilteredPersonList() {
@@ -123,9 +194,30 @@ public ObservableList getFilteredPersonList() {
}
@Override
- public void updateFilteredPersonList(Predicate predicate) {
+ public void clearFilter() {
+ filter = Filter.noFilter();
+ filteredPersons.setPredicate(filter);
+ }
+
+ @Override
+ public void stackFilters(NetConnectPredicate predicate) {
+ requireNonNull(predicate);
+
+ filter = filter.add(predicate);
+ filteredPersons.setPredicate(filter);
+ }
+
+ @Override
+ public void updateFilteredList(NetConnectPredicate predicate) {
requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
+
+ filter = Filter.noFilter();
+ filteredPersons.setPredicate(Filter.of(Collections.singletonList(predicate)));
+ }
+
+ @Override
+ public String printFilters() {
+ return String.format(Filter.MESSAGE_FILTERS_APPLIED, filter.size(), filter.formatFilter());
}
@Override
@@ -140,7 +232,7 @@ public boolean equals(Object other) {
}
ModelManager otherModelManager = (ModelManager) other;
- return addressBook.equals(otherModelManager.addressBook)
+ return netConnect.equals(otherModelManager.netConnect)
&& userPrefs.equals(otherModelManager.userPrefs)
&& filteredPersons.equals(otherModelManager.filteredPersons);
}
diff --git a/src/main/java/seedu/address/model/NetConnect.java b/src/main/java/seedu/address/model/NetConnect.java
new file mode 100644
index 00000000000..a6e5533c5cf
--- /dev/null
+++ b/src/main/java/seedu/address/model/NetConnect.java
@@ -0,0 +1,236 @@
+package seedu.address.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Id;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.util.IdTuple;
+import seedu.address.model.util.RelatedList;
+
+/**
+ * Wraps all data at the address-book level
+ * Duplicates are not allowed (by .isSamePerson comparison)
+ */
+public class NetConnect implements ReadOnlyNetConnect {
+
+ private final UniquePersonList persons;
+ private final RelatedList relatedList;
+
+ /**
+ * Represents the main class for the NetConnect application.
+ * It contains the constructor for initializing the application.
+ */
+ public NetConnect() {
+ persons = new UniquePersonList();
+ relatedList = new RelatedList();
+ }
+
+ /**
+ * Creates an NetConnect using the Persons in the {@code toBeCopied}
+ */
+ public NetConnect(ReadOnlyNetConnect toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// list overwrite operations
+
+ /**
+ * Replaces the contents of the person list with {@code persons}.
+ * {@code persons} must not contain duplicate persons.
+ */
+ public void setPersons(List persons) {
+ this.persons.setPersons(persons);
+ }
+
+ /**
+ * Replaces the contents of the related list with {@code relatedList}.
+ * {@code relatedList} must not contain duplicate related persons.
+ */
+ public void setRelatedList(List relatedList) {
+ this.relatedList.setRelatedList(relatedList);
+ }
+
+ /**
+ * Resets the existing data of this {@code NetConnect} with {@code newData}.
+ */
+ public void resetData(ReadOnlyNetConnect newData) {
+ requireNonNull(newData);
+
+ setPersons(newData.getPersonList());
+ setRelatedList(newData.getListIdTuple());
+
+ }
+
+ //// person-level operations
+
+ /**
+ * 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 person with the specified id exists in the NetConnect.
+ */
+ public boolean hasId(Id id) {
+ requireNonNull(id);
+ return persons.hasId(id);
+ }
+
+ /**
+ * Returns the {@code Person} with the specified id.
+ */
+ public Person getPersonById(Id id) {
+ requireNonNull(id);
+ return persons.getPersonById(id);
+ }
+
+ /**
+ * Returns count if the NetConnect has exactly one {@code Person}
+ * with the specified name.
+ */
+ public int countPersonsWithName(Name name) {
+ requireNonNull(name);
+ return persons.countPersonsWithName(name);
+ }
+
+ /**
+ * Returns the {@code Person} with the specified name.
+ */
+ public Person getPersonByName(Name name) {
+ requireNonNull(name);
+ return persons.getPersonByName(name);
+ }
+
+ /**
+ * Adds a person to the address book.
+ * The person must not already exist in the address book.
+ */
+ public void addPerson(Person p) {
+ persons.add(p);
+ }
+
+ /**
+ * Replaces the given person {@code target} in the list with {@code editedPerson}.
+ * {@code target} must exist in the list.
+ * The person identity of {@code editedPerson} must not be the same as another
+ * existing person in the list.
+ * {@code target} and {@code editedPerson} must have the same id.
+ */
+ public void setPerson(Person target, Person editedPerson) {
+ requireNonNull(editedPerson);
+
+ persons.setPerson(target, editedPerson);
+ }
+
+ /**
+ * Removes {@code key} from this {@code NetConnect}.
+ * {@code key} must exist in the address book.
+ */
+ public void removePerson(Person key) {
+ persons.remove(key);
+ Id id = key.getId();
+ relatedList.removeId(id);
+ }
+
+ //// related list operations
+ /**
+ * Returns true if the specified IdTuple is present in the relatedList, false otherwise.
+ *
+ * @param idTuple The IdTuple to check for presence in the relatedList. Must not be null.
+ * @return true if the specified IdTuple is present in the relatedList, false otherwise.
+ */
+ public boolean hasRelatedId(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ return relatedList.hasId(idTuple);
+ }
+
+ /**
+ * Allows the addition of an IdTuple to the NetConnect model.
+ *
+ * @param idTuple The IdTuple to be added.
+ * @throws NullPointerException if idTuple is null.
+ */
+ public void allowAddIdTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ relatedList.allowAddIdTuple(idTuple);
+ }
+
+ /**
+ * Removes the specified IdTuple from the relatedList.
+ *
+ * @param idTuple The IdTuple to be removed.
+ * @return true if the IdTuple is removed, false otherwise.
+ */
+ public boolean removeRelatedIdTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ return relatedList.removeTuple(idTuple);
+ }
+
+ //// util methods
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("persons", persons)
+ .toString();
+ }
+
+ /**
+ * Returns an unmodifiable view of the person list.
+ * This list will not contain any duplicate persons.
+ *
+ * @return An unmodifiable {@link ObservableList} of {@link Person}.
+ */
+ @Override
+ public ObservableList getPersonList() {
+ return persons.asUnmodifiableObservableList();
+ }
+
+ /**
+ * Returns a list of IdTuple objects.
+ *
+ * @return A list of IdTuple objects.
+ */
+ @Override
+ public List getListIdTuple() {
+ return relatedList.getListIdTuple();
+ }
+
+ /**
+ * Represents a list of related items.
+ */
+ public RelatedList getRelatedList() {
+ return relatedList;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof NetConnect)) {
+ return false;
+ }
+
+ NetConnect otherNetConnect = (NetConnect) other;
+ return persons.equals(otherNetConnect.persons);
+ }
+
+ @Override
+ public int hashCode() {
+ return persons.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
deleted file mode 100644
index 6ddc2cd9a29..00000000000
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package seedu.address.model;
-
-import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-
-/**
- * Unmodifiable view of an address book
- */
-public interface ReadOnlyAddressBook {
-
- /**
- * Returns an unmodifiable view of the persons list.
- * This list will not contain any duplicate persons.
- */
- ObservableList getPersonList();
-
-}
diff --git a/src/main/java/seedu/address/model/ReadOnlyNetConnect.java b/src/main/java/seedu/address/model/ReadOnlyNetConnect.java
new file mode 100644
index 00000000000..877b8780dfd
--- /dev/null
+++ b/src/main/java/seedu/address/model/ReadOnlyNetConnect.java
@@ -0,0 +1,25 @@
+package seedu.address.model;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.person.Person;
+import seedu.address.model.util.IdTuple;
+
+/**
+ * Unmodifiable view of an address book
+ */
+public interface ReadOnlyNetConnect {
+
+ /**
+ * Returns an unmodifiable view of the persons list.
+ * This list will not contain any duplicate persons and any duplicate ids.
+ */
+ ObservableList getPersonList();
+
+ /**
+ * Returns an unmodifiable view of the related list.
+ * This list will not contain any duplicate related persons.
+ */
+ List getListIdTuple();
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
index befd58a4c73..2f7c34a507f 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
@@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs {
GuiSettings getGuiSettings();
- Path getAddressBookFilePath();
+ Path getNetConnectFilePath();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 6be655fb4c7..317444d5432 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -14,12 +14,13 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path netConnectFilePath = Paths.get("data", "netconnect.json");
/**
* Creates a {@code UserPrefs} with default values.
*/
- public UserPrefs() {}
+ public UserPrefs() {
+ }
/**
* Creates a {@code UserPrefs} with the prefs in {@code userPrefs}.
@@ -35,7 +36,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) {
public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
- setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setNetConnectFilePath(newUserPrefs.getNetConnectFilePath());
}
public GuiSettings getGuiSettings() {
@@ -47,13 +48,13 @@ public void setGuiSettings(GuiSettings guiSettings) {
this.guiSettings = guiSettings;
}
- public Path getAddressBookFilePath() {
- return addressBookFilePath;
+ public Path getNetConnectFilePath() {
+ return netConnectFilePath;
}
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- this.addressBookFilePath = addressBookFilePath;
+ public void setNetConnectFilePath(Path netConnectFilePath) {
+ requireNonNull(netConnectFilePath);
+ this.netConnectFilePath = netConnectFilePath;
}
@Override
@@ -69,20 +70,19 @@ public boolean equals(Object other) {
UserPrefs otherUserPrefs = (UserPrefs) other;
return guiSettings.equals(otherUserPrefs.guiSettings)
- && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath);
+ && netConnectFilePath.equals(otherUserPrefs.netConnectFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, netConnectFilePath);
}
@Override
public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
- return sb.toString();
+ String sb = "Gui Settings : " + guiSettings
+ + "\nLocal data file location : " + netConnectFilePath;
+ return sb;
}
}
diff --git a/src/main/java/seedu/address/model/person/Client.java b/src/main/java/seedu/address/model/person/Client.java
new file mode 100644
index 00000000000..3647197845c
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Client.java
@@ -0,0 +1,119 @@
+package seedu.address.model.person;
+
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Represents a Client in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Client extends Person {
+ private final Products products;
+ private final String preferences;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Client(Name name, Phone phone, Email email, Address address, Remark remark,
+ Set tags, Products products, String preferences) {
+ super(name, phone, email, address, tags, remark);
+ this.products = products;
+ this.preferences = preferences;
+ }
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Client(Id id, Name name, Phone phone, Email email, Address address, Remark remark,
+ Set tags, Products products, String preferences) {
+ super(id, name, phone, email, address, tags, remark);
+ this.products = products;
+ this.preferences = preferences;
+ }
+
+
+ /**
+ * Returns the products preferred by the client.
+ *
+ * @return The products preferred by the client.
+ */
+ public Products getProducts() {
+ return this.products;
+ }
+
+ /**
+ * Returns the products preferred by the client.
+ *
+ * @return The products preferred by the client as a String.
+ */
+ public String getProductsAsString() {
+ String result = String.join(", ", products.getProducts());
+ if (result.isEmpty()) {
+ return result;
+ }
+ return "\"" + result + "\"";
+ }
+
+ /**
+ * Returns the preferences of the client.
+ *
+ * @return The preferences of the client.
+ */
+ public String getPreferences() {
+ return this.preferences;
+ }
+
+ @Override
+ public String getRole() {
+ return "Client";
+ }
+
+ /**
+ * Checks if this client is the same as another client.
+ * Two clients are considered the same if they have the same attributes.
+ *
+ * @param other The client to compare with.
+ * @return True if the clients are the same, false otherwise.
+ */
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Client
+ && super.equals(other)
+ && products.equals(((Client) other).products)
+ && preferences.equals(((Client) other).preferences));
+ }
+
+ /**
+ * Returns the hash code of this client.
+ *
+ * @return The hash code of this client.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), products, preferences);
+ }
+
+ /**
+ * Returns the string representation of this client.
+ *
+ * @return The string representation of this client.
+ */
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("id", id)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("remark", remark)
+ .add("tags", tags)
+ .add("products", products)
+ .add("preferences", preferences)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Department.java b/src/main/java/seedu/address/model/person/Department.java
new file mode 100644
index 00000000000..d7a80c284de
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Department.java
@@ -0,0 +1,84 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.model.util.TestCommandFormatUtil.checkArgumentContainSlash;
+
+/**
+ * Represents a Person's department in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidDepartment(String)}
+ */
+public class Department {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Department names should only contain alphanumeric characters and spaces.";
+
+ /*
+ * 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}- ]*";
+
+ private final String departmentName;
+
+ /**
+ * Represents a department in an organization.
+ */
+ public Department() {
+ departmentName = "";
+ }
+
+ /**
+ * Constructs a {@code Department}.
+ *
+ * @param departmentName A valid department name.
+ */
+ public Department(String departmentName) {
+ requireNonNull(departmentName);
+ checkArgumentContainSlash(departmentName);
+ checkArgument(isValidDepartment(departmentName), MESSAGE_CONSTRAINTS);
+ this.departmentName = departmentName;
+ }
+
+ /**
+ * Returns true if the given department name is valid.
+ * A department name is considered valid if it matches the validation regex.
+ *
+ * @param test The department name to be validated.
+ * @return True if the department name is valid, false otherwise.
+ */
+ public static boolean isValidDepartment(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ public String getDepartmentName() {
+ return departmentName;
+ }
+
+ @Override
+ public String toString() {
+ if (departmentName == "-") {
+ return "";
+ }
+ return departmentName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Department)) {
+ return false;
+ }
+
+ Department otherDepartment = (Department) other;
+ return departmentName.equals(otherDepartment.departmentName);
+ }
+
+ @Override
+ public int hashCode() {
+ return departmentName.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index c62e512bc29..aebb16cb412 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.model.util.TestCommandFormatUtil.checkArgumentContainSlash;
/**
* Represents a Person's email in the address book.
@@ -40,6 +41,7 @@ public class Email {
*/
public Email(String email) {
requireNonNull(email);
+ checkArgumentContainSlash(email);
checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
value = email;
}
diff --git a/src/main/java/seedu/address/model/person/Employee.java b/src/main/java/seedu/address/model/person/Employee.java
new file mode 100644
index 00000000000..79dd48af69d
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Employee.java
@@ -0,0 +1,133 @@
+package seedu.address.model.person;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Represents a Employee in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Employee extends Person {
+
+ private final Department department;
+ private final JobTitle jobTitle;
+ private Skills skills = new Skills(new HashSet<>());
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Employee(Name name, Phone phone, Email email, Address address, Remark remark, Set tags,
+ Department department, JobTitle jobTitle, Skills skills) {
+ super(name, phone, email, address, tags, remark);
+ this.department = department;
+ this.jobTitle = jobTitle;
+ this.skills = skills;
+ }
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Employee(Id id, Name name, Phone phone, Email email, Address address, Remark remark, Set tags,
+ Department department, JobTitle jobTitle, Skills skills) {
+ super(id, name, phone, email, address, tags, remark);
+ this.department = department;
+ this.jobTitle = jobTitle;
+ this.skills = skills;
+ }
+
+ /**
+ * Returns the department of the employee.
+ *
+ * @return The department of the employee.
+ */
+ public Department getDepartment() {
+ return this.department;
+ }
+
+ /**
+ * Returns the job title of the employee.
+ *
+ * @return The job title of the employee.
+ */
+ public JobTitle getJobTitle() {
+ return this.jobTitle;
+ }
+
+ /**
+ * Returns the skills of the employee.
+ *
+ * @return The skills of the employee.
+ */
+ public Skills getSkills() {
+ return this.skills;
+ }
+
+ /**
+ * Returns the skills of the employee.
+ *
+ * @return The skills of the employee as a string.
+ */
+ public String getSkillsAsString() {
+ String result = String.join(", ", skills.getSkills());
+ if (result.isEmpty()) {
+ return "";
+ }
+ return "\"" + result + "\"";
+ }
+
+ public String getRole() {
+ return "Employee";
+ }
+
+ /**
+ * Checks if this employee is equal to another object.
+ * Two employees are considered equal if they have the same attributes.
+ *
+ * @param other The object to compare with.
+ * @return True if the employees are equal, false otherwise.
+ */
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Employee
+ && super.equals(other)
+ && department.equals(((Employee) other).department)
+ && jobTitle.equals(((Employee) other).jobTitle)
+ && skills.equals(((Employee) other).skills));
+ }
+
+ /**
+ * Returns the hash code of the employee.
+ *
+ * @return The hash code of the employee.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), department, jobTitle, skills);
+ }
+
+ /**
+ * Returns the string representation of the employee.
+ *
+ * @return The string representation of the employee.
+ */
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("id", id)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("remark", remark)
+ .add("tags", tags)
+ .add("department", department)
+ .add("jobTitle", jobTitle)
+ .add("skills", skills)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Id.java b/src/main/java/seedu/address/model/person/Id.java
new file mode 100644
index 00000000000..77c7a05d3da
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Id.java
@@ -0,0 +1,93 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Person's unique id in the address book, smallest id is 1.
+ * Guarantees: immutable; is valid as declared in {@link #isValidId(int)}
+ */
+public class Id {
+
+ public static final String MESSAGE_CONSTRAINTS = "Ids should only be any integer value larger than 0";
+
+ private static int nextId = 1;
+
+ public final int value;
+
+ /**
+ * Constructs an {@code Id} containing the next available id.
+ */
+ private Id() {
+ value = nextId++;
+ }
+
+ /**
+ * Constructs an {@code Id} with the given id.
+ */
+ private Id(int id) {
+ value = id;
+ }
+
+ /**
+ * Factory method to generate an {@code Id} with the next available id.
+ * @return A new {@code Id} instance containing the next available id.
+ */
+ public static Id generateNextId() {
+ return new Id();
+ }
+
+ /**
+ * Factory method to generate an {@code Id} with the given id, and updates the next available id.
+ * @return A new {@code Id} instance containing the given id.
+ */
+ public static Id generateId(int id) {
+ checkArgument(isValidId(id), MESSAGE_CONSTRAINTS);
+ if (id >= nextId) {
+ nextId = id + 1;
+ }
+ return new Id(id);
+ }
+
+ /**
+ * Factory method to generate an {@code Id} with the given id. Does not affect the next available id.
+ * @return A new {@code Id} instance containing the given id.
+ */
+ public static Id generateTempId(int id) {
+ checkArgument(isValidId(id), MESSAGE_CONSTRAINTS);
+ return new Id(id);
+ }
+
+ /**
+ * Returns true if a given int is a valid id.
+ */
+ public static boolean isValidId(int test) {
+ return test > 0;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Id)) {
+ return false;
+ }
+
+ Id otherId = (Id) other;
+ return value == otherId.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/model/person/JobTitle.java b/src/main/java/seedu/address/model/person/JobTitle.java
new file mode 100644
index 00000000000..1d82087b130
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/JobTitle.java
@@ -0,0 +1,80 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.model.util.TestCommandFormatUtil.checkArgumentContainSlash;
+
+/**
+ * Represents a Person's job title in the address book.
+ * Guarantees: immutable; is valid as declared in
+ * {@link #isValidJobTitle(String)}
+ */
+public class JobTitle {
+
+ public static final String MESSAGE_CONSTRAINTS = "Job titles should only contain "
+ + "alphanumeric characters and spaces.";
+ public static final String VALIDATION_REGEX = "[\\p{Alnum}- ]*";
+ private final String title;
+
+ /**
+ * Represents a job title in an organization.
+ */
+ public JobTitle() {
+ title = "";
+ }
+
+ /**
+ * Constructs a {@code JobTitle}.
+ *
+ * @param title A valid job title.
+ */
+ public JobTitle(String title) {
+ requireNonNull(title);
+ title = title.trim();
+ checkArgumentContainSlash(title);
+ checkArgument(isValidJobTitle(title), MESSAGE_CONSTRAINTS);
+ this.title = title;
+ }
+
+ /**
+ * Returns true if a given string is a valid job title.
+ * A valid job title must match the validation regex pattern.
+ *
+ * @param test The string to be tested.
+ * @return True if the string is a valid job title, false otherwise.
+ */
+ public static boolean isValidJobTitle(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public String toString() {
+ if (title == "-") {
+ return "";
+ }
+ return title;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof JobTitle)) {
+ return false;
+ }
+
+ JobTitle otherJobTitle = (JobTitle) other;
+ return title.equals(otherJobTitle.title);
+ }
+
+ @Override
+ public int hashCode() {
+ return title.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..9fe4d624ac8 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.model.util.TestCommandFormatUtil.checkArgumentContainSlash;
/**
* Represents a Person's name in the address book.
@@ -27,6 +28,7 @@ public class Name {
*/
public Name(String name) {
requireNonNull(name);
+ checkArgumentContainSlash(name);
checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
fullName = name;
}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..fb13cdf37cf 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -14,29 +14,56 @@
* Represents a Person in the address book.
* Guarantees: details are present and not null, field values are validated, immutable.
*/
-public class Person {
+public abstract class Person {
+
+ public static final String MESSAGE_ROLE_CONSTRAINTS = "Invalid role specified. "
+ + "Must be one of: client, employee, supplier.";
+
+ // Unique id
+ protected final Id id;
// Identity fields
- private final Name name;
- private final Phone phone;
- private final Email email;
+ protected final Name name;
+ protected final Phone phone;
+ protected final Email email;
// Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
+ protected final Address address;
+ protected final Remark remark;
+ protected final Set tags = new HashSet<>();
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(Name name, Phone phone, Email email, Address address, Set tags, Remark remark) {
+ requireAllNonNull(name, phone, email, address, tags, remark);
+ this.id = Id.generateNextId();
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.tags.addAll(tags);
+ this.remark = remark;
+ }
+
+ /**
+ * Create a {@code Person} object with a specified id.
+ */
+ public Person(Id id, Name name, Phone phone, Email email, Address address, Set tags, Remark remark) {
+ requireAllNonNull(id, name, phone, email, address, tags, remark);
+ this.id = id;
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.remark = remark;
this.tags.addAll(tags);
}
+ public Id getId() {
+ return id;
+ }
+
public Name getName() {
return name;
}
@@ -53,6 +80,12 @@ public Address getAddress() {
return address;
}
+ public Remark getRemark() {
+ return remark;
+ }
+
+ public abstract String getRole();
+
/**
* Returns an immutable tag set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
@@ -62,7 +95,40 @@ public Set getTags() {
}
/**
- * Returns true if both persons have the same name.
+ * Checks if the given role is valid.
+ *
+ * @param role The role to check.
+ * @return {@code true} if the role is valid, {@code false} otherwise.
+ */
+ public static boolean isValidRole(String role) {
+ return role.equalsIgnoreCase("employee")
+ || role.equalsIgnoreCase("client")
+ || role.equalsIgnoreCase("supplier");
+ }
+
+ /**
+ * Returns a string representation of the tags associated with the person.
+ * The tags are concatenated with a comma and space separator.
+ *
+ * @return A string representation of the tags.
+ */
+ public String getTagsAsString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Tag tag : tags) {
+ stringBuilder.append(tag.getTagName()).append(", ");
+ }
+ if (stringBuilder.length() > 0) {
+ stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
+ }
+ String result = stringBuilder.toString();
+ if (result.isEmpty()) {
+ return "";
+ }
+ return "\"" + result + "\"";
+ }
+
+ /**
+ * Returns true if both persons have the same name, phone, and email.
* This defines a weaker notion of equality between two persons.
*/
public boolean isSamePerson(Person otherPerson) {
@@ -71,7 +137,21 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.getName().equals(getName())
+ && otherPerson.getPhone().equals(getPhone())
+ && otherPerson.getEmail().equals(getEmail());
+ }
+
+ /**
+ * Returns true if both persons have the same id.
+ */
+ public boolean hasSameId(Person otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null
+ && otherPerson.getId().equals(getId());
}
/**
@@ -94,7 +174,8 @@ public boolean equals(Object other) {
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
&& address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
+ && tags.equals(otherPerson.tags)
+ && remark.equals(otherPerson.remark);
}
@Override
@@ -106,6 +187,7 @@ public int hashCode() {
@Override
public String toString() {
return new ToStringBuilder(this)
+ .add("id", id)
.add("name", name)
.add("phone", phone)
.add("email", email)
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index d733f63d739..28cc90ccde5 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.model.util.TestCommandFormatUtil.checkArgumentContainSlash;
/**
* Represents a Person's phone number in the address book.
@@ -22,6 +23,7 @@ public class Phone {
*/
public Phone(String phone) {
requireNonNull(phone);
+ checkArgumentContainSlash(phone);
checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
value = phone;
}
diff --git a/src/main/java/seedu/address/model/person/Products.java b/src/main/java/seedu/address/model/person/Products.java
new file mode 100644
index 00000000000..d40c8603f5c
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Products.java
@@ -0,0 +1,126 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Represents a Client's products in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Products {
+
+ public static final String MESSAGE_CONSTRAINTS = "Products should only contain alphanumeric characters and spaces";
+
+ private final List products;
+
+ /**
+ * Represents the products of a client.
+ */
+ public Products() {
+ this.products = List.of();
+ }
+
+ /**
+ * Constructs a {@code Products}.
+ *
+ * @param products A valid list of products.
+ */
+ public Products(List products) {
+ this.products = products;
+ }
+
+ /**
+ * Constructs a {@code Products} from a string of products.
+ *
+ * @param products A string of products separated by spaces.
+ */
+ public Products(String products) {
+ this.products = List.of(products.split(" "));
+ }
+
+ /**
+ * Checks if a list of products is valid.
+ *
+ * @param test The list of products to be checked.
+ * @return {@code true} if all products are alphanumeric, {@code false} otherwise.
+ */
+ public static boolean isValidProducts(List test) {
+ return test.stream().allMatch(product -> product.matches("[\\p{Alnum}- ]*"));
+ }
+
+ /**
+ * Returns the list of products.
+ *
+ * @return The list of products.
+ */
+ public List getProducts() {
+ return products;
+ }
+
+ /**
+ * Adds a list of products to the existing products.
+ *
+ * @param products The list of products to be added.
+ */
+ public void addProducts(List products) {
+ this.products.addAll(products);
+ }
+
+ /**
+ * Removes a product from the existing products.
+ *
+ * @param product The product to be removed.
+ */
+ public void removeProduct(String product) {
+ products.remove(product);
+ }
+
+ /**
+ * Checks if the list of products is empty.
+ *
+ * @return {@code true} if the list of products is empty, {@code false} otherwise.
+ */
+ public boolean isEmpty() {
+ return products.isEmpty();
+ }
+
+ /**
+ * Returns a string representation of the products.
+ *
+ * @return A string representation of the products.
+ */
+ @Override
+ public String toString() {
+ return products.stream().map(Object::toString).collect(Collectors.joining(", "));
+ }
+
+ /**
+ * Checks if this {@code Products} object is equal to another object.
+ *
+ * @param other The object to compare with.
+ * @return {@code true} if the objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Products)) {
+ return false;
+ }
+
+ Products otherProducts = (Products) other;
+ return otherProducts.getProducts().equals(getProducts());
+ }
+
+ /**
+ * Returns the hash code of the products.
+ *
+ * @return The hash code of the products.
+ */
+ @Override
+ public int hashCode() {
+ return products.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Remark.java b/src/main/java/seedu/address/model/person/Remark.java
new file mode 100644
index 00000000000..b8cd262a1de
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Remark.java
@@ -0,0 +1,67 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Function;
+
+/**
+ * Represents a Person's remark in the address book.
+ * Guarantees: immutable; is always valid
+ */
+public class Remark {
+
+ public final String value;
+
+ /**
+ * Constructs a {@code Remark}.
+ *
+ * @param remark Any remark.
+ */
+ public Remark(String remark) {
+ requireNonNull(remark);
+ value = remark;
+ }
+
+ /**
+ * Executes the given function if the value is present.
+ *
+ * @param function the function to be executed if the value is present
+ */
+ public void ifPresent(Function function) {
+ if (value != null) {
+ function.apply(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ /**
+ * Returns true if this remark is equal to the specified object.
+ * Two remarks are considered equal if they have the same value.
+ *
+ * @param other the object to compare this remark with
+ * @return true if the specified object is equal to this remark, false otherwise
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Remark)) {
+ return false;
+ }
+
+ Remark otherRemark = (Remark) other;
+ return value.equals(otherRemark.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Skills.java b/src/main/java/seedu/address/model/person/Skills.java
new file mode 100644
index 00000000000..dfcaa395610
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Skills.java
@@ -0,0 +1,91 @@
+package seedu.address.model.person;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents a Person's skills in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidSkills(String)}
+ */
+public class Skills {
+
+ public static final String MESSAGE_CONSTRAINTS = "Skills should only contain alphanumeric characters "
+ + "+, #, -, and spaces";
+
+ private final Set skills;
+
+ /**
+ * Represents the skills of a person.
+ */
+ public Skills() {
+ skills = new HashSet<>();
+ }
+
+ /**
+ * Constructs a {@code Skills}.
+ *
+ * @param skills A valid set of skills.
+ */
+ public Skills(Set skills) {
+ this.skills = new HashSet<>(skills);
+ }
+
+ /**
+ * Constructs a {@code Skills}.
+ *
+ * @param skills A valid string of skills.
+ */
+ public Skills(String skills) {
+ this.skills = new HashSet<>();
+ String[] skillsArray = skills.split(" ");
+ Collections.addAll(this.skills, skillsArray);
+ }
+
+ public static boolean isValidSkills(String test) {
+ return test.matches("[\\p{Alnum}+#\\- ]*");
+ }
+
+ public Set getSkills() {
+ return new HashSet<>(skills);
+ }
+
+ public void addSkills(Set skills) {
+ this.skills.addAll(skills);
+ }
+
+ public void removeSkill(String skill) {
+ skills.remove(skill);
+ }
+
+ public boolean containsSkill(String skill) {
+ return skills.contains(skill);
+ }
+
+ @Override
+ public String toString() {
+ if (skills.isEmpty()) {
+ return "";
+ }
+ return skills.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Skills)) {
+ return false;
+ }
+
+ Skills otherSkills = (Skills) other;
+ return otherSkills.getSkills().equals(getSkills());
+ }
+
+ @Override
+ public int hashCode() {
+ return skills.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Supplier.java b/src/main/java/seedu/address/model/person/Supplier.java
new file mode 100644
index 00000000000..dfc5a3d7434
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Supplier.java
@@ -0,0 +1,119 @@
+package seedu.address.model.person;
+
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Represents a supplier in the address book.
+ * Subclass of the Person class.
+ * Contains information about the supplier's products and terms of service.
+ */
+public class Supplier extends Person {
+ private final Products products;
+ private final TermsOfService termsOfService;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Supplier(Name name, Phone phone, Email email, Address address, Remark remark, Set tags,
+ Products products, TermsOfService termsOfService) {
+ super(name, phone, email, address, tags, remark);
+ this.products = products;
+ this.termsOfService = termsOfService;
+ }
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Supplier(Id id, Name name, Phone phone, Email email, Address address, Remark remark, Set tags,
+ Products products, TermsOfService termsOfService) {
+ super(id, name, phone, email, address, tags, remark);
+ this.products = products;
+ this.termsOfService = termsOfService;
+ }
+
+
+ /**
+ * Returns the products offered by the supplier.
+ *
+ * @return The products offered by the supplier.
+ */
+ public Products getProducts() {
+ return this.products;
+ }
+
+ /**
+ * Returns the products preferred by the client.
+ *
+ * @return The products preferred by the client as a String.
+ */
+ public String getProductsAsString() {
+ String result = String.join(", ", products.getProducts());
+ if (result.isEmpty()) {
+ return "";
+ }
+ return "\"" + result + "\"";
+ }
+
+ /**
+ * Returns the terms of service provided by the supplier.
+ *
+ * @return The terms of service provided by the supplier.
+ */
+ public TermsOfService getTermsOfService() {
+ return termsOfService;
+ }
+
+ public String getRole() {
+ return "Supplier";
+ }
+
+ /**
+ * Checks if this supplier is equal to another object.
+ * Two suppliers are considered equal if they have the same attributes.
+ *
+ * @param other The object to compare with.
+ * @return True if the suppliers are equal, false otherwise.
+ */
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Supplier
+ && super.equals(other)
+ && products.equals(((Supplier) other).products)
+ && termsOfService.equals(((Supplier) other).termsOfService));
+ }
+
+ /**
+ * Returns the hash code of the supplier.
+ *
+ * @return The hash code of the supplier.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), products, termsOfService);
+ }
+
+ /**
+ * Returns a string representation of the supplier.
+ *
+ * @return A string representation of the supplier.
+ */
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("id", id)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("remark", remark)
+ .add("tags", tags)
+ .add("products", products)
+ .add("termsOfService", termsOfService)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/TermsOfService.java b/src/main/java/seedu/address/model/person/TermsOfService.java
new file mode 100644
index 00000000000..06e87de8851
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/TermsOfService.java
@@ -0,0 +1,60 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.util.TestCommandFormatUtil.checkArgumentContainSlash;
+
+/**
+ * Represents a Terms of Service in the address book.
+ */
+public class TermsOfService {
+
+ public final String terms;
+
+ /**
+ * Represents the terms of service of an organization.
+ */
+ public TermsOfService() {
+ terms = "";
+ }
+
+ /**
+ * Constructs a {@code TermsOfService}.
+ *
+ * @param terms A valid terms of service.
+ */
+ public TermsOfService(String terms) {
+ requireNonNull(terms);
+ checkArgumentContainSlash(terms);
+ this.terms = terms;
+ }
+
+ public String getTerms() {
+ return terms;
+ }
+
+ @Override
+ public String toString() {
+ return terms;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TermsOfService)) {
+ return false;
+ }
+
+ TermsOfService otherTermsOfService = (TermsOfService) other;
+ return terms.equals(otherTermsOfService.terms);
+ }
+
+ @Override
+ public int hashCode() {
+ return terms.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index cc0a68d79f9..ef657b542c4 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -8,16 +8,18 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.person.exceptions.DuplicateIdException;
import seedu.address.model.person.exceptions.DuplicatePersonException;
+import seedu.address.model.person.exceptions.IdModifiedException;
import seedu.address.model.person.exceptions.PersonNotFoundException;
/**
* A list of persons that enforces uniqueness between its elements and does not allow nulls.
- * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of
- * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is
- * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so
- * as to ensure that the person with exactly the same fields will be removed.
- *
+ * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. Unique ids is also enforced.
+ * As such, adding and updating of persons uses Person#isSamePerson(Person) for equality so as to ensure that
+ * the person being added or updated is unique in terms of identity in the UniquePersonList. However, the removal of a
+ * person uses Person#equals(Object) so as to ensure that the person with exactly the same fields will be removed.
+ *
* Supports a minimal set of list operations.
*
* @see Person#isSamePerson(Person)
@@ -36,6 +38,51 @@ public boolean contains(Person toCheck) {
return internalList.stream().anyMatch(toCheck::isSamePerson);
}
+ /**
+ * Returns true if the list contains a person with the same id as in the given argument.
+ */
+ public boolean hasId(Id toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().map(Person::getId).anyMatch(id -> id.equals(toCheck));
+ }
+
+ /**
+ * Returns the first Person in the list with the same id as in the given argument.
+ *
+ * @throws PersonNotFoundException if no Person in the list has the {@code id}
+ */
+ public Person getPersonById(Id id) {
+ requireNonNull(id);
+ return internalList.stream().filter(p -> p.getId().equals(id))
+ .findFirst().orElseThrow(PersonNotFoundException::new);
+ }
+
+ /**
+ * Returns true if the list has exactly one {@code Person}
+ * with the specified name. The check is case-insensitive.
+ */
+ public int countPersonsWithName(Name toCheck) {
+ requireNonNull(toCheck);
+ return (int) internalList.stream()
+ .filter(p -> p.getName().fullName.equalsIgnoreCase(toCheck.fullName)).count();
+ }
+
+ /**
+ * Returns the first Person in the list with the same name as in the given argument.
+ * The match is case-insensitive.
+ *
+ * @throws PersonNotFoundException if no Person in the list has the {@code name}
+ */
+ public Person getPersonByName(Name name) {
+ requireNonNull(name);
+ if (countPersonsWithName(name) != 1) {
+ throw new PersonNotFoundException();
+ }
+ return internalList.stream()
+ .filter(p -> p.getName().fullName.equalsIgnoreCase(name.fullName))
+ .findFirst().orElseThrow(PersonNotFoundException::new);
+ }
+
/**
* Adds a person to the list.
* The person must not already exist in the list.
@@ -45,6 +92,9 @@ public void add(Person toAdd) {
if (contains(toAdd)) {
throw new DuplicatePersonException();
}
+ if (hasId(toAdd.getId())) {
+ throw new DuplicateIdException();
+ }
internalList.add(toAdd);
}
@@ -52,6 +102,7 @@ public void add(Person toAdd) {
* Replaces the person {@code target} in the list with {@code editedPerson}.
* {@code target} must exist in the list.
* The person identity of {@code editedPerson} must not be the same as another existing person in the list.
+ * {@code target} and {@code editedPerson} must have the same id.
*/
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
@@ -65,6 +116,10 @@ public void setPerson(Person target, Person editedPerson) {
throw new DuplicatePersonException();
}
+ if (!target.getId().equals(editedPerson.getId())) {
+ throw new IdModifiedException();
+ }
+
internalList.set(index, editedPerson);
}
@@ -86,13 +141,16 @@ public void setPersons(UniquePersonList replacement) {
/**
* Replaces the contents of this list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
+ * {@code persons} must not contain duplicate persons or persons with duplicate ids.
*/
public void setPersons(List persons) {
requireAllNonNull(persons);
if (!personsAreUnique(persons)) {
throw new DuplicatePersonException();
}
+ if (!idsAreUnique(persons)) {
+ throw new DuplicateIdException();
+ }
internalList.setAll(persons);
}
@@ -147,4 +205,11 @@ private boolean personsAreUnique(List persons) {
}
return true;
}
+
+ /**
+ * Returns true if {@code persons} contains only unique ids.
+ */
+ private boolean idsAreUnique(List persons) {
+ return persons.stream().map(Person::getId).distinct().count() == persons.size();
+ }
}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicateIdException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicateIdException.java
new file mode 100644
index 00000000000..2824624b0ed
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/exceptions/DuplicateIdException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.person.exceptions;
+
+/**
+ * Signals that the operation will result in Persons with duplicate Ids.
+ */
+public class DuplicateIdException extends RuntimeException {
+ public DuplicateIdException() {
+ super("Operation would result in persons with duplicate id");
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/exceptions/IdModifiedException.java b/src/main/java/seedu/address/model/person/exceptions/IdModifiedException.java
new file mode 100644
index 00000000000..7aa1466d1ab
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/exceptions/IdModifiedException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.person.exceptions;
+
+/**
+ * Signals that the operation will cause a {@code Person}'s id
+ * to be modified.
+ */
+public class IdModifiedException extends RuntimeException {
+ public IdModifiedException() {
+ super("Operation would result in id of person to be modified");
+ }
+}
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..588cfb5fbf8 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,5 @@
/**
* 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/person/filter/Filter.java b/src/main/java/seedu/address/model/person/filter/Filter.java
new file mode 100644
index 00000000000..bd3fa98deaf
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/Filter.java
@@ -0,0 +1,112 @@
+package seedu.address.model.person.filter;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Collection of filters that is applied to the displayed NetConnect person list.
+ */
+public class Filter extends NetConnectPredicate {
+
+ public static final String MESSAGE_FILTERS_APPLIED = "%1$d filter(s) applied:\n%2$s";
+
+ /** Cached empty filter object */
+ private static final Filter EMPTY_FILTER = new Filter(List.of());
+
+ private final List> filters;
+
+ /**
+ * Returns a {@code Filter} object with the given list of predicates.
+ */
+ private Filter(List> predicates) {
+ requireNonNull(predicates);
+ filters = Collections.unmodifiableList(predicates);
+ }
+
+ /**
+ * Returns a Filter with the give list of predicates, or return the
+ * empty Filter if the given list of predicates is empty.
+ */
+ public static Filter of(List> predicates) {
+ requireNonNull(predicates);
+ if (predicates.isEmpty()) {
+ return EMPTY_FILTER;
+ }
+ return new Filter(predicates);
+ }
+
+ /**
+ * Returns a new empty Filter.
+ */
+ public static Filter noFilter() {
+ return EMPTY_FILTER;
+ }
+
+ /**
+ * Returns a new {@code Filter} object with the given predicate added with the
+ * existing predicates.
+ */
+ public Filter add(NetConnectPredicate p) {
+ ArrayList> newFilters = new ArrayList<>(filters);
+ newFilters.add(p);
+ return new Filter(Collections.unmodifiableList(newFilters));
+ }
+
+ /**
+ * Returns the count of predicates in the {@code Filter}.
+ */
+ public int size() {
+ return filters.size();
+ }
+
+ /**
+ * Formats the filter in a user-readable format.
+ */
+ @Override
+ public String formatFilter() {
+ List formatted = filters.stream()
+ .map(NetConnectPredicate::formatFilter)
+ .collect(Collectors.toList());
+ return IntStream.range(1, formatted.size() + 1).boxed()
+ .map(i -> i.toString() + ". " + formatted.get(i - 1))
+ .collect(Collectors.joining("\n"));
+ }
+
+ @Override
+ public boolean test(Person p) {
+ return filters.stream().allMatch(predicate -> predicate.test(p));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Filter)) {
+ return false;
+ }
+
+ Filter otherFilter = (Filter) other;
+ return filters.equals(otherFilter.filters);
+ }
+
+ @Override
+ public int hashCode() {
+ return filters.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("filters", filters).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/filter/IdContainsDigitsPredicate.java b/src/main/java/seedu/address/model/person/filter/IdContainsDigitsPredicate.java
new file mode 100644
index 00000000000..b793d4c21da
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/IdContainsDigitsPredicate.java
@@ -0,0 +1,53 @@
+package seedu.address.model.person.filter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code ID} matches any of the IDs given.
+ */
+public class IdContainsDigitsPredicate extends NetConnectPredicate {
+ private final List ids;
+
+ public IdContainsDigitsPredicate(List ids) {
+ this.ids = ids;
+ }
+
+ @Override
+ public String formatFilter() {
+ return ids.stream()
+ .map(id -> "i/" + id).collect(Collectors.joining(" "));
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return ids.stream()
+ .anyMatch(id -> person.getId().value == id);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof IdContainsDigitsPredicate)) {
+ return false;
+ }
+
+ IdContainsDigitsPredicate otherIdContainsDigitsPredicate = (IdContainsDigitsPredicate) other;
+ return ids.equals(otherIdContainsDigitsPredicate.ids);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("ids", ids)
+ .toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/filter/NameContainsKeywordsPredicate.java
similarity index 68%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/seedu/address/model/person/filter/NameContainsKeywordsPredicate.java
index 62d19be2977..41b9f3cb1d5 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/person/filter/NameContainsKeywordsPredicate.java
@@ -1,25 +1,32 @@
-package seedu.address.model.person;
+package seedu.address.model.person.filter;
import java.util.List;
-import java.util.function.Predicate;
+import java.util.stream.Collectors;
import seedu.address.commons.util.StringUtil;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
/**
* Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
*/
-public class NameContainsKeywordsPredicate implements Predicate {
+public class NameContainsKeywordsPredicate extends NetConnectPredicate {
private final List keywords;
public NameContainsKeywordsPredicate(List keywords) {
this.keywords = keywords;
}
+ @Override
+ public String formatFilter() {
+ return keywords.stream()
+ .map(keyword -> "n/" + keyword).collect(Collectors.joining(" "));
+ }
+
@Override
public boolean test(Person person) {
return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ .anyMatch(keyword -> StringUtil.hasPartialMatchIgnoreCase(keyword, person.getName().fullName));
}
@Override
diff --git a/src/main/java/seedu/address/model/person/filter/NetConnectPredicate.java b/src/main/java/seedu/address/model/person/filter/NetConnectPredicate.java
new file mode 100644
index 00000000000..34a61025cb1
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/NetConnectPredicate.java
@@ -0,0 +1,34 @@
+package seedu.address.model.person.filter;
+
+import java.util.function.Predicate;
+
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a predicate used in NetConnect. Supports {@link #formatFilter()}
+ * to format the text to output to users.
+ */
+public abstract class NetConnectPredicate implements Predicate {
+
+ /**
+ * Returns the predicate in a user readable format.
+ */
+ public abstract String formatFilter();
+
+ /**
+ * Boxes a predicate into a NetConnectPredicate, with a null formatFilter.
+ */
+ public static NetConnectPredicate box(Predicate predicate) {
+ return new NetConnectPredicate() {
+ @Override
+ public String formatFilter() {
+ return null;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return predicate.test(person);
+ }
+ };
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/filter/PhoneMatchesDigitsPredicate.java b/src/main/java/seedu/address/model/person/filter/PhoneMatchesDigitsPredicate.java
new file mode 100644
index 00000000000..ec0d6311c99
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/PhoneMatchesDigitsPredicate.java
@@ -0,0 +1,48 @@
+package seedu.address.model.person.filter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given.
+ */
+public class PhoneMatchesDigitsPredicate extends NetConnectPredicate {
+ private final List phones;
+
+ public PhoneMatchesDigitsPredicate(List phones) {
+ this.phones = phones;
+ }
+
+ @Override
+ public String formatFilter() {
+ return phones.stream().map(keyword -> "p/" + keyword).collect(Collectors.joining(" "));
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return phones.stream().anyMatch(person.getPhone().value::equals);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PhoneMatchesDigitsPredicate)) {
+ return false;
+ }
+
+ PhoneMatchesDigitsPredicate otherNameContainsKeywordsPredicate = (PhoneMatchesDigitsPredicate) other;
+ return phones.equals(otherNameContainsKeywordsPredicate.phones);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("phones", phones).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/filter/RemarkContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/filter/RemarkContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..6f8e2af90b5
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/RemarkContainsKeywordsPredicate.java
@@ -0,0 +1,65 @@
+package seedu.address.model.person.filter;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Tests that a {@code Person}'s {@code Remark} contains any of the keywords given.
+ */
+public class RemarkContainsKeywordsPredicate extends NetConnectPredicate {
+ private final boolean hasEmptyKeyword;
+ private final List keywords;
+
+ /**
+ * Constructs a {@code RemarkContainsKeywordsPredicate} with a list of keywords.
+ *
+ * @param keywords The list of keywords to match against the person's remarks.
+ */
+ public RemarkContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords.stream().filter(Predicate.not(String::isBlank)).collect(Collectors.toList());
+ this.hasEmptyKeyword = keywords.stream().anyMatch(String::isBlank);
+ }
+
+ @Override
+ public String formatFilter() {
+ return keywords.stream().map(s -> PREFIX_REMARK + s).collect(Collectors.joining(" "))
+ + (hasEmptyKeyword ? PREFIX_REMARK + "EMPTY-REMARK" : "");
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream()
+ .anyMatch(keyword -> Arrays.stream(keyword.split("\\s+"))
+ .allMatch(word -> StringUtil.containsWordIgnoreCase(person.getRemark().value, word)))
+ || (hasEmptyKeyword && person.getRemark().value.isBlank());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof RemarkContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ RemarkContainsKeywordsPredicate otherRemarkContainsKeywordsPredicate = (RemarkContainsKeywordsPredicate) other;
+ return keywords.equals(otherRemarkContainsKeywordsPredicate.keywords)
+ && (hasEmptyKeyword == otherRemarkContainsKeywordsPredicate.hasEmptyKeyword);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("remarks", keywords).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/filter/RoleMatchesKeywordsPredicate.java b/src/main/java/seedu/address/model/person/filter/RoleMatchesKeywordsPredicate.java
new file mode 100644
index 00000000000..2dd8715ba78
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/RoleMatchesKeywordsPredicate.java
@@ -0,0 +1,65 @@
+package seedu.address.model.person.filter;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a predicate that checks if a person's role matches any of the specified keywords.
+ * This predicate is used to filter a list of persons based on their roles.
+ */
+public class RoleMatchesKeywordsPredicate extends NetConnectPredicate {
+ private final List keywords;
+
+ /**
+ * Constructs a {@code RoleContainsKeywordsPredicate} with a list of keywords.
+ *
+ * @param keywords The list of keywords to match against the person's role.
+ */
+ public RoleMatchesKeywordsPredicate(List keywords) {
+ this.keywords = Collections.unmodifiableList(keywords);
+ }
+
+ @Override
+ public String formatFilter() {
+ return keywords.stream().map(s -> PREFIX_ROLE + s).collect(Collectors.joining(" "));
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream().anyMatch(person.getRole()::equalsIgnoreCase);
+ }
+
+ /**
+ * Checks if this predicate is equal to another object.
+ *
+ * @param other The object to compare with.
+ * @return {@code true} if the objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof RoleMatchesKeywordsPredicate)) {
+ return false;
+ }
+ RoleMatchesKeywordsPredicate otherPredicate = (RoleMatchesKeywordsPredicate) other;
+ return keywords.equals(otherPredicate.keywords);
+ }
+
+ /**
+ * Returns a string representation of the predicate.
+ *
+ * @return A string representation of the predicate.
+ */
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("keywords", keywords).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/filter/TagsContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/filter/TagsContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..5b600059fe8
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/filter/TagsContainsKeywordsPredicate.java
@@ -0,0 +1,72 @@
+package seedu.address.model.person.filter;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a predicate that checks if a person's tags contains a specified keyword.
+ * This predicate is used to filter a list of persons based on their tags.
+ */
+public class TagsContainsKeywordsPredicate extends NetConnectPredicate {
+ private final List keywords;
+
+ /**
+ * Constructs a {@code TagsContainsKeywordsPredicate} with the specified keyword.
+ *
+ * @param keywords The keyword to match against the person's tags.
+ */
+ public TagsContainsKeywordsPredicate(List keywords) {
+ requireNonNull(keywords);
+
+ this.keywords = keywords.stream().map(String::toLowerCase).collect(Collectors.toList());
+ }
+
+ @Override
+ public String formatFilter() {
+ return keywords.stream().map(keyword -> "t/" + keyword).collect(Collectors.joining(" "));
+ }
+
+ /**
+ * Tests if the given person's tags contains the keyword.
+ *
+ * @param person The person to test.
+ * @return {@code true} if the person's tags contain any of the keyword, {@code false} otherwise.
+ */
+ @Override
+ public boolean test(Person person) {
+ return person.getTags().stream()
+ .map(t -> t.tagName.toLowerCase())
+ .anyMatch(keywords::contains);
+ }
+
+ /**
+ * Checks if this predicate is equal to another object.
+ *
+ * @param other The object to compare with.
+ * @return {@code true} if the objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TagsContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ TagsContainsKeywordsPredicate otherTagsContainsKeywordsPredicate = (TagsContainsKeywordsPredicate) other;
+ return keywords.equals(otherTagsContainsKeywordsPredicate.keywords);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("keywords", keywords).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index f1a0d4e233b..0d5bcd982b3 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -59,4 +59,7 @@ public String toString() {
return '[' + tagName + ']';
}
+ public String getTagName() {
+ return tagName;
+ }
}
diff --git a/src/main/java/seedu/address/model/util/IdTuple.java b/src/main/java/seedu/address/model/util/IdTuple.java
new file mode 100644
index 00000000000..eb7c909f0bb
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/IdTuple.java
@@ -0,0 +1,68 @@
+package seedu.address.model.util;
+
+
+import seedu.address.model.person.Id;
+
+/**
+ * Contains tuple methods for relate command storage.
+ */
+public class IdTuple {
+ private final Id firstPersonId;
+ private final Id secondPersonId;
+
+ /**
+ * Constructor for the IdTuple.
+ *
+ * @param firstPersonId The first person's id.
+ * @param secondPersonId The second person's id.
+ */
+ public IdTuple(Id firstPersonId, Id secondPersonId) {
+ this.firstPersonId = firstPersonId;
+ this.secondPersonId = secondPersonId;
+ }
+
+ // needs a checker for if id is valid, construct the person tuple, and have an overloaded constructor
+
+ public Id getFirstPersonId() {
+ return firstPersonId;
+ }
+
+ public Id getSecondPersonId() {
+ return secondPersonId;
+ }
+
+ public IdTuple getReversedTuple() {
+ return new IdTuple(secondPersonId, firstPersonId);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof IdTuple)) {
+ return false;
+ }
+
+ IdTuple otherTuple = (IdTuple) other;
+
+ if (firstPersonId.equals(otherTuple.getFirstPersonId())
+ && secondPersonId.equals(otherTuple.getSecondPersonId())) {
+ return true;
+ }
+
+ return firstPersonId.equals(otherTuple.getSecondPersonId())
+ && secondPersonId.equals(otherTuple.getFirstPersonId());
+ }
+
+ public boolean relatesItself() {
+ return firstPersonId.equals(secondPersonId);
+ }
+
+ @Override
+ public String toString() {
+ return firstPersonId + "relates" + secondPersonId;
+ }
+}
diff --git a/src/main/java/seedu/address/model/util/RelatedList.java b/src/main/java/seedu/address/model/util/RelatedList.java
new file mode 100644
index 00000000000..2be173adb3e
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/RelatedList.java
@@ -0,0 +1,180 @@
+package seedu.address.model.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.person.Id;
+
+/**
+ * Contains tuple methods for relate command storage.
+ */
+public class RelatedList implements Iterable {
+
+ private final ObservableList relatedPersons = FXCollections.observableArrayList();
+ private final ObservableList relatedPersonsUnmodifiableList =
+ FXCollections.unmodifiableObservableList(relatedPersons);
+
+ public List getListIdTuple() {
+ return relatedPersons;
+ }
+
+ /**
+ * Converts a `String` list of related persons to a RelatedList.
+ *
+ * @return The string representation of the list of related persons.
+ */
+ public RelatedList toArrayList(String string) {
+ RelatedList relatedList = new RelatedList();
+ if (string.equals("") || string.equals("[]")) {
+ return relatedList;
+ }
+ string = string.replace("]", "").replace("[", "");
+ String[] idTuples = string.split(", ");
+
+ for (String idTuple : idTuples) {
+ String[] ids = idTuple.split("relates");
+ Id id1 = Id.generateTempId(Integer.parseInt(ids[0]));
+ Id id2 = Id.generateTempId(Integer.parseInt(ids[1]));
+ relatedPersons.add(new IdTuple(id1, id2));
+ }
+ return relatedList;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof RelatedList)) {
+ return false;
+ }
+
+ RelatedList otherList = (RelatedList) other;
+
+ return relatedPersons.equals(otherList);
+ }
+
+ public IdTuple get(int index) {
+ return relatedPersons.get(index);
+ }
+
+ public void setRelatedList(List idTuples) {
+ relatedPersons.addAll(idTuples);
+ }
+
+ /**
+ * Adds a related person to the list.
+ *
+ * @param idTuple The related person to be added.
+ * @return True if the related person is added, false otherwise.
+ */
+ public boolean allowAddIdTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ for (IdTuple oneIdTuple : relatedPersons) {
+ if (oneIdTuple.equals(idTuple)) {
+ return false;
+ }
+ }
+ relatedPersons.add(idTuple);
+ return true;
+ }
+
+ /**
+ * Checks if the list contains the related person.
+ *
+ * @param idTuple The related person to be checked.
+ * @return True if the related person is in the list, false otherwise.
+ */
+ public boolean hasId(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ for (IdTuple oneIdTuple : relatedPersons) {
+ if (oneIdTuple.equals(idTuple)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes a idTuple from the RelatedList.
+ *
+ * @param idTuple The relation to be deleted.
+ * @return True if the relation is deleted, false otherwise.
+ */
+ public boolean removeTuple(IdTuple idTuple) {
+ requireNonNull(idTuple);
+ if (relatedPersons.contains(idTuple)) {
+ relatedPersons.remove(idTuple);
+ return true;
+ }
+ return relatedPersons.remove(idTuple.getReversedTuple());
+ }
+
+ /**
+ * Retrieves all related IDs from the RelatedList.
+ *
+ * @param relatedList The list of related persons.
+ * @param id The ID to be checked.
+ * @return The list of related IDs.
+ */
+ public List getAllRelatedIds(RelatedList relatedList, Id id) {
+ List relatedIds = new ArrayList<>();
+
+ // Iterate through all IdTuple objects in the RelatedList
+ for (int i = 0; i < relatedList.size(); i++) {
+ assert i >= 0 && i < relatedList.size();
+
+ IdTuple idTuple = relatedList.get(i);
+
+ // Check if the provided ID matches either the first or the second ID in the
+ // tuple
+ if (idTuple.getFirstPersonId().equals(id)) {
+ relatedIds.add(idTuple.getSecondPersonId().value);
+ } else if (idTuple.getSecondPersonId().equals(id)) {
+ relatedIds.add(idTuple.getFirstPersonId().value);
+ }
+ }
+ return relatedIds;
+ }
+
+ /**
+ * Removes all tuples containing specified ID from the relatedList.
+ *
+ * @param id The ID to be removed.
+ */
+ public void removeId(Id id) {
+ requireNonNull(id);
+ relatedPersons.removeIf(idTuple ->
+ idTuple.getFirstPersonId().equals(id) || idTuple.getSecondPersonId().equals(id));
+ }
+
+ public ObservableList asUnmodifiableObservableList() {
+ return relatedPersonsUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return relatedPersons.iterator();
+ }
+
+
+ public int size() {
+ return relatedPersons.size();
+ }
+
+ public boolean isEmpty() {
+ return relatedPersons.isEmpty();
+ }
+
+ public String toString() {
+ return relatedPersons.toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..f5113f58317 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,51 +1,90 @@
package seedu.address.model.util;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.NetConnect;
+import seedu.address.model.ReadOnlyNetConnect;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Client;
+import seedu.address.model.person.Department;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Employee;
+import seedu.address.model.person.JobTitle;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Products;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Skills;
+import seedu.address.model.person.Supplier;
+import seedu.address.model.person.TermsOfService;
import seedu.address.model.tag.Tag;
/**
- * Contains utility methods for populating {@code AddressBook} with sample data.
+ * Contains utility methods for populating {@code NetConnect} with sample data.
*/
public class SampleDataUtil {
+
+ public static final Remark EMPTY_REMARK = new Remark("");
+
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"),
+ return new Person[]{
+ new Client(new Name("Alex Yeoh"), new Phone("87438807"),
+ new Email("alexyeoh@example.com"),
+ new Address("Blk 30 Geylang Street 29, #06-40"), EMPTY_REMARK,
+ getTagSet("friends"),
+ new Products(Arrays.asList("milk", "bread", "eggs", "cheese", "yogurt",
+ "butter", "jam")),
+ "milk"),
+ new Employee(new Name("Bernice Yu"), new Phone("99272758"),
+ new Email("berniceyu@example.com"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_REMARK,
+ getTagSet("colleagues", "friends"), new Department("HR"),
+ new JobTitle("HR Manager"),
+ new Skills(new HashSet(
+ Arrays.asList("Recruitment", "Training", "Payroll")))),
+ new Supplier(new Name("Charlotte Oliveiro"), new Phone("93210283"),
+ new Email("charlotte@example.com"),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), EMPTY_REMARK,
+ getTagSet("neighbours"),
+ new Products(Arrays.asList("milk", "bread", "eggs", "cheese", "yogurt",
+ "butter",
+ "jam")),
+ new TermsOfService("high flexibility")),
+ new Client(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"))
+ EMPTY_REMARK,
+ getTagSet("family"),
+ new Products(Arrays.asList("milk", "yogurt", "butter", "jam")),
+ "yogurt"),
+ new Employee(new Name("Irfan Ibrahim"), new Phone("92492021"),
+ new Email("irfan@example.com"),
+ new Address("Blk 47 Tampines Street 20, #17-35"), EMPTY_REMARK,
+ getTagSet("classmates"), new Department("IT"),
+ new JobTitle("Software Engineer"),
+ new Skills(new HashSet(
+ Arrays.asList("Java", "Python", "C++")))),
+ new Supplier(new Name("Roy Balakrishnan"), new Phone("92624417"),
+ new Email("royb@example.com"),
+ new Address("Blk 45 Aljunied Street 85, #11-31"), EMPTY_REMARK,
+ getTagSet("colleagues"),
+ new Products(Arrays.asList("milk", "bread", "eggs", "cheese", "yogurt",
+ "butter",
+ "jam")),
+ new TermsOfService("low flexibility"))
};
}
- public static ReadOnlyAddressBook getSampleAddressBook() {
- AddressBook sampleAb = new AddressBook();
+ public static ReadOnlyNetConnect getSampleNetConnect() {
+ NetConnect sampleNc = new NetConnect();
for (Person samplePerson : getSamplePersons()) {
- sampleAb.addPerson(samplePerson);
+ sampleNc.addPerson(samplePerson);
}
- return sampleAb;
+ return sampleNc;
}
/**
@@ -57,4 +96,12 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ public static Products getProducts(String... strings) {
+ return new Products(Arrays.asList(strings));
+ }
+
+ public static Skills getSkills(String... strings) {
+ return new Skills(new HashSet(Arrays.asList(strings)));
+ }
+
}
diff --git a/src/main/java/seedu/address/model/util/TestCommandFormatUtil.java b/src/main/java/seedu/address/model/util/TestCommandFormatUtil.java
new file mode 100644
index 00000000000..74c85e00778
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/TestCommandFormatUtil.java
@@ -0,0 +1,25 @@
+package seedu.address.model.util;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Utility class for testing command format validation.
+ */
+public class TestCommandFormatUtil {
+ public static final String MESSAGE_CONSTRAINTS = "Invalid prefix has been detected.";
+ private static final String VALIDATION_STRING = "[^/]*";
+
+ /**
+ * Checks if the given input contains a slash ("/").
+ *
+ * @param input the input string to be checked
+ * @throws IllegalArgumentException if the input does not contain a slash
+ */
+ public static void checkArgumentContainSlash(String input) {
+ checkArgument(validate(input), MESSAGE_CONSTRAINTS);
+ }
+
+ public static boolean validate(String input) {
+ return input.matches(VALIDATION_STRING);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java
deleted file mode 100644
index f2e015105ae..00000000000
--- a/src/main/java/seedu/address/storage/AddressBookStorage.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * Represents a storage for {@link seedu.address.model.AddressBook}.
- */
-public interface AddressBookStorage {
-
- /**
- * Returns the file path of the data file.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns AddressBook data as a {@link ReadOnlyAddressBook}.
- * Returns {@code Optional.empty()} if storage file is not found.
- *
- * @throws DataLoadingException if loading the data from storage failed.
- */
- Optional readAddressBook() throws DataLoadingException;
-
- /**
- * @see #getAddressBookFilePath()
- */
- Optional readAddressBook(Path filePath) throws DataLoadingException;
-
- /**
- * Saves the given {@link ReadOnlyAddressBook} to the storage.
- * @param addressBook cannot be null.
- * @throws IOException if there was any problem writing to the file.
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
- /**
- * @see #saveAddressBook(ReadOnlyAddressBook)
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedIdTuple.java b/src/main/java/seedu/address/storage/JsonAdaptedIdTuple.java
new file mode 100644
index 00000000000..41c45ae7e7f
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedIdTuple.java
@@ -0,0 +1,61 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.model.person.Id;
+import seedu.address.model.util.IdTuple;
+
+/**
+ * Represents a JSON-adapted version of the IdTuple.
+ * An instance of this class is used to serialize/deserialize IdTuple objects to/from JSON format.
+ */
+class JsonAdaptedIdTuple {
+ private final String firstPersonId;
+ private final String secondPersonId;
+
+ /**
+ * Represents a JSON-adapted version of the IdTuple.
+ * An instance of this class is used to marshal/unmarshal IdTuple objects to/from JSON.
+ */
+ public JsonAdaptedIdTuple(@JsonProperty("firstPersonId") String firstPersonId,
+ @JsonProperty("secondPersonId") String secondPersonId) {
+ requireNonNull(firstPersonId);
+ requireNonNull(secondPersonId);
+ this.firstPersonId = firstPersonId;
+ this.secondPersonId = secondPersonId;
+ }
+
+ /**
+ * Represents a JSON-adapted version of the IdTuple.
+ * JsonAdaptedIdTuple is used for JSON serialization and deserialization of IdTuple objects.
+ */
+ public JsonAdaptedIdTuple(IdTuple source) {
+ this.firstPersonId = source.getFirstPersonId().toString();
+ this.secondPersonId = source.getSecondPersonId().toString();
+ }
+
+ /**
+ * Returns the ID of the first person in the tuple.
+ *
+ * @return the ID of the first person
+ */
+ public String getFirstPersonId() {
+ return firstPersonId;
+ }
+
+ /**
+ * Returns the ID of the second person in the tuple.
+ *
+ * @return the ID of the second person
+ */
+ public String getSecondPersonId() {
+ return secondPersonId;
+ }
+
+ public IdTuple toModelType() {
+ return new IdTuple(Id.generateTempId(Integer.parseInt(firstPersonId)),
+ Id.generateTempId(Integer.parseInt(secondPersonId)));
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..b2303237c46 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -11,10 +11,20 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Client;
+import seedu.address.model.person.Department;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Employee;
+import seedu.address.model.person.Id;
+import seedu.address.model.person.JobTitle;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Products;
+import seedu.address.model.person.Remark;
+import seedu.address.model.person.Skills;
+import seedu.address.model.person.Supplier;
+import seedu.address.model.person.TermsOfService;
import seedu.address.model.tag.Tag;
/**
@@ -23,20 +33,39 @@
class JsonAdaptedPerson {
public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
+ public static final String INVALID_ROLE_MESSAGE_FORMAT = "Person's role is invalid!";
+ private final int id;
private final String name;
private final String phone;
private final String email;
private final String address;
private final List tags = new ArrayList<>();
+ private final String remark;
+ private final Department department;
+ private final JobTitle jobTitle;
+ private final JsonAdaptedProducts products;
+ private final String preferences;
+ private final TermsOfService termsOfService;
+ private final JsonAdaptedSkills skills;
+ private final String role;
/**
* 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("id") int id, @JsonProperty("name") String name,
+ @JsonProperty("phone") String phone,
+ @JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("tags") List tags, @JsonProperty("role") String role,
+ @JsonProperty("products") JsonAdaptedProducts products,
+ @JsonProperty("preferences") String preferences,
+ @JsonProperty("department") Department department,
+ @JsonProperty("jobTitle") JobTitle jobTitle,
+ @JsonProperty("termsOfService") TermsOfService termsOfService,
+ @JsonProperty("skills") JsonAdaptedSkills skills,
+ @JsonProperty("remark") String remark) {
+ this.id = id;
this.name = name;
this.phone = phone;
this.email = email;
@@ -44,12 +73,21 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone
if (tags != null) {
this.tags.addAll(tags);
}
+ this.remark = remark;
+ this.role = role;
+ this.products = products == null ? new JsonAdaptedProducts(new ArrayList<>()) : products;
+ this.preferences = preferences == null ? "" : preferences;
+ this.department = department == null ? new Department() : department;
+ this.jobTitle = jobTitle == null ? new JobTitle() : jobTitle;
+ this.termsOfService = termsOfService == null ? new TermsOfService() : termsOfService;
+ this.skills = skills == null ? new JsonAdaptedSkills(new HashSet<>()) : skills;
}
/**
* Converts a given {@code Person} into this class for Jackson use.
*/
public JsonAdaptedPerson(Person source) {
+ id = source.getId().value;
name = source.getName().fullName;
phone = source.getPhone().value;
email = source.getEmail().value;
@@ -57,19 +95,138 @@ public JsonAdaptedPerson(Person source) {
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ remark = source.getRemark().value;
+ if (source instanceof Client) {
+ role = "client";
+ products = new JsonAdaptedProducts(((Client) source).getProducts());
+ skills = new JsonAdaptedSkills(new HashSet<>());
+ preferences = ((Client) source).getPreferences();
+ department = null;
+ jobTitle = null;
+ termsOfService = null;
+ } else if (source instanceof Employee) {
+ role = "employee";
+ department = ((Employee) source).getDepartment();
+ jobTitle = ((Employee) source).getJobTitle();
+ preferences = "";
+ termsOfService = null;
+ skills = new JsonAdaptedSkills(((Employee) source).getSkills().getSkills());
+ products = new JsonAdaptedProducts(new ArrayList<>());
+ } else if (source instanceof Supplier) {
+ role = "supplier";
+ products = new JsonAdaptedProducts(((Supplier) source).getProducts());
+ skills = new JsonAdaptedSkills(new HashSet<>());
+ termsOfService = ((Supplier) source).getTermsOfService();
+ preferences = "";
+ department = null;
+ jobTitle = null;
+ } else {
+ role = "unknown";
+ preferences = "";
+ department = null;
+ jobTitle = null;
+ termsOfService = null;
+ products = new JsonAdaptedProducts(new ArrayList<>());
+ skills = new JsonAdaptedSkills(new HashSet<>());
+ }
+ }
+
+ public Person toModelType() throws IllegalValueException {
+ if (role == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "role"));
+ } else if (role.equals("client")) {
+ return toClientModelType();
+ } else if (role.equals("employee")) {
+ return toEmployeeModelType();
+ } else if (role.equals("supplier")) {
+ return toSupplierModelType();
+ } else {
+ throw new IllegalValueException(INVALID_ROLE_MESSAGE_FORMAT);
+ }
}
/**
- * Converts this Jackson-friendly adapted person object into the model's {@code Person} object.
+ * Converts this Jackson-friendly adapted person object into the model's
+ * {@code Client} object.
*
- * @throws IllegalValueException if there were any data constraints violated in the adapted person.
+ * @throws IllegalValueException if there were any data constraints violated in
+ * the adapted person.
*/
- public Person toModelType() throws IllegalValueException {
+ public Client toClientModelType() throws IllegalValueException {
+ final List personTags = new ArrayList<>();
+ for (JsonAdaptedTag tag : tags) {
+ personTags.add(tag.toModelType());
+ }
+
+ if (!Id.isValidId(id)) {
+ throw new IllegalValueException(Id.MESSAGE_CONSTRAINTS);
+ }
+ final Id modelId = Id.generateId(id);
+
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ }
+ if (!Name.isValidName(name)) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+ final Name modelName = new Name(name);
+
+ if (phone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValidPhone(phone)) {
+ throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ }
+ 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);
+
+ if (remark == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName()));
+ }
+ final Remark modelRemark = new Remark(remark);
+
+ final Set modelTags = new HashSet<>(personTags);
+
+ final Products transformedProducts = products == null ? new Products()
+ : new Products(this.products.getProducts());
+
+ return new Client(modelId, modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags,
+ transformedProducts, preferences);
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted person object into the model's
+ * {@code Employee} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in
+ * the adapted person.
+ */
+ public Employee toEmployeeModelType() throws IllegalValueException {
final List personTags = new ArrayList<>();
for (JsonAdaptedTag tag : tags) {
personTags.add(tag.toModelType());
}
+ if (!Id.isValidId(id)) {
+ throw new IllegalValueException(Id.MESSAGE_CONSTRAINTS);
+ }
+ final Id modelId = Id.generateId(id);
+
if (name == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
}
@@ -102,8 +259,73 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
+ final Remark modelRemark = new Remark(remark);
+
+ final Skills transformedSkills = skills == null ? new Skills() : this.skills.toModelType();
+
+ final Set modelTags = new HashSet<>(personTags);
+ return new Employee(modelId, modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags,
+ department, jobTitle, transformedSkills);
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted person object into the model's
+ * {@code Supplier} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in
+ * the adapted person.
+ */
+ public Supplier toSupplierModelType() throws IllegalValueException {
+ final List personTags = new ArrayList<>();
+ for (JsonAdaptedTag tag : tags) {
+ personTags.add(tag.toModelType());
+ }
+
+ if (!Id.isValidId(id)) {
+ throw new IllegalValueException(Id.MESSAGE_CONSTRAINTS);
+ }
+ final Id modelId = Id.generateId(id);
+
+ final Products transformedProducts = new Products(this.products.getProducts());
+
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ }
+ if (!Name.isValidName(name)) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+ final Name modelName = new Name(name);
+
+ if (phone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValidPhone(phone)) {
+ throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ }
+ 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 Remark modelRemark = new Remark(remark);
+
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ return new Supplier(modelId, modelName, modelPhone, modelEmail, modelAddress, modelRemark, modelTags,
+ transformedProducts,
+ termsOfService);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedProducts.java b/src/main/java/seedu/address/storage/JsonAdaptedProducts.java
new file mode 100644
index 00000000000..5a4a7508888
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedProducts.java
@@ -0,0 +1,53 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Products;
+
+/**
+ * Jackson-friendly version of {@link Products}.
+ */
+public class JsonAdaptedProducts {
+ private List products = new ArrayList<>();
+
+ public JsonAdaptedProducts() {
+ this.products = new ArrayList<>();
+ }
+
+ /**
+ * Constructs a {@code JsonAdaptedProducts} with the given {@code products}.
+ */
+ public JsonAdaptedProducts(Products products) {
+ this.products = products.getProducts();
+ }
+
+ public JsonAdaptedProducts(List products) {
+ this.products = products;
+ }
+
+ public List getProducts() {
+ return products;
+ }
+
+ public void setProducts(List products) {
+ this.products = products;
+ }
+
+ public void addProducts(List products) {
+ this.products.addAll(products);
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted products object into the model's {@code Products} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted products.
+ */
+ public Products toModelType() throws IllegalValueException {
+ if (!Products.isValidProducts(products)) {
+ throw new IllegalValueException(Products.MESSAGE_CONSTRAINTS);
+ }
+ return new Products(products);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedSkills.java b/src/main/java/seedu/address/storage/JsonAdaptedSkills.java
new file mode 100644
index 00000000000..def3d8442bc
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedSkills.java
@@ -0,0 +1,55 @@
+package seedu.address.storage;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Skills;
+
+/**
+ * Jackson-friendly version of {@link Skills}.
+ */
+public class JsonAdaptedSkills {
+ private Set skills = new HashSet<>();
+
+ public JsonAdaptedSkills() {
+ this.skills = new HashSet<>();
+ }
+
+ /**
+ * Constructs a {@code JsonAdaptedSkills} with the given {@code skills}.
+ */
+ public JsonAdaptedSkills(Skills skills) {
+ this.skills = new HashSet<>(skills.getSkills());
+ }
+
+ public JsonAdaptedSkills(Set skills) {
+ this.skills = new HashSet<>(skills);
+ }
+
+ public Set getSkills() {
+ return new HashSet<>(skills);
+ }
+
+ public void setSkills(Set skills) {
+ this.skills = new HashSet<>(skills);
+ }
+
+ public void addSkills(Set skills) {
+ this.skills.addAll(skills);
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted skills object into the model's {@code Skills} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted skills.
+ */
+ public Skills toModelType() throws IllegalValueException {
+ for (String skill : skills) {
+ if (!Skills.isValidSkills(skill)) {
+ throw new IllegalValueException(Skills.MESSAGE_CONSTRAINTS);
+ }
+ }
+ return new Skills(skills);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
deleted file mode 100644
index 41e06f264e1..00000000000
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package seedu.address.storage;
-
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.commons.util.FileUtil;
-import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * A class to access AddressBook data stored as a json file on the hard disk.
- */
-public class JsonAddressBookStorage implements AddressBookStorage {
-
- private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class);
-
- private Path filePath;
-
- public JsonAddressBookStorage(Path filePath) {
- this.filePath = filePath;
- }
-
- public Path getAddressBookFilePath() {
- return filePath;
- }
-
- @Override
- public Optional readAddressBook() throws DataLoadingException {
- return readAddressBook(filePath);
- }
-
- /**
- * Similar to {@link #readAddressBook()}.
- *
- * @param filePath location of the data. Cannot be null.
- * @throws DataLoadingException if loading the data from storage failed.
- */
- public Optional readAddressBook(Path filePath) throws DataLoadingException {
- requireNonNull(filePath);
-
- Optional jsonAddressBook = JsonUtil.readJsonFile(
- filePath, JsonSerializableAddressBook.class);
- if (!jsonAddressBook.isPresent()) {
- return Optional.empty();
- }
-
- try {
- return Optional.of(jsonAddressBook.get().toModelType());
- } catch (IllegalValueException ive) {
- logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
- throw new DataLoadingException(ive);
- }
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, filePath);
- }
-
- /**
- * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}.
- *
- * @param filePath location of the data. Cannot be null.
- */
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- requireNonNull(addressBook);
- requireNonNull(filePath);
-
- FileUtil.createIfMissing(filePath);
- JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonNetConnectStorage.java b/src/main/java/seedu/address/storage/JsonNetConnectStorage.java
new file mode 100644
index 00000000000..7c03e21cbf1
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonNetConnectStorage.java
@@ -0,0 +1,80 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.model.ReadOnlyNetConnect;
+
+/**
+ * A class to access NetConnect data stored as a json file on the hard disk.
+ */
+public class JsonNetConnectStorage implements NetConnectStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonNetConnectStorage.class);
+
+ private final Path filePath;
+
+ public JsonNetConnectStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public Path getNetConnectFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readNetConnect() throws DataLoadingException {
+ return readNetConnect(filePath);
+ }
+
+ /**
+ * Similar to {@link #readNetConnect()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ public Optional readNetConnect(Path filePath) throws DataLoadingException {
+ requireNonNull(filePath);
+
+ Optional jsonNetConnect = JsonUtil.readJsonFile(
+ filePath, JsonSerializableNetConnect.class);
+ if (!jsonNetConnect.isPresent()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonNetConnect.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataLoadingException(ive);
+ }
+ }
+
+ @Override
+ public void saveNetConnect(ReadOnlyNetConnect netConnect) throws IOException {
+ saveNetConnect(netConnect, filePath);
+ }
+
+ /**
+ * Similar to {@link #saveNetConnect(ReadOnlyNetConnect)}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ */
+ public void saveNetConnect(ReadOnlyNetConnect netConnect, Path filePath) throws IOException {
+ requireNonNull(netConnect);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableNetConnect(netConnect), filePath);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
deleted file mode 100644
index 5efd834091d..00000000000
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ /dev/null
@@ -1,60 +0,0 @@
-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 com.fasterxml.jackson.annotation.JsonRootName;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-
-/**
- * An Immutable AddressBook that is serializable to JSON format.
- */
-@JsonRootName(value = "addressbook")
-class JsonSerializableAddressBook {
-
- public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
-
- private final List persons = new ArrayList<>();
-
- /**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
- */
- @JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
- this.persons.addAll(persons);
- }
-
- /**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
- *
- * @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()));
- }
-
- /**
- * Converts this address book into the model's {@code AddressBook} object.
- *
- * @throws IllegalValueException if there were any data constraints violated.
- */
- public AddressBook toModelType() throws IllegalValueException {
- AddressBook addressBook = new AddressBook();
- for (JsonAdaptedPerson jsonAdaptedPerson : persons) {
- Person person = jsonAdaptedPerson.toModelType();
- if (addressBook.hasPerson(person)) {
- throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
- }
- addressBook.addPerson(person);
- }
- return addressBook;
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableNetConnect.java b/src/main/java/seedu/address/storage/JsonSerializableNetConnect.java
new file mode 100644
index 00000000000..8cd2c0ca915
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonSerializableNetConnect.java
@@ -0,0 +1,78 @@
+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 com.fasterxml.jackson.annotation.JsonRootName;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.NetConnect;
+import seedu.address.model.ReadOnlyNetConnect;
+import seedu.address.model.person.Person;
+import seedu.address.model.util.IdTuple;
+
+/**
+ * An Immutable NetConnect that is serializable to JSON format.
+ */
+@JsonRootName(value = "netconnect")
+class JsonSerializableNetConnect {
+
+ public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_ID = "Persons list contains duplicate id(s).";
+ public static final String MESSAGE_DUPLICATE_ID_TUPLE = "Related Ids list contains duplicate id(s).";
+
+ private final List persons = new ArrayList<>();
+ private final List relatedIds = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonSerializableNetConnect} with the given persons.
+ */
+ @JsonCreator
+ public JsonSerializableNetConnect(@JsonProperty("persons") List persons,
+ @JsonProperty("relatedIds") List relatedIds) {
+ this.persons.addAll(persons);
+ this.relatedIds.addAll(relatedIds);
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyNetConnect} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created
+ * {@code JsonSerializableNetConnect}.
+ */
+ public JsonSerializableNetConnect(ReadOnlyNetConnect source) {
+ persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList()));
+ relatedIds.addAll(source.getListIdTuple().stream().map(JsonAdaptedIdTuple::new).collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this address book into the model's {@code NetConnect} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public NetConnect toModelType() throws IllegalValueException {
+ NetConnect netConnect = new NetConnect();
+ for (JsonAdaptedPerson jsonAdaptedPerson : persons) {
+ Person person = jsonAdaptedPerson.toModelType();
+ if (netConnect.hasPerson(person)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
+ }
+ if (netConnect.hasId(person.getId())) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_ID);
+ }
+ netConnect.addPerson(person);
+ }
+ for (JsonAdaptedIdTuple idTuple : relatedIds) {
+ IdTuple idTupleModel = idTuple.toModelType();
+ if (netConnect.hasRelatedId(idTupleModel)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_ID_TUPLE);
+ }
+ netConnect.allowAddIdTuple(idTupleModel);
+ }
+ return netConnect;
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
index 48a9754807d..f9fbe40ec9b 100644
--- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
+++ b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
@@ -14,7 +14,7 @@
*/
public class JsonUserPrefsStorage implements UserPrefsStorage {
- private Path filePath;
+ private final Path filePath;
public JsonUserPrefsStorage(Path filePath) {
this.filePath = filePath;
@@ -32,6 +32,7 @@ public Optional readUserPrefs() throws DataLoadingException {
/**
* Similar to {@link #readUserPrefs()}
+ *
* @param prefsFilePath location of the data. Cannot be null.
* @throws DataLoadingException if the file format is not as expected.
*/
diff --git a/src/main/java/seedu/address/storage/NetConnectStorage.java b/src/main/java/seedu/address/storage/NetConnectStorage.java
new file mode 100644
index 00000000000..c744902c643
--- /dev/null
+++ b/src/main/java/seedu/address/storage/NetConnectStorage.java
@@ -0,0 +1,46 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.ReadOnlyNetConnect;
+
+/**
+ * Represents a storage for {@link seedu.address.model.NetConnect}.
+ */
+public interface NetConnectStorage {
+
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getNetConnectFilePath();
+
+ /**
+ * Returns NetConnect data as a {@link ReadOnlyNetConnect}.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ *
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ Optional readNetConnect() throws DataLoadingException;
+
+ /**
+ * @see #getNetConnectFilePath()
+ */
+ Optional readNetConnect(Path filePath) throws DataLoadingException;
+
+ /**
+ * Saves the given {@link ReadOnlyNetConnect} to the storage.
+ *
+ * @param netConnect cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveNetConnect(ReadOnlyNetConnect netConnect) throws IOException;
+
+ /**
+ * @see #saveNetConnect(ReadOnlyNetConnect)
+ */
+ void saveNetConnect(ReadOnlyNetConnect netConnect, Path filePath) throws IOException;
+
+}
diff --git a/src/main/java/seedu/address/storage/StateStorage.java b/src/main/java/seedu/address/storage/StateStorage.java
new file mode 100644
index 00000000000..915d48e4ef0
--- /dev/null
+++ b/src/main/java/seedu/address/storage/StateStorage.java
@@ -0,0 +1,32 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+
+/**
+ * Represents the database to store the previous state of command before the application is closed.
+ */
+public interface StateStorage {
+ /**
+ * Returns the file path of the StateStorage data file.
+ */
+ Path getStateStorageFilePath();
+
+ /**
+ * Returns command box state data from storage.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ *
+ * @throws DataLoadingException if the loading of data from preference file failed.
+ */
+ String readState() throws DataLoadingException;
+
+ /**
+ * Saves the given string retrieved from the command box to the state storage.
+ *
+ * @param input retrieved from the command box.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveState(String input) throws IOException;
+}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index 9fba0c7a1d6..4598dc16dff 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -5,14 +5,14 @@
import java.util.Optional;
import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyNetConnect;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends NetConnectStorage, UserPrefsStorage, StateStorage {
@Override
Optional readUserPrefs() throws DataLoadingException;
@@ -21,12 +21,20 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage {
void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
@Override
- Path getAddressBookFilePath();
+ Path getNetConnectFilePath();
@Override
- Optional readAddressBook() throws DataLoadingException;
+ Optional readNetConnect() throws DataLoadingException;
@Override
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
+ void saveNetConnect(ReadOnlyNetConnect netConnect) throws IOException;
+ @Override
+ String readState() throws DataLoadingException;
+
+ @Override
+ void saveState(String input) throws IOException;
+
+ @Override
+ Path getStateStorageFilePath();
}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 8b84a9024d5..660fb66ad60 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -7,25 +7,29 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyNetConnect;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
/**
- * Manages storage of AddressBook data in local storage.
+ * Manages storage of NetConnect data in local storage.
*/
public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
- private AddressBookStorage addressBookStorage;
- private UserPrefsStorage userPrefsStorage;
+ private final NetConnectStorage netConnectStorage;
+ private final UserPrefsStorage userPrefsStorage;
+ private final StateStorage stateStorage;
/**
- * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
+ * Creates a {@code StorageManager} with the given {@code NetConnectStorage} and
+ * {@code UserPrefStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
- this.addressBookStorage = addressBookStorage;
+ public StorageManager(NetConnectStorage netConnectStorage,
+ UserPrefsStorage userPrefsStorage, StateStorage stateStorage) {
+ this.netConnectStorage = netConnectStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.stateStorage = stateStorage;
}
// ================ UserPrefs methods ==============================
@@ -45,34 +49,51 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
userPrefsStorage.saveUserPrefs(userPrefs);
}
-
- // ================ AddressBook methods ==============================
+ // ================ NetConnect methods ==============================
@Override
- public Path getAddressBookFilePath() {
- return addressBookStorage.getAddressBookFilePath();
+ public Path getNetConnectFilePath() {
+ return netConnectStorage.getNetConnectFilePath();
}
@Override
- public Optional readAddressBook() throws DataLoadingException {
- return readAddressBook(addressBookStorage.getAddressBookFilePath());
+ public Optional readNetConnect() throws DataLoadingException {
+ return readNetConnect(netConnectStorage.getNetConnectFilePath());
}
@Override
- public Optional readAddressBook(Path filePath) throws DataLoadingException {
+ public Optional readNetConnect(Path filePath) throws DataLoadingException {
logger.fine("Attempting to read data from file: " + filePath);
- return addressBookStorage.readAddressBook(filePath);
+ return netConnectStorage.readNetConnect(filePath);
}
@Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath());
+ public void saveNetConnect(ReadOnlyNetConnect netConnect) throws IOException {
+ saveNetConnect(netConnect, netConnectStorage.getNetConnectFilePath());
}
@Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
+ public void saveNetConnect(ReadOnlyNetConnect netConnect, Path filePath) throws IOException {
logger.fine("Attempting to write to data file: " + filePath);
- addressBookStorage.saveAddressBook(addressBook, filePath);
+ netConnectStorage.saveNetConnect(netConnect, filePath);
+ }
+
+ // ================ StateStorage methods ==============================
+
+ @Override
+ public void saveState(String input) throws IOException {
+ logger.fine("Attempting to write to data file: " + getStateStorageFilePath());
+ stateStorage.saveState(input);
}
+ @Override
+ public String readState() throws DataLoadingException {
+ logger.fine("Attempting to read data from file: " + stateStorage.getStateStorageFilePath());
+ return stateStorage.readState();
+ }
+
+ @Override
+ public Path getStateStorageFilePath() {
+ return stateStorage.getStateStorageFilePath();
+ }
}
diff --git a/src/main/java/seedu/address/storage/TextStateStorage.java b/src/main/java/seedu/address/storage/TextStateStorage.java
new file mode 100644
index 00000000000..0b251be03ba
--- /dev/null
+++ b/src/main/java/seedu/address/storage/TextStateStorage.java
@@ -0,0 +1,129 @@
+package seedu.address.storage;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+import seedu.address.MainApp;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+
+/**
+ * A class to access state of command box stored in the hard disk as a text file.
+ */
+public class TextStateStorage implements StateStorage {
+ private static final String FILE_PATH_STRING = "./data/state.txt";
+ private static final Path DIRECTORY_PATH = Paths.get("./data");
+ private static final Path FILE_PATH = Paths.get(FILE_PATH_STRING);
+ private static final Logger logger = LogsCenter.getLogger(MainApp.class);
+
+ /**
+ * Constructor for the storage.
+ *
+ * The constructor creates a new file and/or directory if the filepath for ./data/state.txt does not exist.
+ */
+ public TextStateStorage() {
+ assert !FILE_PATH_STRING.isBlank() : "the file path should not be blank";
+
+ try {
+ if (!Files.exists(DIRECTORY_PATH)) {
+ Files.createDirectories(DIRECTORY_PATH);
+ }
+ if (!Files.exists(FILE_PATH)) {
+ Files.createFile(FILE_PATH);
+ }
+ } catch (IOException e) {
+ logger.info("Error clearing creating state storage: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Clears all the text in the state storage file.
+ */
+ public static void clearState() throws IOException {
+ Path filePath = Paths.get(FILE_PATH_STRING);
+ Files.delete(filePath);
+ Files.createFile(filePath);
+ }
+
+ /**
+ * Checks if the state storage file exists.
+ *
+ * @return True if the file exists, else false.
+ */
+ public static boolean isStateStorageExists() {
+ return Files.exists(FILE_PATH);
+ }
+
+ /**
+ * Deletes the entire state storage file.
+ */
+ public static void deleteStateStorage() throws IOException {
+ Files.delete(FILE_PATH);
+ }
+
+ /**
+ * Saves the command to the state storage by writing to the file.
+ *
+ * @param input Updated command input (at every change) to be written to the storage file.
+ */
+ public void saveState(String input) throws IOException {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH_STRING))) {
+ writer.write(input);
+ }
+ }
+
+ /**
+ * Retrieves the past state of the command box if found, else it will return an empty command.
+ *
+ * @return The last input in the command box, or and empty string if not found.
+ * @throws DataLoadingException If the file is not found or cannot be read.
+ */
+ public String readState() throws DataLoadingException {
+ String lastCommand = "";
+ try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH_STRING))) {
+ String data = reader.readLine();
+
+ while (data != null) {
+ lastCommand = lastCommand + data;
+ data = reader.readLine();
+ }
+ } catch (IOException e) {
+ throw new DataLoadingException(e);
+ }
+ return lastCommand;
+ }
+
+ /**
+ * Returns the location of the state file.
+ *
+ * @return The path of the state file.
+ */
+ public Path getStateStorageFilePath() {
+ return FILE_PATH;
+ }
+
+ /**
+ * Returns the location of the state file as a String.
+ *
+ * @return The path of the state file as a String.
+ */
+ public static String getFilePathString() {
+ return FILE_PATH_STRING;
+ }
+
+ /**
+ * Returns the location of the state directory.
+ *
+ * @return The path of the state directory.
+ */
+ public static Path getDirectoryPath() {
+ return DIRECTORY_PATH;
+ }
+}
diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java
index e94ca422ea8..39a0baac068 100644
--- a/src/main/java/seedu/address/storage/UserPrefsStorage.java
+++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java
@@ -28,6 +28,7 @@ public interface UserPrefsStorage {
/**
* Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage.
+ *
* @param userPrefs cannot be null.
* @throws IOException if there was any problem writing to the file.
*/
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
index 9e75478664b..478538f10e5 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/seedu/address/ui/CommandBox.java
@@ -1,12 +1,19 @@
package seedu.address.ui;
+import java.io.IOException;
+import java.util.logging.Logger;
+
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
+import seedu.address.MainApp;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.storage.TextStateStorage;
/**
* The UI component that is responsible for receiving user command inputs.
@@ -15,9 +22,9 @@ public class CommandBox extends UiPart {
public static final String ERROR_STYLE_CLASS = "error";
private static final String FXML = "CommandBox.fxml";
-
+ private static final Logger logger = LogsCenter.getLogger(MainApp.class);
private final CommandExecutor commandExecutor;
-
+ private final TextStateStorage stateStorage = new TextStateStorage();
@FXML
private TextField commandTextField;
@@ -27,10 +34,27 @@ public class CommandBox extends UiPart {
public CommandBox(CommandExecutor commandExecutor) {
super(FXML);
this.commandExecutor = commandExecutor;
- // calls #setStyleToDefault() whenever there is a change to the text of the command box.
- commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
+
+ try {
+ commandTextField.setText(stateStorage.readState());
+ commandTextField.end();
+ } catch (DataLoadingException e) {
+ logger.warning("State file at " + stateStorage.getStateStorageFilePath() + " could not be loaded."
+ + " Starting with an empty command box.");
+ }
+
+
+ commandTextField.textProperty().addListener((observable, oldValue, newValue) -> {
+ setStyleToDefault();
+ try {
+ stateStorage.saveState(newValue);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
+
/**
* Handles the Enter button pressed event.
*/
@@ -52,14 +76,14 @@ private void handleCommandEntered() {
/**
* Sets the command box style to use the default style.
*/
- private void setStyleToDefault() {
+ void setStyleToDefault() {
commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
}
/**
* Sets the command box style to indicate a failed command.
*/
- private void setStyleToIndicateCommandFailure() {
+ void setStyleToIndicateCommandFailure() {
ObservableList styleClass = commandTextField.getStyleClass();
if (styleClass.contains(ERROR_STYLE_CLASS)) {
@@ -69,6 +93,15 @@ private void setStyleToIndicateCommandFailure() {
styleClass.add(ERROR_STYLE_CLASS);
}
+ /**
+ * Returns the command text field for the command box instance.
+ *
+ * @return The command text field.
+ */
+ protected TextField getCommandTextField() {
+ return commandTextField;
+ }
+
/**
* Represents a function that can execute commands.
*/
diff --git a/src/main/java/seedu/address/ui/DestructiveConfirmationWindow.java b/src/main/java/seedu/address/ui/DestructiveConfirmationWindow.java
new file mode 100644
index 00000000000..38bfc160ca0
--- /dev/null
+++ b/src/main/java/seedu/address/ui/DestructiveConfirmationWindow.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import seedu.address.commons.core.LogsCenter;
+
+/**
+ * Controller for a Destructive Action page
+ */
+public class DestructiveConfirmationWindow {
+ private static final Logger logger = LogsCenter.getLogger(DestructiveConfirmationWindow.class);
+
+ private static final String TITLE = "Destructive Action Confirmation";
+ private static final String CLEAR_HEADER_TEXT =
+ "Are you sure you want to clear the address book? This action cannot be undone.";
+ private static final String DELETE_HEADER_TEXT =
+ "Are you sure you want to delete this person? This action cannot be undone";
+
+ /**
+ * Shows a confirmation dialog for destructive actions.
+ *
+ * @param isDeleteCommand true if the command is a delete command
+ * @param isClearCommand true if the command is a clear command
+ * @return true if the user confirms the action, false otherwise
+ */
+ @FXML
+ public static boolean handleDestructiveConfirmation(boolean isDeleteCommand, boolean isClearCommand) {
+
+ logger.info("Showing destructive action confirmation");
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.setTitle(TITLE);
+ if (isClearCommand) {
+ alert.setHeaderText(CLEAR_HEADER_TEXT);
+ } else if (isDeleteCommand) {
+ alert.setHeaderText(DELETE_HEADER_TEXT);
+ }
+
+ Optional result = alert.showAndWait();
+
+ if (result.isPresent() && result.get() == ButtonType.OK) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..d1fb73c52ec 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-f12-1.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
@@ -46,21 +46,21 @@ public HelpWindow() {
/**
* Shows the help window.
- * @throws IllegalStateException
- *
- *
- * if this method is called on a thread other than the JavaFX Application Thread.
- *
- *
- * if this method is called during animation or layout processing.
- *
- *
- * if this method is called on the primary stage.
- *
- *
- * if {@code dialogStage} is already showing.
- *
- *
+ *
+ * @throws IllegalStateException
+ *
+ * if this method is called on a thread other than the JavaFX Application Thread.
+ *
+ *
+ * if this method is called during animation or layout processing.
+ *
+ *
+ * if this method is called on the primary stage.
+ *
+ *
+ * if {@code dialogStage} is already showing.
+ *