diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml
new file mode 100644
index 00000000000..fec0c4cbbd1
--- /dev/null
+++ b/.github/auto_assign.yml
@@ -0,0 +1,37 @@
+# https://probot.github.io/apps/auto-assign/
+
+
+
+# Set to true to add reviewers to PRs
+addReviewers: true
+
+# List of reviewers to add to PRs (GitHub username)
+reviewers:
+ - bwangpj
+ - Cloud7050
+ - mamayuan
+ - rayshawntan
+
+# Number of reviewers to randomly add to PRs.
+# Set 0 to add all reviewers.
+# Defaults to 0
+# numberOfReviewers: 0
+
+# Set to true to add assignees to PRs
+# addAssignees: true
+
+# List of assignees to add to PRs (GitHub username).
+# Presumably takes priority over reviewers, for usernames set in both
+# assignees:
+# - assigneeA
+# - assigneeB
+# - assigneeC
+
+# Number of assignees to randomly add to PRs.
+# Set to 0 to add all assignees.
+# Defaults to numberOfReviewers
+# numberOfAssignees: 0
+
+# Keywords used to decide whether to skip all adding for a PR
+skipKeywords:
+ - no-assign
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000000..c506a79d4ff
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,5 @@
+[Enter your description here...]
+
+[Closes #0.]
+
+(Please remember to assign the appropriate milestone, via the sidebar at the bottom right. This will allow your PR to be counted by the grading scripts (https://nus-cs2103-ay2324s1.github.io/dashboards/contents/tp-progress.html). Feel free to delete this message when done.)
diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..264b5a0ce1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,24 @@
-# Gradle build files
-/.gradle/
-/build/
-src/main/resources/docs/
+# macOS Finder files
+.DS_Store
# IDEA files
/.idea/
/out/
/*.iml
-# Storage/log files
+# VSCode extension build files
+/bin/
+
+# JVM crash logs
+/hs_err_pid[0-9]*.log
+
+# Gradle build files
+/.gradle/
+/build/
+/src/main/resources/docs/
+
+# App data/log files
/data/
/config.json
-/preferences.json
+/settings.json
/*.log.*
-hs_err_pid[0-9]*.log
-
-# Test sandbox files
-src/test/data/sandbox/
-
-# MacOS custom attributes files created by Finder
-.DS_Store
-docs/_site/
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000000..0fb677e1f57
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "editor.detectIndentation": false,
+ "editor.insertSpaces": true
+}
diff --git a/README.md b/README.md
index 13f5c77403f..f2dfb5857cb 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,10 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-
-![Ui](docs/images/Ui.png)
-
-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
-* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+# ConText
+
+[![Java CI](https://github.com/AY2324S1-CS2103-W14-3/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S1-CS2103-W14-3/tp/actions/workflows/gradle.yml)
+[![codecov](https://codecov.io/gh/AY2324S1-CS2103-W14-3/tp/graph/badge.svg?token=KT7MNHKALX)](https://codecov.io/gh/AY2324S1-CS2103-W14-3/tp)
+
+For information as well as detailed user/developer documentation, check out the [product website](https://ay2324s1-cs2103-w14-3.github.io/tp/).
+
+![UI](./docs/images/Ui.png)
+
+This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
diff --git a/build.gradle b/build.gradle
index a2951cc709e..5a047ecc6fd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,23 +1,76 @@
plugins {
- id 'java'
- id 'checkstyle'
- id 'com.github.johnrengelman.shadow' version '7.1.2'
+ // application includes java
id 'application'
+ id 'checkstyle'
+
id 'jacoco'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
}
-mainClassName = 'seedu.address.Main'
-
-sourceCompatibility = JavaVersion.VERSION_11
-targetCompatibility = JavaVersion.VERSION_11
-
repositories {
mavenCentral()
- maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
-checkstyle {
- toolVersion = '10.2'
+dependencies {
+ String versionJUnit = '5.10.0'
+ /* NOTE
+ * Upgrading Jackson to 2.15.2 causes some tests to fail, as saving Config's
+ * Path results in a full path instead of the original relative one.
+ */
+ String versionJackson = '2.7.0'
+ /* NOTE
+ * We stay on JavaFX 17 instead of 21, for better Java 11 compatibility in
+ * general.
+ *
+ * The documentation states that Java 17 is required for either, but Gluon's
+ * download page states otherwise:
+ * • https://openjfx.io/openjfx-docs/
+ * • https://gluonhq.com/products/javafx/
+ */
+ String versionJavaFx = '17.0.9'
+
+ implementation group: 'org.openjfx', name: 'javafx-base', version: versionJavaFx, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: versionJavaFx, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: versionJavaFx, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: versionJavaFx, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: versionJavaFx, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: versionJavaFx, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: versionJavaFx, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: versionJavaFx, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: versionJavaFx, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: versionJavaFx, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: versionJavaFx, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: versionJavaFx, classifier: 'linux'
+
+ implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: versionJackson
+ implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: versionJackson
+
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: versionJUnit
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: versionJUnit
+}
+
+
+
+// https://docs.gradle.org/current/userguide/img/javaPluginTasks.png
+defaultTasks 'clean', 'test'
+
+java {
+ // targetCompatibility also defaults to sourceCompatibility
+ sourceCompatibility JavaVersion.VERSION_11
+}
+
+application {
+ mainClass = 'swe.context.Main'
+}
+
+clean {
+ delete './data/'
+ delete './settings.json'
+ delete fileTree('./') { include '*.log.*' }
+}
+
+run {
+ enableAssertions true
}
test {
@@ -26,6 +79,11 @@ test {
}
task coverage(type: JacocoReport) {
+ // "Task ':coverage' uses this output of task ':test' without declaring an
+ // explicit or implicit dependency. This can lead to incorrect results being
+ // produced, depending on what order the tasks are executed."
+ dependsOn test
+
sourceDirectories.from files(sourceSets.main.allSource.srcDirs)
classDirectories.from files(sourceSets.main.output)
executionData.from files(jacocoTestReport.executionData)
@@ -40,33 +98,10 @@ task coverage(type: JacocoReport) {
}
}
-dependencies {
- String jUnitVersion = '5.4.0'
- String javaFxVersion = '17.0.7'
-
- implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
- implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
- implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
- implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
- implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
- implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
- implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
- implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
- implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
- implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
- implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
- implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
-
- implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0'
- implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4'
-
- testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion
-
- testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion
+checkstyle {
+ toolVersion = '10.2'
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'context.jar'
}
-
-defaultTasks 'clean', 'test'
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index eb761a9b9a7..44048c50540 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -1,434 +1,425 @@
-
-
-
-
+ "https://checkstyle.org/dtds/configuration_1_3.dtd"
+>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
-
-
+
+
+
-
+
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
-
-
-
diff --git a/copyright.txt b/copyright.txt
deleted file mode 100644
index 93aa2a39ce2..00000000000
--- a/copyright.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob
-
-Copyright by Susumu Yoshida - http://www.mcdodesign.com/
-- address_book_32.png
-- AddressApp.ico
-
-Copyright by Jan Jan Kovařík - http://glyphicons.com/
-- calendar.png
-- edit.png
diff --git a/deliverables/[CS2103-W14-3][ConText].jar b/deliverables/[CS2103-W14-3][ConText].jar
new file mode 100644
index 00000000000..1ad63f36b42
Binary files /dev/null and b/deliverables/[CS2103-W14-3][ConText].jar differ
diff --git a/deliverables/[CS2103-W14-3][ConText]DG.pdf b/deliverables/[CS2103-W14-3][ConText]DG.pdf
new file mode 100644
index 00000000000..6b4ba4a4663
Binary files /dev/null and b/deliverables/[CS2103-W14-3][ConText]DG.pdf differ
diff --git a/deliverables/[CS2103-W14-3][ConText]UG.pdf b/deliverables/[CS2103-W14-3][ConText]UG.pdf
new file mode 100644
index 00000000000..d01510b3713
Binary files /dev/null and b/deliverables/[CS2103-W14-3][ConText]UG.pdf differ
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..f5a9d0f1afe 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -3,57 +3,50 @@ layout: page
title: About Us
---
-We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
+We are a team taking CS2103 at NUS in AY23/24 S1.
-You can reach us at the email `seer[at]comp.nus.edu.sg`
+This page is designed to closely follow the format required by grading scripts.
## Project team
-### John Doe
+### Cloud7050
-
+*The public name and image are placeholders.*
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+
-* Role: Project Advisor
+[[github](https://github.com/Cloud7050)]
+[[portfolio](./team/cloud7050.md)]
-### Jane Doe
+- Role: Team Lead
+- Responsibilities: Code quality. Assists with: Integration (e.g. maintaining repo, merging PRs), scheduling & tracking (e.g. defining/assigning/editing issues/PRs)
-
+### mamayuan
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+
-* Role: Team Lead
-* Responsibilities: UI
-
-### Johnny Doe
-
-
-
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/mamayuan)]
+[[portfolio](./team/mamayuan.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Testing and Integration (e.g. maintaining repo, merging PRs)
-### Jean Doe
+### bwangpj
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/bwangpj)]
+[[portfolio](./team/bwangpj.md)]
-* Role: Developer
-* Responsibilities: Dev Ops + Threading
+- Role: Developer
+- Responsibilities: Documentation
-### James Doe
+### rayshawntan
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/rayshawntan)]
+[[portfolio](./team/rayshawntan.md)]
-* Role: Developer
-* Responsibilities: UI
+- Role: Developer
+- Responsibilities: Scheduling and tracking
diff --git a/docs/Configuration.md b/docs/Configuration.md
deleted file mode 100644
index 13cf0faea16..00000000000
--- a/docs/Configuration.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-layout: page
-title: Configuration guide
----
-
-Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`).
diff --git a/docs/DevOps.md b/docs/DevOps.md
deleted file mode 100644
index d2fd91a6001..00000000000
--- a/docs/DevOps.md
+++ /dev/null
@@ -1,79 +0,0 @@
----
-layout: page
-title: DevOps guide
----
-
-* Table of Contents
-{:toc}
-
---------------------------------------------------------------------------------------------------------------------
-
-## Build automation
-
-This project uses Gradle for **build automation and dependency management**. **You are recommended to read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**.
-
-
-Given below are how to use Gradle for some important project tasks.
-
-
-* **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
- e.g. `./gradlew clean`
-
-* **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, *if the current file is outdated*.
- e.g. `./gradlew shadowJar`.
-
-* **`run`**: Builds and runs the application.
- **`runShadow`**: Builds the application as a fat JAR, and then runs it.
-
-* **`checkstyleMain`**: Runs the code style check for the main code base.
- **`checkstyleTest`**: Runs the code style check for the test code base.
-
-* **`test`**: Runs all tests.
- * `./gradlew test` — Runs all tests
- * `./gradlew clean test` — Cleans the project and runs tests
-
---------------------------------------------------------------------------------------------------------------------
-
-## Continuous integration (CI)
-
-This project uses GitHub Actions for CI. The project comes with the necessary GitHub Actions configurations files (in the `.github/workflows` folder). No further setting up required.
-
-### Code coverage
-
-As part of CI, this project uses Codecov to generate coverage reports. When CI runs, it will generate code coverage data (based on the tests run by CI) and upload that data to the CodeCov website, which in turn can provide you more info about the coverage of your tests.
-
-However, because Codecov is known to run into intermittent problems (e.g., report upload fails) due to issues on the Codecov service side, the CI is configured to pass even if the Codecov task failed. Therefore, developers are advised to check the code coverage levels periodically and take corrective actions if the coverage level falls below desired levels.
-
-To enable Codecov for forks of this project, follow the steps given in [this se-edu guide](https://se-education.org/guides/tutorials/codecov.html).
-
-### Repository-wide checks
-
-In addition to running Gradle checks, CI includes some repository-wide checks. Unlike the Gradle checks which only cover files used in the build process, these repository-wide checks cover all files in the repository. They check for repository rules which are hard to enforce on development machines such as line ending requirements.
-
-These checks are implemented as POSIX shell scripts, and thus can only be run on POSIX-compliant operating systems such as macOS and Linux. To run all checks locally on these operating systems, execute the following in the repository root directory:
-
-`./config/travis/run-checks.sh`
-
-Any warnings or errors will be printed out to the console.
-
-**If adding new checks:**
-
-* Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest.
-
-* Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE`
- * SEVERITY is either ERROR or WARN.
- * FILENAME is the path to the file relative to the current directory.
- * LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error.
-
-* Check scripts must exit with a non-zero exit code if any errors occur.
-
---------------------------------------------------------------------------------------------------------------------
-
-## Making a release
-
-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. 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 8a861859bfd..f5994d6f149 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,49 +2,37 @@
layout: page
title: Developer Guide
---
-* Table of Contents
-{:toc}
-
---------------------------------------------------------------------------------------------------------------------
-
-## **Acknowledgements**
-
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
---------------------------------------------------------------------------------------------------------------------
-
-## **Setting up, getting started**
-
-Refer to the guide [_Setting up and getting started_](SettingUp.md).
+- Table of Contents
+{:toc}
---------------------------------------------------------------------------------------------------------------------
+---
-## **Design**
+## Design
-
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
+:bulb: **Tip:** The `.puml` files used to create diagrams in this document are in the `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
### Architecture
-The ***Architecture Diagram*** given above explains the high-level design of the App.
+The ***Architecture Diagram*** given above explains the high-level design of the app.
Given below is a quick overview of main components and how they interact with each other.
**Main components of the architecture**
-**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
+**`Main`** (consisting of classes [`Main`](https://github.com/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/MainApp.java)) is in charge of the app launch and shut down.
* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
The bulk of the app's work is done by the following four components:
-* [**`UI`**](#ui-component): The UI of the App.
+* [**`UI`**](#ui-component): The UI of the app.
* [**`Logic`**](#logic-component): The command executor.
-* [**`Model`**](#model-component): Holds the data of the App in memory.
+* [**`Model`**](#model-component): Holds the data of the app in memory.
* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
@@ -68,41 +56,42 @@ 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/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/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.
+(Remark: The overlapping triangles into `UiPart` are all class inheritance triangles (limitation of PlantUML).)
+
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `ContactListPanel`, `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/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
* executes user commands using the `Logic` component.
* listens for changes to `Model` data so that the UI can be updated with the modified data.
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
-* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+* depends on some classes in the `Model` component, as it displays `Contact` object residing in the `Model`.
+
+The `CommandBox` part keeps a reference to a `CommandBoxHistory` object, which stores the history of entered commands, allowing for the functionality of navigating to previous commands using the up/down arrow keys within the `CommandBox`.
### 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/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/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 1")` API call as an example.
+The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1 3 5")` 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 reaches the end of diagram.
-
+![Interactions Inside the Logic Component for the `delete 1 3 5` Command](images/DeleteSequenceDiagram.png)
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 `InputParser` 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).
+1. The command can communicate with the `Model` when it is executed (e.g. to delete a contact).
1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
@@ -110,268 +99,491 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha
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 `InputParser` 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 `InputParser` 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/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/model/Model.java)
+
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
-* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
-* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
+* stores the contacts data i.e., all `Contact` objects (which are contained in a `UniqueContactList` object).
+* stores the currently 'selected' `Contact` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
+* stores a `Settings` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlySettings` 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.
-
-
-
-
-
-
### 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/AY2324S1-CS2103-W14-3/tp/tree/master/src/main/java/swe/context/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 contacts data and user preference data in JSON format, and read them back into corresponding objects.
+* inherits from both `ContactsStorage` and `SettingsStorage`, 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 `swe.context.commons` package.
---------------------------------------------------------------------------------------------------------------------
+---
-## **Implementation**
+## Implementation
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Add feature
-#### Proposed Implementation
+The add feature is facilitated by `ModelManager` and implements `Model`.
-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 following sequence diagram shows how the add command works:
-* `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.
+![AddSequenceDiagram](images/AddSequenceDiagram.png)
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+It adds an contact by calling `Model#addContact`, which adds the newly created contact into the `UniqueContactList`.
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+The following activity diagram summarises what happens when a user executes a new command.
-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.
+![AddActivityDiagram](images/AddActivityDiagram.png)
-![UndoRedoState0](images/UndoRedoState0.png)
+### Filter feature
-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.
+The following sequence diagram shows how the filter command works:
-![UndoRedoState1](images/UndoRedoState1.png)
+![FilterSequenceDiagram](images/FilterSequenceDiagram.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`.
+It filters contacts by calling `Model#setContactsFilter` with a `ContainsTagPredicate predicate` as argument, which sets the `predicate`
+on the list of contacts in the `ModelManager`.
-![UndoRedoState2](images/UndoRedoState2.png)
+### Maintaining sorting while supporting filtering
-
: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`.
+The contact list is automatically kept in a constantly sorted state by leveraging `SortedList` from the JavaFX Collections library. Since the class works with `ObservableList`s, which the Model's `Contacts` also utilises, we are able to leverage this class more easily.
-
+The Model obtains an unsorted, unmodifiable list from `Contacts` and wraps it in a `SortedList`. We specify an `AlphabeticalComparator` to define our own alphabetical sorting order, which takes capitalization into account. This facilitates the intended propagation of changes from the nested list to the sorted list.
-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.
+For operability with the find and filter feature, this sorted list is further wrapped in a `FilteredList` to limit the scope of what the user sees as needed. A dummy filter `Predicate` which allows all contacts to pass is used as the default filter. It is this filtered list that the model stores in a field.
-![UndoRedoState3](images/UndoRedoState3.png)
+### Edit feature
-
: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
-than attempting to perform the undo.
+The edit feature is facilitated by `ModelManager` and implements `Model`.
-
+It is similar in implementation to the add feature,
+except it edits a contact by calling `Model#updateContact`,
+which replaces the old contact with the edited contact in the `UniqueContactList`.
-The following sequence diagram shows how the undo operation works:
+The following activity diagram summarises what happens when a user executes an edit command.
-![UndoSequenceDiagram](images/UndoSequenceDiagram.png)
+![EditActivityDiagram](images/EditActivityDiagram.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.
-
+### Navigating to previous commands
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+The feature of navigating between command history using the up/down arrow keys, is facilitated by `CommandBoxHistory`. `CommandBox` keeps a reference to a `CommandBoxHistory` object which stores the history of entered commands.
-
: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.
+`CommandBoxHistory` stores a list of commands, which behaves externally as if its last element is always the empty string. This is so that the user can enter a new command when at the last position in the command history.
-
+The following sequence diagram summarises what happens when the user presses the up arrow key to navigate to the previous command in history.
+
+![CommandBoxHistorySequenceDiagram](images/CommandBoxHistorySequenceDiagram.png)
+
+The behaviour is very similar when the user presses the down arrow key to navigate to the next command in history, so we omit the corresponding sequence diagram.
+
+The following sequence diagram summarises what happens when the user successfully executes a new command, and this new command is stored in the command history.
+
+![CommandBoxHistoryNewSequenceDiagram](images/CommandBoxHistoryNewSequenceDiagram.png)
+
+The command is only stored in history if the execution of the command (by `CommandExecutor`) is successful. The position in history is also reset to the last position (empty string) using `CommandBoxHistory#resetPointer`.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+---
-![UndoRedoState4](images/UndoRedoState4.png)
+## Acknowledgements
-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.
+- Libraries: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+- App icon from [McDo Design](https://www.flickr.com/photos/mcdodesign/) by Susumu Yoshida
+- Some code adapted from by Marco Jakob
-![UndoRedoState5](images/UndoRedoState5.png)
+---
-The following activity diagram summarizes what happens when a user executes a new command:
+## Appendix: Requirements
-
+### Product scope
-#### Design considerations:
+**Target user profile**: NUS SoC students, who:
-**Aspect: How undo & redo executes:**
+- Can type fast and prefer typing
+- Are reasonably comfortable with command-line inputs
+- Wish to label contacts by category (e.g. professors, classmates from certain courses, friends)
+- Have many different ways to reach their contacts (e.g. social media like Telegram/Discord, additional phone numbers like house phone)
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+**Value proposition**: Manage contacts quickly via text commands, with useful features relevant to SoC students.
-* **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.
+### User stories
-_{more aspects and alternatives to be added}_
+Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-### \[Proposed\] Data archiving
+| Priority | As a … | I want to … | So that I can… |
+|----------|---------------------------------|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
+| `* * *` | user | add contacts | keep track of my friends and schoolmates |
+| `* * *` | user | delete contacts | remove my contact with that person |
+| `* * *` | user | view my contacts | know who I have as contacts |
+| `* * ` | user | edit contacts | make changes to my contact info when they occur |
+| `* *` | user | search for contacts | find a specific contact directly and easily |
+| `* *` | user | add tags to contacts | classify them based on contact type |
+| `* *` | user | merge duplicate contacts | my contact list stays clean |
+| `* *` | user | sort contacts by certain criteria | find contacts satisfying a certain criteria easily |
+| `* *` | user | save alternate contact info for my contacts | keep track of the various ways I can contact the same person |
+| `* *` | user | pin frequent contacts so they appear at the top when I open the app | access my most important contacts quickly |
+| `* *` | user | indicate where I met each contact | keep track of people I have various levels of familiarity with |
+| `* *` | user | view contacts by groups or type | more easily manage related contacts |
+| `* *` | user | export my contacts to an external file | backup my contacts’ information |
+| `* *` | user | import my contacts from an external file | quickly populate the app with my existing contacts |
+| `* *` | user | clear all contacts data | quickly erase all information stored when I will no longer use the app |
+| `*` | user who prefers CLI | use keyboard shortcuts | perform tasks more efficiently |
+| `*` | user | see a different background colour for each contact | differentiate between contacts more easily |
+| `*` | infrequent user | view a "cheatsheet" or help dialog for the text commands | remember some basic commands I may have forgotten |
+| `*` | advanced user | search/filter by specific parts of contacts (e.g. containing certain words) | narrow down contacts to exactly what I am looking for |
+| `*` | user who prefers CLI | switch between previously entered commands in history | easily repeat previous commands |
+| `*` | busy user | use icons to denote certain contact information | identify the information I want at a glance |
-_{Explain here how the data archiving feature will be implemented}_
+### Use cases
+(For all use cases below, the **System** is the `ConText` and the **Actor** is the `user`, unless specified otherwise)
---------------------------------------------------------------------------------------------------------------------
+**Use case: UC01 - Add a contact**
-## **Documentation, logging, testing, configuration, dev-ops**
+**MSS**
-* [Documentation guide](Documentation.md)
-* [Testing guide](Testing.md)
-* [Logging guide](Logging.md)
-* [Configuration guide](Configuration.md)
-* [DevOps guide](DevOps.md)
+1. User requests to add a contact.
+2. ConText adds the contact.
---------------------------------------------------------------------------------------------------------------------
+ Use case ends.
-## **Appendix: Requirements**
+**Extensions**
-### Product scope
+* 1a. The given data is invalid.
-**Target user profile**:
+ * 1a1. ConText shows an error message.
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+ Use case resumes at step 1.
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Use case: UC02 - Delete a contact**
+**MSS**
-### User stories
+1. User requests to view the list of contacts (UC03).
+2. User requests to delete a specific contact in the list.
+3. ConText deletes the contact.
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+ Use case ends.
+
+**Extensions**
-| 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 |
+* 2a. An invalid contact to edit is specified.
-*{More to be added}*
+ * 2a1. ConText shows an error message.
-### Use cases
+ Use case resumes at step 2.
+
+**Use case: UC03 - List all contacts**
+
+**MSS**
+
+1. User requests to list contacts.
+2. ConText shows a list of contacts.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+ Use case ends.
-**Use case: Delete a person**
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends
+
+**Use case: UC04 - Edit a contact**
**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 view the list of contacts (UC03).
+2. User requests to edit a contact.
+3. ConText edits the contact.
Use case ends.
**Extensions**
-* 2a. The list is empty.
+* 2a. An invalid contact to edit is specified.
+
+ * 2a1. ConText shows an error message.
+
+ Use case resumes at step 2.
+
+* 2b. The given data is incorrect.
+
+ * 2b1. ConText shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC05 - Clear all contacts**
+
+**MSS**
+
+1. User requests to clear all contacts.
+2. ConText clears all contacts.
+
+ Use case ends.
+
+**Use case: UC06 - Find a contact**
+
+**MSS**
+
+1. User requests to find a contact.
+2. ConText displays a list of contacts matching the given data.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The filtered list is empty.
Use case ends.
-* 3a. The given index is invalid.
+**Use case: UC07 - Filter tags**
+
+**MSS**
- * 3a1. AddressBook shows an error message.
+1. User requests to filter the list of contacts by a given tag.
+2. ConText displays a filtered list of contacts based on the given tag.
- Use case resumes at step 2.
+ Use case ends.
-*{More to be added}*
+**Extensions**
-### Non-Functional Requirements
+* 2a. The filtered list is empty.
-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}*
+### Non-functional requirements
+
+1. "Brownfield" - Changes to the codebase must be done in small increments.
+1. "Typing preferred" - The product must target users who can type fast and prefer CLI as their means of input.
+1. "Single user" - The product must be designed for a single user.
+1. "Incremental" - The product must be developed breadth-first as well as consistently each week.
+1. "Human editable file" - Data must be stored locally in a human-editable text file format.
+1. "No DBMS" - DataBase Management Systems must not be used.
+1. "OO" - Software must mostly follow the object-oriented paradigm.
+1. "Platform independent" - Software must work on Windows, Linux, and OSX. I.e., avoid OS-dependent libraries and OS-specific features.
+1. "Java version" - Software must work on a computer that has Java version 11 installed.
+1. "Portable" - Software must work without requiring an installer.
+1. "No remote server" - Software must not depend on a remote server.
+1. "External software" - Any 3rd party frameworks/libraries/services used must:
+ 1. Be free and open-source (except services), with permissive license terms (e.g. non-time limited trial).
+ 1. Not require installation by users. Services that require account creation on their 3rd party service are strongly discouraged.
+ 1. Not violate other project constraints.
+ 1. Be approved by the teaching team.
+1. "Screen resolution" - GUI must work well for standard screen resolutions and scales, as specified in the admin info. GUI must still be usable if those factors are non-standard.
+1. "Single file" - Software must all be packed into a single JAR file.
+1. "File size" - Software must not exceed 100MB and must not be unnecessarily bloated. Documents must not exceed 15MB per file.
+1. "PDF-friendly" - Developer and user guides must be PDF-friendly, without using expandable panels, embedded videos, animated GIFs etc.
+1. "Minimal network" - Any public APIs used should have a fallback mechanism in the event that they are down. Any NUS data used should have the approval of NUS IT.
+1. "Testability" - Features should not be hard to test or make the product hard to test, be the testing manual or automated.
+1. "CLI first" - Users who can type fast should be able to accomplish most tasks faster via the CLI as compared to if they were to use a hypothetical GUI-only version of the product.
+1. There must exist an image with the exact name and format `docs/images/Ui.png` depicting the final product, with similar proportions as the original AB3 image.
+1. There must exist an `AboutUs` page that closely follows the original template, such that CS2103 grading scripts can understand it.
+ 1. Each team member must have an appropriately named lowercase PNG of their profile picture, as specified in the admin info.
+1. There must exist Project Portfolio Pages (PPPs) in `docs/team/`, containing sections specified in the admin info.
+ 1. Each team member must have their own appropriately named lowercase page file, as specified in the admin info.
+ 1. The page must be written to account for paged PDF conversion.
+1. Documentation must be built using Jekyll or MarkBind, then hosted via GitHub Pages, such that they are compatible with CS2103 grading scripts.
+1. Branches must not be deleted after their associated PRs have been merged, so that CS2103 grading scripts can detect that the correct workflow was used.
+1. The `README.md` must acknowledge SE-EDU's AB3, which this project is based on. It should also contain the `Ui.png`, as well as the repo's GitHub Actions build status badge.
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Architecture Diagram**: A visual representation that depicts the high-level design and structure of the software application.
+
+* **Component**: A modular part of the system with a distinct responsibility. The main components mentioned are UI, Logic, Model, and Storage.
+
+* **Commons**: Classes or utilities used by multiple components of the application.
+
+* **UI (User Interface)**: The space where interactions between humans and the software occur. The goal of this interaction is to allow effective operation and control of the machine from the human end.
+
+* **GUI (Graphical User Interface)**: A type of user interface that allows users to interact with electronic devices through graphical elements such as images, buttons, icons, and windows instead of text-based command lines.
+
+* **Logic**: In the context of software, it refers to the set of rules and algorithms that process and respond to user inputs.
+
+* **Model**: The part of the application that manages data and application logic.
+
+* **Storage**: The part of the application responsible for saving and loading data to and from persistent storage.
+
+* **API (Application Programming Interface)**: A set of rules and tools that allows different software applications to communicate with each other. In this context, it refers to the interfaces defined for each component, such as `Logic.java`, `Model.java`, etc.
+
+* **Sequence Diagram**: A type of UML diagram that shows how objects interact in a specific order.
---------------------------------------------------------------------------------------------------------------------
+* **UML (Unified Modeling Language)**: A standardized modeling language enabling developers to specify, visualize, construct, and document artifacts of a software system.
-## **Appendix: Instructions for manual testing**
+* **PlantUML**: A tool that allows users to create UML diagrams using a simple and intuitive language.
+
+* **`puml` files**: Files written in a text-based markup language used by PlantUML to generate UML diagrams.
+
+* **MSS (Main Success Scenario)**: Represents the sequence of steps that describe a successful execution of a use case.
+
+* **CLI (Command Line Interface)**: A user interface that allows users to interact with the software by typing text-based commands.
+
+* **JavaFX**: A Java library used to create desktop applications. It is used for designing the user interface of this application.
+
+* **ObservableList**: A list that allows listeners to track changes when they occur. Used in the context of JavaFX to automatically update the UI when the data changes.
+
+* **JSON (JavaScript Object Notation)**: A lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate. Used for storing data in this application.
+
+* **JUnit**: A testing framework for Java programming language. JUnit5 refers to the fifth major version of this framework.
+
+* **Predicate**: A functional interface that represents a condition (test) and is used to filter data.
+
+* **Brownfield**: A term used in software development to describe a project that has existing constraints, typically an existing system or codebase, as opposed to a greenfield project which starts from scratch.
+
+* **Platform independent**: Software that can run on any computer regardless of its operating system, such as Mac/Windows/Linux.
+
+* **Human editable file**: A file format designed to be easily readable and editable by humans.
+
+* **Portable**: Software that doesn't require installation and can be run from any location, such as from a USB stick.
+
+---
+
+## Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
-
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
+
+:information_source: **Note:** These instructions only provide a starting point for testers to work on;
testers are expected to do more *exploratory* testing.
-
### Launch and shutdown
1. Initial launch
- 1. Download the jar file and copy into an empty folder
+ 1. Download the jar file and place it into an empty folder
+
+ 1. Open a command terminal, `cd` into the folder you put the JAR file in, and use the `java -jar context.jar` command to run the app.
+
+### Adding a new contact
+
+1. Adding a contact with all fields
+
+ 1. Test case: `add n/John Doe p/12345678 e/john@example.com o/notes t/friend a/Telegram: johndoe`
+ Expected: A new contact with the name "John Doe", phone number "12345678", email "john@example.com", a note "notes", tagged as "friend", and an alternate contact "Telegram: johndoe" is added to the list.
+
+ 1. Test case: `add n/Alice p/87654321 e/alice@example.com`
+ Expected: A new contact with the name "Alice", phone number "87654321", and email "alice@example.com" is added. Optional fields are left blank.
+
+ 1. Other incorrect add commands to try: `add`, `add n/John Doe`, `add e/john@example.com`, `add n/John Doe p/12`
+ Expected: Error message indicating the correct format for the `add` command.
+
+### Editing an existing contact
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+1. Editing a contact's name and email
-1. Saving window preferences
+ 1. Prerequisites: Have at least one contact in the list.
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+ 1. Test case: `edit 1 n/Jane Doe e/jane@example.com`
+ Expected: The first contact in the list has its name changed to "Jane Doe" and email to "jane@example.com".
- 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
+ 1. Test case: `edit 1 t/`
+ Expected: All of the existing tags of the first contact in the list are removed while other information of the contact remains unchanged.
-1. _{ more test cases … }_
+ 1. Test case: `edit 2 n/Bob`
+ Expected: Error message indicating the correct format for the `edit` command.
-### Deleting a person
+ 1. Other incorrect edit commands to try: `edit`, `edit x` (where x is larger than the number of contacts in ConText), `edit 1 n/`, `edit 1 e/`
+ Expected: Error message indicating the correct or valid usage of the `edit` command.
-1. Deleting a person while all persons are being shown
+### Deleting a contact
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+1. Deleting a contact while all contacts are being shown
+
+ 1. Prerequisites: List all contacts using the `list` command. Multiple contacts in the list.
1. Test case: `delete 1`
Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+ Expected: No contact is deleted. Error details shown in the status message. Status bar remains the same.
+
+ 1. Test case: `delete 1 2 3`
+ Expected: First, second, and third contacts are deleted from the list. Details of the deleted contacts shown in the status message. Timestamp in the status bar is updated.
+
+ 1. Test case: `delete 1 1 2`
+ Expected: First and second contacts are deleted from the list without duplication. Details of the deleted contacts shown in the status message. Timestamp in the status bar is updated.
1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
+### Filtering contacts by tag
+
+1. Filtering contacts using a specific tag
+
+ 1. Test case: `filter friend`
+ Expected: List all contacts tagged as "friend".
+
+ 1. Other incorrect filter commands to try: `filter`, `filter @#$%` (assuming no such tags)
+ Expected: Error message indicating the correct usage of the `filter` command or no contacts found message.
### 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 … }_
+ 1. Test case: Navigate to the folder with the JAR file, and remove all files and subfolders except the JAR file. Relaunch the app.
+ Expected: The app will launch normally, with sample contact data.
+
+ 1. Test case: Navigate to the folder with the JAR file, and make arbitrary changes to the `\data\contacts.json` file, so as to render it invalid. Relaunch the app.
+ Expected: The app will launch normally, with an empty contact list.
+
+## Appendix: Planned enhancements
+
+1. Currently, error messages displayed in ConText are generic.
+For example, if a user enters a negative index, the error message `Invalid command format` is displayed, even if the command format is technically correct.
+We plan to add more specific error messages for invalid indices (e.g. non-positive, too large, or does not exist in the list), to let the user know that the index itself is invalid, and why.
+
+1. Currently, special characters such as `-` and `/` are not allowed in names, even though they could conceivably be part of a contact's legal name.
+We plan to allow for special characters to be included in a contact's name.
+
+1. Currently, the `find` command only allows for matching of full words.
+For example, the input keyword `John` will not match the name `Johnny` stored in the list.
+This may lead to unintended behaviour for some users. We plan to allow partial matches of contact names for `find`.
+
+1. Currently, duplicate contacts are only detected by contacts having completely identical names, and not by other fields such as email address.
+Although this is meant to remove ambiguity in duplicate detection, it may be counter-intuitive for some users.
+We plan to include additional warning messages for the detection of duplicate contacts.
+We will warn users if they are adding contacts with duplicate information, such as duplicate email address, or names which differ only by whitespace (in the middle).
+The user can then confirm whether they would like the duplicate contact to go through, or whether they would like to make changes to the duplicate contact.
+
+1. Currently, duplicate values for deletion indices, as well as parameters like `t/` and `a/`, get silently merged.
+This is not outright rejected for the convenience of users.
+However, users may have accidentally entered such duplicate values, which may result in the app's behaviour differing from users' expectations.
+In such cases, we plan to display additional warning messages for commands like `add`, `edit`, and `delete`, so that users may check if their specifying of duplicate values is intentional.
+Users may then press enter again to confirm the command's execution, or edit the command.
+
+1. Currently, if no note (i.e. no `o/` parameter) is specified when adding a contact, the note's value defaults to being empty (`""`).
+The UI accounts for empty notes by not taking up an extra line to display the empty note.
+When users do specify a note, they may explicitly specify an empty note (i.e. `o/` with no value).
+This is not outright rejected for the convenience of users, since empty notes are allowed.
+However, users may have forgotten to specify a value for the note, which may result in the app's behaviour differing from users' expectations.
+In such cases, we plan to display an additional warning message for commands like `add`, so that users may check if their specifying of empty notes is intentional.
+
+1. Currently, users are allowed to enter very flexible phone number values.
+The only validation requirement is that the phone number begins with 3 digits.
+This allows users to use the feature as they wish.
+For example, although the standard intended usage is for users to enter just the digits of a phone number (e.g. `98765432`), they are also allowed to enter a value such as `65432109 (office); 98765432 (mobile)`.
+However, this flexibility may result in users accidentally entering invalid phone numbers, such as `9876p/5432`.
+Therefore, if users enter a phone number that does not contain only 3-15 digits, we plan to display an additional warning message for commands like `add` and `edit`, so that users may check if their specifying of such a value is intentional.
+Users may then press enter again to confirm the command's execution, or edit the command.
+The added need to confirm non-standard phone numbers nudges users towards using the alternate contacts feature for additional phone numbers instead.
+
+1. Currently, alternate contacts do not allow spaces in the "type" portion, and do not allow special characters such as `@` in the "username" portion.
+We plan to allow spaces in the "type" portion for added flexibility.
+We also plan to allow special characters in the "username" portion, by allowing username values which match existing phone number/email validation.
+Similarly, we would also allow all phone numbers to optionally start with the special character `+` before the required digits.
diff --git a/docs/Documentation.md b/docs/Documentation.md
deleted file mode 100644
index 3e68ea364e7..00000000000
--- a/docs/Documentation.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-layout: page
-title: Documentation guide
----
-
-**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)
-
-
-**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)
-
-**Diagrams:**
-
-* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html)
-
-**Converting a document to the PDF format:**
-
-* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html)
diff --git a/docs/Logging.md b/docs/Logging.md
deleted file mode 100644
index 5e4fb9bc217..00000000000
--- a/docs/Logging.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-layout: page
-title: 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.
-* Log messages are output through the console and to a `.log` file.
-* The output logging level can be controlled using the `logLevel` setting in the configuration file (See the [Configuration guide](Configuration.md) section).
-* **When choosing a level for a log message**, follow the conventions given in [_[se-edu/guides] Java: Logging conventions_](https://se-education.org/guides/conventions/java/logging.html).
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
deleted file mode 100644
index 275445bd551..00000000000
--- a/docs/SettingUp.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-layout: page
-title: Setting up and getting started
----
-
-* Table of Contents
-{:toc}
-
-
---------------------------------------------------------------------------------------------------------------------
-
-## Setting up the project in your computer
-
-
:exclamation: **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. **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.
-
---------------------------------------------------------------------------------------------------------------------
-
-## Before writing code
-
-1. **Configure the coding style**
-
- 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:**
-
- 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**
-
- This project comes with a GitHub Actions config files (in `.github/workflows` folder). When GitHub detects those files, it will run the CI for your project automatically at each push to the `master` branch or to any PR. No set up required.
-
-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).
-
-1. **Do the tutorials**
- These tutorials will help you get acquainted with the codebase.
-
- * [Tracing code](tutorials/TracingCode.md)
- * [Adding a new command](tutorials/AddRemark.md)
- * [Removing fields](tutorials/RemovingFields.md)
diff --git a/docs/Testing.md b/docs/Testing.md
deleted file mode 100644
index 8a99e82438a..00000000000
--- a/docs/Testing.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-layout: page
-title: Testing guide
----
-
-* Table of Contents
-{:toc}
-
---------------------------------------------------------------------------------------------------------------------
-
-## Running tests
-
-There are two ways to run tests.
-
-* **Method 1: Using IntelliJ JUnit test runner**
- * To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'`
- * To run a subset of tests, you can right-click on a test package,
- test class, or a test and choose `Run 'ABC'`
-* **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.
-
-
---------------------------------------------------------------------------------------------------------------------
-
-## Types of tests
-
-This project has three types of tests:
-
-1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest`
-1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest`
-1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 57437026c7b..fb635a2dbe4 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,195 +3,323 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+**ConText** is a desktop app that allows for managing contacts quickly via text commands.
+It is optimized for use via an in-app Command Line Interface (CLI), while still having the benefits of a Graphical User Interface (GUI).
-* Table of Contents
+It has useful features relevant to NUS SoC students:
+
+- Tagging contacts by category: You can tag your professors and classmates with custom tags such as "prof", "friend", "CS2103 course" etc., then filter by tag to view all contacts with a certain tag.
+- Storing different ways to reach people: By adding alternate contact details, you could have Telegram, Discord, mobile phone, house phone etc. all in the same contact.
+- Works like a usual CLI: You can use the up/down arrow keys to switch between previously-entered commands, making entering and repeating commands (e.g. adding many new contacts) easier.
+
+If you can type fast, prefer typing, and are reasonably comfortable with CLI inputs, ConText can let you manage contacts faster than traditional GUI apps.
+
+- Table of Contents
{:toc}
---------------------------------------------------------------------------------------------------------------------
+---
## Quick start
-1. Ensure you have Java `11` or above installed in your Computer.
+1. Ensure you have [Java 11](https://openjdk.org/) or above installed.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+1. Download the latest `context.jar` [here](https://github.com/AY2324S1-CS2103-W14-3/tp/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Place the JAR file in the folder you want to use as the app's home folder.
-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. Open a command terminal, `cd` into the folder you put the JAR file in, and use the `java -jar context.jar` command to run the application.\
+A window should open with a GUI similar to the one below. Note how the app starts off with 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:
+1. The text box at the top of the window should be automatically selected. This is where you can type your text commands. Press Enter to execute them.\
+The feedback from each command's execution will be displayed below the text box, with the currently displayed list of contacts below that.
- * `list` : Lists all contacts.
+1. Refer to the [Features](#features) section below to find out about the various commands.
- * `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.
+
+:bulb: **Tip:**
+You could test out some commands on the sample data.
+Once you are familiarised, feel free to use the `clear` command to delete all the sample data, and start adding your own contacts!
+
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+---
- * `clear` : Deletes all contacts.
+## Features
- * `exit` : Exits the app.
+At a glance: the information that can be stored with each contact, and how it will appear in the application, is labelled below.
-1. Refer to the [Features](#features) below for details of each command.
+![ContactCard](images/ContactCard.png)
---------------------------------------------------------------------------------------------------------------------
+
+**:information_source: About the command format:**
-## Features
+- Some commands take in parameters.
+e.g. in `add n/NAME`, the `add` command takes in an `n/` parameter.
+
+- Words in `UPPER_CASE` are placeholders for values to be specified.
+e.g. in `edit INDEX`, you should specify an `INDEX` such as `edit 1`.
+e.g. in `add n/NAME`, you should specify a `NAME` such as `add n/John Doe`.
+
+- Parameters in square brackets are optional.
+e.g in `n/NAME [o/NOTE]`, you could specify `n/John Doe o/Good at SE.`, or just `n/John Doe`.
+
+
+- Parameters with `…` after them can be specified multiple times.
+e.g. in `[t/TAG]…`, which is optional but can also be specified multiple times, you could specify ` ` (none specified), `t/NUS`, `t/NUS t/CS2103 course` etc.
+
+- Parameters can be specified in any order.
+e.g. in `n/NAME p/PHONE_NUMBER`, the order `p/PHONE_NUMBER n/NAME` also works.
+
+- Extra words for commands that do not take any parameters (such as `list` or `help`) will be ignored.
+e.g. `list 10 n/John Doe z/Extra` will be interpreted as just `list`.
+
+- If you are using the PDF version of this user guide, be careful when copy-pasting commands that span multiple lines. Spaces surrounding line breaks may get omitted when copied over to the app.
+
+
+### Adding a contact: `add`
+
+Adds a new contact.
+
+Note that contacts are identified by their name in ConText, and contacts with exactly the same name (including casing) are considered the same contact.
+Hence, you will not be able to add a new contact with the same name as an existing contact.
+
+For example, if you already have a contact with name `John Doe`, you will not be able to add another contact with the same name `John Doe`.
+For two names to be considered the same, they must be identical in every way, and that includes casing, as well as whitespace in the middle of the name, etc.
+Therefore, you may add another contact with name `John doe`, `John Doe`, or `Alex John Doe`.
+You may also first modify the name of the existing contact.
+
+Should you need to make changes to an existing contact, use the `edit` command as explained below.
+
+**Format:**
+`add n/NAME p/PHONE_NUMBER e/EMAIL [o/NOTE] [t/TAG]... [a/ALTERNATE_CONTACT]...`
+
+
+:information_source: **About phone number**
+Phone numbers must contain at least 3 digits. You can add extra information after digits for you to keep track of what the number is for.
+For additional phone numbers, please make use of `ALTERNATE_CONTACT`
+
+:information_source: **About tags:**
+
+- Duplicate tags are only counted once.
+- Specifying empty tags (`t/` with no value) is not allowed when adding contacts. If you wish to specify no tags, you may simply leave out the `t/` parameter.
+
+
+
+:information_source: **About the alternate contact format:**
+- The format for `ALTERNATE_CONTACT` is `TYPE: USERNAME`, roughly looking like `SocialMedia: Username`. Ensure to include a space between the colon `:` and the `Username`.
+- Do note at this moment whitespace is not supported for `TYPE` and `USERNAME`. `USERNAME` only supports alphanumerics and some special characters `.`, `_` and `-`.
+- Specifying empty alternate contacts (`a/` with no value) is not allowed when adding contacts. If you wish to specify no alternate contacts, you may simply leave out the `a/` parameter.
+
-**:information_source: 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`.
+**Examples:**
-* 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.
+- `add n/John Doe p/98765432 e/john.doe@email.com`
+(Adds a contact with the name `John Doe`, phone `98765432` and email `john.doe@email.com`)
+- `add n/John Doe p/98765432 e/john.doe@email.com o/Likes SE. t/NUS t/CS2103 course a/Telegram: JohnDoe`
+(Adds a contact with the name `John Doe`, phone `98765432`, email `john.doe@email.com`, note `Likes SE.`, tags `NUS` and `CS2103 course` and alternate contact `Telegram: JohnDoe`)
-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+### Editing a contact: `edit`
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+Edits an existing contact at the specified `INDEX`.
-* 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.
+**Format:**
+`edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [o/NOTE] [t/TAG]... [a/ALTERNATE_CONTACT]...`
+
+
+:information_source: **About index numbers:**
+`INDEX` refers to the index number currently shown in the displayed contact list (#1, #2, #3 etc.).
+Indices must be a positive integer to be valid (1, 2, 3 etc.), and must exist in the displayed contact list.
+Contacts are 1-indexed, that is, the first contact has index number 1. Index number 0 is not valid.
+
+
+
+:information_source: **About phone/tags/the alternate contact format:**
+Please refer to the [above](#adding-a-contact-add).
-### Viewing help : `help`
+- At least one of the optional parameters must be specified.
-Shows a message explaning how to access the help page.
+- Each specified parameter will have its new value(s) replace all existing value(s) for that parameter.\
+e.g. `edit 1 n/The Myth` will edit the name of contact #`1` to `The Myth`, without changing any other parameter values for that contact.
+ - When editing tags, the new specified tag(s) will similarly replace all existing tag(s).\
+ You can specify no tags (i.e. clear all tags) via a _single_ `t/` without a value.
+ - Likewise, when editing alternate contacts, you can specify no alternate contacts (i.e. clear all alternate contacts) via a _single_ `a/` without a value.
-![help message](images/helpMessage.png)
+**Examples:**
-Format: `help`
+- `edit 1 p/87654321 e/jane_doe@nus.edu.sg`
+(Edits the phone number and email address of contact #`1` to `87654321` and `jane_doe@nus.edu.sg` respectively.)
+- `edit 3 o/Member of NUS S/U t/` (Edits the note of contact #`3` to `Member of NUS S/U` and clears any of its existing tags.)
-### Adding a person: `add`
+### Deleting contacts: `delete`
-Adds a person to the address book.
+Deletes the contact(s) at the specified `INDEX` or indices.
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+**Format:**
+`delete INDEX...`
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+
+:information_source: **About index numbers:**
+Please refer to the [above](#editing-a-contact-edit).
-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`
+- You can delete multiple contacts at once by specifying multiple indices separated by spaces.
+
+- Duplicate indices are only counted once.\
+e.g. `delete 1 1` will only delete the contact at index #`1`.
+- Invalid indices will cause abortion of the delete command.
-### Listing all persons : `list`
-Shows a list of all persons in the address book.
+**Examples:**
-Format: `list`
+- `delete 1`
+(Deletes the contact at index #`1`.)
-### Editing a person : `edit`
+- `delete 1 3 5`
+(Deletes the contacts at indices #`1`, #`3`, and #`5`.)
-Edits an existing person in the address book.
+### Clearing all contacts: `clear`
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+
+:exclamation: **Caution:**
+This command will immediately delete all your contacts. **Use with care!**
+
-* 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.
+**Format:**
+`clear`
-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.
+### Listing all contacts: `list`
-### Locating persons by name: `find`
+Shows all contacts.
-Finds persons whose names contain any of the given keywords.
+**Format:**
+`list`
-Format: `find KEYWORD [MORE_KEYWORDS]`
+### Finding by name: `find`
-* 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`
+Shows contacts whose names have a word that fully matches any of the specified keywords.
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+**Format:**
+`find KEYWORD...`
-### Deleting a person : `delete`
+- The search is case-insensitive.\
+e.g. Keyword `john` will match the name `John`.
-Deletes the specified person from the address book.
+- The order of the keywords does not matter.\
+e.g. Keywords `Amy John` will show the same contacts as keywords `John Amy`.
-Format: `delete INDEX`
+- Only full words will be matched.\
+e.g. Keyword `John` will not match the names `Johnny` or `Jo`.
-* 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, …
+- Each name only needs one word to fully match at least one keyword (i.e. `OR` search).\
+e.g. `find Bee John` will match the names `Amy Bee` and `John Doe`.
-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.
+**Examples:**
-### Clearing all entries : `clear`
+- `find John`
+- `find amy Ben CHARLOTTE`
-Clears all entries from the address book.
+### Filtering by tag: `filter`
-Format: `clear`
+Shows contacts with a tag that fully matches the specified tag (case-insensitive).
-### Exiting the program : `exit`
+**Format:**
+`filter TAG`
-Exits the program.
+- The search is case-insensitive.\
+ e.g. `filter friend` will match the tag `Friend`.
-Format: `exit`
+- Only full tags will be matched.\
+ e.g. `filter Fri` will _not_ match the tag `Friend`.\
+ e.g. `filter Friend` will _not_ match the tag `Close Friend`.
-### Saving the data
+- The keyword can contain spaces.\
+ e.g. `filter Close Friend` will match the tag `Close Friend` (and this tag only).\
+ e.g. `filter Close⠀⠀⠀⠀⠀⠀⠀⠀Friend` will _not_ match the tag `Close Friend`.
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+- In summary, `filter` looks for tags which are an exact match, ignoring casing only.
-### Editing the data file
+**Examples:**
-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.
+- `filter NUS`
+- `filter CS2103 course`
-
: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.
-
+### Viewing help: `help`
+
+Opens a subwindow with a convenient link to the user guide.
+
+**Format:**
+`help`
+
+Alternatively, you can press F1 or click Help → Help in the top toolbar.
+
+### Exiting the app: `exit`
+
+Exits the app.
+
+**Format:**
+`exit`
+
+Alternatively, you can click File → Exit in the top toolbar.
-### Archiving data files `[coming in v2.0]`
+### Automatic sorting
-_Details coming soon ..._
+The displayed contact list is always automatically sorted in ascending alphabetical order, regardless of capitalization.
---------------------------------------------------------------------------------------------------------------------
+### Automatic saving
+Your contacts get automatically saved to the file system after each successful command execution. There is no need to save manually.
+
+### Editing the data file
+
+ConText data are saved automatically as a JSON file `[JAR file location]/data/contacts.json`. Advanced users are welcome to update data directly by editing that data file.
+
+
:exclamation: **Caution:**
+If your changes to the data file make its format invalid, ConText 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.
+
+
+---
## 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 ConText home folder.
---------------------------------------------------------------------------------------------------------------------
+---
-## Known issues
+## Command summary
-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.
+| Action | Command Format | Example Usage |
+|-------------------|-------------------------------------------------------------------------|--------------------------------------------------------------------|
+| Adding a contact | `add n/NAME p/PHONE_NUMBER e/EMAIL [o/NOTE] [t/TAG]... [a/ALTERNATE_CONTACT]...` | `add n/John Doe p/98765432 e/john.doe@email.com` |
+| | | `add n/John Doe p/98765432 e/john.doe@email.com o/Likes SE. t/NUS t/CS2103 course a/Telegram: JohnDoe` |
+| Editing a contact | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [o/NOTE] [t/TAG]... [a/ALTERNATE_CONTACT]...` | `edit 1 p/87654321 e/jane_doe@nus.edu.sg` |
+| | | `edit 3 o/Member of NUS S/U t/` |
+| Deleting contacts | `delete INDEX...` | `delete 1` |
+| | | `delete 1 3 5` |
+| Clearing all contacts | `clear` | `clear` |
+| Listing all contacts | `list` | `list` |
+| Finding by name | `find KEYWORD...` | `find John` |
+| | | `find amy Ben CHARLOTTE` |
+| Filtering by tag | `filter TAG` | `filter NUS` |
+| | | `filter CS2103 course` |
+| Viewing help | `help` | `help` |
+| Exiting the app | `exit` | `exit` |
---------------------------------------------------------------------------------------------------------------------
-## Command summary
+---
+
+## Known limitations
+
+1. **Long contact details are not in the product's scope.**\
+e.g. names/phone numbers/emails/notes/tags/alternate contacts with hundreds of characters.\
+Such long text is likely to get shortened with ellipses (`...`) or cut off by the app's window.\
+You may try to remedy this by resizing the app's window to be wider.
-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`
+1. **Multiple monitors are not in the product's scope.**\
+When using multiple monitors, if you move the app's window to a secondary monitor, then later switch to using just the primary monitor, the GUI will reopen off-screen.\
+You can remedy this by deleting the `settings.json` file created by the app before running the app again.
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..81a9fdb89c9 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "ConText"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2324S1-CS2103-W14-3/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..8ade2f397d9 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,8 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "ConText";
font-size: 32px;
}
}
-
diff --git a/docs/_sass/minima/custom-styles.scss b/docs/_sass/minima/custom-styles.scss
index 56b5d56b430..08da042ebf0 100644
--- a/docs/_sass/minima/custom-styles.scss
+++ b/docs/_sass/minima/custom-styles.scss
@@ -31,4 +31,3 @@ h2, h3, h4, h5, h6 {
@include alert-variant(color-level($value, $alert-bg-level), color-level($value, $alert-border-level), color-level($value, $alert-color-level));
}
}
-
diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/AddActivityDiagram.puml
similarity index 68%
rename from docs/diagrams/CommitActivityDiagram.puml
rename to docs/diagrams/AddActivityDiagram.puml
index 8c0892d6a70..ded4ac4b932 100644
--- a/docs/diagrams/CommitActivityDiagram.puml
+++ b/docs/diagrams/AddActivityDiagram.puml
@@ -5,13 +5,13 @@ skinparam ArrowFontSize 12
start
:User executes command;
+:Contact object is created;
+
'Since the beta syntax does not support placing the condition outside the
'diamond we place it as the true branch instead.
-
-if () then ([command commits AddressBook])
- :Purge redundant states;
- :Save AddressBook to
- addressBookStateList;
+if () then ([Contact object is unique])
+ :Save Contact to
+ UniqueContactList;
else ([else])
endif
stop
diff --git a/docs/diagrams/AddSequenceDiagram.puml b/docs/diagrams/AddSequenceDiagram.puml
new file mode 100644
index 00000000000..e838536c8e0
--- /dev/null
+++ b/docs/diagrams/AddSequenceDiagram.puml
@@ -0,0 +1,78 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":InputParser" as InputParser LOGIC_COLOR
+participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR
+participant "a:AddCommand" as AddCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "contact:Contact" as Contact MODEL_COLOR
+end box
+
+[-> LogicManager : execute("add n/Alice ...")
+activate LogicManager
+
+LogicManager -> InputParser : parseCommand("add n/Alice ...")
+activate InputParser
+
+create AddCommandParser
+InputParser -> AddCommandParser
+activate AddCommandParser
+
+AddCommandParser --> InputParser
+deactivate AddCommandParser
+
+InputParser -> AddCommandParser : parse("n/Alice ...")
+activate AddCommandParser
+
+
+
+create Contact
+AddCommandParser -> Contact
+activate Contact
+
+Contact --> AddCommandParser : contact
+deactivate Contact
+
+create AddCommand
+AddCommandParser -> AddCommand
+activate AddCommand
+
+AddCommand --> AddCommandParser : a
+deactivate AddCommand
+
+AddCommandParser --> InputParser : a
+deactivate AddCommandParser
+
+InputParser --> LogicManager : a
+deactivate InputParser
+
+LogicManager -> AddCommand : execute()
+activate AddCommand
+
+AddCommand -> Model : addContact(contact)
+activate Model
+
+Model --> AddCommand
+deactivate Model
+
+create CommandResult
+AddCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddCommand
+deactivate CommandResult
+
+AddCommand --> LogicManager : result
+deactivate AddCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index 48b6cc4333c..23d0e289600 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -14,13 +14,13 @@ activate ui UI_COLOR
ui -[UI_COLOR]> logic : execute("delete 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : removeContact(p)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
deactivate model
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
+logic -[LOGIC_COLOR]> storage : saveContacts(contacts)
activate storage STORAGE_COLOR
storage -[STORAGE_COLOR]> storage : Save to file
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
deleted file mode 100644
index 598474a5c82..00000000000
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam arrowThickness 1.1
-skinparam arrowColor MODEL_COLOR
-skinparam classBackgroundColor MODEL_COLOR
-
-AddressBook *-right-> "1" UniquePersonList
-AddressBook *-right-> "1" UniqueTagList
-UniqueTagList -[hidden]down- UniquePersonList
-UniqueTagList -[hidden]down- UniquePersonList
-
-UniqueTagList -right-> "*" Tag
-UniquePersonList -right-> Person
-
-Person -up-> "*" Tag
-
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-@enduml
diff --git a/docs/diagrams/CommandBoxHistoryNewSequenceDiagram.puml b/docs/diagrams/CommandBoxHistoryNewSequenceDiagram.puml
new file mode 100644
index 00000000000..8e9d17b49c4
--- /dev/null
+++ b/docs/diagrams/CommandBoxHistoryNewSequenceDiagram.puml
@@ -0,0 +1,30 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Ui UI_COLOR_T1
+participant ":CommandBox" as CommandBox UI_COLOR
+participant ":CommandExecutor" as CommandExecutor UI_COLOR
+participant ":CommandBoxHistory" as CommandBoxHistory UI_COLOR
+end box
+
+create CommandBox
+[-> CommandBox : handleCommandEntered()
+
+CommandBox -> CommandExecutor : execute(commandText)
+activate CommandExecutor
+
+CommandExecutor --> CommandBox
+deactivate CommandExecutor
+
+CommandBox -> CommandBoxHistory : add(commandText)
+activate CommandBoxHistory
+CommandBoxHistory --> CommandBox
+deactivate CommandBoxHistory
+
+CommandBox -> CommandBoxHistory : resetPointer()
+activate CommandBoxHistory
+CommandBoxHistory --> CommandBox
+deactivate CommandBoxHistory
+
+@enduml
diff --git a/docs/diagrams/CommandBoxHistorySequenceDiagram.puml b/docs/diagrams/CommandBoxHistorySequenceDiagram.puml
new file mode 100644
index 00000000000..50335fb709e
--- /dev/null
+++ b/docs/diagrams/CommandBoxHistorySequenceDiagram.puml
@@ -0,0 +1,29 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Ui UI_COLOR_T1
+participant ":CommandBox" as CommandBox UI_COLOR
+participant ":CommandBoxHistory" as CommandBoxHistory UI_COLOR
+end box
+
+create CommandBox
+[-> CommandBox : handleKeyPress(UP)
+
+CommandBox -> CommandBox :switchToPreviousCommand()
+activate CommandBox
+
+CommandBox -> CommandBoxHistory : previous()
+activate CommandBoxHistory
+CommandBoxHistory --> CommandBox : previousCommand
+deactivate CommandBoxHistory
+
+CommandBox -> CommandBox : replaceText(previousCommand)
+activate CommandBox
+CommandBox --> CommandBox
+deactivate CommandBox
+
+CommandBox --> CommandBox
+deactivate CommandBox
+
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 40ea6c9dc4c..ec1bc45de82 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -4,7 +4,7 @@ skinparam ArrowFontStyle plain
box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":InputParser" as InputParser LOGIC_COLOR
participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
@@ -14,20 +14,20 @@ box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
end box
-[-> LogicManager : execute("delete 1")
+[-> LogicManager : execute("delete 1 3 5")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
+LogicManager -> InputParser : parseCommand("delete 1 3 5")
+activate InputParser
create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
+InputParser -> DeleteCommandParser
activate DeleteCommandParser
-DeleteCommandParser --> AddressBookParser
+DeleteCommandParser --> InputParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+InputParser -> DeleteCommandParser : parse("1 3 5")
activate DeleteCommandParser
create DeleteCommand
@@ -37,24 +37,27 @@ activate DeleteCommand
DeleteCommand --> DeleteCommandParser : d
deactivate DeleteCommand
-DeleteCommandParser --> AddressBookParser : d
+DeleteCommandParser --> InputParser : d
deactivate DeleteCommandParser
-'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
-destroy DeleteCommandParser
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
+InputParser --> LogicManager : d
+deactivate InputParser
LogicManager -> DeleteCommand : execute()
activate DeleteCommand
-DeleteCommand -> Model : deletePerson(1)
+DeleteCommand -> Model : getFilteredContactList()
activate Model
-
-Model --> DeleteCommand
+Model --> DeleteCommand : currentContactList
deactivate Model
+loop for each contact in contactsToDelete
+ DeleteCommand -> Model : removeContact(contact)
+ activate Model
+ Model --> DeleteCommand
+ deactivate Model
+end
+
create CommandResult
DeleteCommand -> CommandResult
activate CommandResult
@@ -67,4 +70,5 @@ deactivate DeleteCommand
[<--LogicManager
deactivate LogicManager
+deactivate CommandResult
@enduml
diff --git a/docs/diagrams/EditActivityDiagram.puml b/docs/diagrams/EditActivityDiagram.puml
new file mode 100644
index 00000000000..4e0625874da
--- /dev/null
+++ b/docs/diagrams/EditActivityDiagram.puml
@@ -0,0 +1,18 @@
+@startuml
+skin rose
+skinparam ActivityFontSize 15
+skinparam ArrowFontSize 12
+start
+:User executes command;
+
+:New Contact object is created;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+if () then ([new Contact is not duplicate])
+ :Replace existing Contact with new Contact
+ in UniqueContactList;
+else ([else])
+endif
+stop
+@enduml
diff --git a/docs/diagrams/EditSequenceDiagram.puml b/docs/diagrams/EditSequenceDiagram.puml
new file mode 100644
index 00000000000..e838536c8e0
--- /dev/null
+++ b/docs/diagrams/EditSequenceDiagram.puml
@@ -0,0 +1,78 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":InputParser" as InputParser LOGIC_COLOR
+participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR
+participant "a:AddCommand" as AddCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "contact:Contact" as Contact MODEL_COLOR
+end box
+
+[-> LogicManager : execute("add n/Alice ...")
+activate LogicManager
+
+LogicManager -> InputParser : parseCommand("add n/Alice ...")
+activate InputParser
+
+create AddCommandParser
+InputParser -> AddCommandParser
+activate AddCommandParser
+
+AddCommandParser --> InputParser
+deactivate AddCommandParser
+
+InputParser -> AddCommandParser : parse("n/Alice ...")
+activate AddCommandParser
+
+
+
+create Contact
+AddCommandParser -> Contact
+activate Contact
+
+Contact --> AddCommandParser : contact
+deactivate Contact
+
+create AddCommand
+AddCommandParser -> AddCommand
+activate AddCommand
+
+AddCommand --> AddCommandParser : a
+deactivate AddCommand
+
+AddCommandParser --> InputParser : a
+deactivate AddCommandParser
+
+InputParser --> LogicManager : a
+deactivate InputParser
+
+LogicManager -> AddCommand : execute()
+activate AddCommand
+
+AddCommand -> Model : addContact(contact)
+activate Model
+
+Model --> AddCommand
+deactivate Model
+
+create CommandResult
+AddCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddCommand
+deactivate CommandResult
+
+AddCommand --> LogicManager : result
+deactivate AddCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/FilterSequenceDiagram.puml b/docs/diagrams/FilterSequenceDiagram.puml
new file mode 100644
index 00000000000..b48a218a9ae
--- /dev/null
+++ b/docs/diagrams/FilterSequenceDiagram.puml
@@ -0,0 +1,35 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FilterCommand" as FilterCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("filter Friend")
+
+create FilterCommand
+LogicManager -> FilterCommand :
+activate FilterCommand
+FilterCommand --> LogicManager
+deactivate FilterCommand
+
+
+LogicManager -> FilterCommand : execute(model)
+activate FilterCommand
+
+
+FilterCommand -> Model : setContactsFilter(predicate)
+activate Model
+Model --> FilterCommand
+deactivate Model
+
+FilterCommand --> LogicManager
+deactivate FilterCommand
+
+@enduml
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index a57720890ee..6b34880d4ad 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -6,7 +6,7 @@ skinparam classBackgroundColor LOGIC_COLOR
package Logic as LogicPackage {
-Class AddressBookParser
+Class InputParser
Class XYZCommand
Class CommandResult
Class "{abstract}\nCommand" as Command
@@ -27,8 +27,8 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Logic
LogicManager .right.|> Logic
-LogicManager -right->"1" AddressBookParser
-AddressBookParser ..> XYZCommand : creates >
+LogicManager -right->"1" InputParser
+InputParser ..> XYZCommand : creates >
XYZCommand -up-|> Command
LogicManager .left.> Command : executes >
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..30beb54270b 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,20 +5,21 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model as ModelPackage <>{
-Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
-Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
+Class "<>\nReadOnlyContacts" as ReadOnlyContacts
+Class "<>\nReadOnlySettings" as ReadOnlySettings
Class "<>\nModel" as Model
-Class AddressBook
+Class Contacts
Class ModelManager
-Class UserPrefs
+Class Settings
-Class UniquePersonList
-Class Person
-Class Address
+Class UniqueContactList
+Class Contact
+Class Note
Class Email
Class Name
Class Phone
Class Tag
+Class AlternateContact
Class I #FFFFFF
}
@@ -26,29 +27,30 @@ Class I #FFFFFF
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+Contacts .up.|> ReadOnlyContacts
ModelManager .up.|> Model
-Model .right.> ReadOnlyUserPrefs
-Model .left.> ReadOnlyAddressBook
-ModelManager -left-> "1" AddressBook
-ModelManager -right-> "1" UserPrefs
-UserPrefs .up.|> ReadOnlyUserPrefs
-
-AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
-
-Person -[hidden]up--> I
-UniquePersonList -[hidden]right-> I
+Model .right.> ReadOnlySettings
+Model .left.> ReadOnlyContacts
+ModelManager -left-> "1" Contacts
+ModelManager -right-> "1" Settings
+Settings .up.|> ReadOnlySettings
+
+Contacts *--> "1" UniqueContactList
+UniqueContactList --> "~* all" Contact
+Contact *--> "1" Name
+Contact *--> "1" Phone
+Contact *--> "1" Email
+Contact *--> "1" Note
+Contact *--> "*" Tag
+Contact *--> "*" AlternateContact
+
+Contact -[hidden]up--> I
+UniqueContactList -[hidden]right-> I
Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+Phone -[hidden]right-> Email
+Email -[hidden]right-> Note
-ModelManager --> "~* filtered" Person
+ModelManager --> "~* filtered" Contact
@enduml
diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml
index 0c7424de6e0..7e18f7bfc91 100644
--- a/docs/diagrams/ParserClasses.puml
+++ b/docs/diagrams/ParserClasses.puml
@@ -9,7 +9,7 @@ Class XYZCommand
package "Parser classes"{
Class "<>\nParser" as Parser
-Class AddressBookParser
+Class InputParser
Class XYZCommandParser
Class CliSyntax
Class ParserUtil
@@ -19,12 +19,12 @@ Class Prefix
}
Class HiddenOutside #FFFFFF
-HiddenOutside ..> AddressBookParser
+HiddenOutside ..> InputParser
-AddressBookParser .down.> XYZCommandParser: creates >
+InputParser .down.> XYZCommandParser: creates >
XYZCommandParser ..> XYZCommand : creates >
-AddressBookParser ..> Command : returns >
+InputParser ..> Command : returns >
XYZCommandParser .up.|> Parser
XYZCommandParser ..> ArgumentMultimap
XYZCommandParser ..> ArgumentTokenizer
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..33135549b39 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -6,20 +6,20 @@ skinparam classBackgroundColor STORAGE_COLOR
package Storage as StoragePackage {
-package "UserPrefs Storage" #F4F6F6{
-Class "<>\nUserPrefsStorage" as UserPrefsStorage
-Class JsonUserPrefsStorage
+package "Settings Storage" #F4F6F6{
+Class "<>\nSettingsStorage" as SettingsStorage
+Class JsonSettingsStorage
}
Class "<>\nStorage" as Storage
Class StorageManager
-package "AddressBook Storage" #F4F6F6{
-Class "<>\nAddressBookStorage" as AddressBookStorage
-Class JsonAddressBookStorage
-Class JsonSerializableAddressBook
-Class JsonAdaptedPerson
-Class JsonAdaptedTag
+package "ConText Storage" #F4F6F6{
+Class "<>\nContactsStorage" as ContactsStorage
+Class JsonContactsStorage
+Class JsonContacts
+Class JsonContact
+Class JsonTag
}
}
@@ -28,16 +28,16 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Storage
StorageManager .up.|> Storage
-StorageManager -up-> "1" UserPrefsStorage
-StorageManager -up-> "1" AddressBookStorage
+StorageManager -up-> "1" SettingsStorage
+StorageManager -up-> "1" ContactsStorage
-Storage -left-|> UserPrefsStorage
-Storage -right-|> AddressBookStorage
+Storage -left-|> SettingsStorage
+Storage -right-|> ContactsStorage
-JsonUserPrefsStorage .up.|> UserPrefsStorage
-JsonAddressBookStorage .up.|> AddressBookStorage
-JsonAddressBookStorage ..> JsonSerializableAddressBook
-JsonSerializableAddressBook --> "*" JsonAdaptedPerson
-JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonSettingsStorage .up.|> SettingsStorage
+JsonContactsStorage .up.|> ContactsStorage
+JsonContactsStorage ..> JsonContacts
+JsonContacts --> "*" JsonContact
+JsonContact --> "*" JsonTag
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..7f0355f56f7 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -11,10 +11,11 @@ Class UiManager
Class MainWindow
Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
+Class ContactListPanel
+Class ContactCard
Class StatusBarFooter
Class CommandBox
+Class CommandBoxHistory
}
package Model <> {
@@ -32,26 +33,28 @@ UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "1" ContactListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
-PersonListPanel -down-> "*" PersonCard
+CommandBox --> "1" CommandBoxHistory
+
+ContactListPanel -down-> "*" ContactCard
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+ContactListPanel --|> UiPart
+ContactCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
-PersonCard ..> Model
+ContactCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
-PersonListPanel -[hidden]left- HelpWindow
+ContactListPanel -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
ResultDisplay -[hidden]left- StatusBarFooter
diff --git a/docs/diagrams/UiMockup.pptx b/docs/diagrams/UiMockup.pptx
new file mode 100644
index 00000000000..58075fff238
Binary files /dev/null and b/docs/diagrams/UiMockup.pptx differ
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
deleted file mode 100644
index 43a45903ac9..00000000000
--- a/docs/diagrams/UndoRedoState0.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-skinparam ClassBackgroundColor #FFFFAA
-
-title Initial state
-
-package States {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
-}
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-hide State2
-hide State3
-
-class Pointer as "Current State" #FFFFFF
-Pointer -up-> State1
-@end
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
deleted file mode 100644
index 5a41e9e1651..00000000000
--- a/docs/diagrams/UndoRedoState1.puml
+++ /dev/null
@@ -1,23 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-skinparam ClassBackgroundColor #FFFFAA
-
-title After command "delete 5"
-
-package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-hide State3
-
-class Pointer as "Current State" #FFFFFF
-
-Pointer -up-> State2
-@end
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
deleted file mode 100644
index ad32fce1b0b..00000000000
--- a/docs/diagrams/UndoRedoState2.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-skinparam ClassBackgroundColor #FFFFAA
-
-title After command "add n/David"
-
-package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFFF
-
-Pointer -up-> State3
-@end
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
deleted file mode 100644
index 9187a690036..00000000000
--- a/docs/diagrams/UndoRedoState3.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-skinparam ClassBackgroundColor #FFFFAA
-
-title After command "undo"
-
-package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFFF
-
-Pointer -up-> State2
-@end
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
deleted file mode 100644
index 2bc631ffcd0..00000000000
--- a/docs/diagrams/UndoRedoState4.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-skinparam ClassBackgroundColor #FFFFAA
-
-title After command "list"
-
-package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab2:AddressBook"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFFF
-
-Pointer -up-> State2
-@end
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
deleted file mode 100644
index e77b04104aa..00000000000
--- a/docs/diagrams/UndoRedoState5.puml
+++ /dev/null
@@ -1,22 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-skinparam ClassBackgroundColor #FFFFAA
-
-title After command "clear"
-
-package States <> {
- class State1 as "ab0:AddressBook"
- class State2 as "ab1:AddressBook"
- class State3 as "ab3:AddressBook"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFFF
-
-Pointer -up-> State3
-note right on link: State ab2 deleted.
-@end
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
deleted file mode 100644
index 87ff3e9237e..00000000000
--- a/docs/diagrams/UndoSequenceDiagram.puml
+++ /dev/null
@@ -1,54 +0,0 @@
-@startuml
-!include style.puml
-skinparam ArrowFontStyle plain
-
-box Logic LOGIC_COLOR_T1
-participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
-participant "u:UndoCommand" as UndoCommand LOGIC_COLOR
-end box
-
-box Model MODEL_COLOR_T1
-participant ":Model" as Model MODEL_COLOR
-participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR
-end box
-[-> LogicManager : execute(undo)
-activate LogicManager
-
-LogicManager -> AddressBookParser : parseCommand(undo)
-activate AddressBookParser
-
-create UndoCommand
-AddressBookParser -> UndoCommand
-activate UndoCommand
-
-UndoCommand --> AddressBookParser
-deactivate UndoCommand
-
-AddressBookParser --> LogicManager : u
-deactivate AddressBookParser
-
-LogicManager -> UndoCommand : execute()
-activate UndoCommand
-
-UndoCommand -> Model : undoAddressBook()
-activate Model
-
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
-
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
-
-Model --> UndoCommand
-deactivate Model
-
-UndoCommand --> LogicManager : result
-deactivate UndoCommand
-UndoCommand -[hidden]-> LogicManager : result
-destroy UndoCommand
-
-[<--LogicManager
-deactivate LogicManager
-@enduml
diff --git a/docs/diagrams/add-remark/ParserClass.puml b/docs/diagrams/add-remark/ParserClass.puml
deleted file mode 100644
index 24d390a4023..00000000000
--- a/docs/diagrams/add-remark/ParserClass.puml
+++ /dev/null
@@ -1,14 +0,0 @@
-@startuml
-hide circle
-skinparam classAttributeIconSize 0
-
-Class "<>\nParser" as Parser
-Class RemarkCommandParser {
- +parse(): RemarkCommand
-}
-Class ParserException
-
-RemarkCommandParser .up.|> Parser
-Parser .right.> ParserException: throws >
-RemarkCommandParser .right.> ParserException: throws >
-@enduml
diff --git a/docs/diagrams/add-remark/RemarkClass.puml b/docs/diagrams/add-remark/RemarkClass.puml
deleted file mode 100644
index 019c1ecbbf1..00000000000
--- a/docs/diagrams/add-remark/RemarkClass.puml
+++ /dev/null
@@ -1,19 +0,0 @@
-@startuml
-hide circle
-skinparam classAttributeIconSize 0
-
-Class "{abstract}\nCommand" as Command {
- +execute(Model): CommandResult
-}
-Class RemarkCommand {
- +COMMAND_WORD: String
- +MESSAGE_USAGE: String
- +MESSAGE_NOT_IMPLEMENTED_YET: String
- +execute(Model): CommandResult
-}
-Class CommandException
-
-RemarkCommand -up-|> Command
-Command ..> CommandException: throws >
-RemarkCommand .right.> CommandException: throws >
-@enduml
diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml
deleted file mode 100644
index 42bf46d3ce8..00000000000
--- a/docs/diagrams/tracing/LogicSequenceDiagram.puml
+++ /dev/null
@@ -1,22 +0,0 @@
-@startuml
-!include ../style.puml
-skinparam ArrowFontStyle plain
-
-Participant ":LogicManager" as logic LOGIC_COLOR
-Participant ":AddressBookParser" as abp LOGIC_COLOR
-Participant ":EditCommandParser" as ecp LOGIC_COLOR
-Participant "command:EditCommand" as ec LOGIC_COLOR
-
-[-> logic : execute
-activate logic
-logic -> abp ++: parseCommand(commandText)
-create ecp
-abp -> ecp
-abp -> ecp ++: parse(arguments)
-create ec
-ecp -> ec ++: index, editPersonDescriptor
-ec --> ecp --
-ecp --> abp --: command
-abp --> logic --: command
-
-@enduml
diff --git a/docs/images/AddActivityDiagram.png b/docs/images/AddActivityDiagram.png
new file mode 100644
index 00000000000..4192edc4a19
Binary files /dev/null and b/docs/images/AddActivityDiagram.png differ
diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png
new file mode 100644
index 00000000000..9d130251f89
Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 37ad06a2803..9e4264e1180 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
deleted file mode 100644
index 02a42e35e76..00000000000
Binary files a/docs/images/BetterModelClassDiagram.png and /dev/null differ
diff --git a/docs/images/CommandBoxHistoryNewSequenceDiagram.png b/docs/images/CommandBoxHistoryNewSequenceDiagram.png
new file mode 100644
index 00000000000..918ba818718
Binary files /dev/null and b/docs/images/CommandBoxHistoryNewSequenceDiagram.png differ
diff --git a/docs/images/CommandBoxHistorySequenceDiagram.png b/docs/images/CommandBoxHistorySequenceDiagram.png
new file mode 100644
index 00000000000..a3b448055eb
Binary files /dev/null and b/docs/images/CommandBoxHistorySequenceDiagram.png differ
diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png
deleted file mode 100644
index 5b464126b35..00000000000
Binary files a/docs/images/CommitActivityDiagram.png and /dev/null differ
diff --git a/docs/images/ComponentManagers.png b/docs/images/ComponentManagers.png
index ae52a35718a..96ed098e500 100644
Binary files a/docs/images/ComponentManagers.png and b/docs/images/ComponentManagers.png differ
diff --git a/docs/images/ContactCard.png b/docs/images/ContactCard.png
new file mode 100644
index 00000000000..ab6bed45212
Binary files /dev/null and b/docs/images/ContactCard.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index e186f7ba096..19da5bc8642 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/EditActivityDiagram.png b/docs/images/EditActivityDiagram.png
new file mode 100644
index 00000000000..1eb618cf5d5
Binary files /dev/null and b/docs/images/EditActivityDiagram.png differ
diff --git a/docs/images/EditSequenceDiagram.png b/docs/images/EditSequenceDiagram.png
new file mode 100644
index 00000000000..9d130251f89
Binary files /dev/null and b/docs/images/EditSequenceDiagram.png differ
diff --git a/docs/images/FilterSequenceDiagram.png b/docs/images/FilterSequenceDiagram.png
new file mode 100644
index 00000000000..d5988eee4ba
Binary files /dev/null and b/docs/images/FilterSequenceDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index e3b784310fe..24f7aca60a9 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/LogicSequenceDiagram.png b/docs/images/LogicSequenceDiagram.png
new file mode 100644
index 00000000000..9ba3f76d1b1
Binary files /dev/null and b/docs/images/LogicSequenceDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..c2cd37ff867 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png
index edfd1ff7897..720c5caafcb 100644
Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ
diff --git a/docs/images/SeEduLogo.png b/docs/images/SeEduLogo.png
deleted file mode 100644
index 31ad50b6f88..00000000000
Binary files a/docs/images/SeEduLogo.png and /dev/null differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..f4f97a2f51d 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..23d8e203243 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 11f06d68671..af80e581b72 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png
deleted file mode 100644
index c5f91b58533..00000000000
Binary files a/docs/images/UndoRedoState0.png and /dev/null differ
diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png
deleted file mode 100644
index 2d3ad09c047..00000000000
Binary files a/docs/images/UndoRedoState1.png and /dev/null differ
diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png
deleted file mode 100644
index 20853694e03..00000000000
Binary files a/docs/images/UndoRedoState2.png and /dev/null differ
diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png
deleted file mode 100644
index 1a9551b31be..00000000000
Binary files a/docs/images/UndoRedoState3.png and /dev/null differ
diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png
deleted file mode 100644
index 46dfae78c94..00000000000
Binary files a/docs/images/UndoRedoState4.png and /dev/null differ
diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png
deleted file mode 100644
index f45889b5fdf..00000000000
Binary files a/docs/images/UndoRedoState5.png and /dev/null differ
diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png
deleted file mode 100644
index c7a7e637266..00000000000
Binary files a/docs/images/UndoSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/add-remark/$Remark.png b/docs/images/add-remark/$Remark.png
deleted file mode 100644
index 959c634406d..00000000000
Binary files a/docs/images/add-remark/$Remark.png and /dev/null differ
diff --git a/docs/images/add-remark/ContextMenu.png b/docs/images/add-remark/ContextMenu.png
deleted file mode 100644
index 77536724e45..00000000000
Binary files a/docs/images/add-remark/ContextMenu.png and /dev/null differ
diff --git a/docs/images/add-remark/CreateTest.png b/docs/images/add-remark/CreateTest.png
deleted file mode 100644
index 6b7d6dcafec..00000000000
Binary files a/docs/images/add-remark/CreateTest.png and /dev/null differ
diff --git a/docs/images/add-remark/GradleRun.png b/docs/images/add-remark/GradleRun.png
deleted file mode 100644
index 281cc45f098..00000000000
Binary files a/docs/images/add-remark/GradleRun.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkBound.png b/docs/images/add-remark/RemarkBound.png
deleted file mode 100644
index d335382b286..00000000000
Binary files a/docs/images/add-remark/RemarkBound.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkCommandClass.png b/docs/images/add-remark/RemarkCommandClass.png
deleted file mode 100644
index 5687a3e9585..00000000000
Binary files a/docs/images/add-remark/RemarkCommandClass.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkCommandParserClass.png b/docs/images/add-remark/RemarkCommandParserClass.png
deleted file mode 100644
index d5ad9c8a02f..00000000000
Binary files a/docs/images/add-remark/RemarkCommandParserClass.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkComplete.png b/docs/images/add-remark/RemarkComplete.png
deleted file mode 100644
index 124ced2c752..00000000000
Binary files a/docs/images/add-remark/RemarkComplete.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkFailureOutput.png b/docs/images/add-remark/RemarkFailureOutput.png
deleted file mode 100644
index 351257ea332..00000000000
Binary files a/docs/images/add-remark/RemarkFailureOutput.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkHello.png b/docs/images/add-remark/RemarkHello.png
deleted file mode 100644
index aad48d02f8f..00000000000
Binary files a/docs/images/add-remark/RemarkHello.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkNotImplemented.png b/docs/images/add-remark/RemarkNotImplemented.png
deleted file mode 100644
index 1d187f39403..00000000000
Binary files a/docs/images/add-remark/RemarkNotImplemented.png and /dev/null differ
diff --git a/docs/images/bwangpj.png b/docs/images/bwangpj.png
new file mode 100644
index 00000000000..b7afc738c48
Binary files /dev/null and b/docs/images/bwangpj.png differ
diff --git a/docs/images/cloud7050.png b/docs/images/cloud7050.png
new file mode 100644
index 00000000000..ee6a0bfdfe1
Binary files /dev/null and b/docs/images/cloud7050.png differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
deleted file mode 100644
index 235da1c273e..00000000000
Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/johndoe.png b/docs/images/johndoe.png
deleted file mode 100644
index 1ce7ce16dc8..00000000000
Binary files a/docs/images/johndoe.png and /dev/null differ
diff --git a/docs/images/mamayuan.png b/docs/images/mamayuan.png
new file mode 100644
index 00000000000..371e2972576
Binary files /dev/null and b/docs/images/mamayuan.png differ
diff --git a/docs/images/rayshawntan.png b/docs/images/rayshawntan.png
new file mode 100644
index 00000000000..0bf6df2b2f1
Binary files /dev/null and b/docs/images/rayshawntan.png differ
diff --git a/docs/images/remove/$address.png b/docs/images/remove/$address.png
deleted file mode 100644
index cf0434e0e83..00000000000
Binary files a/docs/images/remove/$address.png and /dev/null differ
diff --git a/docs/images/remove/SafeDeleteConflicts.png b/docs/images/remove/SafeDeleteConflicts.png
deleted file mode 100644
index 8f0abeffd4d..00000000000
Binary files a/docs/images/remove/SafeDeleteConflicts.png and /dev/null differ
diff --git a/docs/images/remove/UnsafeDelete.png b/docs/images/remove/UnsafeDelete.png
deleted file mode 100644
index 9e376d02a0c..00000000000
Binary files a/docs/images/remove/UnsafeDelete.png and /dev/null differ
diff --git a/docs/images/remove/UnsafeDeleteOnField.png b/docs/images/remove/UnsafeDeleteOnField.png
deleted file mode 100644
index 44d5bb0a442..00000000000
Binary files a/docs/images/remove/UnsafeDeleteOnField.png and /dev/null differ
diff --git a/docs/images/request_access.png b/docs/images/request_access.png
deleted file mode 100644
index 12e8a81bd28..00000000000
Binary files a/docs/images/request_access.png and /dev/null differ
diff --git a/docs/images/tracing/DebuggerStep1.png b/docs/images/tracing/DebuggerStep1.png
deleted file mode 100644
index 6d088ae63de..00000000000
Binary files a/docs/images/tracing/DebuggerStep1.png and /dev/null differ
diff --git a/docs/images/tracing/EditCommand.png b/docs/images/tracing/EditCommand.png
deleted file mode 100644
index ed34ad08b98..00000000000
Binary files a/docs/images/tracing/EditCommand.png and /dev/null differ
diff --git a/docs/images/tracing/FindUsages.png b/docs/images/tracing/FindUsages.png
deleted file mode 100644
index e074d0e86c9..00000000000
Binary files a/docs/images/tracing/FindUsages.png and /dev/null differ
diff --git a/docs/images/tracing/LeftGutter.png b/docs/images/tracing/LeftGutter.png
deleted file mode 100644
index 571acf99e7b..00000000000
Binary files a/docs/images/tracing/LeftGutter.png and /dev/null differ
diff --git a/docs/images/tracing/LogicSequenceDiagram.png b/docs/images/tracing/LogicSequenceDiagram.png
deleted file mode 100644
index 25c8b66b9f1..00000000000
Binary files a/docs/images/tracing/LogicSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/tracing/ShowExecutionPoint.png b/docs/images/tracing/ShowExecutionPoint.png
deleted file mode 100644
index ea72176fa64..00000000000
Binary files a/docs/images/tracing/ShowExecutionPoint.png and /dev/null differ
diff --git a/docs/images/tracing/StepInto.png b/docs/images/tracing/StepInto.png
deleted file mode 100644
index ddfa0e2aeb8..00000000000
Binary files a/docs/images/tracing/StepInto.png and /dev/null differ
diff --git a/docs/images/tracing/StepOver.png b/docs/images/tracing/StepOver.png
deleted file mode 100644
index ed5fb276e29..00000000000
Binary files a/docs/images/tracing/StepOver.png and /dev/null differ
diff --git a/docs/images/tracing/StructureToolWindow.png b/docs/images/tracing/StructureToolWindow.png
deleted file mode 100644
index c377c331d5f..00000000000
Binary files a/docs/images/tracing/StructureToolWindow.png and /dev/null differ
diff --git a/docs/images/tracing/Variables.png b/docs/images/tracing/Variables.png
deleted file mode 100644
index 02ea7b15520..00000000000
Binary files a/docs/images/tracing/Variables.png and /dev/null differ
diff --git a/docs/images/tracing/searchResultsForExecuteMethod.png b/docs/images/tracing/searchResultsForExecuteMethod.png
deleted file mode 100644
index 845a0c94687..00000000000
Binary files a/docs/images/tracing/searchResultsForExecuteMethod.png and /dev/null differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..24df5531558 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,15 @@
---
layout: page
-title: AddressBook Level-3
+title: ConText
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![Java CI](https://github.com/AY2324S1-CS2103-W14-3/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S1-CS2103-W14-3/tp/actions/workflows/gradle.yml)
+[![codecov](https://codecov.io/gh/AY2324S1-CS2103-W14-3/tp/graph/badge.svg?token=KT7MNHKALX)](https://codecov.io/gh/AY2324S1-CS2103-W14-3/tp)
-![Ui](images/Ui.png)
+![UI](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**ConText** is a desktop app that allows for managing contacts quickly via text commands, with useful features relevant to NUS SoC students.
+While it has a Graphical User Interface, most of its user interactions happen via an in-app Command Line Interface.
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
-
-
-**Acknowledgements**
-
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+- If you are interested in using ConText, please check out the [**User Guide**](./UserGuide.html).
+- If you are interested about developing ConText, the [**Developer Guide**](./DeveloperGuide.html) is a good place to start.
diff --git a/docs/team/bwangpj.md b/docs/team/bwangpj.md
new file mode 100644
index 00000000000..f49753c80a4
--- /dev/null
+++ b/docs/team/bwangpj.md
@@ -0,0 +1,62 @@
+---
+layout: page
+title: Bryan's Project Portfolio Page
+---
+
+
+
+
+## Overview
+
+**ConText** is a desktop app that allows for managing contacts quickly via text commands, with useful features relevant to NUS SoC students.
+While it has a Graphical User Interface, most of its user interactions happen via an in-app Command Line Interface.
+
+## Summary of contributions
+
+### Code contributed
+
+[TP RepoSense Code Dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=bwangpj&breakdown=true)
+
+### Enhancements implemented
+
+1. Filter command to allow users to filter contacts by Tag, and associated tests
+2. Store command history and allow navigating between previous commands using up/down arrow keys, similar to an actual CLI, and associated tests
+
+### Contributions to the UG
+
+- Describe useful features relevant to NUS SoC students at the start of the UG, i.e. filtering and navigating between commands using up/down arrow keys
+- Include labelled UI diagram of the various information that is stored and displayed in the UI
+- Include documentation for `filter` command
+- Explain and emphasise that indexing is 1-based and not 0-based
+- Explain and emphasise how duplicate contacts are handled - only by name, and only with an exact match of name
+- Clarify how input commands with empty fields are handled
+
+### Contributions to the DG
+
+- Include initial list of all user stories brainstormed
+- Describe implementation of:
+ - `edit` command
+ - `filter` command
+ - navigation between command history using up/down arrow keys; including the `UI` component as well as description of how the feature is implemented using `CommandBoxHistory`
+
+ and their associated UML diagrams.
+- Add Planned Enhancements appendix to the DG
+
+### Contributions to team-based tasks
+
+- Rename objects in the code to remove traces of AB3, and the associated big refactor
+- Remove traces of AB3 from Jekyll
+- Remove tutorials and images used in tutorials from the docs
+- Release of v1.3 (trial)
+- Triage and collate the PE-D bugs for Planned Enhancements
+- Help with miscellaneous checkstyle corrections to pass checkstyle
+
+### Review/mentoring contributions
+
+- Review PRs to catch things that may have been missed.
+E.g. https://github.com/AY2324S1-CS2103-W14-3/tp/pull/71#discussion_r1361430312
+- Contribute to discussions on GitHub, Telegram and Zoom.
+
+### Contributions beyond the project team
+
+- Put a good amount of effort into [finding bugs during the PE-D](https://github.com/AY2324S1-CS2103T-T17-4/tp/issues?q=is%3Aissue+c%5D).
diff --git a/docs/team/cloud7050.md b/docs/team/cloud7050.md
new file mode 100644
index 00000000000..5015a517bf3
--- /dev/null
+++ b/docs/team/cloud7050.md
@@ -0,0 +1,70 @@
+---
+layout: page
+title: Cloud7050's Project Portfolio Page
+---
+
+
+
+
+
+### Overview
+
+**ConText** is a desktop app that allows for managing contacts quickly via text commands, with useful features relevant to NUS SoC students.
+While it has a Graphical User Interface, most of its user interactions happen via an in-app Command Line Interface.
+
+### Code contributed
+
+[TP RepoSense Code Dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=cloud7050&breakdown=true)
+
+### Enhancements implemented
+
+- Notes feature with flexible validation, integrated with `add`, `edit`, UI, tests.
+- Automatic sorting, integrated with `find`, `filter`.
+- Designed & helped implement `TestData`, `Messages`.
+- Helped with planning & doing wide-sweeping refactoring, including simplifications, code quality, test case improvements. Scrapped various overkill parts of AB3, e.g. `Version`. Helped clean dead/irrelevant files from AB3 (e.g. images, tutorials).
+- Helped update many test cases to match our product. Improved existing tests (e.g. `@TempDir` instead of sandbox). Wrote new tests.
+- Updated/enhanced/fixed various Javadocs.
+
+### Contributions to the UG
+
+- Wrote sections: Intro, quick start, command format, command details, sorting/saving, limitations.
+- Merged AB3 UG back into draft UG. Full pass over UG to add/update/enhance contents.
+- Proofread & fixed/filed issues after pages finished building, i.e. after PR reviews.
+
+### Contributions to the DG
+
+
+
+
+
+
+- Wrote sections: Sorting, acknowledgements, product scope, NFRs.
+- Added to sections: Enhancements.
+- Helped update to match our product.
+- Scrapped separate out-of-scope pages.
+
+### Contributions to team-based tasks
+
+- Set up: GitHub org, repo with labels/branch protections/user stories board, Jekyll, Codecov.
+- Repo-wide refactor to match our product.
+- Managed gradle configs, updated wrapper (e.g. dependencies, fix deprecation, simplification, `clean`). Relaxed checkstyle over time. Kept misc files like gitignore up to date.
+- Defined most tasks in [issue tracker](https://github.com/AY2324S1-CS2103-W14-3/tp/issues?q=is%3Aissue+author%3ACloud7050), kept it updated & tidy. Managed milestones, labelled/tracked/edited/assigned tasks. Very active in [issue comments](https://nus-cs2103-ay2324s1.github.io/dashboards/contents/tp-comments.html#16-joel-leow-cloud7050-60-comments) (e.g. adding details/meeting notes).
+- Helped perform milestone wrapups, releases. Performed week-specific tasks (e.g. triaging PE-D bugs, collating progress into MS Teams message).
+- Tracked meeting agendas, tasks pending assignment/discussion. Made forum posts for clarifications. Tracked deliverables, ensured done before deadlines. Helped keep meetings moving, e.g. screenshare with annotations to facilitate discussions.
+- Worked on docs sections not specific to anybody's features (e.g. [UG](#contributions-to-the-ug), [DG](#contributions-to-the-dg), README, `Ui.png`, `index.md`).
+- Made PR template. Added reviewer assignment GitHub bot.
+
+### Review/mentoring contributions
+
+- Reviewed good portion of [team PRs](https://github.com/AY2324S1-CS2103-W14-3/tp/pulls?q=is%3Apr), [quite thoroughly](https://nus-cs2103-ay2324s1.github.io/dashboards/contents/tp-comments.html#16-joel-leow-cloud7050-60-comments) (spotlight: [#71](https://github.com/AY2324S1-CS2103-W14-3/tp/pull/71#issuecomment-1768352563)). Reviewed already merged PRs, opened various followup issues for things missed (e.g. [#53](https://github.com/AY2324S1-CS2103-W14-3/tp/issues/53), [#80](https://github.com/AY2324S1-CS2103-W14-3/tp/issues/80), [#122](https://github.com/AY2324S1-CS2103-W14-3/tp/issues/122)).
+- Helped clarify/answer questions in GitHub/Telegram/Zoom discussions. Discussed steps for accomplishing certain tasks (e.g. git), ways to enhance certain aspects (e.g. regex).
+- Pointed out notable things, e.g. [git details configured different from grading scripts](https://github.com/AY2324S1-CS2103-W14-3/tp/commits/master?after=7575f07d444894d96d1849ee81bbb0bdfcd05802+454&branch=master&qualified_name=refs%2Fheads%2Fmaster#:~:text=Remove%20rubbish-,DESKTOP%2DITF4GUD%5C94866,-committed%20on%20Oct), team repo watchers, [quiz part 1 automation](https://github.com/Cloud7050/js-canvastransfer).
+
+### Contributions beyond the project team
+
+- Put good effort into [finding HouR bugs during PE-D](https://github.com/AY2324S1-CS2103T-W12-1/tp/issues?q=is%3Aissue+c%5D). Checked on transferred issues in case I could help clarify.
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 773a07794e2..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-### Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/docs/team/mamayuan.md b/docs/team/mamayuan.md
new file mode 100644
index 00000000000..870ed2ed5d8
--- /dev/null
+++ b/docs/team/mamayuan.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: mamayuan's Project Portfolio Page
+---
+
+## Overview
+
+**ConText** is a desktop app that allows for managing contacts quickly via text commands, with useful features relevant to NUS SoC students.
+While it has a Graphical User Interface, most of its user interactions happen via an in-app Command Line Interface.
+
+### Code contributed
+
+[TP RepoSense Code Dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=mamayuan&breakdown=true)
+
+### Enhancements implemented
+- Enabled processing of multiple indices in the delete command.
+- Developed a Messages.java class to ensure accurate message displays to users.
+- Enhanced test files with updated test cases.
+
+### Contributions to the UG
+- Initiated the User Guide setup.
+- Addressed and corrected style and wording inconsistencies throughout the User Guide.
+- Revised the section detailing the delete command.
+
+### Contributions to the DG
+- Authored the Glossary and Manual Testing sections.
+- Refinec `User Stories` section
+- Specified implementation of `delete` command
+- Updated the DeleteSequenceDiagram UML diagram to reflect new implementations and addressed potential bugs in other sequence diagrams.
+- Fixed potential bugs for other sequence diagrams
+- Edit `Appendix: Planned enhancements`
+- Resolved naming inconsistencies inherited from AB3.
+
+### Contributions to team-based tasks
+- Established a Messages.java class consolidating all user messages, previously dispersed across various files.
+- Removed traces of AB3 in documentations and code
+- Provided feedback for peer PRs
+- Reminded team of deadlines
+- Refactored code for better
+
+### Review/mentoring contributions
+- Helped review some of [team PRs](https://github.com/AY2324S1-CS2103-W14-3/tp/pulls?q=is%3Apr) that required approval before merging into the master branch and provided relevant feedbacks.
+- Contributed to GitHub/Telegram/Zoom discussions to help clarify/answer questions and add my thoughts.
+
+### Contributions beyond the project team
+- During PE-D, I have put in extra effort to [find bugs](https://github.com/AY2324S1-CS2103T-T08-4/tp/issues) for the team assigned.
diff --git a/docs/team/rayshawntan.md b/docs/team/rayshawntan.md
new file mode 100644
index 00000000000..95b8384e47d
--- /dev/null
+++ b/docs/team/rayshawntan.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: rayshawntan's Project Portfolio Page
+---
+
+## Overview
+
+**ConText** is a desktop app that allows for managing contacts quickly via text commands.
+It is optimized for use via an in-app Command Line Interface (CLI), while still having the benefits of a Graphical User Interface (GUI).
+
+## Summary of contributions
+
+### Code contributed
+
+My code contributions for this project can be seen here: [TP RepoSense Code Dashboard](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=rayshawntan&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos)
+
+### Enhancements implemented
+
+* `AlternateContact` to allow users to store alternate means of contact.
+
+### Contributions to the UG
+
+* Update implementations of `add` and `edit` features
+* Merge portions of AB3 UG back into our UG draft
+
+### Contributions to the DG
+
+* Include the list of use cases
+* Describe implementation of `add` commands and the associated diagram
+
+### Contributions to team-based tasks
+
+* Add UI mockup of how we want our project to be initially
+* Update the homepage of project to remove traces of AB3
+* Reorganise the test data used in test cases for easier locating of test data origin
+* Remove traces of AB3 in documentations and code
+* Release of v1.2b
+* Demo of v1.2 and v1.3
+
+### Review/mentoring contributions
+* Review some of the [team PRs](https://github.com/AY2324S1-CS2103-W14-3/tp/pulls?q=is%3Apr)
+* Examples of PRs reviewed with non-trivial comments: [\#112](https://github.com/AY2324S1-CS2103-W14-3/tp/pull/112), [\#197](https://github.com/AY2324S1-CS2103-W14-3/tp/pull/197)
+
+### Contributions beyond the project team
+* During PE-D, I have put in effort to [find bugs](https://github.com/AY2324S1-CS2103T-T09-4/tp/issues?q=is%3Aissue+%22%5BPE-D%5D%5BTester+D%5D%22) that do not correspond to their UG
+
diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md
deleted file mode 100644
index d98f38982e7..00000000000
--- a/docs/tutorials/AddRemark.md
+++ /dev/null
@@ -1,399 +0,0 @@
----
-layout: page
-title: "Tutorial: Adding a command"
----
-
-Let's walk you through the implementation of a new command — `remark`.
-
-This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format:
-
-`remark INDEX r/REMARK` (e.g., `remark 2 r/Likes baseball`)
-
-We’ll assume that you have already set up the development environment as outlined in the Developer’s Guide.
-
-
-## Create a new `remark` command
-
-Looking in the `logic.command` package, you will notice that each existing command have their own class. All the commands inherit from the abstract class `Command` which means that they must override `execute()`. Each `Command` returns an instance of `CommandResult` upon success and `CommandResult#feedbackToUser` is printed to the `ResultDisplay`.
-
-Let’s start by creating a new `RemarkCommand` class in the `src/main/java/seedu/address/logic/command` directory.
-
-For now, let’s keep `RemarkCommand` as simple as possible and print some output. We accomplish that by returning a `CommandResult` with an accompanying message.
-
-**`RemarkCommand.java`:**
-
-``` java
-package seedu.address.logic.commands;
-
-import seedu.address.model.Model;
-
-/**
- * Changes the remark of an existing person in the address book.
- */
-public class RemarkCommand extends Command {
-
- public static final String COMMAND_WORD = "remark";
-
- @Override
- public CommandResult execute(Model model) {
- return new CommandResult("Hello from remark");
- }
-}
-```
-
-### Hook `RemarkCommand` into the application
-
-Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`.
-
-You can refer to the changes in this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-399c284cb892c20b7c04a69116fcff6ccc0666c5230a1db8e4a9145def8fa4ee).
-
-### Run the application
-
-Run `Main#main` and try out your new `RemarkCommand`. If everything went well, you should see something like this:
-
-![Output displayed](../images/add-remark/RemarkHello.png)
-
-## Change `RemarkCommand` to throw an exception
-
-While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. Let’s change the command to throw a `CommandException` to accurately reflect that our command is still a work in progress.
-
-![The relationship between RemarkCommand and Command](../images/add-remark/RemarkCommandClass.png)
-
-Following the convention in other commands, we add relevant messages as constants and use them.
-
-**`RemarkCommand.java`:**
-
-``` java
- public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Edits the remark of the person identified "
- + "by the index number used in the last person listing. "
- + "Existing remark will be overwritten by the input.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "r/ [REMARK]\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + "r/ Likes to swim.";
-
- public static final String MESSAGE_NOT_IMPLEMENTED_YET =
- "Remark command not implemented yet";
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- throw new CommandException(MESSAGE_NOT_IMPLEMENTED_YET);
- }
-```
-
-## Enhancing `RemarkCommand`
-
-Let’s change `RemarkCommand` to parse input from the user.
-
-### Make the command accept parameters
-
-We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended.
-
-``` java
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-//...
-public class RemarkCommand extends Command {
- //...
- public static final String MESSAGE_ARGUMENTS = "Index: %1$d, Remark: %2$s";
-
- private final Index index;
- private final String remark;
-
- /**
- * @param index of the person in the filtered person list to edit the remark
- * @param remark of the person to be updated to
- */
- public RemarkCommand(Index index, String remark) {
- requireAllNonNull(index, remark);
-
- this.index = index;
- this.remark = remark;
- }
- @Override
- public CommandResult execute(Model model) throws CommandException {
- throw new CommandException(
- String.format(MESSAGE_ARGUMENTS, index.getOneBased(), remark));
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof RemarkCommand)) {
- return false;
- }
-
- RemarkCommand e = (RemarkCommand) other;
- return index.equals(e.index)
- && remark.equals(e.remark);
- }
-}
-```
-
-Your code should look something like [this](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-a8e35af8f9c251525063fae36c9852922a7e7195763018eacec60f3a4d87c594) after you are done.
-
-### Parse user input
-
-Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user.
-
-Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface.
-
-![The relationship between Parser and RemarkCommandParser](../images/add-remark/RemarkCommandParserClass.png)
-
-Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc provided for the function to understand what it does.
-
-**`ArgumentTokenizer.java`:**
-
-``` java
-/**
- * Tokenizes an arguments string and returns an {@code ArgumentMultimap}
- * object that maps prefixes to their respective argument values. Only the
- * given prefixes will be recognized in the arguments string.
- *
- * @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
- */
-```
-
-We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` and it will return us an instance of `ArgumentMultimap`. Now let’s find out what we need to do in order to obtain the Index and String that we need. Let’s look through `ArgumentMultimap` :
-
-**`ArgumentMultimap.java`:**
-
-``` java
-/**
- * Returns the last value of {@code prefix}.
- */
-public Optional getValue(Prefix prefix) {
- List values = getAllValues(prefix);
- return values.isEmpty() ? Optional.empty() :
- Optional.of(values.get(values.size() - 1));
-}
-```
-
-This appears to be what we need to get a String of the remark. But what about the Index? Let's take a quick peek at existing `Command` that uses an index to see how it is done.
-
-**`DeleteCommandParser.java`:**
-
-``` java
-Index index = ParserUtil.parseIndex(args);
-return new DeleteCommand(index);
-```
-
-There appears to be another utility class that obtains an `Index` from the input provided by the user.
-
-Now that we have the know-how to extract the data that we need from the user’s input, we can parse the user command and create a new instance of `RemarkCommand`, as given below.
-
-**`RemarkCommandParser.java`:**
-
-``` java
-public RemarkCommand parse(String args) throws ParseException {
- requireNonNull(args);
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
- PREFIX_REMARK);
-
- Index index;
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (IllegalValueException ive) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
- RemarkCommand.MESSAGE_USAGE), ive);
- }
-
- String remark = argMultimap.getValue(PREFIX_REMARK).orElse("");
-
- return new RemarkCommand(index, remark);
-}
-```
-
-
-
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
-
-
-
-If you are stuck, check out the sample
-[here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a).
-
-## Add `Remark` to the model
-
-Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person.
-
-### Add a new `Remark` class
-
-Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
-
-A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input
-validation.
-
-### Make use of `Remark`
-
-Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` class instead of plain `String`. These should be relatively simple changes.
-
-## Add a placeholder element for remark to the UI
-
-Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
-
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
-
-**`PersonCard.java`:**
-
-``` java
-@FXML
-private Label remark;
-```
-
-
-`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like Greek to you right now, don’t worry — we will get back to it later.
-
-Then insert the following into [`main/resources/view/PersonListCard.fxml`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-d44c4f51c24f6253c277a2bb9bc440b8064d9c15ad7cb7ceda280bca032efce9).
-
-**`PersonListCard.fxml`:**
-
-``` xml
-
-```
-
-That’s it! Fire up the application again and you should see something like this:
-
-![$remark shows up in each entry](../images/add-remark/$Remark.png)
-
-## Modify `Person` to support a `Remark` field
-
-Since `PersonCard` displays data from a `Person`, we need to update `Person` to get our `Remark` displayed!
-
-### Modify `Person`
-
-We change the constructor of `Person` to take a `Remark`. We will also need to define new fields and accessors accordingly to store our new addition.
-
-### Update other usages of `Person`
-
-Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`!
-
-
-
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
-
-
-
-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`!
-
-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!
-
-
-
-Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
-to see what the changes entail.
-
-## Finalizing the UI
-
-Now that we have finalized the `Person` class and its dependencies, we can now bind the `Remark` field to the UI.
-
-Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692)
-
-**`PersonCard.java`:**
-
-``` java
-public PersonCard(Person person, int displayedIndex) {
- //...
- remark.setText(person.getRemark().value);
-}
-```
-
-![The remark label is bound properly!](../images/add-remark/RemarkBound.png)
-
-## Putting everything together
-
-After the previous step, we notice a peculiar regression — we went from displaying something to nothing at all. However, this is expected behavior as we are yet to update the `RemarkCommand` to make use of the code we've been adding in the last few steps.
-
-### Update `RemarkCommand` and `RemarkCommandParser`
-
-In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Person`. Since all fields in a `Person` are immutable, we create a new instance of a `Person` with the values that we want and
-save it with `Model#setPerson()`.
-
-**`RemarkCommand.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";
-//...
- @Override
- public CommandResult execute(Model model) throws CommandException {
- List lastShownList = model.getFilteredPersonList();
-
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = new Person(
- personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
- personToEdit.getAddress(), remark, personToEdit.getTags());
-
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
-
- return new CommandResult(generateSuccessMessage(editedPerson));
- }
-
- /**
- * Generates a command execution success message based on whether
- * the remark is added to or removed from
- * {@code personToEdit}.
- */
- private String generateSuccessMessage(Person personToEdit) {
- String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS;
- return String.format(message, personToEdit);
- }
-```
-
-![Congratulations!](../images/add-remark/RemarkComplete.png)
-
-## Writing tests
-
-Tests are crucial to ensuring that bugs don’t slip into the codebase unnoticed. This is especially true for large code bases where a change might lead to unintended behavior.
-
-Let’s verify the correctness of our code by writing some tests!
-
-Of course you can simply add the test cases manually, like you've been doing all along this tutorial. The result would be like the test cases in [here](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01). Alternatively, you can get the help of IntelliJ to generate the skeletons of the test cases, as explained in the next section.
-
-### Automatically generating tests
-
-The goal is to write effective and efficient tests to ensure that `RemarkCommand#execute()` behaves as expected.
-
-The convention for test names is `methodName_testScenario_expectedResult`. An example would be
-`execute_filteredList_success`.
-
-Let’s create a test for `RemarkCommand#execute()` to test that adding a remark works. On `IntelliJ IDEA` you can bring up the context menu and choose to `Go To` \> `Test` or use the appropriate keyboard shortcut.
-
-![Using the context menu to jump to tests](../images/add-remark/ContextMenu.png)
-
-Then, create a test for the `execute` method.
-
-![Creating a test for `execute`.](../images/add-remark/CreateTest.png)
-
-Following convention, let’s change the name of the generated method to `execute_addRemarkUnfilteredList_success`.
-
-Let’s use the utility functions provided in `CommandTestUtil`. The functions ensure that commands produce the expected `CommandResult` and output the correct message. In this case, `CommandTestUtil#assertCommandSuccess` is the best fit as we are testing that a `RemarkCommand` will successfully add a `Remark`.
-
-You should end up with a test that looks something like [this](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01R36-R49).
-
-## Conclusion
-
-This concludes the tutorial for adding a new `Command` to AddressBook.
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
deleted file mode 100644
index f29169bc924..00000000000
--- a/docs/tutorials/RemovingFields.md
+++ /dev/null
@@ -1,112 +0,0 @@
----
-layout: page
-title: "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
-
-When working on an existing code base, you will most likely find that some features that are no longer necessary.
-This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class.
-
-
-
-**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.
-
-
-However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.**
-
-
-
-* Table of Contents
-{:toc}
-
-## Safely deleting `Address`
-
-IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a removal easily. Let’s try to use it as much as we can.
-
-### 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)
-
-Choose to `View Usages` and you should be presented with a list of `Safe Delete Conflicts`. These conflicts describe locations in which the `Address` class is used.
-
-![List of conflicts](../images/remove/SafeDeleteConflicts.png)
-
-Remove usages of `Address` by performing `Safe Delete`s on each entry i.e., double-click on the entry (which takes you to the code in concern, right-click on that entity, and choose `Refactor` -> `Safe delete` as before). You will need to exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well. Other usages like in `EditPersonDescriptor` may require more careful inspection.
-
-Let’s try removing references to `Address` in `EditPersonDescriptor`.
-
-1. Safe delete the field `address` in `EditPersonDescriptor`.
-
-1. Select `Yes` when prompted to remove getters and setters.
-
-1. Select `View Usages` again.
- ![UnsafeDeleteOnField](../images/remove/UnsafeDeleteOnField.png)
-
-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.
-
-
-1. Repeat the steps for the remaining usages of `Address`
-
-After you are done, verify that the application still works by compiling and running it again.
-
-### Manual refactoring
-
-Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`).
-
-Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `PersonCard` that has not been removed nor identified.
-
-![$address](../images/remove/$address.png)
-
-A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
-
-**`PersonCard.java`**
-
-``` java
-...
-@FXML
-private Label address;
-...
-```
-
-**`PersonCard.fxml`**
-
-``` xml
-...
-
-
-
-...
-```
-
-After removing the `Label`, we can proceed to formally test our code. If everything went well, you should have most of your tests pass. Fix any remaining errors until the tests all pass.
-
-## Tidying up
-
-At this point, your application is working as intended and all your tests are passing. What’s left to do is to clean up references to `Address` in test data and documentation.
-
-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`:**
-
-```json
-{
- "persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
- } ]
-}
-```
-
-You can go through each individual `json` file and manually remove the `address` field.
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
deleted file mode 100644
index 4fb62a83ef6..00000000000
--- a/docs/tutorials/TracingCode.md
+++ /dev/null
@@ -1,301 +0,0 @@
----
-layout: page
-title: "Tutorial: Tracing code"
----
-
-> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …\[Therefore,\] making it easy to read makes it easier to write.
->
-> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
-
-When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command.
-
-* Table of Contents
-{:toc}
-
-## Before we start
-
-Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components.
-
-![ArchitectureDiagram](../images/ArchitectureDiagram.png)
-
-It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App.
-
-
-
-Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works.
-
-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
- * If you are using a different IDE, we'll leave it to you to figure out the equivalent feature to use in your IDE.
- * If you are not using an IDE, we'll let you figure out how to achieve the same using your coding toolchain.
-
-## Setting a breakpoint
-
-As you know, the first step of debugging is to put in a breakpoint where you want the debugger to pause the execution. For example, if you are trying to understand how the App starts up, you would put a breakpoint in the first statement of the `main` method.
-
-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`.
-
-
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
-
-```java
-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.
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
-...
-}
-```
-
-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.
-
-
-![`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)
-
-## 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.
-
-
-1. To start the debugging session, simply `Run` \> `Debug Main`
-
-1. When the GUI appears, enter `edit 1 n/Alice Yeoh` into the command box and press `Enter`.
-
-1. The Debugger tool window should show up and show something like this:
- ![DebuggerStep1](../images/tracing/DebuggerStep1.png)
-
-1. Use the _Show execution point_ feature to jump to the line of code that we stopped at:
- ![ShowExecutionPoint](../images/tracing/ShowExecutionPoint.png)
- `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)
-
-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
- @Override
- public CommandResult execute(String commandText)
- throws CommandException, ParseException {
-
- //Logging, safe to ignore
- logger.info("----------------[USER COMMAND][" + commandText + "]");
-
- CommandResult commandResult;
- //Parse user input from String to a Command
- Command command = addressBookParser.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());
- } catch (IOException ioe) {
- throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
- }
-
- return commandResult;
- }
- ```
-
-1. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look at each one.
-
-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
- public Command parseCommand(String userInput) throws ParseException {
- ...
- final String commandWord = matcher.group("commandWord");
- final String arguments = matcher.group("arguments");
- ...
- ```
-
-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)
-
-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);
- ...
- ```
-
-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!
-
-
-1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
-
-1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the 'Variables' window.
- ![EditCommand](../images/tracing/EditCommand.png)
-
-1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component.
-
-
-1. Let’s continue stepping through until we return to `LogicManager#execute()`.
-
- The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png)
-
-1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below):
-
- **`EditCommand#execute()`:**
- ``` java
- @Override
- public CommandResult execute(Model model) throws CommandException {
- ...
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
- }
- ```
-
-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):
-
- **`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. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`.
- 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);
- }
- ```
-
-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.
-
-Here are some quick questions you can try to answer based on your execution path tracing. In some cases, you can do further tracing for the given commands to find exactly what happens.
-
-1. In this tutorial, we traced the "happy path" (i.e., no errors). What
- do you think will happen if we traced the following commands
- instead? What exceptions do you think will be thrown (if any), where
- will the exceptions be thrown and where will they be handled?
-
- 1. `redit 1 n/Alice Yu`
-
- 2. `edit 0 n/Alice Yu`
-
- 3. `edit 1 n/Alex Yeoh`
-
- 4. `edit 1`
-
- 5. `edit 1 n/アリス ユー`
-
- 6. `edit 1 t/one t/two t/three t/one`
-
-2. What components will you have to modify to perform the following
- enhancements to the application?
-
- 1. Make command words case-insensitive
-
- 2. Allow `delete` to remove more than one index at a time
-
- 3. Save the address book in the CSV format instead
-
- 4. Add a new command
-
- 5. Add a new field to `Person`
-
- 6. Add a new entity to the address book
diff --git a/gradle.properties b/gradle.properties
index 40764dc1791..8bee08b60de 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,6 +1,2 @@
org.gradle.parallel=false
org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -Dfile.encoding=utf-8
-
-# TODO: This is a workaround for a JDK11 bug which causes test coverage upload to fail.
-# Remove it when https://bugs.openjdk.java.net/browse/JDK-8221253 is fixed.
-systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c023e..7f93135c49b 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index aa991fceae6..ac72c34e8ac 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c787337f..0adc8e1a532 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then
done
fi
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
@@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 107acd32c4e..93e3f59f135 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
deleted file mode 100644
index 3d6bd06d5af..00000000000
--- a/src/main/java/seedu/address/MainApp.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package seedu.address;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import javafx.application.Application;
-import javafx.stage.Stage;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.core.Version;
-import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.commons.util.ConfigUtil;
-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.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.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
-import seedu.address.storage.UserPrefsStorage;
-import seedu.address.ui.Ui;
-import seedu.address.ui.UiManager;
-
-/**
- * Runs the application.
- */
-public class MainApp extends Application {
-
- public static final Version VERSION = new Version(0, 2, 2, true);
-
- private static final Logger logger = LogsCenter.getLogger(MainApp.class);
-
- protected Ui ui;
- protected Logic logic;
- protected Storage storage;
- protected Model model;
- protected Config config;
-
- @Override
- public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
- super.init();
-
- AppParameters appParameters = AppParameters.parse(getParameters());
- config = initConfig(appParameters.getConfigPath());
- initLogging(config);
-
- UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
- UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
-
- model = initModelManager(storage, userPrefs);
-
- logic = new LogicManager(model, storage);
-
- ui = new UiManager(logic);
- }
-
- /**
- * 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());
-
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
- try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Creating a new data file " + storage.getAddressBookFilePath()
- + " populated with a sample AddressBook.");
- }
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
- } catch (DataLoadingException e) {
- logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded."
- + " Will be starting with an empty AddressBook.");
- initialData = new AddressBook();
- }
-
- return new ModelManager(initialData, userPrefs);
- }
-
- private void initLogging(Config config) {
- LogsCenter.init(config);
- }
-
- /**
- * Returns a {@code Config} using the file at {@code configFilePath}.
- * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead
- * if {@code configFilePath} is null.
- */
- protected Config initConfig(Path configFilePath) {
- Config initializedConfig;
- Path configFilePathUsed;
-
- configFilePathUsed = Config.DEFAULT_CONFIG_FILE;
-
- if (configFilePath != null) {
- logger.info("Custom Config file specified " + configFilePath);
- configFilePathUsed = configFilePath;
- }
-
- logger.info("Using config file : " + configFilePathUsed);
-
- try {
- Optional configOptional = ConfigUtil.readConfig(configFilePathUsed);
- if (!configOptional.isPresent()) {
- logger.info("Creating new config file " + configFilePathUsed);
- }
- initializedConfig = configOptional.orElse(new Config());
- } catch (DataLoadingException e) {
- logger.warning("Config file at " + configFilePathUsed + " could not be loaded."
- + " Using default config properties.");
- initializedConfig = new Config();
- }
-
- //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) {
- logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
- }
- return initializedConfig;
- }
-
- /**
- * 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.
- */
- protected UserPrefs initPrefs(UserPrefsStorage storage) {
- Path prefsFilePath = storage.getUserPrefsFilePath();
- logger.info("Using preference file : " + prefsFilePath);
-
- UserPrefs initializedPrefs;
- try {
- Optional prefsOptional = storage.readUserPrefs();
- if (!prefsOptional.isPresent()) {
- logger.info("Creating new preference file " + prefsFilePath);
- }
- initializedPrefs = prefsOptional.orElse(new UserPrefs());
- } catch (DataLoadingException e) {
- logger.warning("Preference file at " + prefsFilePath + " could not be loaded."
- + " Using default preferences.");
- initializedPrefs = new UserPrefs();
- }
-
- //Update prefs file in case it was missing to begin with or there are new/unused fields
- try {
- storage.saveUserPrefs(initializedPrefs);
- } catch (IOException e) {
- logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
- }
-
- return initializedPrefs;
- }
-
- @Override
- public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
- ui.start(primaryStage);
- }
-
- @Override
- public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
- try {
- storage.saveUserPrefs(model.getUserPrefs());
- } catch (IOException e) {
- logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
- }
- }
-}
diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java
deleted file mode 100644
index 485f85a5e05..00000000000
--- a/src/main/java/seedu/address/commons/core/Config.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package seedu.address.commons.core;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Objects;
-import java.util.logging.Level;
-
-import seedu.address.commons.util.ToStringBuilder;
-
-/**
- * Config values used by the app
- */
-public class Config {
-
- public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json");
-
- // Config values customizable through config file
- private Level logLevel = Level.INFO;
- private Path userPrefsFilePath = Paths.get("preferences.json");
-
- public Level getLogLevel() {
- return logLevel;
- }
-
- public void setLogLevel(Level logLevel) {
- this.logLevel = logLevel;
- }
-
- public Path getUserPrefsFilePath() {
- return userPrefsFilePath;
- }
-
- public void setUserPrefsFilePath(Path userPrefsFilePath) {
- this.userPrefsFilePath = userPrefsFilePath;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Config)) {
- return false;
- }
-
- Config otherConfig = (Config) other;
- return Objects.equals(logLevel, otherConfig.logLevel)
- && Objects.equals(userPrefsFilePath, otherConfig.userPrefsFilePath);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(logLevel, userPrefsFilePath);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("logLevel", logLevel)
- .add("userPrefsFilePath", userPrefsFilePath)
- .toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java
deleted file mode 100644
index 491d24559b4..00000000000
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package seedu.address.commons.core;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-/**
- * Represents a version with major, minor and patch number
- */
-public class Version implements Comparable {
-
- public static final String VERSION_REGEX = "V(\\d+)\\.(\\d+)\\.(\\d+)(ea)?";
-
- private static final String EXCEPTION_STRING_NOT_VERSION = "String is not a valid Version. %s";
-
- private static final Pattern VERSION_PATTERN = Pattern.compile(VERSION_REGEX);
-
- private final int major;
- private final int minor;
- private final int patch;
- private final boolean isEarlyAccess;
-
- /**
- * Constructs a {@code Version} with the given version details.
- */
- public Version(int major, int minor, int patch, boolean isEarlyAccess) {
- this.major = major;
- this.minor = minor;
- this.patch = patch;
- this.isEarlyAccess = isEarlyAccess;
- }
-
- public int getMajor() {
- return major;
- }
-
- public int getMinor() {
- return minor;
- }
-
- public int getPatch() {
- return patch;
- }
-
- public boolean isEarlyAccess() {
- return isEarlyAccess;
- }
-
- /**
- * Parses a version number string in the format V1.2.3.
- * @param versionString version number string
- * @return a Version object
- */
- @JsonCreator
- public static Version fromString(String versionString) throws IllegalArgumentException {
- Matcher versionMatcher = VERSION_PATTERN.matcher(versionString);
-
- if (!versionMatcher.find()) {
- throw new IllegalArgumentException(String.format(EXCEPTION_STRING_NOT_VERSION, versionString));
- }
-
- return new Version(Integer.parseInt(versionMatcher.group(1)),
- Integer.parseInt(versionMatcher.group(2)),
- Integer.parseInt(versionMatcher.group(3)),
- versionMatcher.group(4) == null ? false : true);
- }
-
- @JsonValue
- public String toString() {
- return String.format("V%d.%d.%d%s", major, minor, patch, isEarlyAccess ? "ea" : "");
- }
-
- @Override
- public int compareTo(Version other) {
- if (major != other.major) {
- return major - other.major;
- }
- if (minor != other.minor) {
- return minor - other.minor;
- }
- if (patch != other.patch) {
- return patch - other.patch;
- }
- if (isEarlyAccess == other.isEarlyAccess()) {
- return 0;
- }
- if (isEarlyAccess) {
- return -1;
- }
- return 1;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Version)) {
- return false;
- }
-
- Version otherVersion = (Version) other;
- return major == otherVersion.major
- && minor == otherVersion.minor
- && patch == otherVersion.patch
- && isEarlyAccess == otherVersion.isEarlyAccess;
- }
-
- @Override
- public int hashCode() {
- String hash = String.format("%03d%03d%03d", major, minor, patch);
- if (!isEarlyAccess) {
- hash = "1" + hash;
- }
- return Integer.parseInt(hash);
- }
-}
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java
deleted file mode 100644
index b1e2767cdd9..00000000000
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package seedu.address.commons.util;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-/**
- * Writes and reads files
- */
-public class FileUtil {
-
- private static final String CHARSET = "UTF-8";
-
- public static boolean isFileExists(Path file) {
- return Files.exists(file) && Files.isRegularFile(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) {
- try {
- Paths.get(path);
- } catch (InvalidPathException ipe) {
- return false;
- }
- return true;
- }
-
- /**
- * 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 {
- if (!isFileExists(file)) {
- createFile(file);
- }
- }
-
- /**
- * Creates a file if it does not exist along with its missing parent directories.
- */
- public static void createFile(Path file) throws IOException {
- if (Files.exists(file)) {
- return;
- }
-
- createParentDirsOfFile(file);
-
- Files.createFile(file);
- }
-
- /**
- * Creates parent directories of file if it has a parent directory
- */
- public static void createParentDirsOfFile(Path file) throws IOException {
- Path parentDir = file.getParent();
-
- if (parentDir != null) {
- Files.createDirectories(parentDir);
- }
- }
-
- /**
- * Assumes file exists
- */
- public static String readFromFile(Path file) throws IOException {
- return new String(Files.readAllBytes(file), CHARSET);
- }
-
- /**
- * Writes given string to a file.
- * Will create the file if it does not exist yet.
- */
- public static void writeToFile(Path file, String content) throws IOException {
- Files.write(file, content.getBytes(CHARSET));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
deleted file mode 100644
index 92cd8fa605a..00000000000
--- a/src/main/java/seedu/address/logic/Logic.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package seedu.address.logic;
-
-import java.nio.file.Path;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-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.person.Person;
-
-/**
- * API of the Logic component
- */
-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.
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
-
- /**
- * Returns the AddressBook.
- *
- * @see seedu.address.model.Model#getAddressBook()
- */
- ReadOnlyAddressBook getAddressBook();
-
- /** Returns an unmodifiable view of the filtered list of persons */
- ObservableList getFilteredPersonList();
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Set the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
deleted file mode 100644
index 5aa3b91c7d0..00000000000
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package seedu.address.logic;
-
-import java.io.IOException;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.Path;
-import java.util.logging.Logger;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-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.exceptions.ParseException;
-import seedu.address.model.Model;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-import seedu.address.storage.Storage;
-
-/**
- * The main LogicManager of the app.
- */
-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.";
-
- private final Logger logger = LogsCenter.getLogger(LogicManager.class);
-
- private final Model model;
- private final Storage storage;
- private final AddressBookParser addressBookParser;
-
- /**
- * 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();
- }
-
- @Override
- public CommandResult execute(String commandText) throws CommandException, ParseException {
- logger.info("----------------[USER COMMAND][" + commandText + "]");
-
- CommandResult commandResult;
- Command command = addressBookParser.parseCommand(commandText);
- commandResult = command.execute(model);
-
- try {
- storage.saveAddressBook(model.getAddressBook());
- } catch (AccessDeniedException e) {
- throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
- } catch (IOException ioe) {
- throw new CommandException(String.format(FILE_OPS_ERROR_FORMAT, ioe.getMessage()), ioe);
- }
-
- return commandResult;
- }
-
- @Override
- public ReadOnlyAddressBook getAddressBook() {
- return model.getAddressBook();
- }
-
- @Override
- public ObservableList getFilteredPersonList() {
- return model.getFilteredPersonList();
- }
-
- @Override
- public Path getAddressBookFilePath() {
- return model.getAddressBookFilePath();
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- return model.getGuiSettings();
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- model.setGuiSettings(guiSettings);
- }
-}
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
deleted file mode 100644
index ecd32c31b53..00000000000
--- a/src/main/java/seedu/address/logic/Messages.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package seedu.address.logic;
-
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import seedu.address.logic.parser.Prefix;
-import seedu.address.model.person.Person;
-
-/**
- * Container for user visible messages.
- */
-public class Messages {
-
- public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
- public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
- public static final String MESSAGE_DUPLICATE_FIELDS =
- "Multiple values specified for the following single-valued field(s): ";
-
- /**
- * Returns an error message indicating the duplicate prefixes.
- */
- public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePrefixes) {
- assert duplicatePrefixes.length > 0;
-
- Set duplicateFields =
- Stream.of(duplicatePrefixes).map(Prefix::toString).collect(Collectors.toSet());
-
- return MESSAGE_DUPLICATE_FIELDS + String.join(" ", duplicateFields);
- }
-
- /**
- * Formats the {@code person} for display to the user.
- */
- public static String format(Person person) {
- final StringBuilder builder = new StringBuilder();
- builder.append(person.getName())
- .append("; Phone: ")
- .append(person.getPhone())
- .append("; Email: ")
- .append(person.getEmail())
- .append("; Address: ")
- .append(person.getAddress())
- .append("; Tags: ");
- person.getTags().forEach(builder::append);
- return builder.toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
deleted file mode 100644
index 5d7185a9680..00000000000
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Adds a person to the address book.
- */
-public class AddCommand extends Command {
-
- public static final String COMMAND_WORD = "add";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
- + "Parameters: "
- + PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " "
- + PREFIX_NAME + "John Doe "
- + PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
-
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
-
- private final Person toAdd;
-
- /**
- * Creates an AddCommand to add the specified {@code Person}
- */
- public AddCommand(Person person) {
- requireNonNull(person);
- toAdd = person;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
-
- if (model.hasPerson(toAdd)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.addPerson(toAdd);
- return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof AddCommand)) {
- return false;
- }
-
- AddCommand otherAddCommand = (AddCommand) other;
- return toAdd.equals(otherAddCommand.toAdd);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .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
deleted file mode 100644
index 9c86b1fa6e4..00000000000
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-
-/**
- * Clears the address book.
- */
-public class ClearCommand extends Command {
-
- public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.setAddressBook(new AddressBook());
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
deleted file mode 100644
index 1135ac19b74..00000000000
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Deletes a person identified using it's displayed index from the address book.
- */
-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";
-
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
-
- private final Index targetIndex;
-
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof DeleteCommand)) {
- return false;
- }
-
- DeleteCommand otherDeleteCommand = (DeleteCommand) other;
- return targetIndex.equals(otherDeleteCommand.targetIndex);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("targetIndex", targetIndex)
- .toString();
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
deleted file mode 100644
index 4b581c7331e..00000000000
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ /dev/null
@@ -1,242 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
-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.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Edits the details of an existing person in the address book.
- */
-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. "
- + "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "[" + PREFIX_NAME + "NAME] "
- + "[" + PREFIX_PHONE + "PHONE] "
- + "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
-
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
- public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
-
- private final Index index;
- private final EditPersonDescriptor editPersonDescriptor;
-
- /**
- * @param index of the person in the filtered person list to edit
- * @param editPersonDescriptor details to edit the person with
- */
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
- requireNonNull(editPersonDescriptor);
-
- this.index = index;
- 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);
- }
-
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
-
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
- }
-
- /**
- * Creates and returns a {@code Person} with the details of {@code personToEdit}
- * edited with {@code editPersonDescriptor}.
- */
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
- assert personToEdit != null;
-
- 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());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- EditCommand otherEditCommand = (EditCommand) other;
- return index.equals(otherEditCommand.index)
- && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("index", index)
- .add("editPersonDescriptor", editPersonDescriptor)
- .toString();
- }
-
- /**
- * Stores the details to edit the person with. Each non-empty field value will replace the
- * corresponding field value of the person.
- */
- public static class EditPersonDescriptor {
- private Name name;
- private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
-
- public EditPersonDescriptor() {}
-
- /**
- * Copy constructor.
- * A defensive copy of {@code tags} is used internally.
- */
- public EditPersonDescriptor(EditPersonDescriptor toCopy) {
- setName(toCopy.name);
- setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
- }
-
- /**
- * Returns true if at least one field is edited.
- */
- public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
- }
-
- public void setName(Name name) {
- this.name = name;
- }
-
- public Optional getName() {
- return Optional.ofNullable(name);
- }
-
- public void setPhone(Phone phone) {
- this.phone = phone;
- }
-
- public Optional getPhone() {
- return Optional.ofNullable(phone);
- }
-
- public void setEmail(Email email) {
- this.email = email;
- }
-
- public Optional getEmail() {
- return Optional.ofNullable(email);
- }
-
- public void setAddress(Address address) {
- this.address = address;
- }
-
- public Optional getAddress() {
- return Optional.ofNullable(address);
- }
-
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
- }
-
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditPersonDescriptor)) {
- return false;
- }
-
- EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other;
- return Objects.equals(name, otherEditPersonDescriptor.name)
- && Objects.equals(phone, otherEditPersonDescriptor.phone)
- && Objects.equals(email, otherEditPersonDescriptor.email)
- && Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("name", name)
- .add("phone", phone)
- .add("email", email)
- .add("address", address)
- .add("tags", tags)
- .toString();
- }
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
deleted file mode 100644
index 3dd85a8ba90..00000000000
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package seedu.address.logic.commands;
-
-import seedu.address.model.Model;
-
-/**
- * Terminates the program.
- */
-public class ExitCommand extends Command {
-
- public static final String COMMAND_WORD = "exit";
-
- public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
-
- @Override
- public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
deleted file mode 100644
index bf824f91bd0..00000000000
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.address.logic.commands;
-
-import seedu.address.model.Model;
-
-/**
- * Format full help instructions for every command for display.
- */
-public class HelpCommand extends Command {
-
- public static final String COMMAND_WORD = "help";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
- + "Example: " + COMMAND_WORD;
-
- public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
-
- @Override
- public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-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;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
deleted file mode 100644
index 4ff1a97ed77..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Set;
-import java.util.stream.Stream;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new AddCommand object
- */
-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);
-
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
- }
-
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
- Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
- Person person = new Person(name, phone, email, address, tagList);
-
- return new AddCommand(person);
- }
-
- /**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
- */
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
deleted file mode 100644
index 3149ee07e0b..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
-
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses user input.
- */
-public class AddressBookParser {
-
- /**
- * 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);
-
- /**
- * Parses user input into command for execution.
- *
- * @param userInput full user input string
- * @return the command based on the user input
- * @throws ParseException if the user input does not conform the expected format
- */
- public Command parseCommand(String userInput) throws ParseException {
- final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
- if (!matcher.matches()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
- }
-
- 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)
- // 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);
-
- switch (commandWord) {
-
- case AddCommand.COMMAND_WORD:
- return new AddCommandParser().parse(arguments);
-
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
-
- case DeleteCommand.COMMAND_WORD:
- return new DeleteCommandParser().parse(arguments);
-
- case ClearCommand.COMMAND_WORD:
- return new ClearCommand();
-
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
-
- case ListCommand.COMMAND_WORD:
- return new ListCommand();
-
- case ExitCommand.COMMAND_WORD:
- return new ExitCommand();
-
- case HelpCommand.COMMAND_WORD:
- return new HelpCommand();
-
- 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/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
deleted file mode 100644
index 3527fe76a3e..00000000000
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses input arguments and creates a new DeleteCommand object
- */
-public class DeleteCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the DeleteCommand
- * and returns a DeleteCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public 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);
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
deleted file mode 100644
index 46b3309a78b..00000000000
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ /dev/null
@@ -1,85 +0,0 @@
-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_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new EditCommand object
- */
-public class EditCommandParser implements Parser {
-
- /**
- * 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);
-
- Index index;
-
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
- }
-
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
-
- EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
-
- if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
- editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
- }
- if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
- editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
- }
- if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
- editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
- }
- if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
- editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
- }
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
-
- if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
- }
-
- return new EditCommand(index, editPersonDescriptor);
- }
-
- /**
- * 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 {
- assert tags != null;
-
- if (tags.isEmpty()) {
- return Optional.empty();
- }
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
deleted file mode 100644
index b117acb9c55..00000000000
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package seedu.address.logic.parser;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * 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);
- }
- return Index.fromOneBased(Integer.parseInt(trimmedIndex));
- }
-
- /**
- * Parses a {@code String name} into a {@code Name}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code name} is invalid.
- */
- public static Name parseName(String name) throws ParseException {
- requireNonNull(name);
- String trimmedName = name.trim();
- if (!Name.isValidName(trimmedName)) {
- throw new ParseException(Name.MESSAGE_CONSTRAINTS);
- }
- return new Name(trimmedName);
- }
-
- /**
- * Parses a {@code String phone} into a {@code Phone}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code phone} is invalid.
- */
- public static Phone parsePhone(String phone) throws ParseException {
- requireNonNull(phone);
- String trimmedPhone = phone.trim();
- if (!Phone.isValidPhone(trimmedPhone)) {
- throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
- }
- return new Phone(trimmedPhone);
- }
-
- /**
- * Parses a {@code String address} into an {@code Address}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code address} is invalid.
- */
- public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
- String trimmedAddress = address.trim();
- if (!Address.isValidAddress(trimmedAddress)) {
- throw new ParseException(Address.MESSAGE_CONSTRAINTS);
- }
- return new Address(trimmedAddress);
- }
-
- /**
- * Parses a {@code String email} into an {@code Email}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code email} is invalid.
- */
- public static Email parseEmail(String email) throws ParseException {
- requireNonNull(email);
- String trimmedEmail = email.trim();
- if (!Email.isValidEmail(trimmedEmail)) {
- throw new ParseException(Email.MESSAGE_CONSTRAINTS);
- }
- return new Email(trimmedEmail);
- }
-
- /**
- * Parses a {@code String tag} into a {@code Tag}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code tag} is invalid.
- */
- public static Tag parseTag(String tag) throws ParseException {
- requireNonNull(tag);
- String trimmedTag = tag.trim();
- if (!Tag.isValidTagName(trimmedTag)) {
- throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(trimmedTag);
- }
-
- /**
- * Parses {@code Collection tags} into a {@code Set}.
- */
- public static Set parseTags(Collection tags) throws ParseException {
- requireNonNull(tags);
- final Set tagSet = new HashSet<>();
- for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
- }
- return tagSet;
- }
-}
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
deleted file mode 100644
index d54df471c1f..00000000000
--- a/src/main/java/seedu/address/model/Model.java
+++ /dev/null
@@ -1,87 +0,0 @@
-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.Person;
-
-/**
- * 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}.
- */
- void setUserPrefs(ReadOnlyUserPrefs userPrefs);
-
- /**
- * Returns the user prefs.
- */
- ReadOnlyUserPrefs getUserPrefs();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Sets the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Sets the user prefs' address book file path.
- */
- void setAddressBookFilePath(Path addressBookFilePath);
-
- /**
- * Replaces address book data with the data in {@code addressBook}.
- */
- void setAddressBook(ReadOnlyAddressBook addressBook);
-
- /** Returns the AddressBook */
- ReadOnlyAddressBook getAddressBook();
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- boolean hasPerson(Person person);
-
- /**
- * Deletes the given person.
- * The person must exist in the address book.
- */
- void deletePerson(Person target);
-
- /**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
- */
- void addPerson(Person person);
-
- /**
- * 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.
- */
- void setPerson(Person target, Person editedPerson);
-
- /** Returns an unmodifiable view of the filtered person list */
- ObservableList getFilteredPersonList();
-
- /**
- * Updates the filter of the filtered person list to filter by the given {@code predicate}.
- * @throws NullPointerException if {@code predicate} is null.
- */
- void updateFilteredPersonList(Predicate predicate);
-}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
deleted file mode 100644
index 57bc563fde6..00000000000
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-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.model.person.Person;
-
-/**
- * Represents the in-memory model of the address book data.
- */
-public class ModelManager implements Model {
- private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
-
- private final AddressBook addressBook;
- private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
-
- /**
- * Initializes a ModelManager with the given addressBook and userPrefs.
- */
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
-
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
-
- this.addressBook = new AddressBook(addressBook);
- this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
- }
-
- public ModelManager() {
- this(new AddressBook(), new UserPrefs());
- }
-
- //=========== UserPrefs ==================================================================================
-
- @Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
- requireNonNull(userPrefs);
- this.userPrefs.resetData(userPrefs);
- }
-
- @Override
- public ReadOnlyUserPrefs getUserPrefs() {
- return userPrefs;
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- return userPrefs.getGuiSettings();
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- requireNonNull(guiSettings);
- userPrefs.setGuiSettings(guiSettings);
- }
-
- @Override
- public Path getAddressBookFilePath() {
- return userPrefs.getAddressBookFilePath();
- }
-
- @Override
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- userPrefs.setAddressBookFilePath(addressBookFilePath);
- }
-
- //=========== AddressBook ================================================================================
-
- @Override
- public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
- }
-
- @Override
- public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
- }
-
- @Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
- }
-
- @Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
- }
-
- @Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- }
-
- @Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- addressBook.setPerson(target, editedPerson);
- }
-
- //=========== Filtered Person List Accessors =============================================================
-
- /**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
- * {@code versionedAddressBook}
- */
- @Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
- }
-
- @Override
- public void updateFilteredPersonList(Predicate predicate) {
- requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof ModelManager)) {
- return false;
- }
-
- ModelManager otherModelManager = (ModelManager) other;
- return addressBook.equals(otherModelManager.addressBook)
- && userPrefs.equals(otherModelManager.userPrefs)
- && filteredPersons.equals(otherModelManager.filteredPersons);
- }
-
-}
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/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
deleted file mode 100644
index befd58a4c73..00000000000
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package seedu.address.model;
-
-import java.nio.file.Path;
-
-import seedu.address.commons.core.GuiSettings;
-
-/**
- * Unmodifiable view of user prefs.
- */
-public interface ReadOnlyUserPrefs {
-
- GuiSettings getGuiSettings();
-
- Path getAddressBookFilePath();
-
-}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
deleted file mode 100644
index 6be655fb4c7..00000000000
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Objects;
-
-import seedu.address.commons.core.GuiSettings;
-
-/**
- * Represents User's preferences.
- */
-public class UserPrefs implements ReadOnlyUserPrefs {
-
- private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
-
- /**
- * Creates a {@code UserPrefs} with default values.
- */
- public UserPrefs() {}
-
- /**
- * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}.
- */
- public UserPrefs(ReadOnlyUserPrefs userPrefs) {
- this();
- resetData(userPrefs);
- }
-
- /**
- * Resets the existing data of this {@code UserPrefs} with {@code newUserPrefs}.
- */
- public void resetData(ReadOnlyUserPrefs newUserPrefs) {
- requireNonNull(newUserPrefs);
- setGuiSettings(newUserPrefs.getGuiSettings());
- setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
- }
-
- public GuiSettings getGuiSettings() {
- return guiSettings;
- }
-
- public void setGuiSettings(GuiSettings guiSettings) {
- requireNonNull(guiSettings);
- this.guiSettings = guiSettings;
- }
-
- public Path getAddressBookFilePath() {
- return addressBookFilePath;
- }
-
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- this.addressBookFilePath = addressBookFilePath;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof UserPrefs)) {
- return false;
- }
-
- UserPrefs otherUserPrefs = (UserPrefs) other;
- return guiSettings.equals(otherUserPrefs.guiSettings)
- && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
- return sb.toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
deleted file mode 100644
index 469a2cc9a1e..00000000000
--- a/src/main/java/seedu/address/model/person/Address.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's address in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
- */
-public class Address {
-
- public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank";
-
- /*
- * The first character of the address must not be a whitespace,
- * otherwise " " (a blank string) becomes a valid input.
- */
- public static final String VALIDATION_REGEX = "[^\\s].*";
-
- public final String value;
-
- /**
- * Constructs an {@code Address}.
- *
- * @param address A valid address.
- */
- public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
- }
-
- /**
- * Returns true if a given string is a valid email.
- */
- public static boolean isValidAddress(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Address)) {
- return false;
- }
-
- Address otherAddress = (Address) other;
- return value.equals(otherAddress.value);
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
deleted file mode 100644
index c62e512bc29..00000000000
--- a/src/main/java/seedu/address/model/person/Email.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's email in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
- */
-public class Email {
-
- private static final String SPECIAL_CHARACTERS = "+_.-";
- public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain "
- + "and adhere to the following constraints:\n"
- + "1. The local-part should only contain alphanumeric characters and these special characters, excluding "
- + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special "
- + "characters.\n"
- + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels "
- + "separated by periods.\n"
- + "The domain name must:\n"
- + " - end with a domain label at least 2 characters long\n"
- + " - have each domain label start and end with alphanumeric characters\n"
- + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
- // alphanumeric and special characters
- private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore
- private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
- + ALPHANUMERIC_NO_UNDERSCORE + ")*";
- private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE
- + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*";
- private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars
- private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX;
- public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
-
- public final String value;
-
- /**
- * Constructs an {@code Email}.
- *
- * @param email A valid email address.
- */
- public Email(String email) {
- requireNonNull(email);
- checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
- value = email;
- }
-
- /**
- * Returns if a given string is a valid email.
- */
- public static boolean isValidEmail(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Email)) {
- return false;
- }
-
- Email otherEmail = (Email) other;
- return value.equals(otherEmail.value);
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
deleted file mode 100644
index 173f15b9b00..00000000000
--- a/src/main/java/seedu/address/model/person/Name.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's name in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
- */
-public class Name {
-
- public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
-
- /*
- * The first character of the address must not be a whitespace,
- * otherwise " " (a blank string) becomes a valid input.
- */
- public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
-
- public final String fullName;
-
- /**
- * Constructs a {@code Name}.
- *
- * @param name A valid name.
- */
- public Name(String name) {
- requireNonNull(name);
- checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
- fullName = name;
- }
-
- /**
- * Returns true if a given string is a valid name.
- */
- public static boolean isValidName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
-
- @Override
- public String toString() {
- return fullName;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Name)) {
- return false;
- }
-
- Name otherName = (Name) other;
- return fullName.equals(otherName.fullName);
- }
-
- @Override
- public int hashCode() {
- return fullName.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
deleted file mode 100644
index abe8c46b535..00000000000
--- a/src/main/java/seedu/address/model/person/Person.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package seedu.address.model.person;
-
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Collections;
-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 Person in the address book.
- * Guarantees: details are present and not null, field values are validated, immutable.
- */
-public class Person {
-
- // Identity fields
- private final Name name;
- private final Phone phone;
- private final Email email;
-
- // Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
-
- /**
- * Every field must be present and not null.
- */
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- this.tags.addAll(tags);
- }
-
- public Name getName() {
- return name;
- }
-
- public Phone getPhone() {
- return phone;
- }
-
- public Email getEmail() {
- return email;
- }
-
- public Address getAddress() {
- return address;
- }
-
- /**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- */
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
- }
-
- /**
- * Returns true if both persons have the same name.
- * This defines a weaker notion of equality between two persons.
- */
- public boolean isSamePerson(Person otherPerson) {
- if (otherPerson == this) {
- return true;
- }
-
- return otherPerson != null
- && otherPerson.getName().equals(getName());
- }
-
- /**
- * Returns true if both persons have the same identity and data fields.
- * This defines a stronger notion of equality between two persons.
- */
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Person)) {
- return false;
- }
-
- Person otherPerson = (Person) other;
- return name.equals(otherPerson.name)
- && phone.equals(otherPerson.phone)
- && email.equals(otherPerson.email)
- && address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
- }
-
- @Override
- public int hashCode() {
- // use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("name", name)
- .add("phone", phone)
- .add("email", email)
- .add("address", address)
- .add("tags", tags)
- .toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
deleted file mode 100644
index d733f63d739..00000000000
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's phone number in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
- */
-public class Phone {
-
-
- public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
- public final String value;
-
- /**
- * Constructs a {@code Phone}.
- *
- * @param phone A valid phone number.
- */
- public Phone(String phone) {
- requireNonNull(phone);
- checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
- value = phone;
- }
-
- /**
- * Returns true if a given string is a valid phone number.
- */
- public static boolean isValidPhone(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Phone)) {
- return false;
- }
-
- Phone otherPhone = (Phone) other;
- return value.equals(otherPhone.value);
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
deleted file mode 100644
index cc0a68d79f9..00000000000
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Iterator;
-import java.util.List;
-
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
-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.
- *
- * Supports a minimal set of list operations.
- *
- * @see Person#isSamePerson(Person)
- */
-public class UniquePersonList implements Iterable {
-
- private final ObservableList internalList = FXCollections.observableArrayList();
- private final ObservableList internalUnmodifiableList =
- FXCollections.unmodifiableObservableList(internalList);
-
- /**
- * Returns true if the list contains an equivalent person as the given argument.
- */
- public boolean contains(Person toCheck) {
- requireNonNull(toCheck);
- return internalList.stream().anyMatch(toCheck::isSamePerson);
- }
-
- /**
- * Adds a person to the list.
- * The person must not already exist in the list.
- */
- public void add(Person toAdd) {
- requireNonNull(toAdd);
- if (contains(toAdd)) {
- throw new DuplicatePersonException();
- }
- internalList.add(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.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- int index = internalList.indexOf(target);
- if (index == -1) {
- throw new PersonNotFoundException();
- }
-
- if (!target.isSamePerson(editedPerson) && contains(editedPerson)) {
- throw new DuplicatePersonException();
- }
-
- internalList.set(index, editedPerson);
- }
-
- /**
- * Removes the equivalent person from the list.
- * The person must exist in the list.
- */
- public void remove(Person toRemove) {
- requireNonNull(toRemove);
- if (!internalList.remove(toRemove)) {
- throw new PersonNotFoundException();
- }
- }
-
- public void setPersons(UniquePersonList replacement) {
- requireNonNull(replacement);
- internalList.setAll(replacement.internalList);
- }
-
- /**
- * Replaces the contents of this list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- requireAllNonNull(persons);
- if (!personsAreUnique(persons)) {
- throw new DuplicatePersonException();
- }
-
- internalList.setAll(persons);
- }
-
- /**
- * Returns the backing list as an unmodifiable {@code ObservableList}.
- */
- public ObservableList asUnmodifiableObservableList() {
- return internalUnmodifiableList;
- }
-
- @Override
- public Iterator iterator() {
- return internalList.iterator();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof UniquePersonList)) {
- return false;
- }
-
- UniquePersonList otherUniquePersonList = (UniquePersonList) other;
- return internalList.equals(otherUniquePersonList.internalList);
- }
-
- @Override
- public int hashCode() {
- return internalList.hashCode();
- }
-
- @Override
- public String toString() {
- return internalList.toString();
- }
-
- /**
- * Returns true if {@code persons} contains only unique persons.
- */
- private boolean personsAreUnique(List persons) {
- for (int i = 0; i < persons.size() - 1; i++) {
- for (int j = i + 1; j < persons.size(); j++) {
- if (persons.get(i).isSamePerson(persons.get(j))) {
- return false;
- }
- }
- }
- return true;
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
deleted file mode 100644
index d7290f59442..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
- * identity).
- */
-public class DuplicatePersonException extends RuntimeException {
- public DuplicatePersonException() {
- super("Operation would result in duplicate persons");
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
deleted file mode 100644
index fa764426ca7..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation is unable to find the specified person.
- */
-public class PersonNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
deleted file mode 100644
index f1a0d4e233b..00000000000
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package seedu.address.model.tag;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Tag in the address book.
- * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
- */
-public class Tag {
-
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
-
- public final String tagName;
-
- /**
- * Constructs a {@code Tag}.
- *
- * @param tagName A valid tag name.
- */
- public Tag(String tagName) {
- requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
- this.tagName = tagName;
- }
-
- /**
- * Returns true if a given string is a valid tag name.
- */
- public static boolean isValidTagName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Tag)) {
- return false;
- }
-
- Tag otherTag = (Tag) other;
- return tagName.equals(otherTag.tagName);
- }
-
- @Override
- public int hashCode() {
- return tagName.hashCode();
- }
-
- /**
- * Format state as text for viewing.
- */
- public String toString() {
- return '[' + tagName + ']';
- }
-
-}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
deleted file mode 100644
index 1806da4facf..00000000000
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.model.util;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Contains utility methods for populating {@code AddressBook} with sample data.
- */
-public class SampleDataUtil {
- public static Person[] getSamplePersons() {
- return new Person[] {
- new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
- new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
- new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
- new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
- new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
- };
- }
-
- public static ReadOnlyAddressBook getSampleAddressBook() {
- AddressBook sampleAb = new AddressBook();
- for (Person samplePerson : getSamplePersons()) {
- sampleAb.addPerson(samplePerson);
- }
- return sampleAb;
- }
-
- /**
- * Returns a tag set containing the list of strings given.
- */
- public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
- .map(Tag::new)
- .collect(Collectors.toSet());
- }
-
-}
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/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
deleted file mode 100644
index bd1ca0f56c8..00000000000
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package seedu.address.storage;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Jackson-friendly version of {@link Person}.
- */
-class JsonAdaptedPerson {
-
- public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
-
- private final String name;
- private final String phone;
- private final String email;
- private final String address;
- private final List tags = new ArrayList<>();
-
- /**
- * Constructs a {@code JsonAdaptedPerson} with the given person details.
- */
- @JsonCreator
- public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- if (tags != null) {
- this.tags.addAll(tags);
- }
- }
-
- /**
- * Converts a given {@code Person} into this class for Jackson use.
- */
- public JsonAdaptedPerson(Person source) {
- name = source.getName().fullName;
- phone = source.getPhone().value;
- email = source.getEmail().value;
- address = source.getAddress().value;
- tags.addAll(source.getTags().stream()
- .map(JsonAdaptedTag::new)
- .collect(Collectors.toList()));
- }
-
- /**
- * Converts this Jackson-friendly adapted person object into the model's {@code Person} object.
- *
- * @throws IllegalValueException if there were any data constraints violated in the adapted person.
- */
- public Person toModelType() throws IllegalValueException {
- final List personTags = new ArrayList<>();
- for (JsonAdaptedTag tag : tags) {
- personTags.add(tag.toModelType());
- }
-
- 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 Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
deleted file mode 100644
index 0df22bdb754..00000000000
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package seedu.address.storage;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Jackson-friendly version of {@link Tag}.
- */
-class JsonAdaptedTag {
-
- private final String tagName;
-
- /**
- * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}.
- */
- @JsonCreator
- public JsonAdaptedTag(String tagName) {
- this.tagName = tagName;
- }
-
- /**
- * Converts a given {@code Tag} into this class for Jackson use.
- */
- public JsonAdaptedTag(Tag source) {
- tagName = source.tagName;
- }
-
- @JsonValue
- public String getTagName() {
- return tagName;
- }
-
- /**
- * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object.
- *
- * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
- */
- public Tag toModelType() throws IllegalValueException {
- if (!Tag.isValidTagName(tagName)) {
- throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(tagName);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
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/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/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
deleted file mode 100644
index 48a9754807d..00000000000
--- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
+++ /dev/null
@@ -1,47 +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.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * A class to access UserPrefs stored in the hard disk as a json file
- */
-public class JsonUserPrefsStorage implements UserPrefsStorage {
-
- private Path filePath;
-
- public JsonUserPrefsStorage(Path filePath) {
- this.filePath = filePath;
- }
-
- @Override
- public Path getUserPrefsFilePath() {
- return filePath;
- }
-
- @Override
- public Optional readUserPrefs() throws DataLoadingException {
- return readUserPrefs(filePath);
- }
-
- /**
- * Similar to {@link #readUserPrefs()}
- * @param prefsFilePath location of the data. Cannot be null.
- * @throws DataLoadingException if the file format is not as expected.
- */
- public Optional readUserPrefs(Path prefsFilePath) throws DataLoadingException {
- return JsonUtil.readJsonFile(prefsFilePath, UserPrefs.class);
- }
-
- @Override
- public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
- JsonUtil.saveJsonFile(userPrefs, filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
deleted file mode 100644
index 9fba0c7a1d6..00000000000
--- a/src/main/java/seedu/address/storage/Storage.java
+++ /dev/null
@@ -1,32 +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;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * API of the Storage component
- */
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
-
- @Override
- Optional readUserPrefs() throws DataLoadingException;
-
- @Override
- void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
-
- @Override
- Path getAddressBookFilePath();
-
- @Override
- Optional readAddressBook() throws DataLoadingException;
-
- @Override
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
deleted file mode 100644
index 8b84a9024d5..00000000000
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package seedu.address.storage;
-
-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.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * Manages storage of AddressBook data in local storage.
- */
-public class StorageManager implements Storage {
-
- private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
- private AddressBookStorage addressBookStorage;
- private UserPrefsStorage userPrefsStorage;
-
- /**
- * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
- */
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
- this.addressBookStorage = addressBookStorage;
- this.userPrefsStorage = userPrefsStorage;
- }
-
- // ================ UserPrefs methods ==============================
-
- @Override
- public Path getUserPrefsFilePath() {
- return userPrefsStorage.getUserPrefsFilePath();
- }
-
- @Override
- public Optional readUserPrefs() throws DataLoadingException {
- return userPrefsStorage.readUserPrefs();
- }
-
- @Override
- public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
- userPrefsStorage.saveUserPrefs(userPrefs);
- }
-
-
- // ================ AddressBook methods ==============================
-
- @Override
- public Path getAddressBookFilePath() {
- return addressBookStorage.getAddressBookFilePath();
- }
-
- @Override
- public Optional readAddressBook() throws DataLoadingException {
- return readAddressBook(addressBookStorage.getAddressBookFilePath());
- }
-
- @Override
- public Optional readAddressBook(Path filePath) throws DataLoadingException {
- logger.fine("Attempting to read data from file: " + filePath);
- return addressBookStorage.readAddressBook(filePath);
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath());
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- logger.fine("Attempting to write to data file: " + filePath);
- addressBookStorage.saveAddressBook(addressBook, filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java
deleted file mode 100644
index e94ca422ea8..00000000000
--- a/src/main/java/seedu/address/storage/UserPrefsStorage.java
+++ /dev/null
@@ -1,36 +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.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * Represents a storage for {@link seedu.address.model.UserPrefs}.
- */
-public interface UserPrefsStorage {
-
- /**
- * Returns the file path of the UserPrefs data file.
- */
- Path getUserPrefsFilePath();
-
- /**
- * Returns UserPrefs data from storage.
- * Returns {@code Optional.empty()} if storage file is not found.
- *
- * @throws DataLoadingException if the loading of data from preference file failed.
- */
- Optional readUserPrefs() throws DataLoadingException;
-
- /**
- * 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.
- */
- void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
deleted file mode 100644
index 9e75478664b..00000000000
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package seedu.address.ui;
-
-import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
-import javafx.scene.control.TextField;
-import javafx.scene.layout.Region;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * The UI component that is responsible for receiving user command inputs.
- */
-public class CommandBox extends UiPart {
-
- public static final String ERROR_STYLE_CLASS = "error";
- private static final String FXML = "CommandBox.fxml";
-
- private final CommandExecutor commandExecutor;
-
- @FXML
- private TextField commandTextField;
-
- /**
- * Creates a {@code CommandBox} with the given {@code CommandExecutor}.
- */
- 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());
- }
-
- /**
- * Handles the Enter button pressed event.
- */
- @FXML
- private void handleCommandEntered() {
- String commandText = commandTextField.getText();
- if (commandText.equals("")) {
- return;
- }
-
- try {
- commandExecutor.execute(commandText);
- commandTextField.setText("");
- } catch (CommandException | ParseException e) {
- setStyleToIndicateCommandFailure();
- }
- }
-
- /**
- * Sets the command box style to use the default style.
- */
- private void setStyleToDefault() {
- commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
- }
-
- /**
- * Sets the command box style to indicate a failed command.
- */
- private void setStyleToIndicateCommandFailure() {
- ObservableList styleClass = commandTextField.getStyleClass();
-
- if (styleClass.contains(ERROR_STYLE_CLASS)) {
- return;
- }
-
- styleClass.add(ERROR_STYLE_CLASS);
- }
-
- /**
- * Represents a function that can execute commands.
- */
- @FunctionalInterface
- public interface CommandExecutor {
- /**
- * Executes the command and returns the result.
- *
- * @see seedu.address.logic.Logic#execute(String)
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
- }
-
-}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
deleted file mode 100644
index 094c42cda82..00000000000
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package seedu.address.ui;
-
-import java.util.Comparator;
-
-import javafx.fxml.FXML;
-import javafx.scene.control.Label;
-import javafx.scene.layout.FlowPane;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Region;
-import seedu.address.model.person.Person;
-
-/**
- * An UI component that displays information of a {@code Person}.
- */
-public class PersonCard extends UiPart {
-
- private static final String FXML = "PersonListCard.fxml";
-
- /**
- * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
- * As a consequence, UI elements' variable names cannot be set to such keywords
- * or an exception will be thrown by JavaFX during runtime.
- *
- * @see The issue on AddressBook level 4
- */
-
- public final Person person;
-
- @FXML
- private HBox cardPane;
- @FXML
- private Label name;
- @FXML
- private Label id;
- @FXML
- private Label phone;
- @FXML
- private Label address;
- @FXML
- private Label email;
- @FXML
- private FlowPane tags;
-
- /**
- * Creates a {@code PersonCode} with the given {@code Person} and index to display.
- */
- public PersonCard(Person person, int displayedIndex) {
- super(FXML);
- this.person = person;
- id.setText(displayedIndex + ". ");
- name.setText(person.getName().fullName);
- phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
- email.setText(person.getEmail().value);
- person.getTags().stream()
- .sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
- }
-}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
deleted file mode 100644
index f4c501a897b..00000000000
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package seedu.address.ui;
-
-import java.util.logging.Logger;
-
-import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.scene.layout.Region;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
-
-/**
- * Panel containing the list of persons.
- */
-public class PersonListPanel extends UiPart {
- private static final String FXML = "PersonListPanel.fxml";
- private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
-
- @FXML
- private ListView personListView;
-
- /**
- * Creates a {@code PersonListPanel} with the given {@code ObservableList}.
- */
- public PersonListPanel(ObservableList personList) {
- super(FXML);
- personListView.setItems(personList);
- personListView.setCellFactory(listView -> new PersonListViewCell());
- }
-
- /**
- * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
- */
- class PersonListViewCell extends ListCell {
- @Override
- protected void updateItem(Person person, boolean empty) {
- super.updateItem(person, empty);
-
- if (empty || person == null) {
- setGraphic(null);
- setText(null);
- } else {
- setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
- }
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/address/ui/Ui.java
deleted file mode 100644
index 17aa0b494fe..00000000000
--- a/src/main/java/seedu/address/ui/Ui.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package seedu.address.ui;
-
-import javafx.stage.Stage;
-
-/**
- * API of UI component
- */
-public interface Ui {
-
- /** Starts the UI (and the App). */
- void start(Stage primaryStage);
-
-}
diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/swe/context/AppParameters.java
similarity index 92%
rename from src/main/java/seedu/address/AppParameters.java
rename to src/main/java/swe/context/AppParameters.java
index 3d603622d4e..82114390633 100644
--- a/src/main/java/seedu/address/AppParameters.java
+++ b/src/main/java/swe/context/AppParameters.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package swe.context;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -7,9 +7,9 @@
import java.util.logging.Logger;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.FileUtil;
-import seedu.address.commons.util.ToStringBuilder;
+import swe.context.commons.core.LogsCenter;
+import swe.context.commons.util.FileUtil;
+import swe.context.commons.util.ToStringBuilder;
/**
* Represents the parsed command-line parameters given to the application.
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/swe/context/Main.java
similarity index 96%
rename from src/main/java/seedu/address/Main.java
rename to src/main/java/swe/context/Main.java
index ec1b7958746..25bedaf5b37 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/swe/context/Main.java
@@ -1,9 +1,9 @@
-package seedu.address;
+package swe.context;
import java.util.logging.Logger;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
+import swe.context.commons.core.LogsCenter;
/**
* The main entry point to the application.
diff --git a/src/main/java/swe/context/MainApp.java b/src/main/java/swe/context/MainApp.java
new file mode 100644
index 00000000000..1624a2265fb
--- /dev/null
+++ b/src/main/java/swe/context/MainApp.java
@@ -0,0 +1,208 @@
+package swe.context;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import javafx.application.Application;
+import javafx.stage.Stage;
+import swe.context.annotation.Nullable;
+import swe.context.commons.core.Config;
+import swe.context.commons.core.LogsCenter;
+import swe.context.commons.exceptions.DataLoadingException;
+import swe.context.commons.util.ConfigUtil;
+import swe.context.commons.util.StringUtil;
+import swe.context.logic.Logic;
+import swe.context.logic.LogicManager;
+import swe.context.model.Contacts;
+import swe.context.model.Model;
+import swe.context.model.ModelManager;
+import swe.context.model.ReadOnlyContacts;
+import swe.context.model.ReadOnlySettings;
+import swe.context.model.Settings;
+import swe.context.model.util.SampleContactsUtil;
+import swe.context.storage.ContactsStorage;
+import swe.context.storage.JsonContactsStorage;
+import swe.context.storage.JsonSettingsStorage;
+import swe.context.storage.SettingsStorage;
+import swe.context.storage.Storage;
+import swe.context.storage.StorageManager;
+import swe.context.ui.Ui;
+import swe.context.ui.UiManager;
+
+
+
+/**
+ * Runs as a JavaFX application.
+ */
+public class MainApp extends Application {
+ public static final String NAME = "ConText";
+
+ private static final Logger logger = LogsCenter.getLogger(MainApp.class);
+
+ private Config config;
+
+ private Ui ui;
+ private Logic logic;
+ private Model model;
+ private Storage storage;
+
+ @Override
+ public void init() throws Exception {
+ super.init();
+
+ AppParameters appParameters = AppParameters.parse(this.getParameters());
+ config = this.initConfig(appParameters.getConfigPath());
+
+ this.initLogging(config);
+
+ SettingsStorage settingsStorage = new JsonSettingsStorage(config.getSettingsPath());
+ ReadOnlySettings settings = this.initSettings(settingsStorage);
+
+ ContactsStorage contactsStorage = new JsonContactsStorage(settings.getContactsPath());
+ storage = new StorageManager(contactsStorage, settingsStorage);
+
+ model = this.initModel(storage, settings);
+
+ logic = new LogicManager(model, storage);
+
+ ui = new UiManager(logic);
+ }
+
+ @Override
+ public void start(Stage primaryStage) {
+ ui.start(primaryStage);
+ }
+
+ @Override
+ public void stop() {
+ try {
+ storage.saveSettings(model.getSettings());
+ } catch (IOException e) {
+ logger.severe(String.format(
+ "Failed to save settings: %s",
+ StringUtil.getDetails(e)
+ ));
+ }
+ }
+
+ /**
+ * Returns a {@code Config} using the file at {@code configFilePath}.
+ * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead
+ * if {@code configFilePath} is null.
+ */
+ private Config initConfig(@Nullable Path configFilePath) {
+ Config initializedConfig;
+ Path configFilePathUsed;
+
+ configFilePathUsed = Config.DEFAULT_PATH;
+
+ if (configFilePath != null) {
+ logger.info("Custom Config file specified " + configFilePath);
+ configFilePathUsed = configFilePath;
+ }
+
+ logger.info("Using config file : " + configFilePathUsed);
+
+ try {
+ Optional configOptional = ConfigUtil.readConfig(configFilePathUsed);
+ if (!configOptional.isPresent()) {
+ logger.info("Creating new config file " + configFilePathUsed);
+ }
+ initializedConfig = configOptional.orElse(new Config());
+ } catch (DataLoadingException e) {
+ logger.warning("Config file at " + configFilePathUsed + " could not be loaded."
+ + " Using default config properties.");
+ initializedConfig = new Config();
+ }
+
+ // 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) {
+ logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
+ }
+ return initializedConfig;
+ }
+
+ private void initLogging(Config config) {
+ LogsCenter.init(config);
+ }
+
+ /**
+ * Returns {@link Settings} from reading via the specified
+ * {@link SettingsStorage}.
+ *
+ * If no file is found for the {@link SettingsStorage} or an error occurs
+ * while trying to read the file, default settings will be populated.
+ *
+ * This always resaves the updated file.
+ */
+ private Settings initSettings(SettingsStorage settingsStorage) {
+ Path settingsPath = settingsStorage.getSettingsPath();
+ logger.info(String.format(
+ "Settings storage path: %s",
+ settingsPath
+ ));
+
+ @Nullable Settings settings = null;
+ try {
+ Optional settingsOptional = settingsStorage.readSettings();
+ if (settingsOptional.isPresent()) {
+ settings = settingsOptional.get();
+ } else {
+ logger.info("No settings file found, populating with default settings.");
+ settings = new Settings();
+ }
+ } catch (DataLoadingException e) {
+ logger.warning("Failed to read settings file, populating with default settings.");
+ settings = new Settings();
+ }
+
+ try {
+ settingsStorage.saveSettings(settings);
+ } catch (IOException e) {
+ logger.severe(String.format(
+ "Failed to save settings: %s",
+ StringUtil.getDetails(e)
+ ));
+ }
+
+ return settings;
+ }
+
+ /**
+ * Returns a {@link Model} with the {@link ReadOnlyContacts} from reading via the
+ * specified {@link Storage}, and with the specified
+ * {@link ReadOnlySettings}.
+ *
+ * If no contacts file is found for the {@link Storage}, sample contacts
+ * will be populated.
+ *
+ * If an error occurs while trying to read the file, no contacts will be
+ * populated.
+ */
+ private Model initModel(Storage storage, ReadOnlySettings settings) {
+ logger.info(String.format(
+ "Storage contacts path: %s",
+ storage.getContactsPath()
+ ));
+
+ @Nullable ReadOnlyContacts contacts = null;
+ try {
+ Optional extends ReadOnlyContacts> contactsOptional = storage.readContacts();
+ if (contactsOptional.isPresent()) {
+ contacts = contactsOptional.get();
+ } else {
+ logger.info("No contacts file found, populating with sample contacts.");
+ contacts = SampleContactsUtil.getSampleContacts();
+ }
+ } catch (DataLoadingException e) {
+ logger.warning("Failed to read contacts file.");
+ contacts = new Contacts();
+ }
+
+ return new ModelManager(contacts, settings);
+ }
+}
diff --git a/src/main/java/swe/context/annotation/Nullable.java b/src/main/java/swe/context/annotation/Nullable.java
new file mode 100644
index 00000000000..d909b437af7
--- /dev/null
+++ b/src/main/java/swe/context/annotation/Nullable.java
@@ -0,0 +1,9 @@
+package swe.context.annotation;
+
+
+
+/**
+ * Explicitly marks types as potentially being null, meaning appropriate null
+ * checking is in order.
+ */
+public @interface Nullable {}
diff --git a/src/main/java/swe/context/commons/core/Config.java b/src/main/java/swe/context/commons/core/Config.java
new file mode 100644
index 00000000000..bedf86d3065
--- /dev/null
+++ b/src/main/java/swe/context/commons/core/Config.java
@@ -0,0 +1,65 @@
+package swe.context.commons.core;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.logging.Level;
+
+import swe.context.commons.util.ToStringBuilder;
+
+
+
+/**
+ * Configuration for the app, read from a file.
+ */
+public class Config {
+ public static final Path DEFAULT_PATH = Paths.get("config.json");
+
+ private Path settingsPath = Paths.get("settings.json");
+ private Level logLevel = Level.INFO;
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("settingsPath", this.settingsPath)
+ .add("logLevel", this.logLevel)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Config)) {
+ return false;
+ }
+ Config otherConfig = (Config) other;
+
+ return Objects.equals(this.settingsPath, otherConfig.settingsPath)
+ && Objects.equals(this.logLevel, otherConfig.logLevel);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.logLevel, this.settingsPath);
+ }
+
+ public Path getSettingsPath() {
+ return this.settingsPath;
+ }
+
+ public void setSettingsPath(Path settingsPath) {
+ this.settingsPath = settingsPath;
+ }
+
+ public Level getLogLevel() {
+ return this.logLevel;
+ }
+
+ public void setLogLevel(Level logLevel) {
+ this.logLevel = logLevel;
+ }
+}
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/swe/context/commons/core/GuiSettings.java
similarity index 84%
rename from src/main/java/seedu/address/commons/core/GuiSettings.java
rename to src/main/java/swe/context/commons/core/GuiSettings.java
index a97a86ee8d7..4d43946c24f 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/swe/context/commons/core/GuiSettings.java
@@ -1,19 +1,18 @@
-package seedu.address.commons.core;
+package swe.context.commons.core;
import java.awt.Point;
-import java.io.Serializable;
import java.util.Objects;
-import seedu.address.commons.util.ToStringBuilder;
+import swe.context.commons.util.ToStringBuilder;
+
+
/**
- * A Serializable class that contains the GUI settings.
- * Guarantees: immutable.
+ * Immutably represents GUI settings.
*/
-public class GuiSettings implements Serializable {
-
- private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+public class GuiSettings {
+ private static final double DEFAULT_HEIGHT = 650;
+ private static final double DEFAULT_WIDTH = 1250;
private final double windowWidth;
private final double windowHeight;
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/swe/context/commons/core/LogsCenter.java
similarity index 93%
rename from src/main/java/seedu/address/commons/core/LogsCenter.java
rename to src/main/java/swe/context/commons/core/LogsCenter.java
index 8cf8e15a0f0..322bedcedb8 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/swe/context/commons/core/LogsCenter.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package swe.context.commons.core;
import static java.util.Objects.requireNonNull;
@@ -10,6 +10,10 @@
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
+import swe.context.MainApp;
+
+
+
/**
* Configures and manages loggers and handlers, including their logging level
* Named {@link Logger}s can be obtained from this class
@@ -20,7 +24,7 @@
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 = String.format("%s.log", MainApp.NAME);
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 +79,11 @@ private static void removeHandlers(Logger logger) {
}
/**
- * Creates a logger named 'ab3', containing a {@code ConsoleHandler} and a {@code FileHandler}.
+ * Creates a base logger for the app, 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(MainApp.NAME);
baseLogger.setUseParentHandlers(false);
removeHandlers(baseLogger);
@@ -102,5 +106,4 @@ private static void setBaseLogger() {
}
}
-
}
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/swe/context/commons/core/index/Index.java
similarity index 95%
rename from src/main/java/seedu/address/commons/core/index/Index.java
rename to src/main/java/swe/context/commons/core/index/Index.java
index dd170d8b68d..7a7d7201ca9 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/swe/context/commons/core/index/Index.java
@@ -1,6 +1,6 @@
-package seedu.address.commons.core.index;
+package swe.context.commons.core.index;
-import seedu.address.commons.util.ToStringBuilder;
+import swe.context.commons.util.ToStringBuilder;
/**
* Represents a zero-based or one-based index.
diff --git a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java b/src/main/java/swe/context/commons/exceptions/DataLoadingException.java
similarity index 82%
rename from src/main/java/seedu/address/commons/exceptions/DataLoadingException.java
rename to src/main/java/swe/context/commons/exceptions/DataLoadingException.java
index 9904ba47afe..daf18a8367a 100644
--- a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java
+++ b/src/main/java/swe/context/commons/exceptions/DataLoadingException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package swe.context.commons.exceptions;
/**
* Represents an error during loading of data from a file.
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/swe/context/commons/exceptions/IllegalValueException.java
similarity index 93%
rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
rename to src/main/java/swe/context/commons/exceptions/IllegalValueException.java
index 19124db485c..78d737e6a01 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/swe/context/commons/exceptions/IllegalValueException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package swe.context.commons.exceptions;
/**
* Signals that some given data does not fulfill some constraints.
diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/swe/context/commons/util/AppUtil.java
similarity index 94%
rename from src/main/java/seedu/address/commons/util/AppUtil.java
rename to src/main/java/swe/context/commons/util/AppUtil.java
index 87aa89c0326..5828ddf1b95 100644
--- a/src/main/java/seedu/address/commons/util/AppUtil.java
+++ b/src/main/java/swe/context/commons/util/AppUtil.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.util;
+package swe.context.commons.util;
import static java.util.Objects.requireNonNull;
import javafx.scene.image.Image;
-import seedu.address.MainApp;
+import swe.context.MainApp;
/**
* A container for App specific utility functions
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/swe/context/commons/util/CollectionUtil.java
similarity index 96%
rename from src/main/java/seedu/address/commons/util/CollectionUtil.java
rename to src/main/java/swe/context/commons/util/CollectionUtil.java
index eafe4dfd681..07cdadfe6a2 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/swe/context/commons/util/CollectionUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package swe.context.commons.util;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/swe/context/commons/util/ConfigUtil.java
similarity index 77%
rename from src/main/java/seedu/address/commons/util/ConfigUtil.java
rename to src/main/java/swe/context/commons/util/ConfigUtil.java
index 7b829c3c4cc..3787f2621b2 100644
--- a/src/main/java/seedu/address/commons/util/ConfigUtil.java
+++ b/src/main/java/swe/context/commons/util/ConfigUtil.java
@@ -1,11 +1,11 @@
-package seedu.address.commons.util;
+package swe.context.commons.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.exceptions.DataLoadingException;
+import swe.context.commons.core.Config;
+import swe.context.commons.exceptions.DataLoadingException;
/**
* A class for accessing the Config File.
diff --git a/src/main/java/swe/context/commons/util/FileUtil.java b/src/main/java/swe/context/commons/util/FileUtil.java
new file mode 100644
index 00000000000..1d26b24c6af
--- /dev/null
+++ b/src/main/java/swe/context/commons/util/FileUtil.java
@@ -0,0 +1,55 @@
+package swe.context.commons.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import swe.context.annotation.Nullable;
+
+
+
+/**
+ * Contains utility methods for reading/writing files and working with {@link Path}s.
+ */
+public class FileUtil {
+ private static final String CHARSET = "UTF-8";
+
+ /**
+ * Assumes file exists.
+ */
+ public static String readFromFile(Path file) throws IOException {
+ return new String(Files.readAllBytes(file), CHARSET);
+ }
+
+ /**
+ * Writes the specified string content to the specified file path.
+ *
+ * Will create the parent folders/file if they do not exist yet.
+ */
+ public static void writeToFile(Path path, String content) throws IOException {
+ File file = path.toFile();
+ @Nullable File parentFolder = file.getParentFile();
+ if (parentFolder != null) {
+ parentFolder.mkdirs();
+ }
+
+ Files.write(path, content.getBytes(CHARSET));
+ }
+
+ /**
+ * 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) {
+ try {
+ Paths.get(path);
+ } catch (InvalidPathException ipe) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/swe/context/commons/util/JsonUtil.java
similarity index 87%
rename from src/main/java/seedu/address/commons/util/JsonUtil.java
rename to src/main/java/swe/context/commons/util/JsonUtil.java
index 100cb16c395..f48f46c8a61 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/swe/context/commons/util/JsonUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package swe.context.commons.util;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,8 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataLoadingException;
+import swe.context.commons.core.LogsCenter;
+import swe.context.commons.exceptions.DataLoadingException;
/**
* Converts a Java object instance to JSON and vice versa
@@ -39,13 +39,13 @@ public class JsonUtil {
.addSerializer(Level.class, new ToStringSerializer())
.addDeserializer(Level.class, new LevelDeserializer(Level.class)));
- static void serializeObjectToJsonFile(Path jsonFile, T objectToSerialize) throws IOException {
+ static void serializeToFile(Path jsonFile, T objectToSerialize) throws IOException {
FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize));
}
- static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObjectToDeserialize)
+ static T deserializeFromFile(Path jsonFile, Class clazz)
throws IOException {
- return fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize);
+ return fromJsonString(FileUtil.readFromFile(jsonFile), clazz);
}
/**
@@ -53,11 +53,11 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject
* 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 classOfObjectToDeserialize JSON file has to correspond to the structure in the class given here.
+ * @param clazz JSON file has to correspond to the structure in the class given here.
* @throws DataLoadingException if loading of the JSON file failed.
*/
public static Optional readJsonFile(
- Path filePath, Class classOfObjectToDeserialize) throws DataLoadingException {
+ Path filePath, Class clazz) throws DataLoadingException {
requireNonNull(filePath);
if (!Files.exists(filePath)) {
@@ -68,7 +68,7 @@ public static Optional readJsonFile(
T jsonFile;
try {
- jsonFile = deserializeObjectFromJsonFile(filePath, classOfObjectToDeserialize);
+ jsonFile = deserializeFromFile(filePath, clazz);
} catch (IOException e) {
logger.warning("Error reading from jsonFile file " + filePath + ": " + e);
throw new DataLoadingException(e);
@@ -88,7 +88,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio
requireNonNull(filePath);
requireNonNull(jsonFile);
- serializeObjectToJsonFile(filePath, jsonFile);
+ serializeToFile(filePath, jsonFile);
}
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/swe/context/commons/util/StringUtil.java
similarity index 71%
rename from src/main/java/seedu/address/commons/util/StringUtil.java
rename to src/main/java/swe/context/commons/util/StringUtil.java
index 61cc8c9a1cb..27fdf5449ea 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/swe/context/commons/util/StringUtil.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.util;
+package swe.context.commons.util;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+import static swe.context.commons.util.AppUtil.checkArgument;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -11,15 +11,17 @@
* Helper functions for handling strings.
*/
public class StringUtil {
+ public static final String MESSAGE_KEYWORD_EMPTY = "Keyword cannot be empty.";
+ public static final String MESSAGE_KEYWORD_MULTI_WORD = "Keyword should be a single word.";
/**
* Returns true if the {@code sentence} contains the {@code word}.
- * 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
- *
+ * 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
*/
@@ -28,8 +30,8 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
requireNonNull(word);
String preppedWord = word.trim();
- checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty");
- checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word");
+ checkArgument(!preppedWord.isEmpty(), StringUtil.MESSAGE_KEYWORD_EMPTY);
+ checkArgument(preppedWord.split("\\s+").length == 1, StringUtil.MESSAGE_KEYWORD_MULTI_WORD);
String preppedSentence = sentence;
String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
@@ -38,6 +40,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
.anyMatch(preppedWord::equalsIgnoreCase);
}
+
/**
* Returns a detailed message of the t, including the stack trace.
*/
diff --git a/src/main/java/seedu/address/commons/util/ToStringBuilder.java b/src/main/java/swe/context/commons/util/ToStringBuilder.java
similarity index 97%
rename from src/main/java/seedu/address/commons/util/ToStringBuilder.java
rename to src/main/java/swe/context/commons/util/ToStringBuilder.java
index d979b926734..e35d6116024 100644
--- a/src/main/java/seedu/address/commons/util/ToStringBuilder.java
+++ b/src/main/java/swe/context/commons/util/ToStringBuilder.java
@@ -1,4 +1,6 @@
-package seedu.address.commons.util;
+package swe.context.commons.util;
+
+
/**
* Builds a string representation of an object that is suitable as the return value of {@link Object#toString()}.
diff --git a/src/main/java/swe/context/logic/Logic.java b/src/main/java/swe/context/logic/Logic.java
new file mode 100644
index 00000000000..4aa03536c1e
--- /dev/null
+++ b/src/main/java/swe/context/logic/Logic.java
@@ -0,0 +1,35 @@
+package swe.context.logic;
+
+import java.nio.file.Path;
+
+import javafx.collections.ObservableList;
+import swe.context.commons.core.GuiSettings;
+import swe.context.logic.commands.CommandResult;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * API of the Logic component.
+ */
+public interface Logic {
+ public GuiSettings getGuiSettings();
+ public void setGuiSettings(GuiSettings guiSettings);
+
+ public ObservableList getFilteredContactList();
+
+ public Path getContactsPath();
+
+ /**
+ * Returns the {@link CommandResult} of executing the specified command
+ * text.
+ *
+ * @throws ParseException If an error occurs during parsing.
+ * @throws CommandException If an error occurs during command execution.
+ */
+ public CommandResult execute(
+ String commandText
+ ) throws ParseException, CommandException;
+}
diff --git a/src/main/java/swe/context/logic/LogicManager.java b/src/main/java/swe/context/logic/LogicManager.java
new file mode 100644
index 00000000000..b78a54be067
--- /dev/null
+++ b/src/main/java/swe/context/logic/LogicManager.java
@@ -0,0 +1,74 @@
+package swe.context.logic;
+
+import java.io.IOException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Path;
+
+import javafx.collections.ObservableList;
+import swe.context.commons.core.GuiSettings;
+import swe.context.logic.commands.Command;
+import swe.context.logic.commands.CommandResult;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.logic.parser.InputParser;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.Model;
+import swe.context.model.contact.Contact;
+import swe.context.storage.Storage;
+
+
+
+/**
+ * Implementation of the Logic component.
+ */
+public class LogicManager implements Logic {
+ private Model model;
+ private Storage storage;
+
+ /**
+ * Constructs with the specified values.
+ */
+ public LogicManager(Model model, Storage storage) {
+ this.model = model;
+ this.storage = storage;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return this.model.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ this.model.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public ObservableList getFilteredContactList() {
+ return this.model.getFilteredContactList();
+ }
+
+ @Override
+ public Path getContactsPath() {
+ return this.storage.getContactsPath();
+ }
+
+ @Override
+ public CommandResult execute(
+ String commandText
+ ) throws ParseException, CommandException {
+ Command command = InputParser.parseCommand(commandText);
+ CommandResult result = command.execute(model);
+
+ try {
+ storage.saveContacts(model.getContacts());
+ } catch (AccessDeniedException e) {
+ throw new CommandException(
+ Messages.fileOpsPermissionErrorFormat(e.getMessage()), e);
+ } catch (IOException e) {
+ throw new CommandException(
+ Messages.fileOpsErrorFormat(e.getMessage()), e);
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/swe/context/logic/Messages.java b/src/main/java/swe/context/logic/Messages.java
new file mode 100644
index 00000000000..205ef32bfaf
--- /dev/null
+++ b/src/main/java/swe/context/logic/Messages.java
@@ -0,0 +1,154 @@
+package swe.context.logic;
+
+/**
+ * Holds message strings used by the logic for display to the user.
+ *
+ * Public messages do not need formatting and can be used directly after import.
+ *
+ * Private messages need formatting and have the prefix {@code UNFORMATTED_}.
+ * These contain raw format specifiers (e.g. {@code %s}) that should be
+ * populated by calling their associated methods.
+ */
+public final class Messages {
+ // Generic commands
+ public static final String DUPLICATE_FIELDS =
+ "Multiple values specified for the following single-valued parameters(s): ";
+ public static final String COMMAND_DUPLICATE_CONTACT =
+ "There is an existing contact with the same name.";
+ private static final String UNFORMATTED_COMMAND_INVALID_FORMAT = "Invalid command format.%n%s";
+ private static final String UNFORMATTED_COMMAND_UNKNOWN = "Unknown command.%n%s";
+ private static final String UNFORMATTED_CONTACTS_LISTED_OVERVIEW = "%d contacts listed.";
+ private static final String UNFORMATTED_ADD_COMMAND_SUCCESS = "New contact added: %s";
+ private static final String UNFORMATTED_DELETE_COMMAND_SUCCESS = "Deleted contact(s):%n%s";
+ private static final String UNFORMATTED_EDIT_COMMAND_SUCCESS = "Edited contact: %s";
+
+ // Specific commands
+ public static final String COMMAND_EDIT_NO_PARAM =
+ "At least one optional parameter to edit must be provided.";
+ public static final String COMMAND_CLEAR_SUCCESS = "Removed all contacts!";
+ public static final String COMMAND_LIST_SUCCESS = "Listed all contacts.";
+ public static final String COMMAND_HELP_SUCCESS = "Opened help window.";
+ public static final String COMMAND_EXIT_SUCCESS = "Exiting app...";
+
+ public static final String INVALID_DELETE_INDEX =
+ "One or more of the contact indices provided are invalid.";
+ public static final String INVALID_EDIT_INDEX = "The contact index provided is invalid.";
+
+ // Validation messages for command parameter constraints
+ public static final String NAME_INVALID = "Names must be alphanumeric (spaces allowed).";
+ public static final String PHONE_INVALID = "Phone numbers must start with at least 3 digits.";
+ public static final String EMAIL_INVALID =
+ "Emails must roughly be of the form \"example_email@foo-domain.sg.\"";
+ private static final String UNFORMATTED_TAG_INVALID =
+ "\"%s\" is not a valid tag. Tags must be alphanumeric (spaces allowed).";
+ private static final String UNFORMATTED_ALTERNATECONTACT_INVALID =
+ "\"%s\" is not a valid alternate contact. "
+ + "Alternate contacts must roughly be of the form \"SocialMedia: Username\"";
+
+ // JSON
+ public static final String CONVERT_CONTACTS_DUPLICATE =
+ "Encountered duplicate while converting contacts.";
+
+ // Exceptions
+ public static final String DUPLICATE_CONTACT_EXCEPTION =
+ "Operation would result in duplicate contacts";
+ private static final String UNFORMATTED_FILE_OPS_ERROR_FORMAT =
+ "Could not save data due to the following error: %s";
+ private static final String UNFORMATTED_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 static final String UNFORMATTED_FIELD_MISSING = "Contact's %s field is missing.";
+
+ /**
+ * Returns a formatted message about the command format being invalid, with
+ * the specified help text.
+ */
+ public static String commandInvalidFormat(String helpText) {
+ return String.format(
+ Messages.UNFORMATTED_COMMAND_INVALID_FORMAT,
+ helpText
+ );
+ }
+
+ /**
+ * Returns a formatted message about the command being unknown, with the
+ * specified help text.
+ */
+ public static String commandUnknown(String helpText) {
+ return String.format(
+ Messages.UNFORMATTED_COMMAND_UNKNOWN,
+ helpText
+ );
+ }
+
+ /**
+ * Returns a formatted message about the specified tag name being invalid.
+ */
+ public static String tagInvalid(String invalidName) {
+ return String.format(
+ Messages.UNFORMATTED_TAG_INVALID,
+ invalidName
+ );
+ }
+
+ /**
+ * Returns a formatted message showing the number of contacts listed.
+ */
+ public static String contactsListedOverview(int count) {
+ return String.format(UNFORMATTED_CONTACTS_LISTED_OVERVIEW, count);
+ }
+
+ /**
+ * Returns a formatted message indicating a successful addition of a contact.
+ */
+ public static String addCommandSuccess(String contactDetails) {
+ return String.format(UNFORMATTED_ADD_COMMAND_SUCCESS, contactDetails);
+ }
+
+ /**
+ * Returns a formatted message indicating successful deletion of contact(s).
+ */
+ public static String deleteCommandSuccess(String contactDetails) {
+ return String.format(UNFORMATTED_DELETE_COMMAND_SUCCESS, contactDetails);
+ }
+
+ /**
+ * Returns a formatted message indicating successful editing of a contact.
+ */
+ public static String editCommandSuccess(String contactDetails) {
+ return String.format(UNFORMATTED_EDIT_COMMAND_SUCCESS, contactDetails);
+ }
+
+ /**
+ * Returns a formatted message indicating a file operation error.
+ */
+ public static String fileOpsErrorFormat(String errorDetails) {
+ return String.format(UNFORMATTED_FILE_OPS_ERROR_FORMAT, errorDetails);
+ }
+
+ /**
+ * Returns a formatted message indicating a file permission error.
+ */
+ public static String fileOpsPermissionErrorFormat(String filePath) {
+ return String.format(UNFORMATTED_FILE_OPS_PERMISSION_ERROR_FORMAT, filePath);
+ }
+
+ /**
+ * Returns a formatted message indicating a missing field in a contact.
+ */
+ public static String fieldMissing(String fieldName) {
+ return String.format(UNFORMATTED_FIELD_MISSING, fieldName);
+ }
+
+ /**
+ * Returns a formatted message about the specified alternate contact being invalid.
+ */
+ public static String alternateContactInvalid(String invalid) {
+ return String.format(
+ Messages.UNFORMATTED_ALTERNATECONTACT_INVALID, invalid);
+ }
+
+ private Messages() {
+ // No instantiation
+ }
+}
diff --git a/src/main/java/swe/context/logic/commands/AddCommand.java b/src/main/java/swe/context/logic/commands/AddCommand.java
new file mode 100644
index 00000000000..d8b223261ac
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/AddCommand.java
@@ -0,0 +1,88 @@
+package swe.context.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static swe.context.logic.parser.CliSyntax.PREFIX_ALTERNATE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NAME;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NOTE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_PHONE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_TAG;
+
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.model.Model;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * Adds a {@link Contact}.
+ */
+public class AddCommand extends Command {
+ public static final String COMMAND_WORD = "add";
+
+ public static final String MESSAGE_USAGE = String.format(
+ "%s: Adds a contact."
+ + "%nParameters: %sNAME %sPHONE_NUMBER %sEMAIL"
+ + " [%sNOTE] [%sTAG]... [%sALTERNATE_CONTACT]..."
+ + "%nExample: %s %sJohn Doe %s98765432 %sjohn.doe@email.com"
+ + " %sLikes SE. %sNUS %sCS2103 course %sTelegram: JohnDoe",
+ AddCommand.COMMAND_WORD,
+ PREFIX_NAME,
+ PREFIX_PHONE,
+ PREFIX_EMAIL,
+ PREFIX_NOTE,
+ PREFIX_TAG,
+ PREFIX_ALTERNATE,
+ AddCommand.COMMAND_WORD,
+ PREFIX_NAME,
+ PREFIX_PHONE,
+ PREFIX_EMAIL,
+ PREFIX_NOTE,
+ PREFIX_TAG,
+ PREFIX_TAG,
+ PREFIX_ALTERNATE
+ );
+
+ private final Contact toAdd;
+ /**
+ * Creates an AddCommand to add the specified {@code Contact}
+ */
+ public AddCommand(Contact contact) {
+ requireNonNull(contact);
+ toAdd = contact;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ if (model.containsContact(toAdd)) {
+ throw new CommandException(Messages.COMMAND_DUPLICATE_CONTACT);
+ }
+
+ model.addContact(toAdd);
+ return new CommandResult(Messages.addCommandSuccess(Contact.format(toAdd)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddCommand)) {
+ return false;
+ }
+
+ AddCommand otherAddCommand = (AddCommand) other;
+ return toAdd.equals(otherAddCommand.toAdd);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toAdd", toAdd)
+ .toString();
+ }
+}
diff --git a/src/main/java/swe/context/logic/commands/ClearCommand.java b/src/main/java/swe/context/logic/commands/ClearCommand.java
new file mode 100644
index 00000000000..8e67ac8ae2e
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/ClearCommand.java
@@ -0,0 +1,20 @@
+package swe.context.logic.commands;
+
+import swe.context.logic.Messages;
+import swe.context.model.Model;
+
+
+
+/**
+ * Removes all {@link Contact}s.
+ */
+public class ClearCommand extends Command {
+ public static final String COMMAND_WORD = "clear";
+
+ @Override
+ public CommandResult execute(Model model) {
+ model.removeAllContacts();
+
+ return new CommandResult(Messages.COMMAND_CLEAR_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/swe/context/logic/commands/Command.java
similarity index 78%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/swe/context/logic/commands/Command.java
index 64f18992160..63af142acea 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/swe/context/logic/commands/Command.java
@@ -1,13 +1,14 @@
-package seedu.address.logic.commands;
+package swe.context.logic.commands;
+
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.model.Model;
+
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
/**
* Represents a command with hidden internal logic and the ability to be executed.
*/
public abstract class Command {
-
/**
* Executes the command and returns the result message.
*
@@ -16,5 +17,4 @@ public abstract class Command {
* @throws CommandException If an error occurs during command execution.
*/
public abstract CommandResult execute(Model model) throws CommandException;
-
}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/swe/context/logic/commands/CommandResult.java
similarity index 95%
rename from src/main/java/seedu/address/logic/commands/CommandResult.java
rename to src/main/java/swe/context/logic/commands/CommandResult.java
index 249b6072d0d..3cdae41c5a1 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/swe/context/logic/commands/CommandResult.java
@@ -1,10 +1,10 @@
-package seedu.address.logic.commands;
+package swe.context.logic.commands;
import static java.util.Objects.requireNonNull;
import java.util.Objects;
-import seedu.address.commons.util.ToStringBuilder;
+import swe.context.commons.util.ToStringBuilder;
/**
* Represents the result of a command execution.
diff --git a/src/main/java/swe/context/logic/commands/DeleteCommand.java b/src/main/java/swe/context/logic/commands/DeleteCommand.java
new file mode 100644
index 00000000000..b05931831a8
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/DeleteCommand.java
@@ -0,0 +1,91 @@
+package swe.context.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import swe.context.commons.core.index.Index;
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.model.Model;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * Deletes one or more {@link Contact}s based on their displayed indices in the UI list.
+ * Duplicate indices are considered only once.
+ */
+public class DeleteCommand extends Command {
+ public static final String COMMAND_WORD = "delete";
+
+ public static final String MESSAGE_USAGE = String.format(
+ "%s: Deletes contact(s)."
+ + "%nParameters: INDEX..."
+ + "%nExample: %s 1 3 5",
+ DeleteCommand.COMMAND_WORD,
+ DeleteCommand.COMMAND_WORD
+ );
+
+ private final List targetIndices;
+
+ public DeleteCommand(List targetIndices) {
+ this.targetIndices = targetIndices;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List currentContactList = model.getFilteredContactList();
+ validateIndices(currentContactList);
+
+ List contactsToDelete = collectContactsToDelete(currentContactList);
+ contactsToDelete.forEach(model::removeContact);
+
+ String formattedContacts = formatContactsForMessage(contactsToDelete);
+ return new CommandResult(Messages.deleteCommandSuccess(formattedContacts));
+ }
+
+ private void validateIndices(List contactList) throws CommandException {
+ for (Index index : targetIndices) {
+ if (index.getZeroBased() >= contactList.size()) {
+ throw new CommandException(Messages.INVALID_DELETE_INDEX);
+ }
+ }
+ }
+
+ private List collectContactsToDelete(List contactList) {
+ return targetIndices.stream()
+ .distinct()
+ .map(index -> contactList.get(index.getZeroBased()))
+ .collect(Collectors.toList());
+ }
+
+ private String formatContactsForMessage(List contacts) {
+ return contacts.stream()
+ .map(Contact::format)
+ .collect(Collectors.joining(",\n"));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof DeleteCommand)) {
+ return false;
+ }
+ DeleteCommand otherDeleteCommand = (DeleteCommand) other;
+ return targetIndices.equals(otherDeleteCommand.targetIndices);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndices", targetIndices)
+ .toString();
+ }
+}
diff --git a/src/main/java/swe/context/logic/commands/EditCommand.java b/src/main/java/swe/context/logic/commands/EditCommand.java
new file mode 100644
index 00000000000..58e9565ae6e
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/EditCommand.java
@@ -0,0 +1,277 @@
+package swe.context.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static swe.context.logic.parser.CliSyntax.PREFIX_ALTERNATE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NAME;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NOTE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_PHONE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_TAG;
+
+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 swe.context.commons.core.index.Index;
+import swe.context.commons.util.CollectionUtil;
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.model.Model;
+import swe.context.model.ModelManager;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.Email;
+import swe.context.model.contact.Name;
+import swe.context.model.contact.Note;
+import swe.context.model.contact.Phone;
+import swe.context.model.tag.Tag;
+
+
+
+/**
+ * Edits an existing {@link Contact}.
+ */
+public class EditCommand extends Command {
+ public static final String COMMAND_WORD = "edit";
+
+ public static final String MESSAGE_USAGE = String.format(
+ "%s: Edits a contact. At least one optional parameter required."
+ + "%nParameters: INDEX [%sNAME] [%sPHONE_NUMBER] [%sEMAIL]"
+ + " [%sNOTE] [%sTAG]... [%sALTERNATE_CONTACT]..."
+ + "%nExample: %s 3 %sMember of NUS S/U %s",
+ EditCommand.COMMAND_WORD,
+ PREFIX_NAME,
+ PREFIX_PHONE,
+ PREFIX_EMAIL,
+ PREFIX_NOTE,
+ PREFIX_TAG,
+ PREFIX_ALTERNATE,
+ EditCommand.COMMAND_WORD,
+ PREFIX_NOTE,
+ PREFIX_TAG
+ );
+
+ private final Index index;
+ private final EditContactDescriptor editContactDescriptor;
+
+ /**
+ * @param index of the contact in the filtered contact list to edit
+ * @param editContactDescriptor details to edit the contact with
+ */
+ public EditCommand(Index index, EditContactDescriptor editContactDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editContactDescriptor);
+
+ this.index = index;
+ this.editContactDescriptor = new EditContactDescriptor(editContactDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ validateIndex(lastShownList, index);
+
+ Contact contactToEdit = lastShownList.get(index.getZeroBased());
+ Contact editedContact = createEditedContact(contactToEdit, editContactDescriptor);
+
+ ensureUniqueContact(model, contactToEdit, editedContact);
+
+ model.updateContact(contactToEdit, editedContact);
+ model.setContactsFilter(ModelManager.FILTER_NONE);
+
+ return new CommandResult(Messages.editCommandSuccess(Contact.format(editedContact)));
+ }
+
+ private void validateIndex(List contactList, Index index) throws CommandException {
+ if (index.getZeroBased() >= contactList.size()) {
+ throw new CommandException(Messages.INVALID_EDIT_INDEX);
+ }
+ }
+
+ private void ensureUniqueContact(Model model, Contact original, Contact edited) throws CommandException {
+ if (!original.isSameContact(edited) && model.containsContact(edited)) {
+ throw new CommandException(Messages.COMMAND_DUPLICATE_CONTACT);
+ }
+ }
+
+ /**
+ * Creates and returns a {@code Contact} with the details of {@code contactToEdit}
+ * edited with {@code editContactDescriptor}.
+ */
+ private static Contact createEditedContact(Contact contactToEdit, EditContactDescriptor editContactDescriptor) {
+ assert contactToEdit != null;
+
+ Name updatedName = editContactDescriptor.getName().orElse(contactToEdit.getName());
+ Phone updatedPhone = editContactDescriptor.getPhone().orElse(contactToEdit.getPhone());
+ Email updatedEmail = editContactDescriptor.getEmail().orElse(contactToEdit.getEmail());
+ Note updatedAddress = editContactDescriptor.getNote().orElse(contactToEdit.getNote());
+ Set updatedTags = editContactDescriptor.getTags().orElse(contactToEdit.getTags());
+ Set updatedAlternateContacts =
+ editContactDescriptor.getAlternateContacts().orElse(contactToEdit.getAlternates());
+
+ return new Contact(
+ updatedName,
+ updatedPhone,
+ updatedEmail,
+ updatedAddress,
+ updatedTags,
+ updatedAlternateContacts
+ );
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditCommand)) {
+ return false;
+ }
+
+ EditCommand otherEditCommand = (EditCommand) other;
+ return index.equals(otherEditCommand.index)
+ && editContactDescriptor.equals(otherEditCommand.editContactDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("index", index)
+ .add("editContactDescriptor", editContactDescriptor)
+ .toString();
+ }
+
+ /**
+ * Stores the details to edit the contact with. Each non-empty field value will replace the
+ * corresponding field value of the contact.
+ */
+ public static class EditContactDescriptor {
+ private Name name;
+ private Phone phone;
+ private Email email;
+ private Note note;
+ private Set tags;
+ private Set alternateContacts;
+
+ public EditContactDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditContactDescriptor(EditContactDescriptor toCopy) {
+ setName(toCopy.name);
+ setPhone(toCopy.phone);
+ setEmail(toCopy.email);
+ setNote(toCopy.note);
+ setTags(toCopy.tags);
+ setAlternateContacts(toCopy.alternateContacts);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, phone, email, note, tags, alternateContacts);
+ }
+
+ public void setName(Name name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setPhone(Phone phone) {
+ this.phone = phone;
+ }
+
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
+ }
+
+ public void setEmail(Email email) {
+ this.email = email;
+ }
+
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
+ }
+
+ public void setNote(Note _note) {
+ this.note = _note;
+ }
+
+ public Optional getNote() {
+ return Optional.ofNullable(this.note);
+ }
+
+ /**
+ * Sets {@code tags} to this object's {@code tags}.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public void setTags(Set tags) {
+ this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ }
+
+ /**
+ * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ }
+
+ public void setAlternateContacts(Set alternateContacts) {
+ this.alternateContacts = (alternateContacts != null) ? new HashSet<>(alternateContacts) : null;
+ }
+
+ public Optional> getAlternateContacts() {
+ return (alternateContacts != null)
+ ? Optional.of(Collections.unmodifiableSet(alternateContacts))
+ : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditContactDescriptor)) {
+ return false;
+ }
+
+ EditContactDescriptor otherEditContactDescriptor = (EditContactDescriptor) other;
+ return Objects.equals(this.name, otherEditContactDescriptor.name)
+ && Objects.equals(this.phone, otherEditContactDescriptor.phone)
+ && Objects.equals(this.email, otherEditContactDescriptor.email)
+ && Objects.equals(this.note, otherEditContactDescriptor.note)
+ && Objects.equals(this.tags, otherEditContactDescriptor.tags)
+ && Objects.equals(this.alternateContacts, otherEditContactDescriptor.alternateContacts);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", this.name)
+ .add("phone", this.phone)
+ .add("email", this.email)
+ .add("note", this.note)
+ .add("tags", this.tags)
+ .add("alternate contacts", this.alternateContacts)
+ .toString();
+ }
+ }
+}
diff --git a/src/main/java/swe/context/logic/commands/ExitCommand.java b/src/main/java/swe/context/logic/commands/ExitCommand.java
new file mode 100644
index 00000000000..4ded37566fa
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/ExitCommand.java
@@ -0,0 +1,18 @@
+package swe.context.logic.commands;
+
+import swe.context.logic.Messages;
+import swe.context.model.Model;
+
+
+
+/**
+ * Exits the app.
+ */
+public class ExitCommand extends Command {
+ public static final String COMMAND_WORD = "exit";
+
+ @Override
+ public CommandResult execute(Model model) {
+ return new CommandResult(Messages.COMMAND_EXIT_SUCCESS, false, true);
+ }
+}
diff --git a/src/main/java/swe/context/logic/commands/FilterCommand.java b/src/main/java/swe/context/logic/commands/FilterCommand.java
new file mode 100644
index 00000000000..f36efb369a6
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/FilterCommand.java
@@ -0,0 +1,61 @@
+package swe.context.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.logic.Messages;
+import swe.context.model.Model;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.ContainsTagPredicate;
+
+/**
+ * Filters and lists {@link Contact}s whose tags match the specified
+ * tag in full. Case insensitive.
+ */
+public class FilterCommand extends Command {
+ public static final String COMMAND_WORD = "filter";
+
+ public static final String MESSAGE_USAGE = String.format(
+ "%s: Filters contacts by tag. Case insensitive. Requires full tag match."
+ + "%nParameters: TAG"
+ + "%nExample: %s CS2103 course",
+ FilterCommand.COMMAND_WORD,
+ FilterCommand.COMMAND_WORD
+ );
+
+ private final ContainsTagPredicate predicate;
+
+ public FilterCommand(ContainsTagPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.setContactsFilter(predicate);
+ return new CommandResult(
+ Messages.contactsListedOverview(model.getFilteredContactList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FilterCommand)) {
+ return false;
+ }
+
+ FilterCommand otherFilterCommand = (FilterCommand) other;
+ return predicate.equals(otherFilterCommand.predicate);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("predicate", predicate)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/swe/context/logic/commands/FindCommand.java
similarity index 53%
rename from src/main/java/seedu/address/logic/commands/FindCommand.java
rename to src/main/java/swe/context/logic/commands/FindCommand.java
index 72b9eddd3a7..e69a62ec2ad 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/swe/context/logic/commands/FindCommand.java
@@ -1,24 +1,29 @@
-package seedu.address.logic.commands;
+package swe.context.logic.commands;
import static java.util.Objects.requireNonNull;
-import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.logic.Messages;
+import swe.context.model.Model;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.NameContainsKeywordsPredicate;
+
+
/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
+ * Finds and lists {@link Contact}s whose names match any of the specified
+ * words in full. Case insensitive.
*/
public class FindCommand extends Command {
-
public static final String COMMAND_WORD = "find";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "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 = String.format(
+ "%s: Finds contacts by name. Case insensitive. Requires full word match."
+ + "%nParameters: KEYWORD..."
+ + "%nExample: %s amy Ben CHARLOTTE",
+ FindCommand.COMMAND_WORD,
+ FindCommand.COMMAND_WORD
+ );
private final NameContainsKeywordsPredicate predicate;
@@ -29,9 +34,9 @@ public FindCommand(NameContainsKeywordsPredicate predicate) {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.updateFilteredPersonList(predicate);
+ model.setContactsFilter(predicate);
return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ Messages.contactsListedOverview(model.getFilteredContactList().size()));
}
@Override
diff --git a/src/main/java/swe/context/logic/commands/HelpCommand.java b/src/main/java/swe/context/logic/commands/HelpCommand.java
new file mode 100644
index 00000000000..4a9d441f44b
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/HelpCommand.java
@@ -0,0 +1,23 @@
+package swe.context.logic.commands;
+
+import swe.context.logic.Messages;
+import swe.context.model.Model;
+
+
+
+/**
+ * Format full help instructions for every command for display.
+ */
+public class HelpCommand extends Command {
+ public static final String COMMAND_WORD = "help";
+
+ public static final String MESSAGE_USAGE = String.format(
+ "%s: Opens the help window.",
+ HelpCommand.COMMAND_WORD
+ );
+
+ @Override
+ public CommandResult execute(Model model) {
+ return new CommandResult(Messages.COMMAND_HELP_SUCCESS, true, false);
+ }
+}
diff --git a/src/main/java/swe/context/logic/commands/ListCommand.java b/src/main/java/swe/context/logic/commands/ListCommand.java
new file mode 100644
index 00000000000..be78a359360
--- /dev/null
+++ b/src/main/java/swe/context/logic/commands/ListCommand.java
@@ -0,0 +1,24 @@
+package swe.context.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import swe.context.logic.Messages;
+import swe.context.model.Model;
+import swe.context.model.ModelManager;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * Lists all {@link Contact}s.
+ */
+public class ListCommand extends Command {
+ public static final String COMMAND_WORD = "list";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.setContactsFilter(ModelManager.FILTER_NONE);
+ return new CommandResult(Messages.COMMAND_LIST_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/swe/context/logic/commands/exceptions/CommandException.java
similarity index 89%
rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
rename to src/main/java/swe/context/logic/commands/exceptions/CommandException.java
index a16bd14f2cd..5993eb1303d 100644
--- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
+++ b/src/main/java/swe/context/logic/commands/exceptions/CommandException.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands.exceptions;
+package swe.context.logic.commands.exceptions;
/**
* Represents an error which occurs during execution of a {@link Command}.
diff --git a/src/main/java/swe/context/logic/parser/AddCommandParser.java b/src/main/java/swe/context/logic/parser/AddCommandParser.java
new file mode 100644
index 00000000000..7b4a6fc0030
--- /dev/null
+++ b/src/main/java/swe/context/logic/parser/AddCommandParser.java
@@ -0,0 +1,86 @@
+package swe.context.logic.parser;
+
+import static swe.context.logic.parser.CliSyntax.PREFIX_ALTERNATE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NAME;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NOTE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_PHONE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import swe.context.logic.Messages;
+import swe.context.logic.commands.AddCommand;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.Email;
+import swe.context.model.contact.Name;
+import swe.context.model.contact.Note;
+import swe.context.model.contact.Phone;
+import swe.context.model.tag.Tag;
+
+
+
+/**
+ * Parses input arguments and creates a new {@link AddCommand} object.
+ */
+public class AddCommandParser implements Parser {
+ /**
+ * Returns true if none of the prefixes contains empty {@link Optional} values in the given
+ * {@link ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddCommand
+ * and returns an AddCommand object for execution.
+ * @throws ParseException if the user input does not conform to the expected format
+ */
+ public AddCommand parse(String args) throws ParseException {
+ // Tokenize arguments based on the expected prefixes
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(
+ args,
+ PREFIX_NAME,
+ PREFIX_PHONE,
+ PREFIX_EMAIL,
+ PREFIX_NOTE,
+ PREFIX_TAG,
+ PREFIX_ALTERNATE
+ );
+
+ // Check for mandatory prefixes and empty preamble
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(Messages.commandInvalidFormat(AddCommand.MESSAGE_USAGE));
+ }
+
+ // Verify that there are no duplicate entries for the specified prefixes
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NOTE);
+
+ // Parse the mandatory fields
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
+
+ // Parse the optional tags and alternates
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Set alternateContactList =
+ ParserUtil.parseAlternates(argMultimap.getAllValues(PREFIX_ALTERNATE));
+
+ // Parse note, defaulting to an empty string if not provided
+ Optional noteOptional = argMultimap.getValue(PREFIX_NOTE);
+ Note note = ParserUtil.parseNote(noteOptional.orElse(""));
+
+ // Create the new contact
+ Contact contact = new Contact(name, phone, email, note, tagList, alternateContactList);
+
+ // Return the constructed AddCommand
+ return new AddCommand(contact);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/swe/context/logic/parser/ArgumentMultimap.java
similarity index 79%
rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
rename to src/main/java/swe/context/logic/parser/ArgumentMultimap.java
index 21e26887a83..549b07a433e 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/swe/context/logic/parser/ArgumentMultimap.java
@@ -1,14 +1,16 @@
-package seedu.address.logic.parser;
+package swe.context.logic.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
-import seedu.address.logic.Messages;
-import seedu.address.logic.parser.exceptions.ParseException;
+import swe.context.logic.Messages;
+import swe.context.logic.parser.exceptions.ParseException;
/**
* Stores mapping of prefixes to their respective arguments.
@@ -72,7 +74,20 @@ public void verifyNoDuplicatePrefixesFor(Prefix... prefixes) throws ParseExcepti
.toArray(Prefix[]::new);
if (duplicatedPrefixes.length > 0) {
- throw new ParseException(Messages.getErrorMessageForDuplicatePrefixes(duplicatedPrefixes));
+ throw new ParseException(getErrorMessageForDuplicatePrefixes(duplicatedPrefixes));
}
}
+
+ /**
+ * Returns an error message indicating the duplicate prefixes.
+ */
+ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePrefixes) {
+ assert duplicatePrefixes.length > 0;
+
+ Set duplicateFields =
+ Stream.of(duplicatePrefixes).map(Prefix::toString).collect(Collectors.toSet());
+
+ return Messages.DUPLICATE_FIELDS + String.join(" ", duplicateFields);
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/swe/context/logic/parser/ArgumentTokenizer.java
similarity index 99%
rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
rename to src/main/java/swe/context/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..ec84dfc65ae 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/swe/context/logic/parser/ArgumentTokenizer.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package swe.context.logic.parser;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/swe/context/logic/parser/CliSyntax.java
similarity index 64%
rename from src/main/java/seedu/address/logic/parser/CliSyntax.java
rename to src/main/java/swe/context/logic/parser/CliSyntax.java
index 75b1a9bf119..24d01325c11 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/swe/context/logic/parser/CliSyntax.java
@@ -1,15 +1,16 @@
-package seedu.address.logic.parser;
+package swe.context.logic.parser;
+
+
/**
- * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
+ * Contains Command Line Interface (CLI) syntax definitions common to multiple
+ * commands.
*/
public class CliSyntax {
-
- /* Prefix definitions */
public static final Prefix PREFIX_NAME = new Prefix("n/");
+ public static final Prefix PREFIX_TAG = new Prefix("t/");
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
- public static final Prefix PREFIX_TAG = new Prefix("t/");
-
+ public static final Prefix PREFIX_NOTE = new Prefix("o/");
+ public static final Prefix PREFIX_ALTERNATE = new Prefix("a/");
}
diff --git a/src/main/java/swe/context/logic/parser/DeleteCommandParser.java b/src/main/java/swe/context/logic/parser/DeleteCommandParser.java
new file mode 100644
index 00000000000..8863390a586
--- /dev/null
+++ b/src/main/java/swe/context/logic/parser/DeleteCommandParser.java
@@ -0,0 +1,41 @@
+package swe.context.logic.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import swe.context.commons.core.index.Index;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.DeleteCommand;
+import swe.context.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteCommand object
+ */
+public class DeleteCommandParser implements Parser {
+
+ /**
+ * Returns a {@link DeleteCommand} from parsing the specified arguments.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteCommand parse(String args) throws ParseException {
+ try {
+ String[] splitArgs = args.trim().split("\\s+");
+ List indices = new ArrayList<>();
+
+ // Parse all the index arguments and add to the list if it doesn't already contain it
+ for (String arg : splitArgs) {
+ Index currentIndex = ParserUtil.parseIndex(arg);
+ if (!indices.contains(currentIndex)) {
+ indices.add(currentIndex);
+ }
+ }
+
+ return new DeleteCommand(indices);
+
+ } catch (ParseException e) {
+ throw new ParseException(
+ Messages.commandInvalidFormat(DeleteCommand.MESSAGE_USAGE), e);
+ }
+ }
+}
diff --git a/src/main/java/swe/context/logic/parser/EditCommandParser.java b/src/main/java/swe/context/logic/parser/EditCommandParser.java
new file mode 100644
index 00000000000..658cdbda2a9
--- /dev/null
+++ b/src/main/java/swe/context/logic/parser/EditCommandParser.java
@@ -0,0 +1,112 @@
+package swe.context.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static swe.context.logic.parser.CliSyntax.PREFIX_ALTERNATE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NAME;
+import static swe.context.logic.parser.CliSyntax.PREFIX_NOTE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_PHONE;
+import static swe.context.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+import swe.context.commons.core.index.Index;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.EditCommand;
+import swe.context.logic.commands.EditCommand.EditContactDescriptor;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.tag.Tag;
+
+
+
+/**
+ * Parses input arguments and creates a new {@link EditCommand} object.
+ */
+public class EditCommandParser implements Parser {
+ /**
+ * Returns an {@link EditCommand} from parsing the specified arguments.
+ *
+ * @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_NOTE,
+ PREFIX_TAG,
+ PREFIX_ALTERNATE);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException e) {
+ throw new ParseException(
+ Messages.commandInvalidFormat(EditCommand.MESSAGE_USAGE),
+ e
+ );
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NOTE);
+
+ EditContactDescriptor editContactDescriptor = new EditContactDescriptor();
+
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editContactDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ editContactDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
+ editContactDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
+ }
+ if (argMultimap.getValue(PREFIX_NOTE).isPresent()) {
+ editContactDescriptor.setNote(ParserUtil.parseNote(argMultimap.getValue(PREFIX_NOTE).get()));
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editContactDescriptor::setTags);
+
+ parseAlternateContactsforEdit(argMultimap.getAllValues(PREFIX_ALTERNATE))
+ .ifPresent(editContactDescriptor::setAlternateContacts);
+
+ if (!editContactDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(Messages.COMMAND_EDIT_NO_PARAM);
+ }
+
+ return new EditCommand(index, editContactDescriptor);
+ }
+
+ /**
+ * 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 {
+ assert tags != null;
+
+ if (tags.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
+ return Optional.of(ParserUtil.parseTags(tagSet));
+ }
+
+ private Optional> parseAlternateContactsforEdit(Collection alternateContacts)
+ throws ParseException {
+ assert alternateContacts != null;
+
+ if (alternateContacts.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection alternateContactSet = alternateContacts.size() == 1 && alternateContacts.contains("")
+ ? Collections.emptySet()
+ : alternateContacts;
+ return Optional.of(ParserUtil.parseAlternates(alternateContactSet));
+ }
+}
diff --git a/src/main/java/swe/context/logic/parser/FilterCommandParser.java b/src/main/java/swe/context/logic/parser/FilterCommandParser.java
new file mode 100644
index 00000000000..bb41f69180a
--- /dev/null
+++ b/src/main/java/swe/context/logic/parser/FilterCommandParser.java
@@ -0,0 +1,28 @@
+package swe.context.logic.parser;
+
+import swe.context.logic.Messages;
+import swe.context.logic.commands.FilterCommand;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.contact.ContainsTagPredicate;
+
+
+/**
+ * Parses input arguments and creates a new FilterCommand object
+ */
+public class FilterCommandParser implements Parser {
+ /**
+ * Returns a {@link FilterCommand} from parsing the specified arguments.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FilterCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ Messages.commandInvalidFormat(FilterCommand.MESSAGE_USAGE)
+ );
+ }
+
+ return new FilterCommand(new ContainsTagPredicate(trimmedArgs));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/swe/context/logic/parser/FindCommandParser.java
similarity index 54%
rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java
rename to src/main/java/swe/context/logic/parser/FindCommandParser.java
index 2867bde857b..3b4fdfa0f20 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/swe/context/logic/parser/FindCommandParser.java
@@ -1,33 +1,33 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+package swe.context.logic.parser;
import java.util.Arrays;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.FindCommand;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.contact.NameContainsKeywordsPredicate;
+
+
/**
* Parses input arguments and creates a new FindCommand object
*/
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.
+ * Returns a {@link FindCommand} from parsing the specified arguments.
+ *
* @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()) {
throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ Messages.commandInvalidFormat(FindCommand.MESSAGE_USAGE)
+ );
}
String[] nameKeywords = trimmedArgs.split("\\s+");
return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
}
-
}
diff --git a/src/main/java/swe/context/logic/parser/InputParser.java b/src/main/java/swe/context/logic/parser/InputParser.java
new file mode 100644
index 00000000000..fed90f6b3a4
--- /dev/null
+++ b/src/main/java/swe/context/logic/parser/InputParser.java
@@ -0,0 +1,78 @@
+package swe.context.logic.parser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import swe.context.annotation.Nullable;
+import swe.context.logic.Messages;
+import swe.context.logic.commands.AddCommand;
+import swe.context.logic.commands.ClearCommand;
+import swe.context.logic.commands.Command;
+import swe.context.logic.commands.DeleteCommand;
+import swe.context.logic.commands.EditCommand;
+import swe.context.logic.commands.ExitCommand;
+import swe.context.logic.commands.FilterCommand;
+import swe.context.logic.commands.FindCommand;
+import swe.context.logic.commands.HelpCommand;
+import swe.context.logic.commands.ListCommand;
+import swe.context.logic.parser.exceptions.ParseException;
+
+
+
+/**
+ * Contains methods for parsing user input.
+ */
+public final class InputParser {
+ /**
+ * Used for initial separation of command word and arguments.
+ */
+ private static final Pattern REGEX_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
+
+ /**
+ * Returns a parsed {@link Command} based on the specified user input. The
+ * {@link Command} can then be used for execution.
+ *
+ * @throws ParseException If input doesn't conform to expected format.
+ */
+ public static Command parseCommand(String userInput) throws ParseException {
+ String trimmed = userInput.trim();
+ Matcher matcher = REGEX_COMMAND_FORMAT.matcher(trimmed);
+ if (!matcher.matches()) {
+ throw new ParseException(
+ Messages.commandInvalidFormat(HelpCommand.MESSAGE_USAGE)
+ );
+ }
+
+ @Nullable String commandWord = matcher.group("commandWord");
+ @Nullable String arguments = matcher.group("arguments");
+
+ switch (commandWord) {
+ case AddCommand.COMMAND_WORD:
+ return new AddCommandParser().parse(arguments);
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+ case FindCommand.COMMAND_WORD:
+ return new FindCommandParser().parse(arguments);
+ case FilterCommand.COMMAND_WORD:
+ return new FilterCommandParser().parse(arguments);
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parse(arguments);
+ case DeleteCommand.COMMAND_WORD:
+ return new DeleteCommandParser().parse(arguments);
+ case ClearCommand.COMMAND_WORD:
+ return new ClearCommand();
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+ default:
+ throw new ParseException(
+ Messages.commandUnknown(HelpCommand.MESSAGE_USAGE)
+ );
+ }
+ }
+
+ private InputParser() {
+ // No instantiation
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/swe/context/logic/parser/Parser.java
similarity index 72%
rename from src/main/java/seedu/address/logic/parser/Parser.java
rename to src/main/java/swe/context/logic/parser/Parser.java
index d6551ad8e3f..92d8472f929 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/swe/context/logic/parser/Parser.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.parser;
+package swe.context.logic.parser;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.parser.exceptions.ParseException;
+import swe.context.logic.commands.Command;
+import swe.context.logic.parser.exceptions.ParseException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
diff --git a/src/main/java/swe/context/logic/parser/ParserUtil.java b/src/main/java/swe/context/logic/parser/ParserUtil.java
new file mode 100644
index 00000000000..c3ff39e6fe3
--- /dev/null
+++ b/src/main/java/swe/context/logic/parser/ParserUtil.java
@@ -0,0 +1,165 @@
+package swe.context.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import swe.context.commons.core.index.Index;
+import swe.context.commons.util.StringUtil;
+import swe.context.logic.Messages;
+import swe.context.logic.parser.exceptions.ParseException;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.contact.Email;
+import swe.context.model.contact.Name;
+import swe.context.model.contact.Note;
+import swe.context.model.contact.Phone;
+import swe.context.model.tag.Tag;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserUtil {
+ public static final String MESSAGE_INDEX_NOT_POSITIVE =
+ "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(ParserUtil.MESSAGE_INDEX_NOT_POSITIVE);
+ }
+ return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ }
+
+ /**
+ * Parses a {@code String name} into a {@code Name}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code name} is invalid.
+ */
+ public static Name parseName(String name) throws ParseException {
+ requireNonNull(name);
+ String trimmedName = name.trim();
+ if (!Name.isValid(trimmedName)) {
+ throw new ParseException(Messages.NAME_INVALID);
+ }
+ return new Name(trimmedName);
+ }
+
+ /**
+ * Parses a {@code String phone} into a {@code Phone}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code phone} is invalid.
+ */
+ public static Phone parsePhone(String phone) throws ParseException {
+ requireNonNull(phone);
+ String trimmedPhone = phone.trim();
+ if (!Phone.isValid(trimmedPhone)) {
+ throw new ParseException(Messages.PHONE_INVALID);
+ }
+ return new Phone(trimmedPhone);
+ }
+
+ /**
+ * Parses a {@code String email} into an {@code Email}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code email} is invalid.
+ */
+ public static Email parseEmail(String email) throws ParseException {
+ requireNonNull(email);
+ String trimmedEmail = email.trim();
+ if (!Email.isValid(trimmedEmail)) {
+ throw new ParseException(Messages.EMAIL_INVALID);
+ }
+ return new Email(trimmedEmail);
+ }
+
+ /**
+ * Parses a string into a Note.
+ *
+ * Trims the specified string.
+ */
+ public static Note parseNote(String noteString) {
+ requireNonNull(noteString);
+
+ return new Note(
+ noteString.trim()
+ );
+ }
+
+ /**
+ * Attempts to parse the specified string as a {@link Tag}.
+ *
+ * Trims the specified string as part of parsing.
+ *
+ * @param tagName Tag name.
+ * @throws ParseException If the specified string is not a valid tag.
+ */
+ public static Tag parseTag(String tagName) throws ParseException {
+ String trimmed = tagName.trim();
+
+ if (!Tag.isValid(trimmed)) {
+ throw new ParseException(
+ Messages.tagInvalid(trimmed)
+ );
+ }
+
+ return new Tag(trimmed);
+ }
+
+ /**
+ * Attempts to parse the specified string as a {@link AlternateContact}.
+ *
+ * Trims the specified string as part of parsing.
+ *
+ * @throws ParseException if the specified string is not a valid alternate contact.
+ */
+ public static AlternateContact parseAlternate(String alternateContact) throws ParseException {
+ String trimmed = alternateContact.trim();
+
+ if (!AlternateContact.isValid(trimmed)) {
+ throw new ParseException(
+ Messages.alternateContactInvalid(trimmed)
+ );
+ }
+
+ return new AlternateContact(trimmed);
+ }
+
+ /**
+ * Attempts to parse the specified strings as {@link Tag}s.
+ *
+ * @param tagNames Tag names.
+ */
+ public static Set parseTags(Collection tagNames) throws ParseException {
+ Set tags = new HashSet<>();
+ for (String tagName : tagNames) {
+ tags.add(
+ parseTag(tagName)
+ );
+ }
+ return tags;
+ }
+
+ /**
+ * Attempts to parse the specified strings as {@link AlternateContact}s.
+ *
+ * @param alternateContactNames AlternateContact names.
+ */
+ public static Set parseAlternates(Collection alternateContactNames)
+ throws ParseException {
+ Set alternateContacts = new HashSet<>();
+ for (String alternateContactName : alternateContactNames) {
+ alternateContacts.add(parseAlternate(alternateContactName));
+ }
+ return alternateContacts;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/swe/context/logic/parser/Prefix.java
similarity index 95%
rename from src/main/java/seedu/address/logic/parser/Prefix.java
rename to src/main/java/swe/context/logic/parser/Prefix.java
index 348b7686c8a..ba5d67bf183 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/swe/context/logic/parser/Prefix.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package swe.context.logic.parser;
/**
* A prefix that marks the beginning of an argument in an arguments string.
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/swe/context/logic/parser/exceptions/ParseException.java
similarity index 73%
rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
rename to src/main/java/swe/context/logic/parser/exceptions/ParseException.java
index 158a1a54c1c..59efbaa8b01 100644
--- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
+++ b/src/main/java/swe/context/logic/parser/exceptions/ParseException.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.parser.exceptions;
+package swe.context.logic.parser.exceptions;
-import seedu.address.commons.exceptions.IllegalValueException;
+import swe.context.commons.exceptions.IllegalValueException;
/**
* Represents a parse error encountered by a parser.
diff --git a/src/main/java/swe/context/model/Contacts.java b/src/main/java/swe/context/model/Contacts.java
new file mode 100644
index 00000000000..696f737c9d8
--- /dev/null
+++ b/src/main/java/swe/context/model/Contacts.java
@@ -0,0 +1,109 @@
+package swe.context.model;
+
+import java.util.ArrayList;
+
+import javafx.collections.ObservableList;
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.UniqueContactList;
+
+/**
+ * Mutable {@link Contact}s, both readable and writable.
+ *
+ * Duplicate {@link Contact}s are not allowed. Each {@link Contact} must be
+ * unique as determined by {@link #contains(Contact)}.
+ */
+public class Contacts implements ReadOnlyContacts {
+ private UniqueContactList uniqueList = new UniqueContactList();
+
+ /**
+ * Constructs with no {@link Contact}s.
+ */
+ public Contacts() {}
+
+ /**
+ * Constructs a shallow clone of the specified {@link ReadOnlyContacts}.
+ */
+ public Contacts(ReadOnlyContacts contacts) {
+ this.uniqueList.setContacts(
+ contacts.getUnmodifiableList()
+ );
+ }
+
+ @Override
+ public ObservableList getUnmodifiableList() {
+ return this.uniqueList.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("uniqueList", this.uniqueList)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Contacts)) {
+ return false;
+ }
+ Contacts otherContacts = (Contacts) other;
+
+ return this.uniqueList.equals(otherContacts.uniqueList);
+ }
+
+ @Override
+ public int hashCode() {
+ return uniqueList.hashCode();
+ }
+
+ /**
+ * Adds the specified {@link Contact}.
+ *
+ * The {@link Contact} must not already exist as determined by
+ * {@link #contains(Contact)}.
+ */
+ public void add(Contact contact) {
+ this.uniqueList.add(contact);
+ }
+
+ /**
+ * Returns whether there already exists a {@link Contact} that is the same
+ * as the specified one.
+ */
+ public boolean contains(Contact contact) {
+ return this.uniqueList.contains(contact);
+ }
+
+ /**
+ * Replaces the old {@link Contact} with the new one.
+ *
+ * The new {@link Contact} must not already exist as determined by
+ * {@link #contains(Contact)}.
+ *
+ * @param old The old contact to replace.
+ * @param updated The new, unique contact to take its place.
+ */
+ public void update(Contact old, Contact updated) {
+ this.uniqueList.setContact(old, updated);
+ }
+
+ /**
+ * Removes the specified {@link Contact}.
+ */
+ public void remove(Contact contact) {
+ this.uniqueList.remove(contact);
+ }
+
+ /**
+ * Removes all {@link Contact}s.
+ */
+ public void removeAll() {
+ this.uniqueList.setContacts(new ArrayList<>());
+ }
+}
diff --git a/src/main/java/swe/context/model/Model.java b/src/main/java/swe/context/model/Model.java
new file mode 100644
index 00000000000..0883fa3469e
--- /dev/null
+++ b/src/main/java/swe/context/model/Model.java
@@ -0,0 +1,38 @@
+package swe.context.model;
+
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import swe.context.commons.core.GuiSettings;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * API of the Model component.
+ */
+public interface Model {
+ public ReadOnlyContacts getContacts();
+
+ public void addContact(Contact contact);
+ public boolean containsContact(Contact contact);
+ public void updateContact(Contact old, Contact updated);
+ public void removeContact(Contact contact);
+ public void removeAllContacts();
+
+ public Settings getSettings();
+
+ public GuiSettings getGuiSettings();
+ public void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Filters the filtered contact list by the specified {@link Predicate}.
+ */
+ public void setContactsFilter(Predicate predicate);
+
+ /**
+ * Returns an unmodifiable {@link ObservableList} of currently filtered
+ * {@link Contact}s.
+ */
+ public ObservableList getFilteredContactList();
+}
diff --git a/src/main/java/swe/context/model/ModelManager.java b/src/main/java/swe/context/model/ModelManager.java
new file mode 100644
index 00000000000..ef917898fff
--- /dev/null
+++ b/src/main/java/swe/context/model/ModelManager.java
@@ -0,0 +1,129 @@
+package swe.context.model;
+
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import javafx.collections.transformation.SortedList;
+import swe.context.commons.core.GuiSettings;
+import swe.context.model.contact.Contact;
+import swe.context.model.util.AlphabeticalComparator;
+
+
+
+/**
+ * Implementation of the Model component.
+ */
+public class ModelManager implements Model {
+ /**
+ * {@link Predicate} that always evaluates to true. Using this as the filter
+ * for the filtered contact list will not filter out any {@link Contact}s.
+ */
+ public static final Predicate FILTER_NONE = (_contact) -> true;
+
+ private final Contacts contacts;
+ private final Settings settings;
+
+ private final FilteredList filteredContacts;
+
+ /**
+ * Constructs with default values.
+ */
+ public ModelManager() {
+ this(new Contacts(), new Settings());
+ }
+
+ /**
+ * Constructs with the specified values.
+ */
+ public ModelManager(ReadOnlyContacts contacts, ReadOnlySettings settings) {
+ this.contacts = new Contacts(contacts);
+ this.settings = new Settings(settings);
+
+ ObservableList unmodifiableList = this.contacts.getUnmodifiableList();
+ SortedList sortedList = new SortedList<>(
+ unmodifiableList,
+ new AlphabeticalComparator()
+ );
+ this.filteredContacts = new FilteredList<>(sortedList);
+ }
+
+ @Override
+ public ReadOnlyContacts getContacts() {
+ return this.contacts;
+ }
+
+ /**
+ * Adds the specified {@link Contact}.
+ *
+ * Also resets the contacts filter.
+ */
+ @Override
+ public void addContact(Contact contact) {
+ this.contacts.add(contact);
+
+ this.setContactsFilter(ModelManager.FILTER_NONE);
+ }
+
+ @Override
+ public boolean containsContact(Contact contact) {
+ return this.contacts.contains(contact);
+ }
+
+ @Override
+ public void updateContact(Contact old, Contact updated) {
+ this.contacts.update(old, updated);
+ }
+
+ @Override
+ public void removeContact(Contact contact) {
+ this.contacts.remove(contact);
+ }
+
+ @Override
+ public void removeAllContacts() {
+ this.contacts.removeAll();
+ }
+
+ @Override
+ public Settings getSettings() {
+ return this.settings;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return this.settings.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ this.settings.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public void setContactsFilter(Predicate predicate) {
+ this.filteredContacts.setPredicate(predicate);
+ }
+
+ @Override
+ public ObservableList getFilteredContactList() {
+ return this.filteredContacts;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof ModelManager)) {
+ return false;
+ }
+ ModelManager otherManager = (ModelManager) other;
+
+ return this.contacts.equals(otherManager.contacts)
+ && this.settings.equals(otherManager.settings)
+ && this.filteredContacts.equals(otherManager.filteredContacts);
+ }
+}
diff --git a/src/main/java/swe/context/model/ReadOnlyContacts.java b/src/main/java/swe/context/model/ReadOnlyContacts.java
new file mode 100644
index 00000000000..4673eb67687
--- /dev/null
+++ b/src/main/java/swe/context/model/ReadOnlyContacts.java
@@ -0,0 +1,16 @@
+package swe.context.model;
+
+import javafx.collections.ObservableList;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * Read-only view of all {@link Contact}s.
+ */
+public interface ReadOnlyContacts {
+ /**
+ * Returns an unmodifiable {@link ObservableList} of all {@link Contact}s.
+ */
+ public ObservableList getUnmodifiableList();
+}
diff --git a/src/main/java/swe/context/model/ReadOnlySettings.java b/src/main/java/swe/context/model/ReadOnlySettings.java
new file mode 100644
index 00000000000..36fa724428c
--- /dev/null
+++ b/src/main/java/swe/context/model/ReadOnlySettings.java
@@ -0,0 +1,16 @@
+package swe.context.model;
+
+import java.nio.file.Path;
+
+import swe.context.commons.core.GuiSettings;
+
+
+
+/**
+ * Read-only view of settings.
+ */
+public interface ReadOnlySettings {
+ public Path getContactsPath();
+
+ public GuiSettings getGuiSettings();
+}
diff --git a/src/main/java/swe/context/model/Settings.java b/src/main/java/swe/context/model/Settings.java
new file mode 100644
index 00000000000..6869680b0c5
--- /dev/null
+++ b/src/main/java/swe/context/model/Settings.java
@@ -0,0 +1,80 @@
+package swe.context.model;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+import swe.context.commons.core.GuiSettings;
+import swe.context.commons.util.ToStringBuilder;
+
+
+
+/**
+ * Mutable settings, both readable and writable.
+ */
+public class Settings implements ReadOnlySettings {
+ private Path contactsPath = Paths.get("data" , "contacts.json");
+
+ private GuiSettings guiSettings = new GuiSettings();
+
+ /**
+ * Constructs with default values.
+ */
+ public Settings() {}
+
+ /**
+ * Constructs a clone of the specified {@link ReadOnlySettings}.
+ */
+ public Settings(ReadOnlySettings settings) {
+ this.setContactsPath(settings.getContactsPath());
+
+ this.setGuiSettings(settings.getGuiSettings());
+ }
+
+ @Override
+ public Path getContactsPath() {
+ return this.contactsPath;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return this.guiSettings;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("contactsPath", this.contactsPath)
+ .add("guiSettings", this.guiSettings)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Settings)) {
+ return false;
+ }
+ Settings otherSettings = (Settings) other;
+
+ return this.contactsPath.equals(otherSettings.contactsPath)
+ && this.guiSettings.equals(otherSettings.guiSettings);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(contactsPath, guiSettings);
+ }
+
+ public void setContactsPath(Path newPath) {
+ this.contactsPath = newPath;
+ }
+
+ public void setGuiSettings(GuiSettings newGuiSettings) {
+ this.guiSettings = newGuiSettings;
+ }
+}
diff --git a/src/main/java/swe/context/model/alternate/AlternateContact.java b/src/main/java/swe/context/model/alternate/AlternateContact.java
new file mode 100644
index 00000000000..4353dcf4740
--- /dev/null
+++ b/src/main/java/swe/context/model/alternate/AlternateContact.java
@@ -0,0 +1,64 @@
+package swe.context.model.alternate;
+
+import swe.context.model.contact.Contact;
+
+
+/**
+ * Immutably represents a {@link Contact}'s AlternateContact.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+public final class AlternateContact {
+ /*
+ * Requires a form similar to AlternateContact: name.
+ */
+ public static final String REGEX_VALID =
+ "^[a-zA-Z\\d]+: [a-zA-Z\\d._-]+$";
+
+ public final String value;
+
+ /**
+ * Returns whether the specified value is valid.
+ *
+ * Alternate Contacts must roughly be of the form AlternateContact: name.
+ */
+ public static boolean isValid(String value) {
+ return value.matches(REGEX_VALID);
+ }
+
+ /**
+ * Constructs with the specified value.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+ public AlternateContact(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof AlternateContact)) {
+ return false;
+ }
+ AlternateContact otherAlternateContact = (AlternateContact) other;
+
+ return this.value.equals(otherAlternateContact.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+}
diff --git a/src/main/java/swe/context/model/contact/Contact.java b/src/main/java/swe/context/model/contact/Contact.java
new file mode 100644
index 00000000000..96c5626e1fb
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/Contact.java
@@ -0,0 +1,166 @@
+package swe.context.model.contact;
+
+import static swe.context.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.tag.Tag;
+
+
+/**
+ * Represents a Contact.
+ *
+ * Guarantees that all details are present and validated.
+ */
+public class Contact {
+ // Identity fields
+ private Name name;
+ private Phone phone;
+ private Email email;
+
+ // Data fields
+ private Note note;
+ private Set tags = new HashSet<>();
+ private Set alternates = new HashSet<>();
+
+ /**
+ * Constructs a Contact.
+ */
+ public Contact(
+ Name _name,
+ Phone _phone,
+ Email _email,
+ Note _note,
+ Set _tags,
+ Set _alternates
+ ) {
+ requireAllNonNull(_name, _phone, _email, _note, _tags, _alternates);
+
+ this.name = _name;
+ this.phone = _phone;
+ this.email = _email;
+ this.note = _note;
+ this.tags.addAll(_tags);
+ this.alternates.addAll(_alternates);
+ }
+
+ public Name getName() {
+ return this.name;
+ }
+
+ public Phone getPhone() {
+ return this.phone;
+ }
+
+ public Email getEmail() {
+ return this.email;
+ }
+
+ public Note getNote() {
+ return this.note;
+ }
+
+ /**
+ * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getTags() {
+ return Collections.unmodifiableSet(tags);
+ }
+
+ public Set getAlternates() {
+ return Collections.unmodifiableSet(alternates);
+ }
+
+ /**
+ * Returns true if both contacts have the same name.
+ * This defines a weaker notion of equality between two contacts.
+ */
+ public boolean isSameContact(Contact otherContact) {
+ if (otherContact == this) {
+ return true;
+ }
+
+ return otherContact != null
+ && otherContact.getName().equals(getName());
+ }
+
+ /**
+ * Formats this for display to the user.
+ */
+ public static String format(Contact contact) {
+ String tags = contact
+ .getTags()
+ .stream()
+ .map(Tag::toString)
+ .collect(Collectors.joining(", "));
+ String alternates = contact
+ .getAlternates()
+ .stream()
+ .map(AlternateContact::toString)
+ .collect(Collectors.joining(", "));
+
+ StringBuilder builder = new StringBuilder();
+ builder.append(contact.getName())
+ .append("; Phone: ")
+ .append(contact.getPhone())
+ .append("; Email: ")
+ .append(contact.getEmail())
+ .append("; Note: ")
+ .append(contact.getNote())
+ .append("; Tags: ")
+ .append(tags)
+ .append("; Alternate Contacts: ")
+ .append(alternates);
+ return builder.toString();
+ }
+
+ /**
+ * Returns true if both contacts have the same identity and data fields.
+ * This defines a stronger notion of equality between two contacts.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Contact)) {
+ return false;
+ }
+
+ Contact otherContact = (Contact) other;
+ return name.equals(otherContact.name)
+ && phone.equals(otherContact.phone)
+ && email.equals(otherContact.email)
+ && note.equals(otherContact.note)
+ && tags.equals(otherContact.tags)
+ && alternates.equals(otherContact.alternates);
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(name, phone, email, note, tags, alternates);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("note", note)
+ .add("tags", tags)
+ .add("alternate contacts", alternates)
+ .toString();
+ }
+
+}
diff --git a/src/main/java/swe/context/model/contact/ContainsTagPredicate.java b/src/main/java/swe/context/model/contact/ContainsTagPredicate.java
new file mode 100644
index 00000000000..4c90e17883c
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/ContainsTagPredicate.java
@@ -0,0 +1,50 @@
+package swe.context.model.contact;
+
+import java.util.function.Predicate;
+import java.util.Set;
+
+import swe.context.commons.util.ToStringBuilder;
+import swe.context.model.tag.Tag;
+
+/**
+ * Tests that a {@code Contact}'s Tags matches the tag given in full, case insensitive.
+ */
+public class ContainsTagPredicate implements Predicate {
+ private final String keyword;
+
+ public ContainsTagPredicate(String keyword) {
+ this.keyword = keyword;
+ }
+
+ @Override
+ public boolean test(Contact contact) {
+ Set tagSet = contact.getTags();
+
+ for (Tag tag : tagSet) {
+ if (tag.toString().equalsIgnoreCase(this.keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ContainsTagPredicate)) {
+ return false;
+ }
+
+ ContainsTagPredicate otherContainsTagPredicate = (ContainsTagPredicate) other;
+ return keyword.equals(otherContainsTagPredicate.keyword);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("keyword", keyword).toString();
+ }
+}
diff --git a/src/main/java/swe/context/model/contact/Email.java b/src/main/java/swe/context/model/contact/Email.java
new file mode 100644
index 00000000000..6ab73c3323a
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/Email.java
@@ -0,0 +1,62 @@
+package swe.context.model.contact;
+
+
+
+/**
+ * Immutably represents a {@link Contact}'s email.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+public class Email {
+ /*
+ * Requires a form similar to example_email@foo-domain.sg.
+ */
+ public static final String REGEX_VALID = "^[a-zA-Z\\d]+(?:[+_.-][a-zA-Z\\d]+)*"
+ + "@(?:[a-zA-Z\\d]+(?:-[a-zA-Z\\d]+)*\\.)+"
+ + "[a-zA-Z\\d]+(?:-[a-zA-Z\\d]+)*$";
+
+ public final String value;
+
+ /**
+ * Returns whether the specified value is valid.
+ */
+ public static boolean isValid(String value) {
+ return value.matches(Email.REGEX_VALID);
+ }
+
+ /**
+ * Constructs with the specified value.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+ public Email(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Email)) {
+ return false;
+ }
+ Email otherEmail = (Email) other;
+
+ return this.value.equals(otherEmail.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+}
diff --git a/src/main/java/swe/context/model/contact/Name.java b/src/main/java/swe/context/model/contact/Name.java
new file mode 100644
index 00000000000..c9a770153d2
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/Name.java
@@ -0,0 +1,63 @@
+package swe.context.model.contact;
+
+
+
+/**
+ * Immutably represents a {@link Contact}'s name.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+public class Name {
+ /*
+ * Disallows a space as the first character. This prevents " " from being
+ * valid.
+ */
+ public static final String REGEX_VALID = "^[a-zA-Z\\d][a-zA-Z\\d ]*$";
+
+ public final String value;
+
+ /**
+ * Returns whether the specified value is valid.
+ *
+ * Names must be an alphanumeric and may contain spaces, but cannot start with a space.
+ */
+ public static boolean isValid(String value) {
+ return value.matches(Name.REGEX_VALID);
+ }
+
+ /**
+ * Constructs with the specified value.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+ public Name(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Name)) {
+ return false;
+ }
+ Name otherName = (Name) other;
+
+ return this.value.equals(otherName.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/swe/context/model/contact/NameContainsKeywordsPredicate.java
similarity index 76%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/swe/context/model/contact/NameContainsKeywordsPredicate.java
index 62d19be2977..6d8e54fd7f3 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/swe/context/model/contact/NameContainsKeywordsPredicate.java
@@ -1,15 +1,15 @@
-package seedu.address.model.person;
+package swe.context.model.contact;
import java.util.List;
import java.util.function.Predicate;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.commons.util.ToStringBuilder;
+import swe.context.commons.util.StringUtil;
+import swe.context.commons.util.ToStringBuilder;
/**
- * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ * Tests that a {@code Contact}'s {@code Name} matches any of the keywords given.
*/
-public class NameContainsKeywordsPredicate implements Predicate {
+public class NameContainsKeywordsPredicate implements Predicate {
private final List keywords;
public NameContainsKeywordsPredicate(List keywords) {
@@ -17,9 +17,9 @@ public NameContainsKeywordsPredicate(List keywords) {
}
@Override
- public boolean test(Person person) {
+ public boolean test(Contact contact) {
return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(contact.getName().value, keyword));
}
@Override
diff --git a/src/main/java/swe/context/model/contact/Note.java b/src/main/java/swe/context/model/contact/Note.java
new file mode 100644
index 00000000000..03572194ea4
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/Note.java
@@ -0,0 +1,39 @@
+package swe.context.model.contact;
+
+
+
+/**
+ * Immutably represents a {@link Contact}'s note.
+ */
+public class Note {
+ public final String value;
+
+ public Note(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Note)) {
+ return false;
+ }
+ Note otherNote = (Note) other;
+
+ return this.value.equals(otherNote.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+}
diff --git a/src/main/java/swe/context/model/contact/Phone.java b/src/main/java/swe/context/model/contact/Phone.java
new file mode 100644
index 00000000000..3f8b09fce65
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/Phone.java
@@ -0,0 +1,60 @@
+package swe.context.model.contact;
+
+
+
+/**
+ * Immutably represents a {@link Contact}'s phone number.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+public class Phone {
+ /*
+ * Requires starting with at least 3 digits and extra characters are allowed after.
+ */
+ public static final String REGEX_VALID = "^\\d{3,}.*$";
+
+ public final String value;
+
+ /**
+ * Returns true if a given string is a valid phone number.
+ */
+ public static boolean isValid(String value) {
+ return value.matches(Phone.REGEX_VALID);
+ }
+
+ /**
+ * Constructs with the specified value.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+ public Phone(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Phone)) {
+ return false;
+ }
+ Phone otherPhone = (Phone) other;
+
+ return this.value.equals(otherPhone.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+}
diff --git a/src/main/java/swe/context/model/contact/UniqueContactList.java b/src/main/java/swe/context/model/contact/UniqueContactList.java
new file mode 100644
index 00000000000..e15cb5d5f29
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/UniqueContactList.java
@@ -0,0 +1,146 @@
+package swe.context.model.contact;
+
+import static java.util.Objects.requireNonNull;
+import static swe.context.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import swe.context.model.contact.exceptions.ContactNotFoundException;
+import swe.context.model.contact.exceptions.DuplicateContactException;
+
+
+
+/**
+ * A list of contacts that enforces uniqueness between its elements and does not allow nulls.
+ * A contact is considered unique by comparing using {@code Contact#isSameContact(Contact)}. As such, adding and
+ * updating of contacts uses Contact#isSameContact(Contact) for equality so as to ensure that the contact being added
+ * or updated is unique in terms of identity in the UniqueContactList. However, the removal of a contact uses
+ * Contact#equals(Object) so as to ensure that the contact with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Contact#isSameContact(Contact)
+ */
+public class UniqueContactList implements Iterable {
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent contact as the given argument.
+ */
+ public boolean contains(Contact toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameContact);
+ }
+
+ /**
+ * Adds a contact to the list.
+ * The contact must not already exist in the list.
+ */
+ public void add(Contact toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateContactException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the contact {@code target} in the list with {@code editedContact}.
+ * {@code target} must exist in the list.
+ * The contact identity of {@code editedContact} must not be the same as another existing contact in the list.
+ */
+ public void setContact(Contact target, Contact editedContact) {
+ requireAllNonNull(target, editedContact);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new ContactNotFoundException();
+ }
+
+ if (!target.isSameContact(editedContact) && contains(editedContact)) {
+ throw new DuplicateContactException();
+ }
+
+ internalList.set(index, editedContact);
+ }
+
+ /**
+ * Removes the equivalent contact from the list.
+ * The contact must exist in the list.
+ */
+ public void remove(Contact toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new ContactNotFoundException();
+ }
+ }
+
+ /**
+ * Replaces the contents of this list with {@code contacts}.
+ * {@code contacts} must not contain duplicate contacts.
+ */
+ public void setContacts(List contacts) {
+ requireAllNonNull(contacts);
+ if (!contactsAreUnique(contacts)) {
+ throw new DuplicateContactException();
+ }
+
+ internalList.setAll(contacts);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UniqueContactList)) {
+ return false;
+ }
+
+ UniqueContactList otherUniqueContactList = (UniqueContactList) other;
+ return internalList.equals(otherUniqueContactList.internalList);
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return internalList.toString();
+ }
+
+ /**
+ * Returns true if {@code contacts} contains only unique contacts.
+ */
+ private boolean contactsAreUnique(List contacts) {
+ for (int i = 0; i < contacts.size() - 1; i++) {
+ for (int j = i + 1; j < contacts.size(); j++) {
+ if (contacts.get(i).isSameContact(contacts.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/swe/context/model/contact/exceptions/ContactNotFoundException.java b/src/main/java/swe/context/model/contact/exceptions/ContactNotFoundException.java
new file mode 100644
index 00000000000..c10fdaf1748
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/exceptions/ContactNotFoundException.java
@@ -0,0 +1,6 @@
+package swe.context.model.contact.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified contact.
+ */
+public class ContactNotFoundException extends RuntimeException {}
diff --git a/src/main/java/swe/context/model/contact/exceptions/DuplicateContactException.java b/src/main/java/swe/context/model/contact/exceptions/DuplicateContactException.java
new file mode 100644
index 00000000000..5d007236556
--- /dev/null
+++ b/src/main/java/swe/context/model/contact/exceptions/DuplicateContactException.java
@@ -0,0 +1,13 @@
+package swe.context.model.contact.exceptions;
+
+import swe.context.logic.Messages;
+
+/**
+ * Signals that the operation will result in duplicate Contacts (Contacts are considered duplicates if they have the
+ * same identity).
+ */
+public class DuplicateContactException extends RuntimeException {
+ public DuplicateContactException() {
+ super(Messages.DUPLICATE_CONTACT_EXCEPTION);
+ }
+}
diff --git a/src/main/java/swe/context/model/tag/Tag.java b/src/main/java/swe/context/model/tag/Tag.java
new file mode 100644
index 00000000000..0b714249c54
--- /dev/null
+++ b/src/main/java/swe/context/model/tag/Tag.java
@@ -0,0 +1,60 @@
+package swe.context.model.tag;
+
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.Name;
+
+
+
+/**
+ * Immutably represents a {@link Contact}'s tag.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+public final class Tag {
+ public final String value;
+
+ /**
+ * Returns whether the specified value is valid.
+ *
+ * Tags must be an alphanumeric and may contain spaces, but cannot start with a space.
+ */
+ public static boolean isValid(String value) {
+ return value.matches(Name.REGEX_VALID);
+ }
+
+ /**
+ * Constructs with the specified value.
+ *
+ * Constructor arguments must be valid as determined by
+ * {@link #isValid(String)}.
+ */
+ public Tag(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof also handles nulls
+ if (!(other instanceof Tag)) {
+ return false;
+ }
+ Tag otherTag = (Tag) other;
+
+ return this.value.equals(otherTag.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value.hashCode();
+ }
+}
diff --git a/src/main/java/swe/context/model/util/AlphabeticalComparator.java b/src/main/java/swe/context/model/util/AlphabeticalComparator.java
new file mode 100644
index 00000000000..4e49e260b3f
--- /dev/null
+++ b/src/main/java/swe/context/model/util/AlphabeticalComparator.java
@@ -0,0 +1,27 @@
+package swe.context.model.util;
+
+import java.util.Comparator;
+
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * Sorts {@link Contact}s alphabetically.
+ */
+public class AlphabeticalComparator implements Comparator {
+ @Override
+ public int compare(Contact a, Contact b) {
+ String aName = a.getName().value;
+ String bName = b.getName().value;
+
+ int n = aName.toUpperCase().compareTo(bName.toUpperCase());
+ if (n != 0) {
+ return n;
+ }
+
+ // Only take true capitalization into account if names would otherwise
+ // be identical
+ return aName.compareTo(bName);
+ }
+}
diff --git a/src/main/java/swe/context/model/util/SampleContactsUtil.java b/src/main/java/swe/context/model/util/SampleContactsUtil.java
new file mode 100644
index 00000000000..ba33496f905
--- /dev/null
+++ b/src/main/java/swe/context/model/util/SampleContactsUtil.java
@@ -0,0 +1,92 @@
+package swe.context.model.util;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import swe.context.model.Contacts;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.Email;
+import swe.context.model.contact.Name;
+import swe.context.model.contact.Note;
+import swe.context.model.contact.Phone;
+import swe.context.model.tag.Tag;
+
+
+
+/**
+ * Contains utility methods for working with sample {@link Contact}s.
+ */
+public class SampleContactsUtil {
+ private static Contact[] getSampleContactsArray() {
+ return new Contact[] {
+ new Contact(
+ new Name("Amy Bee"),
+ new Phone("87438807"),
+ new Email("e0705018@u.nus.edu"),
+ new Note("CS2103 tutorial mate."),
+ getTagSet("NUS", "CS2103 course"),
+ getAlternateContactSet("Telegram: amy_bee")
+ ),
+ new Contact(
+ new Name("Ben Lee"),
+ new Phone("99272758"),
+ new Email("ben-lee@gmail.com"),
+ new Note("Dorm at 42 Wallaby Way."),
+ getTagSet("friend"),
+ getAlternateContactSet("HousePhone: 67280491", "Discord: ben.games")
+ ),
+ new Contact(
+ new Name("Charlotte Oliveiro"),
+ new Phone("83210283"),
+ new Email("coliveiro@comp.nus.edu.sg"),
+ new Note("CS2103 Prof."),
+ getTagSet("NUS", "CS2103 course", "prof"),
+ getAlternateContactSet()
+ ),
+ new Contact(
+ new Name("David Li"),
+ new Phone("91031282"),
+ new Email("e0789123@u.nus.edu"),
+ new Note(""),
+ getTagSet("NUS", "CS1101S", "CS1231S"),
+ getAlternateContactSet("Telegram: David99")
+ ),
+ new Contact(
+ new Name("Ethan Ibrahim"),
+ new Phone("92492021"),
+ new Email("ethan.ibrahim@my-mail.com"),
+ new Note("(teaching assistant)"),
+ getTagSet("NUS", "CS3241", "TA"),
+ getAlternateContactSet("X: ethaaan", "Telegram: ethaaan")
+ )
+ };
+ }
+
+ /**
+ * Returns a {@link Set} of {@link Tag}s for the specified strings.
+ */
+ public static Set getTagSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(Tag::new)
+ .collect(Collectors.toSet());
+ }
+
+ public static Set getAlternateContactSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(AlternateContact::new)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns a {@link Contacts} instance populated with sample {@link Contact}s.
+ */
+ public static Contacts getSampleContacts() {
+ Contacts contacts = new Contacts();
+ for (Contact contact : getSampleContactsArray()) {
+ contacts.add(contact);
+ }
+ return contacts;
+ }
+}
diff --git a/src/main/java/swe/context/storage/ContactsStorage.java b/src/main/java/swe/context/storage/ContactsStorage.java
new file mode 100644
index 00000000000..bd19b1286fe
--- /dev/null
+++ b/src/main/java/swe/context/storage/ContactsStorage.java
@@ -0,0 +1,39 @@
+package swe.context.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import swe.context.commons.exceptions.DataLoadingException;
+import swe.context.model.Contacts;
+import swe.context.model.ReadOnlyContacts;
+
+
+
+/**
+ * Represents a storage for {@link Contacts}.
+ */
+public interface ContactsStorage {
+ /**
+ * Returns the {@link Path} of the contacts storage file.
+ */
+ public Path getContactsPath();
+
+ /**
+ * Returns {@link Optional} {@link Contacts} by reading the contacts
+ * storage file.
+ *
+ * If no file is found, returns {@link Optional.empty} instead.
+ *
+ * @throws DataLoadingException If loading data from the file fails.
+ */
+ public Optional readContacts() throws DataLoadingException;
+
+ /**
+ * Saves the specified {@link ReadOnlyContacts} to the contacts storage
+ * file.
+ *
+ * @throws IOException If writing data to the file fails.
+ */
+ public void saveContacts(ReadOnlyContacts contacts) throws IOException;
+}
diff --git a/src/main/java/swe/context/storage/JsonAlternateContact.java b/src/main/java/swe/context/storage/JsonAlternateContact.java
new file mode 100644
index 00000000000..e74e9348b47
--- /dev/null
+++ b/src/main/java/swe/context/storage/JsonAlternateContact.java
@@ -0,0 +1,51 @@
+package swe.context.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import swe.context.commons.exceptions.IllegalValueException;
+import swe.context.logic.Messages;
+import swe.context.model.alternate.AlternateContact;
+
+
+/**
+ * Immutable, Jackson-friendly version of {@link AlternateContact}.
+ *
+ * The data it contains may be invalid if the instance was deserialized from
+ * JSON. Checks are done when converting {@link #toModelType()}.
+ */
+public final class JsonAlternateContact {
+ private final String value;
+
+ /**
+ * Constructs by converting the specified {@link AlternateContact}.
+ */
+ public JsonAlternateContact(AlternateContact alternateContact) {
+ this(alternateContact.value);
+ }
+
+ @JsonCreator
+ public JsonAlternateContact(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * Attempts to convert this to the model's {@link AlternateContact} type.
+ *
+ * @throws IllegalValueException If any data this contains is invalid.
+ */
+ public AlternateContact toModelType() throws IllegalValueException {
+ if (!AlternateContact.isValid(this.value)) {
+ throw new IllegalValueException(
+ Messages.alternateContactInvalid(this.value)
+ );
+ }
+
+ return new AlternateContact(this.value);
+ }
+}
diff --git a/src/main/java/swe/context/storage/JsonContact.java b/src/main/java/swe/context/storage/JsonContact.java
new file mode 100644
index 00000000000..9716512897d
--- /dev/null
+++ b/src/main/java/swe/context/storage/JsonContact.java
@@ -0,0 +1,133 @@
+package swe.context.storage;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import swe.context.annotation.Nullable;
+import swe.context.commons.exceptions.IllegalValueException;
+import swe.context.logic.Messages;
+import swe.context.model.alternate.AlternateContact;
+import swe.context.model.contact.Contact;
+import swe.context.model.contact.Email;
+import swe.context.model.contact.Name;
+import swe.context.model.contact.Note;
+import swe.context.model.contact.Phone;
+import swe.context.model.tag.Tag;
+
+
+
+/**
+ * Immutable, Jackson-friendly version of {@link Contact}.
+ *
+ * The data it contains may be invalid if the instance was deserialized from
+ * JSON. Checks are done when converting {@link #toModelType()}.
+ */
+class JsonContact {
+ private final @Nullable String name;
+ private final @Nullable String phone;
+ private final @Nullable String email;
+ private final @Nullable String note;
+ private final List tags = new ArrayList<>();
+ private final List alternateContacts = new ArrayList<>();
+
+ /**
+ * Constructs by converting the specified {@link Contact}.
+ */
+ public JsonContact(Contact contact) {
+ this(
+ contact.getName().value,
+ contact.getPhone().value,
+ contact.getEmail().value,
+ contact.getNote().value,
+ contact.getTags()
+ .stream()
+ .map(JsonTag::new)
+ .collect(Collectors.toList()),
+ contact.getAlternates()
+ .stream()
+ .map(JsonAlternateContact::new)
+ .collect(Collectors.toList())
+ );
+ }
+
+ @JsonCreator
+ public JsonContact(
+ @JsonProperty("name") @Nullable String name,
+ @JsonProperty("phone") @Nullable String phone,
+ @JsonProperty("email") @Nullable String email,
+ @JsonProperty("note") @Nullable String note,
+ @JsonProperty("tags") @Nullable List tags,
+ @JsonProperty("alternateContacts") @Nullable List alternateContacts
+ ) {
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.note = note;
+
+ if (tags != null) {
+ this.tags.addAll(tags);
+ }
+
+ if (alternateContacts != null) {
+ this.alternateContacts.addAll(alternateContacts);
+ }
+ }
+
+ /**
+ * Attempts to convert this to the model's {@link Contact} type.
+ *
+ * @throws IllegalValueException If any data this contains is invalid.
+ */
+ public Contact toModelType() throws IllegalValueException {
+ final List contactTags = new ArrayList<>();
+ for (JsonTag tag : tags) {
+ contactTags.add(tag.toModelType());
+ }
+
+ final List contactAlternates = new ArrayList<>();
+ for (JsonAlternateContact alternateContact : alternateContacts) {
+ contactAlternates.add(alternateContact.toModelType());
+ }
+
+ if (name == null) {
+ throw new IllegalValueException(Messages.fieldMissing(Name.class.getSimpleName()));
+ }
+ if (!Name.isValid(name)) {
+ throw new IllegalValueException(Messages.NAME_INVALID);
+ }
+ final Name modelName = new Name(name);
+
+ if (phone == null) {
+ throw new IllegalValueException(Messages.fieldMissing(Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValid(phone)) {
+ throw new IllegalValueException(Messages.PHONE_INVALID);
+ }
+ final Phone modelPhone = new Phone(phone);
+
+ if (email == null) {
+ throw new IllegalValueException(Messages.fieldMissing(Email.class.getSimpleName()));
+ }
+ if (!Email.isValid(email)) {
+ throw new IllegalValueException(Messages.EMAIL_INVALID);
+ }
+ final Email modelEmail = new Email(email);
+
+ if (this.note == null) {
+ throw new IllegalValueException(Messages.fieldMissing(Note.class.getSimpleName()));
+ }
+ final Note modelNote = new Note(this.note);
+
+ final Set modelTags = new HashSet<>(contactTags);
+ final Set modelAlternateContacts = new HashSet<>(contactAlternates);
+
+ return new Contact(modelName, modelPhone, modelEmail, modelNote, modelTags, modelAlternateContacts);
+
+ }
+}
diff --git a/src/main/java/swe/context/storage/JsonContacts.java b/src/main/java/swe/context/storage/JsonContacts.java
new file mode 100644
index 00000000000..0488a9e495b
--- /dev/null
+++ b/src/main/java/swe/context/storage/JsonContacts.java
@@ -0,0 +1,65 @@
+package swe.context.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 swe.context.annotation.Nullable;
+import swe.context.commons.exceptions.IllegalValueException;
+import swe.context.logic.Messages;
+import swe.context.model.Contacts;
+import swe.context.model.ReadOnlyContacts;
+import swe.context.model.contact.Contact;
+
+
+
+/**
+ * Immutable, Jackson-friendly version of {@link ReadOnlyContacts}.
+ *
+ * The data it contains may be invalid if the instance was deserialized from
+ * JSON. Checks are done when converting {@link #toModelType()}.
+ */
+class JsonContacts {
+ private final List contacts = new ArrayList<>();
+
+ /**
+ * Constructs by converting the specified {@link ReadOnlyContacts}.
+ */
+ public JsonContacts(ReadOnlyContacts contacts) {
+ this(
+ contacts.getUnmodifiableList()
+ .stream()
+ .map(JsonContact::new)
+ .collect(Collectors.toList())
+ );
+ }
+
+ @JsonCreator
+ public JsonContacts(
+ @JsonProperty("contacts") @Nullable List contacts
+ ) {
+ if (contacts != null) {
+ this.contacts.addAll(contacts);
+ }
+ }
+
+ /**
+ * Attempts to convert this to the model's {@link Contact} type.
+ *
+ * @throws IllegalValueException If any data this contains is invalid.
+ */
+ public Contacts toModelType() throws IllegalValueException {
+ Contacts contacts = new Contacts();
+ for (JsonContact jsonContact : this.contacts) {
+ Contact contact = jsonContact.toModelType();
+ if (contacts.contains(contact)) {
+ throw new IllegalValueException(Messages.CONVERT_CONTACTS_DUPLICATE);
+ }
+ contacts.add(contact);
+ }
+ return contacts;
+ }
+}
diff --git a/src/main/java/swe/context/storage/JsonContactsStorage.java b/src/main/java/swe/context/storage/JsonContactsStorage.java
new file mode 100644
index 00000000000..f21b5d72835
--- /dev/null
+++ b/src/main/java/swe/context/storage/JsonContactsStorage.java
@@ -0,0 +1,68 @@
+package swe.context.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import swe.context.commons.core.LogsCenter;
+import swe.context.commons.exceptions.DataLoadingException;
+import swe.context.commons.exceptions.IllegalValueException;
+import swe.context.commons.util.JsonUtil;
+import swe.context.commons.util.StringUtil;
+import swe.context.model.Contacts;
+import swe.context.model.ReadOnlyContacts;
+
+
+
+/**
+ * Handles reading and saving {@link Contacts} to and from the contacts storage
+ * JSON file.
+ *
+ * Contains an immutable {@link Path}.
+ */
+public class JsonContactsStorage implements ContactsStorage {
+ private static final Logger logger = LogsCenter.getLogger(JsonContactsStorage.class);
+
+ private final Path path;
+
+ public JsonContactsStorage(Path path) {
+ this.path = path;
+ }
+
+ @Override
+ public Path getContactsPath() {
+ return this.path;
+ }
+
+ @Override
+ public Optional readContacts() throws DataLoadingException {
+ Optional jsonContactsOptional = JsonUtil.readJsonFile(
+ this.path,
+ JsonContacts.class
+ );
+ if (!jsonContactsOptional.isPresent()) {
+ return Optional.empty();
+ }
+ JsonContacts jsonContacts = jsonContactsOptional.get();
+
+ try {
+ Contacts contacts = jsonContacts.toModelType();
+ return Optional.of(contacts);
+ } catch (IllegalValueException e) {
+ logger.info(String.format(
+ "Found illegal values after reading contacts storage JSON file."
+ + "\nPath: %s"
+ + "\nDetails: %s",
+ this.path,
+ StringUtil.getDetails(e)
+ ));
+ throw new DataLoadingException(e);
+ }
+ }
+
+ @Override
+ public void saveContacts(ReadOnlyContacts contacts) throws IOException {
+ JsonUtil.saveJsonFile(new JsonContacts(contacts), this.path);
+ }
+}
diff --git a/src/main/java/swe/context/storage/JsonSettingsStorage.java b/src/main/java/swe/context/storage/JsonSettingsStorage.java
new file mode 100644
index 00000000000..53b86fad63b
--- /dev/null
+++ b/src/main/java/swe/context/storage/JsonSettingsStorage.java
@@ -0,0 +1,44 @@
+package swe.context.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import swe.context.commons.exceptions.DataLoadingException;
+import swe.context.commons.util.JsonUtil;
+import swe.context.model.ReadOnlySettings;
+import swe.context.model.Settings;
+
+
+
+/**
+ * Handles reading and saving {@link Settings} to and from the settings storage
+ * JSON file.
+ *
+ * Contains an immutable {@link Path}.
+ */
+public class JsonSettingsStorage implements SettingsStorage {
+ private final Path path;
+
+ public JsonSettingsStorage(Path path) {
+ this.path = path;
+ }
+
+ @Override
+ public Path getSettingsPath() {
+ return this.path;
+ }
+
+ @Override
+ public Optional readSettings() throws DataLoadingException {
+ return JsonUtil.readJsonFile(
+ this.path,
+ Settings.class
+ );
+ }
+
+ @Override
+ public void saveSettings(ReadOnlySettings settings) throws IOException {
+ JsonUtil.saveJsonFile(settings, this.path);
+ }
+}
diff --git a/src/main/java/swe/context/storage/JsonTag.java b/src/main/java/swe/context/storage/JsonTag.java
new file mode 100644
index 00000000000..521c087f795
--- /dev/null
+++ b/src/main/java/swe/context/storage/JsonTag.java
@@ -0,0 +1,52 @@
+package swe.context.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import swe.context.commons.exceptions.IllegalValueException;
+import swe.context.logic.Messages;
+import swe.context.model.tag.Tag;
+
+
+
+/**
+ * Immutable, Jackson-friendly version of {@link Tag}.
+ *
+ * The data it contains may be invalid if the instance was deserialized from
+ * JSON. Checks are done when converting {@link #toModelType()}.
+ */
+public final class JsonTag {
+ private final String value;
+
+ /**
+ * Constructs by converting the specified {@link Tag}.
+ */
+ public JsonTag(Tag tag) {
+ this(tag.value);
+ }
+
+ @JsonCreator
+ public JsonTag(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * Attempts to convert this to the model's {@link Tag} type.
+ *
+ * @throws IllegalValueException If any data this contains is invalid.
+ */
+ public Tag toModelType() throws IllegalValueException {
+ if (!Tag.isValid(this.value)) {
+ throw new IllegalValueException(
+ Messages.tagInvalid(this.value)
+ );
+ }
+
+ return new Tag(this.value);
+ }
+}
diff --git a/src/main/java/swe/context/storage/SettingsStorage.java b/src/main/java/swe/context/storage/SettingsStorage.java
new file mode 100644
index 00000000000..ba12320813b
--- /dev/null
+++ b/src/main/java/swe/context/storage/SettingsStorage.java
@@ -0,0 +1,39 @@
+package swe.context.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import swe.context.commons.exceptions.DataLoadingException;
+import swe.context.model.ReadOnlySettings;
+import swe.context.model.Settings;
+
+
+
+/**
+ * Represents a storage for {@link Settings}.
+ */
+public interface SettingsStorage {
+ /**
+ * Returns the {@link Path} of the settings storage file.
+ */
+ public Path getSettingsPath();
+
+ /**
+ * Returns {@link Optional} {@link Settings} by reading the settings
+ * storage file.
+ *
+ * If no file is found, returns {@link Optional.empty} instead.
+ *
+ * @throws DataLoadingException If loading data from the file fails.
+ */
+ public Optional readSettings() throws DataLoadingException;
+
+ /**
+ * Saves the specified {@link ReadOnlySettings} to the settings storage
+ * file.
+ *
+ * @throws IOException If writing data to the file fails.
+ */
+ public void saveSettings(ReadOnlySettings settings) throws IOException;
+}
diff --git a/src/main/java/swe/context/storage/Storage.java b/src/main/java/swe/context/storage/Storage.java
new file mode 100644
index 00000000000..1d7891c209f
--- /dev/null
+++ b/src/main/java/swe/context/storage/Storage.java
@@ -0,0 +1,8 @@
+package swe.context.storage;
+
+
+
+/**
+ * API of the Storage component.
+ */
+public interface Storage extends ContactsStorage, SettingsStorage {}
diff --git a/src/main/java/swe/context/storage/StorageManager.java b/src/main/java/swe/context/storage/StorageManager.java
new file mode 100644
index 00000000000..f7db4d0c652
--- /dev/null
+++ b/src/main/java/swe/context/storage/StorageManager.java
@@ -0,0 +1,64 @@
+package swe.context.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import swe.context.commons.exceptions.DataLoadingException;
+import swe.context.model.Contacts;
+import swe.context.model.ReadOnlyContacts;
+import swe.context.model.ReadOnlySettings;
+import swe.context.model.Settings;
+
+
+
+/**
+ * Implementation of the Storage component.
+ *
+ * Contains immutable {@link Path}s.
+ */
+public class StorageManager implements Storage {
+ private ContactsStorage contactsStorage;
+ private SettingsStorage settingsStorage;
+
+ /**
+ * Constructs with the specified values.
+ */
+ public StorageManager(
+ ContactsStorage contactsStorage,
+ SettingsStorage settingsStorage
+ ) {
+ this.contactsStorage = contactsStorage;
+ this.settingsStorage = settingsStorage;
+ }
+
+ @Override
+ public Path getContactsPath() {
+ return this.contactsStorage.getContactsPath();
+ }
+
+ @Override
+ public Optional readContacts() throws DataLoadingException {
+ return this.contactsStorage.readContacts();
+ }
+
+ @Override
+ public void saveContacts(ReadOnlyContacts contacts) throws IOException {
+ this.contactsStorage.saveContacts(contacts);
+ }
+
+ @Override
+ public Path getSettingsPath() {
+ return this.settingsStorage.getSettingsPath();
+ }
+
+ @Override
+ public Optional readSettings() throws DataLoadingException {
+ return this.settingsStorage.readSettings();
+ }
+
+ @Override
+ public void saveSettings(ReadOnlySettings settings) throws IOException {
+ this.settingsStorage.saveSettings(settings);
+ }
+}
diff --git a/src/main/java/swe/context/ui/CommandBox.java b/src/main/java/swe/context/ui/CommandBox.java
new file mode 100644
index 00000000000..48fab964f3d
--- /dev/null
+++ b/src/main/java/swe/context/ui/CommandBox.java
@@ -0,0 +1,146 @@
+package swe.context.ui;
+
+import java.util.NoSuchElementException;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Region;
+import swe.context.logic.commands.CommandResult;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.logic.parser.exceptions.ParseException;
+
+/**
+ * The UI component that is responsible for receiving user command inputs.
+ */
+public class CommandBox extends UiPart {
+
+ public static final String ERROR_STYLE_CLASS = "error";
+ private static final String FXML = "CommandBox.fxml";
+
+ private final CommandExecutor commandExecutor;
+ private final CommandBoxHistory commandBoxHistory;
+
+ @FXML
+ private TextField commandTextField;
+
+ /**
+ * Creates a {@code CommandBox} with the given {@code CommandExecutor} and empty {@code CommandBoxHistory}.
+ */
+ public CommandBox(CommandExecutor commandExecutor) {
+ super(FXML);
+ this.commandExecutor = commandExecutor;
+ this.commandBoxHistory = new CommandBoxHistory();
+ // calls #setStyleToDefault() whenever there is a change to the text of the command box.
+ commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
+ }
+
+ /**
+ * Handles the Enter button pressed event.
+ */
+ @FXML
+ private void handleCommandEntered() {
+ String commandText = commandTextField.getText();
+ if (commandText.equals("")) {
+ return;
+ }
+
+ try {
+ commandExecutor.execute(commandText);
+ commandBoxHistory.add(commandText);
+ commandBoxHistory.resetPointer();
+ commandTextField.setText("");
+ } catch (CommandException | ParseException e) {
+ setStyleToIndicateCommandFailure();
+ }
+ }
+
+ //Solution below inspired by
+ //https://github.com/se-edu/addressbook-level4/blob/master/src/main/java/seedu/address/ui/CommandBox.java
+ //(implemented first, then modified after learning about the approach in AB4, which is slightly different)
+ /**
+ * Handles the up or down button pressed event.
+ */
+ @FXML
+ private void handleKeyPress(KeyEvent keyEvent) {
+ switch (keyEvent.getCode()) {
+ case UP:
+ keyEvent.consume();
+ this.switchToPreviousCommand();
+ break;
+ case DOWN:
+ keyEvent.consume();
+ this.switchToNextCommand();
+ break;
+ default:
+ }
+ }
+
+ /**
+ * Switch to the previous command in {@code commandBoxHistory}, if there is such a command.
+ */
+ private void switchToPreviousCommand() {
+ assert commandBoxHistory != null;
+ try {
+ this.replaceText(commandBoxHistory.previous());
+ } catch (NoSuchElementException e) {
+ return;
+ }
+ }
+
+ /**
+ * Switch to the next command in {@code commandBoxHistory}, if there is such a command.
+ */
+ private void switchToNextCommand() {
+ assert commandBoxHistory != null;
+ try {
+ this.replaceText(commandBoxHistory.next());
+ } catch (NoSuchElementException e) {
+ return;
+ }
+ }
+
+ /**
+ * Sets {@code CommandBox}'s text field with {@code text} and
+ * positions the caret to the end of the {@code text}.
+ */
+ private void replaceText(String text) {
+ commandTextField.setText(text);
+ commandTextField.positionCaret(commandTextField.getText().length());
+ }
+
+ /**
+ * Sets the command box style to use the default style.
+ */
+ private void setStyleToDefault() {
+ commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
+ }
+
+ /**
+ * Sets the command box style to indicate a failed command.
+ */
+ private void setStyleToIndicateCommandFailure() {
+ ObservableList styleClass = commandTextField.getStyleClass();
+
+ if (styleClass.contains(ERROR_STYLE_CLASS)) {
+ return;
+ }
+
+ styleClass.add(ERROR_STYLE_CLASS);
+ }
+
+ /**
+ * Represents a function that can execute commands.
+ */
+ @FunctionalInterface
+ public interface CommandExecutor {
+ /**
+ * Executes the command and returns the result.
+ *
+ * @see swe.context.logic.Logic#execute(String)
+ */
+ CommandResult execute(String commandText) throws CommandException, ParseException;
+ }
+
+}
diff --git a/src/main/java/swe/context/ui/CommandBoxHistory.java b/src/main/java/swe/context/ui/CommandBoxHistory.java
new file mode 100644
index 00000000000..5f591549087
--- /dev/null
+++ b/src/main/java/swe/context/ui/CommandBoxHistory.java
@@ -0,0 +1,117 @@
+package swe.context.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+//Solution below inspired by
+//https://github.com/se-edu/addressbook-level4/blob/master/src/main/java/seedu/address/ui/ListElementPointer.java
+//(implemented first, then modified after learning about the approach in AB4, which is slightly different)
+/**
+ * Stores the history of entered commands and a pointer indicating the current position in the list.
+ * The list always behaves externally as if its last element is the empty string.
+ */
+public class CommandBoxHistory {
+ private List commandList;
+ private int commandIndex;
+
+ /**
+ * Constructs {@code CommandBoxHistory} with empty command history.
+ * The pointer is set to the last element in {@code commandList}.
+ */
+ public CommandBoxHistory() {
+ this.commandList = new ArrayList<>();
+ this.commandIndex = this.commandList.size();
+ }
+
+ /**
+ * Constructs {@code CommandBoxHistory} defensively.
+ * The pointer is set to the last element in {@code commandList}.
+ */
+ public CommandBoxHistory(List list) {
+ this.commandList = new ArrayList<>(list);
+ this.commandIndex = this.commandList.size();
+ }
+
+ /**
+ * Appends {@code element} to the end of the commandList.
+ */
+ public void add(String element) {
+ commandList.add(element);
+ }
+
+ /**
+ * Resets the command history pointer to the end of the list.
+ */
+ public void resetPointer() {
+ this.commandIndex = this.commandList.size();
+ }
+
+ /**
+ * Checks if a given index is a valid index in the list.
+ */
+ private boolean isValidIndex(int index) {
+ return index >= 0 && index <= commandList.size();
+ }
+
+ /**
+ * Returns the next command in the commandList and increments the pointer position.
+ * @throws NoSuchElementException if there is no next command in the commandList.
+ */
+ public String next() {
+ if (!isValidIndex(commandIndex + 1)) {
+ throw new NoSuchElementException();
+ }
+ commandIndex++;
+ if (commandIndex == commandList.size()) {
+ return "";
+ } else {
+ return commandList.get(commandIndex);
+ }
+ }
+
+ /**
+ * Returns the previous command in the commandList and decrements the pointer position.
+ * @throws NoSuchElementException if there is no previous command in the commandList.
+ */
+ public String previous() {
+ if (!isValidIndex(commandIndex - 1)) {
+ throw new NoSuchElementException();
+ }
+ commandIndex--;
+ if (commandIndex == commandList.size()) {
+ return "";
+ } else {
+ return commandList.get(commandIndex);
+ }
+ }
+
+ /**
+ * Returns the current command in the commandList.
+ * @throws NoSuchElementException if there is no current command (the commandList is empty).
+ */
+ public String current() {
+ if (!isValidIndex(commandIndex)) {
+ throw new NoSuchElementException();
+ }
+ if (commandIndex == commandList.size()) {
+ return "";
+ } else {
+ return commandList.get(commandIndex);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof CommandBoxHistory)) {
+ return false;
+ }
+
+ CommandBoxHistory iterator = (CommandBoxHistory) other;
+ return commandList.equals(iterator.commandList) && commandIndex == iterator.commandIndex;
+ }
+}
diff --git a/src/main/java/swe/context/ui/ContactCard.java b/src/main/java/swe/context/ui/ContactCard.java
new file mode 100644
index 00000000000..d38b2e6398e
--- /dev/null
+++ b/src/main/java/swe/context/ui/ContactCard.java
@@ -0,0 +1,70 @@
+package swe.context.ui;
+
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import swe.context.model.contact.Contact;
+
+/**
+ * An UI component that displays information of a {@code Contact}.
+ */
+public class ContactCard extends UiPart {
+
+ private static final String FXML = "ContactListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Contact contact;
+
+ @FXML private HBox cardPane;
+
+ @FXML private Label id;
+ @FXML private Label name;
+ @FXML private Label phone;
+ @FXML private Label email;
+ @FXML private Label note;
+
+ @FXML private FlowPane tags;
+ @FXML private FlowPane alternates;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Contact} and index to display.
+ */
+ public ContactCard(Contact contact, int displayedIndex) {
+ super(FXML);
+ this.contact = contact;
+
+ id.setText("#" + displayedIndex);
+ name.setText(contact.getName().value);
+ phone.setText(contact.getPhone().value);
+ email.setText(contact.getEmail().value);
+
+ String noteText = contact.getNote().value;
+ if (!noteText.isEmpty()) {
+ note.setVisible(true);
+ note.setManaged(true);
+ note.setText(contact.getNote().value);
+ } else {
+ note.setVisible(false);
+ note.setManaged(false);
+ }
+
+ contact.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.value))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.value)));
+
+ contact.getAlternates().stream()
+ .sorted(Comparator.comparing(alternate -> alternate.value))
+ .forEach(alternate -> alternates.getChildren().add(new Label(alternate.value)));
+ }
+}
diff --git a/src/main/java/swe/context/ui/ContactListPanel.java b/src/main/java/swe/context/ui/ContactListPanel.java
new file mode 100644
index 00000000000..9303855c21b
--- /dev/null
+++ b/src/main/java/swe/context/ui/ContactListPanel.java
@@ -0,0 +1,45 @@
+package swe.context.ui;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import swe.context.model.contact.Contact;
+
+/**
+ * Panel containing the list of contacts.
+ */
+public class ContactListPanel extends UiPart {
+ private static final String FXML = "ContactListPanel.fxml";
+
+ @FXML
+ private ListView contactListView;
+
+ /**
+ * Creates a {@code ContactListPanel} with the given {@code ObservableList}.
+ */
+ public ContactListPanel(ObservableList contactList) {
+ super(FXML);
+ contactListView.setItems(contactList);
+ contactListView.setCellFactory(listView -> new ContactListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Contact} using a {@code ContactCard}.
+ */
+ class ContactListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Contact contact, boolean empty) {
+ super.updateItem(contact, empty);
+
+ if (empty || contact == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new ContactCard(contact, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/swe/context/ui/HelpWindow.java
similarity index 86%
rename from src/main/java/seedu/address/ui/HelpWindow.java
rename to src/main/java/swe/context/ui/HelpWindow.java
index 3f16b2fcf26..96f68f4b0d9 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/swe/context/ui/HelpWindow.java
@@ -1,6 +1,4 @@
-package seedu.address.ui;
-
-import java.util.logging.Logger;
+package swe.context.ui;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
@@ -8,17 +6,16 @@
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.stage.Stage;
-import seedu.address.commons.core.LogsCenter;
+
+
/**
* Controller for a help page
*/
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://ay2324s1-cs2103-w14-3.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);
private static final String FXML = "HelpWindow.fxml";
@FXML
@@ -63,7 +60,6 @@ public HelpWindow() {
*
*/
public void show() {
- logger.fine("Showing help page about the application.");
getRoot().show();
getRoot().centerOnScreen();
}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/swe/context/ui/MainWindow.java
similarity index 84%
rename from src/main/java/seedu/address/ui/MainWindow.java
rename to src/main/java/swe/context/ui/MainWindow.java
index 79e74ef37c0..e76747162c6 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/swe/context/ui/MainWindow.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package swe.context.ui;
import java.util.logging.Logger;
@@ -10,19 +10,20 @@
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.Logic;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
+import swe.context.commons.core.GuiSettings;
+import swe.context.commons.core.LogsCenter;
+import swe.context.logic.Logic;
+import swe.context.logic.commands.CommandResult;
+import swe.context.logic.commands.exceptions.CommandException;
+import swe.context.logic.parser.exceptions.ParseException;
+
+
/**
* The Main Window. Provides the basic application layout containing
* a menu bar and space where other JavaFX elements can be placed.
*/
public class MainWindow extends UiPart {
-
private static final String FXML = "MainWindow.fxml";
private final Logger logger = LogsCenter.getLogger(getClass());
@@ -31,7 +32,7 @@ public class MainWindow extends UiPart {
private Logic logic;
// Independent Ui parts residing in this Ui container
- private PersonListPanel personListPanel;
+ private ContactListPanel contactListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
@@ -42,7 +43,7 @@ public class MainWindow extends UiPart {
private MenuItem helpMenuItem;
@FXML
- private StackPane personListPanelPlaceholder;
+ private StackPane contactListPanelPlaceholder;
@FXML
private StackPane resultDisplayPlaceholder;
@@ -110,13 +111,13 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
- personListPanel = new PersonListPanel(logic.getFilteredPersonList());
- personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ contactListPanel = new ContactListPanel(logic.getFilteredContactList());
+ contactListPanelPlaceholder.getChildren().add(contactListPanel.getRoot());
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
- StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
+ StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getContactsPath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
CommandBox commandBox = new CommandBox(this::executeCommand);
@@ -152,25 +153,32 @@ void show() {
}
/**
- * Closes the application.
+ * Exits the app.
*/
@FXML
private void handleExit() {
GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(),
(int) primaryStage.getX(), (int) primaryStage.getY());
logic.setGuiSettings(guiSettings);
+
+ /* NOTE
+ * We don't explicitly call Platform.exit() or System.exit(), as it
+ * makes testing the exit command hard. Instead, we close all windows,
+ * and rely on JavaFX to automatically begin the shutdown process and
+ * call Application#stop().
+ */
helpWindow.hide();
primaryStage.hide();
}
- public PersonListPanel getPersonListPanel() {
- return personListPanel;
+ public ContactListPanel getContactListPanel() {
+ return contactListPanel;
}
/**
* Executes the command and returns the result.
*
- * @see seedu.address.logic.Logic#execute(String)
+ * @see swe.context.logic.Logic#execute(String)
*/
private CommandResult executeCommand(String commandText) throws CommandException, ParseException {
try {
diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/swe/context/ui/ResultDisplay.java
similarity index 95%
rename from src/main/java/seedu/address/ui/ResultDisplay.java
rename to src/main/java/swe/context/ui/ResultDisplay.java
index 7d98e84eedf..36fae61a570 100644
--- a/src/main/java/seedu/address/ui/ResultDisplay.java
+++ b/src/main/java/swe/context/ui/ResultDisplay.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package swe.context.ui;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/swe/context/ui/StatusBarFooter.java
similarity index 96%
rename from src/main/java/seedu/address/ui/StatusBarFooter.java
rename to src/main/java/swe/context/ui/StatusBarFooter.java
index b577f829423..4541dd7553d 100644
--- a/src/main/java/seedu/address/ui/StatusBarFooter.java
+++ b/src/main/java/swe/context/ui/StatusBarFooter.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package swe.context.ui;
import java.nio.file.Path;
import java.nio.file.Paths;
diff --git a/src/main/java/swe/context/ui/Ui.java b/src/main/java/swe/context/ui/Ui.java
new file mode 100644
index 00000000000..c4f062ebc56
--- /dev/null
+++ b/src/main/java/swe/context/ui/Ui.java
@@ -0,0 +1,15 @@
+package swe.context.ui;
+
+import javafx.stage.Stage;
+
+
+
+/**
+ * API of the UI component.
+ */
+public interface Ui {
+ /**
+ * Starts the UI, which also starts the other functionality of the app.
+ */
+ void start(Stage primaryStage);
+}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/swe/context/ui/UiManager.java
similarity index 85%
rename from src/main/java/seedu/address/ui/UiManager.java
rename to src/main/java/swe/context/ui/UiManager.java
index fdf024138bc..57c887e46e8 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/swe/context/ui/UiManager.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package swe.context.ui;
import java.util.logging.Logger;
@@ -7,20 +7,20 @@
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.stage.Stage;
-import seedu.address.MainApp;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
+import swe.context.MainApp;
+import swe.context.commons.core.LogsCenter;
+import swe.context.commons.util.StringUtil;
+import swe.context.logic.Logic;
/**
- * The manager of the UI component.
+ * Implementation of the UI component.
*/
public class UiManager implements Ui {
-
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+
+ public static final String PATH_APP_ICON = "/images/app.png";
private Logic logic;
private MainWindow mainWindow;
@@ -37,11 +37,11 @@ public void start(Stage primaryStage) {
logger.info("Starting UI...");
//Set the application icon.
- primaryStage.getIcons().add(getImage(ICON_APPLICATION));
+ primaryStage.getIcons().add(getImage(UiManager.PATH_APP_ICON));
try {
mainWindow = new MainWindow(primaryStage, logic);
- mainWindow.show(); //This should be called before creating other UI parts
+ mainWindow.show(); // This should be called before creating other UI parts
mainWindow.fillInnerParts();
} catch (Throwable e) {
diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/swe/context/ui/UiPart.java
similarity index 97%
rename from src/main/java/seedu/address/ui/UiPart.java
rename to src/main/java/swe/context/ui/UiPart.java
index fc820e01a9c..db461c617f0 100644
--- a/src/main/java/seedu/address/ui/UiPart.java
+++ b/src/main/java/swe/context/ui/UiPart.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package swe.context.ui;
import static java.util.Objects.requireNonNull;
@@ -6,7 +6,7 @@
import java.net.URL;
import javafx.fxml.FXMLLoader;
-import seedu.address.MainApp;
+import swe.context.MainApp;
/**
* Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc.
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/app.png
similarity index 100%
rename from src/main/resources/images/address_book_32.png
rename to src/main/resources/images/app.png
diff --git a/src/main/resources/images/calendar.png b/src/main/resources/images/calendar.png
deleted file mode 100644
index 8b2bdf4f1c1..00000000000
Binary files a/src/main/resources/images/calendar.png and /dev/null differ
diff --git a/src/main/resources/images/clock.png b/src/main/resources/images/clock.png
deleted file mode 100644
index 0807cbf6451..00000000000
Binary files a/src/main/resources/images/clock.png and /dev/null differ
diff --git a/src/main/resources/images/fail.png b/src/main/resources/images/fail.png
deleted file mode 100644
index 6daf01290dd..00000000000
Binary files a/src/main/resources/images/fail.png and /dev/null differ
diff --git a/src/main/resources/images/help_icon.png b/src/main/resources/images/help.png
similarity index 100%
rename from src/main/resources/images/help_icon.png
rename to src/main/resources/images/help.png
diff --git a/src/main/resources/images/info_icon.png b/src/main/resources/images/info_icon.png
deleted file mode 100644
index f8cef714095..00000000000
Binary files a/src/main/resources/images/info_icon.png and /dev/null differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 124283a392e..b7a0da6e752 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -4,6 +4,5 @@
-
+
-
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/ContactListCard.fxml
similarity index 87%
rename from src/main/resources/view/PersonListCard.fxml
rename to src/main/resources/view/ContactListCard.fxml
index f5e812e25e6..b59ec4b840e 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/ContactListCard.fxml
@@ -14,7 +14,7 @@
-
+
@@ -29,8 +29,9 @@
-
+
+
diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/ContactListPanel.fxml
similarity index 77%
rename from src/main/resources/view/PersonListPanel.fxml
rename to src/main/resources/view/ContactListPanel.fxml
index a1bb6bbace8..c4e105169f3 100644
--- a/src/main/resources/view/PersonListPanel.fxml
+++ b/src/main/resources/view/ContactListPanel.fxml
@@ -4,5 +4,5 @@
-
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..093c0c16177 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -350,3 +350,17 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#alternates {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#alternates .label {
+ -fx-text-fill: white;
+ -fx-background-color: #106535;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml
index e01f330de33..a6e1cb36143 100644
--- a/src/main/resources/view/HelpWindow.fxml
+++ b/src/main/resources/view/HelpWindow.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..4d2441702dc 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -12,9 +12,9 @@
+ title="ConText" minWidth="450" minHeight="600" onCloseRequest="#handleExit">
-
+
@@ -40,17 +40,17 @@
+ minHeight="130" prefHeight="130" maxHeight="130">
-
+