diff --git a/README.md b/README.md
index 13f5c77403f..273c3efeebb 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,15 @@
-[](https://github.com/se-edu/addressbook-level3/actions)
+[](https://github.com/AY2223S1-CS2103T-F11-2/tp/actions)
+[](https://codecov.io/gh/AY2223S1-CS2103T-F11-2/tp)

-* This is **a sample project for Software Engineering (SE) students**.
+* This is a **brownfield project** completed by Team CS2103T-F11-2.
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.
+ * Used by NUS students who want to track the progress of assignments and tasks completed for the module
+ * Used by busy NUS students who want to organise their tasks for different modules
+* The project simulates an ongoing software project for a desktop application (called _MODPRO_) used for tracking the progress of modules taken.
* 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.
+* It is named `MODPRO` because it was intended to help NUS students to track the progress of the modules taken.
+* For the detailed documentation of this project, see the **[MODPRO Product Website](https://ay2223s1-cs2103t-f11-2.github.io/tp)**.
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org)
diff --git a/build.gradle b/build.gradle
index 108397716bd..4db8855ad76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -69,4 +69,8 @@ shadowJar {
archiveFileName = 'addressbook.jar'
}
+run {
+ enableAssertions = true
+}
+
defaultTasks 'clean', 'test'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..f90da328bdb 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,54 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Douglas Lim
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/dlimyy)]
+[[portfolio](team/dlimyy.md)]
-* Role: Project Advisor
+* Role: Developer
+* Responsibilities: Integration, Deliverables and deadlines
+
+### Tan Li Xin
-### Jane Doe
+
-
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/tlx02)]
+[[portfolio](team/tlx02.md)]
-* Role: Team Lead
-* Responsibilities: UI
-### Johnny Doe
+* Role: Developer
+* Responsibilities: Code Quality
+
+### Phua Li Ting
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/phualiting)]
+[[portfolio](team/phualiting.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Scheduling and tracking
-### Jean Doe
+### Rachel Chua
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/RachelChua)]
+[[portfolio](team/rachelchua.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Deliverable and deadlines
-### James Doe
+### Samuel Pang
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/Sampy147)]
+[[portfolio](team/sampy147.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Documentation and Code quality
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 46eae8ee565..19ab346e652 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,14 +2,66 @@
layout: page
title: Developer Guide
---
-* Table of Contents
-{:toc}
+## **Table of Contents**
+* [Acknowledgements](#acknowledgements)
+* [Setting up](#setting-up-getting-started)
+* [Design](#design)
+ * [Architecture](#architecture)
+ * [UI component](#ui-component)
+ * [Logic component](#logic-component)
+ * [Model component](#model-component)
+ * [Storage component](#storage-component)
+ * [Common classes](#common-classes)
+* [Implementation](#implementation)
+ * [Sort Task Command](#sort-task-command)
+ * [Filter Tasks Command](#filter-tasks-command)
+ * [Mark Task Command](#mark-task-command)
+ * [Edit Task Command](#edit-task-command)
+ * [Delete Task Command](#delete-task-command)
+ * [Link Exam Feature](#link-exam-feature)
+ * [Add Exam Feature](#add-exam-feature)
+ * [Unink Exam Command](#unlink-exam-command)
+ * [Find Tasks Feature](#find-tasks-feature)
+* [Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops)
+* [Appendix: Requirements](#ui-component)
+ * [Product scope](#product-scope)
+ * [User stories](#user-stories)
+ * [Use cases](#use-cases)
+ * [Non-Functional Requirements](#non-functional-requirements)
+ * [Glossary](#glossary)
+* [Appendix: Instructions for manual testing](#appendix-instructions-for-manual-testing)
+ * [Launch and Shutdown](#launch-and-shutdown)
+ * [Adding a module](#adding-a-module)
+ * [Adding a tag to a task](#adding-a-tag-to-a-task)
+ * [Editing a tag of a task](#editing-a-tag-of-a-task)
+ * [Deleting a tag of a task](#deleting-a-tag-of-a-task)
+ * [Sorting the task list](#sorting-the-task-list)
+ * [Linking the exam to a task](#linking-the-exam-to-a-task)
+ * [Viewing the help window](#viewing-the-help-window)
+ * [Adding an exam](#adding-an-exam)
+ * [Editing an exam](#editing-an-exam)
+ * [Finding a task](#finding-a-task)
+ * [Finding a module](#finding-a-module)
+ * [Listing modules](#listing-modules)
+ * [Listing tasks](#listing-tasks)
+ * [Marking a task](#marking-a-task)
+ * [Editing a task](#editing-a-task)
+ * [Adding a task](#adding-a-task)
+ * [Filtering the task list](#filtering-the-task-list)
+ * [Clearing the task list](#clearing-the-task-list)
+ * [Deleting an exam](#deleting-an-exam)
+ * [Unlinking an exam](#unlinking-an-exam)
+ * [Showing tasks of an exam](#showing-tasks-of-an-exam)
+ * [Clearing all lists](#clearing-all-lists)
--------------------------------------------------------------------------------------------------------------------
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* This project is based on the [AddressBook-Level3](https://github.com/se-edu/addressbook-level3) project created by the [SE-EDU initiative](https://se-education.org/)
+* List of libraries used
+ * [JavaFx](https://openjfx.io/)
+ * [Junit5](https://junit.org/junit5/)
--------------------------------------------------------------------------------------------------------------------
@@ -26,6 +78,12 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/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.
+
+
+:information_source: **Note:** For some classes and methods such as AddressBookParser, our team has decided to
+stick with the naming convention used in AB3.
+
+
### Architecture
@@ -36,7 +94,11 @@ Given below is a quick overview of main components and how they interact with ea
**Main components of the architecture**
-**`Main`** has two classes called [`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). It is responsible for,
+**`Main`** has two classes called
+[`Main`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/Main.java)
+and
+[`MainApp`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/MainApp.java).
+It is responsible for,
* At app launch: Initializes the components in the correct sequence, and connects them up with each other.
* At shut down: Shuts down the components and invokes cleanup methods where necessary.
@@ -52,7 +114,7 @@ The rest of the App consists of four components.
**How the architecture components interact with each other**
-The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
+The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `t delete 3`.
@@ -69,24 +131,26 @@ 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/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/ui/Ui.java)

-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TaskListPanel`,
+`ModuleListPanel`, `ExamListPanel`, `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/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-F11-2/tp/blob/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 `Task`, `Exam`, `Module` objects residing in the `Model`.
### 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/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
@@ -94,15 +158,15 @@ Here's a (partial) class diagram of the `Logic` component:
How the `Logic` component works:
1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command.
-1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`.
-1. The command can communicate with the `Model` when it is executed (e.g. to add a person).
+1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddModuleCommand`) which is executed by the `LogicManager`.
+1. The command can communicate with the `Model` when it is executed (e.g. to add a module).
1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
-The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call.
+The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("t mark 1")` API call.
-
+
-
: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.
+
:information_source: **Note:** The lifeline for `MarkCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
@@ -110,32 +174,31 @@ 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.
-* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
+* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddTaskCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddTaskCommand`) which the `AddressBookParser` returns back as a `Command` object.
+* All `XYZCommandParser` classes (e.g., `AddTaskCommandParser`, `DeleteTaskCommandParser`, ...) 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/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/model/Model.java)
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
-* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
+* stores the address book data
+ * all `Module` objects (which are contained in a `DistinctModuleList` object).
+ * all `Task` objects (which are contained in a `DistinctTaskList` object)
+ * all `Exam` objects (which are contained in a `DistinctExamList` object)
+* stores the currently 'selected' `Module` objects (e.g after executing the find module commands) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
+* stores the currently 'selected' `Task` objects (e.g. after executing the find task command) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
+* stores the currently 'selected' `Exam` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
-
-
-
-
-
### 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/AY2223S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/storage/Storage.java)
@@ -154,89 +217,530 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Sort Task Command
+
+#### Command Format
+
+`t sort c/CRITERIA` where `CRITERIA` refers to the criteria selected by the user.
+
+`CRITERIA` can be one of the following criteria:
+* `priority`
+* `deadline`
+* `module`
+* `description`
+
+#### What is the feature about
+
+The `sort` command allows users to sort the task list by priority status,
+deadline, module code and task description with ease.
+
+#### How does the feature work
+
+The sort task feature is currently implemented through the `SortTaskCommand` which extends the abstract class `Command`.
+The `SortTaskCommand`operates directly on the `ObservableList` stored under `DistinctTaskList` in `AddressBook` so the
+`ObservableList` in `DistinctTaskList` will be permanently sorted to the criteria.
+* When sorting by priority, all tasks with `HIGH` priority status will be positioned at the top of the
+displayed task list, followed by `MEDIUM`, `LOW` and
+lastly all tasks with no priority tags.
+* When sorting by deadline, all tasks with deadline tags will be displayed
+at the top of the displayed task list with the task with the earliest deadline
+being displayed at the top. All the remaining tasks with no deadline tags will
+be displayed below all tasks with deadline tags.
+
+#### Why is the feature implemented in this manner
+
+Our team has made the decision to not remove tasks with no priority status from the displayed task list when
+sorting by priority status as we believed that users should be given the freedom to view all tasks that
+they have added after sorting.
+
+Our team has also made a similar decision to not remove tasks with no deadlines from the displayed task list when
+sorting by deadline.
+
+
+#### UML Diagrams
+
+
+Shown below is a sequence diagram of what occurs when the `execute` method of
+`LogicManager` is invoked.
+
+|  |
+|:-------------------------------------------------------------:|
+| Sequence Diagram of Sort Task Command |
+
+
+
+:information_source: **Note:** The lifeline for `SortTaskCommandParser` and `SortTaskCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+
+
+**Sequence of actions made when `execute` method of `LogicManager` is invoked**
+1. `LogicManager` object takes in `t sort c/priority` which the user keys into the command line.
+2. `LogicManager` object calls the `parseCommand` of the `AddressBookParser` object created during the initialisation
+of `LogicManager` object and passes `t sort c/priority` as the arguments of `parseCommand`
+3. `SortTaskCommandParser` object is created during execution of `parseCommand` of `AddressBookParser`
+4. `SortTaskCommandParser` object calls its `parse` method with `c/priority` being passed in as argument.
+5. `SortTaskCommand` object called st is created from `SortTaskCommandParser`
+6. `execute` method of `SortTaskCommand` object st is invoked and model is passed in as
+an argument.
+7. `sortTaskList` method of model is called with `criteria` being passed as an
+argument of the method.
+8. The `getFilteredTaskList` method of model is called and it returns the `FilteredList` stored in model.
+9. There is a check performed to see if the size of the `FilteredList` returned in the previous step is greater than 0.
+If it is not greater than 0, an error message will be displayed.
+8. The `execute` method of `SortTaskCommand` object returns a `CommandResult` object with
+the sorted successfully message as its argument to the `LogicManager` object.
+
+The following activity diagram summarises what happens when SortTaskCommand is executed
+
+|  |
+|:-------------------------------------------------------------:|
+| Activity Diagram of SortTaskCommand |
+
+### Filter Tasks Command
+
+#### Command Format
+
+`t filter [m/MODULE/]* [c/COMPLETED]* [l/LINKED]*` where `MODULE` refers to the module code of the module to be filtered out, `COMPLETED` refers to the completion status of the tasks to be filtered out, and `LINKED` refers to the link status of the tasks to be filtered out.
+
+#### What is the feature about
+The `filter` command allows users to filter the task list by module, completion status, and/or link status.
+* `COMPLETED` will be `y` for filtering out tasks with `status` of `COMPLETE` and `n` for filtering out tasks with `TaskStatus` of `INCOMPLETE`.
+* `COMPLETED` will be `y` for filtering out tasks with `linkedExam` which is not `null` and `n` for filtering out tasks with `linkedExam` of `null`.
+
+#### How does the feature work
+
+The filter tasks feature is currently implemented through the `FilterTasksCommand` which extends the abstract class `Command`.
+The `FilterTasksCommand` operates by producing a `FilterPredicate` used to update the `FilteredTaskList`.
+Executing a filter command will always filter the full `DistinctTaskList` and not the `FilteredTaskList` if two filter commands are executed one after another.
+
+#### UML Diagrams
+
+Shown below is a sequence diagram of what occurs when the `execute` method of `LogicManager` is invoked.
+
+|  |
+|:----------------------------------------------------------:|
+| Sequence diagram of FilterTasksCommand |
+
+
:information_source: **Note:** The lifeline for `FilterTasksCommand` and `FilterTasksCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+
+
+1. The user executes `t filter m/CS2103T c/y l/n` command to filter the task list to show all CS2103T tasks that have been marked complete and are not linked to any exam.
+2. `LogicManager` calls `AddressBookParser#parseCommand()` with `"t filter m/CS2103T c/y l/n"` as the argument.
+3. `AddressBookParser` calls `FilterTasksCommandParser#parse()` with `"m/CS2103T c/y l/n"` as the argument.
+4. `FilterTasksCommandParser` creates a new `FilterTasksCommand` object with a new `FilterPredicate` object as argument, representing the filter conditions specified by the user.
+5. `LogicManager`calls `FilterTasksCommand#execute()` with `model` as the argument.
+6. `FilterTasksCommand` calls `Model#updateFilteredTaskList()` with `FilterPredicate` as an argument, causing the task list to be updated based on the new conditions for `module`, `isCompleted` and `isLinked` based on the `FilterPredicate`.
+7. `FilterTasksCommand#execute()` returns a `CommandResult` object with the `MESSAGE_SUCCESS` message as argument.
+
+
:information_source: **Note:** If the `module`, `isCompleted` or `isLinked` input is invalid, there will be an error message shown and the address book will continue to show the current `taskFilteredList`.
+
+
+
+The following activity diagram summarizes what happens when FilterTasksCommand is executed.
+
+| |
+|:----------------------------------------------------------:|
+| Activity diagram of FilterTasksCommand |
+
+### Mark Task Command
+
+#### Command Format
+
+`t mark INDEX` where `INDEX` is the index (shown in the displayed task list) of the task to be marked.
+
+#### What is the feature about
+
+The `t mark` command allows users to indicate a specific task is completed.
+The task specified will be ticked.
+
+#### How does the feature work
+
+The mark task feature is currently implemented through the `MarkTaskCommand` which extends the abstract class `Command`.
+A copy of the task to be marked will be created, with its `TaskStatus` set to `COMPLETE`. This marked task will then replace the
+original task in the `DistinctTaskList`.
+
+#### UML diagrams
+Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked.
+
+|  |
+|:----------------------------------------------------------------:|
+|  |
+| Sequence diagram of MarkTaskCommand |
+
+
+
+:information_source: **Note:** The lifeline for `MarkCommandParser` and `MarkCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifelines reach the end of the diagram.
+
+
+
+
+:information_source: **Note:** If the command fails, `Model#replaceTask()` will not be called, so the task list will not change. If so, `MarkCommand` will return an error to the user rather than attempting to perform the command.
+
+
+**Sequence of actions made when `execute` method of `LogicManager` is invoked**
+
+1. The user types the `t mark 1` command.
+2. The `execute()` method of the `LogicManager` is called.
+3. The `LogicManager` then calls `AddressBookParser#parseCommand()` which parses `t mark 1`, creating a `MarkCommandParser` object.
+4. The `AddressBookParser` calls `MarkCommandParser#parse()` which parses `1` and creates a `MarkCommand` object with an `Index` object storing the target index `1`.
+5. Then, the `LogicManager`calls `MarkCommand#execute()`.
+6. The `MarkCommand` retrieves the task at the `Index`, which is the first task in the filtered task list, from the `Model`.
+7. The `MarkCommand` command calls `Task#mark()` to create a marked copy of the `taskToMark`.
+8. This `markedTask` has all fields similar to the original task, except its `TaskStatus` is `COMPLETE`.
+9. Then, `MarkCommand` calls `Model#replaceTask()` which replaces the `taskToMark` in the filtered task list in `Model` with the `markedTask`.
-#### Proposed Implementation
+
+
+:information_source: **Note:** The `UnmarkCommand` works the same — the only difference is that it calls `Task#unmark()`, which returns a copy of the task with `TaskStatus` set to `INCOMPLETE`.
+
+
+The following activity diagram summarizes what happens when MarkCommand is executed
+
+|  |
+|:--------------------------------------------------------------:|
+| Activity diagram of MarkTaskCommand |
-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:
+### Edit Task Command
-* `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.
+#### Command Format
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+`t edit INDEX [m/MODULE]* [d/DESCRIPTION]*` where `INDEX` is the index of the task to edit, and `MODULE` and `DESCRIPTION` are the module and description to replace the current values of the specified task.
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+#### What is the feature about
-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.
+The `t edit` command allows users to update the specified task with the fields provided. The provided fields will replace the existing fields.
-
+#### How does the feature work
+The edit task feature is currently implemented through the `EditTaskCommand` which extends the abstract class `Command`.
+A copy of the task to be edited will be created, with its existing `MODULE` and `DESCRIPTION` replaced with the new
+values provided. This edited task will then replace the
+original task in the `DistinctTaskList`.
-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.
+#### UML diagrams
+Shown below is a sequence diagram of what occurs when the `execute` method of
+`LogicManager` is invoked.
-
+|  |
+|:----------------------------------------------------------------:|
+|  |
+| Sequence diagram of EditTaskCommand |
-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`.
+
-
+:information_source: **Note:** The lifelines for `EditTaskCommandParser` and `EditTaskCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifelines reach the end of the diagram.
+
-
: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`.
+
+:information_source: **Note:** If the command is invalid, `Model#replaceTask()` will not be called, so the task list will not change. If so, `EditTaskCommand` will return an error to the user rather than attempting to perform the command.
-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.
+**Sequence of actions made when `execute` method of `LogicManager` is invoked**
+
+1. The user types the `t edit 1 d/task 1` command.
+2. The `execute()` method of the `LogicManager` is called.
+3. The `LogicManager` then calls `AddressBookParser#parseCommand()` which parses `t edit 1 d/task 1`, creating an `EditTaskCommandParser` object.
+4. The `AddressBookParser` calls `EditTaskCommandParser#parse()` which parses `1 d/task 1` and creates an `EditTaskCommand` object with an `Index` object storing the target index `1` and an `EditTaskDescriptor` object storing the description `task 1` in a `TaskDescription` object.
+5. Then, the `LogicManager`calls `EditTaskCommand#execute()`.
+6. The `EditTaskCommand` retrieves the task at the `Index`, which is the first task in the filtered task list, from the `Model`.
+7. The `EditTaskCommand` command calls `Task#edit()` with the `EditTaskDescriptor` passed as the argument.
+8. The `Task#edit()` method checks that the module of the `taskToEdit` is not changed, so it creates a copy of the `taskToEdit`, with only the description changed. If `taskToEdit` has a linked exam, `linkedEditedTask` is also linked to the same exam.
+9. This `linkedEditedTask` has all fields similar to the original task, except its `TaskDescription` is changed to `Task 1`.
+10. Then, the `EditTaskCommand` calls `Model#replaceTask()` which replaces the `taskToEdit` in the filtered task list in `Model` with the `linkedEditedTask`.
+
+The following activity diagram summarizes what happens when EditTaskCommand is executed
+
+|  |
+|:----------------------------------------------------------------:|
+| Activity diagram of EditTaskCommand |
-
+### Delete Task Command
+
+#### Command Format
+`t del INDEX` where `INDEX` is the index (shown in the displayed task list) of the task to be deleted.
+
+#### What is the feature about
+The `t del` command allows users to delete a specific task from the displayed task list.
+
+#### How does the feature work
+The delete task feature is currently implemented through the `DeleteTaskCommand` which extends the abstract class `Command`.
+The task to be deleted will be retrieved from the `Model` based on the `INDEX` given. This task will then be deleted from the
+the `DistinctTaskList`.
+
+#### UML diagrams
+Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked.
+
+|  |
+|:------------------------------------------------------------------:|
+| Sequence diagram of DeleteTaskCommand |
+
+
+
+:information_source: **Note:** The lifeline for `DeleteTaskCommandParser` and `DeleteTaskCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifelines reach the end of the diagram.
+
-
: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.
+
+:information_source: **Note:** If the command fails, `Model#deleteTask()` will not be called, so the task list will not change. If so, `DeleteTaskCommand` will return an error to the user rather than attempting to perform the command.
-The following sequence diagram shows how the undo operation works:
+**Sequence of actions made when `execute` method of `LogicManager` is invoked**
+
+1. The user types the `t del 1` command.
+2. The `execute()` method of the `LogicManager` is called.
+3. The `LogicManager` then calls `AddressBookParser#parseCommand()` which parses `t del 1`, creating a `DeleteTaskCommandParser` object.
+4. The `AddressBookParser` calls `DeleteTaskCommandParser#parse()` which parses `1` and creates a `DeleteTaskCommand` object with an `Index` object storing the target index `1`.
+5. Then, the `LogicManager`calls `DeleteTaskCommand#execute()`.
+6. The `DeleteTaskCommand` retrieves the task at the `Index`, which is the first task in the filtered task list, from the `Model`.
+7. The `DeleteTaskCommand` command calls `Model#deleteTask()` to delete the `taskToDelete`.
+
+The following activity diagram summarizes what happens when DeleteTaskCommand is executed
+
+|  |
+|:------------------------------------------------------------------:|
+| Activity diagram of DeleteTaskCommand |
+
+### Link Exam feature
+
+#### Command Format
+
+`e link e/EXAM_INDEX t/TASK_INDEX` where `EXAM_INDEX` refers to the index number of the displayed exam list
+and `TASK_INDEX` refers to the index number of the displayed task list.
+
+#### What is the feature about
+
+The link exam feature allows users to link an exam in the exam list to a task in the task list.
+
+#### How does the feature work
+
+The link exam feature is currently implemented through the `LinkExamCommand` class which extends the abstract class `Command`
+. The `LinkExamCommand` takes in two `Index` objects, one being the exam index and the other being
+the task index.
+
+When the user invokes the`execute` method of `LinkExamCommand`,
+the `Task` and `Exam` stored at the specified index of the `FilteredList` and the
+`FilteredList` respectively are retrieved.
+
+There will be checks to see if the `Task` is already linked to the exam and if
+the module code of the `Exam` is same as that of `Task`. Once these checks are passed,
+the `Task` will be linked to the `Exam`
+
+#### UML Diagrams
+
+Shown below is a sequence diagram of what occurs when the `execute` method of
+`LogicManager` is invoked.
+
+|  |
+|:--------------------------------------------------------------:|
+| Sequence diagram of LinkExamCommand |
-
+
-
: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.
+:information_source: **Note:** The lifeline for `LinkExamCommandParser` and `LinkExamCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+**Sequence of actions made when `execute` method of `LogicManager` is invoked**
+1. `LogicManager` object takes in `e link e/1 t/1` which the user keys into the command line.
+2. `LogicManager` object calls the `parseCommand` of the `AddressBookParser` object created during the initialisation
+ of `LogicManager` object and passes `e link e/1 t/1` as the arguments of `parseCommand`.
+3. `LinkExamCommandParser` object is created during execution of `parseCommand` of `AddressBookParser`.
+4. `LinkExamCommandParser` object calls its `parse` method with `e/1 t/1` being passed in as argument.
+5. `LinkExamCommand` object called le is created from `LinkExamCommandParser`.
+6. `execute` method of `LinkExamCommand` object le is invoked and model is passed in as
+ an argument.
+7. `LinkExamCommand` calls the `getFilteredTaskList` method of `Model` and the `FilteredList` stored in model is
+ returned.
+8. `LinkExamCommand` calls the `getFilteredExamList` method of `Model` and the `FilteredList` stored in model is
+ returned.
+9. `Task` object called task is returned when `get` method of `List` is executed.
+10. task calls its own method `isLinked` and returns whether the task is linked. If the task is linked,
+ an error message will be displayed.
+11. `Exam` object called exam is returned when `get` method of `List` is executed.
+12. task calls its own method `getModule` and the `Module` stored in task will be returned.
+13. exam calls its own method `getModule` and the `Module` stored in Exam will be returned.
+14. The static method `isSameModule` of `Module` class is executed, and it checks whether task and exam
+ have the same module code. If they do not have the same module code, an error message will be displayed.
+15. The `linkTask` method of `model` will be executed, and it will create a new `Task` object called linkedTask
+16. The `replaceTask` method of `model` will be executed and replaces task in `DistinctTaskList` in model with
+ linkedTask
+17. The `execute` method of `LinkExamCommand` returns a `CommandResult` object with
+ the exam linked successfully message as its argument to the `LogicManager` object.
+
+The following activity diagram summarises what happens when LinkExamCommand is executed
+
+|  |
+|:--------------------------------------------------------------:|
+| Activity diagram of LinkExamCommand |
+
+
+### Add Exam Feature
+
+#### Command Format:
+`e add m/MODULE ex/EXAM DESCRIPTION ed/EXAM DATE`
+where `MODULE` refers to the exam module, `EXAM DESCRIPTION` refers to the exam description and `EXAM DATE` refers to the exam date of the exam
+to be added into the exam list.
+
+#### What is the feature about:
+This command allows users to add an exam to the exam list, with the following fields of exam module, exam description
+and exam date.
+
+#### How does the feature work:
+This add exam feature is currently implemented through AddExamCommand class which extends the abstract class Command.
+If the module, exam description and exam date are valid, and the module exists in the module list, and the exam is not a duplicate of any existing exam,
+the exam will be added to the `DistinctExamList`.
+
+
+#### UML Diagrams
+Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked.
+
+|  |
+|:------------------------------------------------------------:|
+| Sequence diagram of AddExamCommand |
+
+1. The user types an `e add m/cs2030s ex/midterms ed/20-12-2022` command.
+
+2. The command calls `LogicManager#execute()` with
+the command input as the argument, which then calls `AddressBookParser#parseCommand() `
+with command input as the argument.
+
+3. `AddressBookParser#parseCommand()` matches the command to be
+an add exam command through the command word and the feature type, which then calls `AddExamCommandParser#parse()`.
+
+4. `AddExamCommandParser#parse()` then parses `m/cs2030s ex/midterms ed/20-12-2022` to get `Module`, `ExamDescription`
+and the `ExamDate` objects of the exam by calling their respective `ParserUtil` parse methods.
+Then, an `Exam` object is created with the three objects as arguments.
+
+5. `AddExamCommandParser#parse()` returns a new `AddExamCommand` object created with the `Exam`
+object created previously as the argument in the constructor. `LogicManager` object will call
+`AddExamCommand#execute()` with a `Model` object as the argument.
+
+6. `AddExamCommand#execute()` will check if model already contains the module of the exam
+through `Model#hasModule()`. If it does not contain the module, an exception will be thrown to
+indicate the module is not found. It will also check if the model already contains the exam
+through `Model#hasExam()`. If it contains the exam, an exception will be thrown
+to indicate that the exam already exists in MODPRO.
+
+Otherwise, it will call `ModelManager#addExam()` with the exam as the argument,
+which calls `AddressBook#addExam()` that will add the exam to the `DistinctExamList`,
+which stores all the exams.`AddExamCommand#execute()` method returns a `CommandResult`
+object to display that the exam was successfully added.
+
+
+
+:information_source: **Note:**
+
+* For step 4, the `Exam` object will not be created if the exam description or exam date or module
+ is not valid. Exam description is not valid if it is an empty string, exam date is not valid
+ if it is not in DD-MM-YYYY or if it is earlier than the current date. Module is not valid if it is not at least 6 characters long
+ with the first 2 being alphabetical characters. Hence, in such cases, `Exam` object is not created, and the exam will not be added.
-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 following activity diagram summarises what happens when AddExamCommand is executed
+
+|  |
+|:-------------------------------------------------------------:|
+| Activity diagram of AddExamCommand |
+
+
+### Unlink Exam Command
+
+#### Command format
+`e unlink INDEX` where `INDEX` refers to the index number shown on the displayed task list of the task to be unlinked.
+
+#### What is the feature about
+The `e unlink` command allows users to unlink a task from its exam.
+
+#### How does the feature work
+The unlink exam feature is currently implemented through the `UnlinkExamCommand` class which extends the abstract class `Command`.
+A copy of the task to be unlinked will be created, with its `linkedExam` field set to `null`.
+The original task will be replaced with the new unlinked task in the `DistinctTaskList`.
+
+#### UML Diagrams
-
: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.
+Shown below is a sequence diagram of what occurs when the `execute` method of `LogicManager` is invoked.
+
+|  |
+|:------------------------------------------------------------------:|
+| Sequence diagram of UnlinkExamCommand |
+
+
:information_source: **Note:** The lifeline for `UnlinkExamCommand` and `UnlinkExamCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-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.
+**Sequence of actions made when `execute` method of `LogicManager` is invoked**
+
+1. The user executes `e unlink 1` to unlink the first task in the `FilteredTaskList`.
+2. `LogicManager` calls `AddressBookParser#parseCommand()` with `"e unlink 1"` as the argument.
+3. `AddressBookParser` calls `UnlinkExamCommandParser#parse()` with `"1"` as the argument.
+4. `UnlinkExamCommandParser` creates a new `UnlinkExamCommand` object with a new `Index` object as argument, representing the first index of the task list.
+5. `LogicManager` calls `UnlinkExamCommand#execute()` with `model` as the argument.
+6. `UnlinkExamCommand` calls `Model#getFilteredTaskList()` which returns the current filtered task list.
+7. `List#get()` is called which returns the first task in the filtered task list.
+8. `Task#unlinkTask()` is called which returns a new `Task` object with the same fields as the first task in the filtered task list, but with its `linkedExam` field set to `null`.
+9. `UnlinkExamCommand` calls `Model#replaceTask()` to replace the original task at the first index with the new unlinked task.
+10. `UnlinkExamCommand#execute()` returns a `CommandResult` object with the `EXAM_UNLINKED_SUCCESS` message as argument.
+
+The following activity diagram summarizes what happens when UnlinkExamCommand is executed.
-
+| |
+|:---------------------------------------------------------------:|
+| Activity diagram of UnlinkExamCommand |
-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.
+
+### Find Tasks Feature
-
+#### Command Format:
+`t find KEYWORD` where `KEYWORD` is the keyword inputted by user.
-The following activity diagram summarizes what happens when a user executes a new command:
+#### What is the feature about:
+The command allows users to find tasks whose task description matches the keyword inputted by them fully or partially.
+The keyword is case-insensitive. For example, "TASK" will match "task".
-
+#### Why is the feature implemented in this way:
+It is implemented such that users can find tasks that contain the keyword not just fully but also partially.
+This can be helpful especially if they cannot remember the full description of the task.
+Also, if they want quick access to the task, they can simply type a partial description to find the task instead of writing the whole description.
-#### Design considerations:
+#### UML Diagrams:
+Shown below is a sequence diagram of what occurs when the execute method of LogicManager is invoked.
-**Aspect: How undo & redo executes:**
+|  |
+|:----------------------------------------------------------------:|
+| Sequence diagram of FindTasksCommand |
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+1. The user types an `t find task` command.
-* **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.
+2. The command calls `LogicManager#execute()` with
+the command input as the argument, which then calls `AddressBookParser#parseCommand() `
+with command input as the argument.
-_{more aspects and alternatives to be added}_
+3. `AddressBookParser#parseCommand()` matches the command to be
+a find tasks command through the command word and the feature type, which then calls `FindTasksCommandParser#parse()` which takes in `task` as its argument.
-### \[Proposed\] Data archiving
+4. `FindTasksCommandParser#parse()` will create a `DescriptionContainsKeywordsPredicate` object which is a predicate that takes in the keyword inputted by user and
+tests if the task description matches the keyword- `task`
+Then it will create and return `FindTasksCommand` object,that takes in a single argument-the `DescriptionContainsKeywordsPredicate` object,
+ to `LogicManager` object.
+
+5. `LogicManager` object will call `FindTasksCommand#execute()` with a `Model` object as the argument.
+`FindTasksCommand#execute()` will call `Model#updateFilteredTaskList` to update the filtered task list
+by the predicate (which is the `DescriptionContainsKeywordsPredicate` object created previously) to only
+display the tasks which match the keyword. Then, a `CommandResult` object is returned to the `LogicManager` object.
+
+
+
+:information_source: **Note:**
+
+* For Step 4, `FindTasksCommandParser#parse` will not return a new `FindTasksCommand` object if the keyword is empty.
+
+
+The following activity diagram summarises what happens when FindTasksCommand is executed
+
+|  |
+|:----------------------------------------------------------------:|
+| Activity diagram of FindTasksCommand |
-_{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
@@ -257,71 +761,637 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
+* has a need to manage a significant number of modules, tasks and exams
* prefer desktop apps over other types
* can type fast
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: manage module tasks faster than a typical mouse/GUI driven app
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
+| Priority | As a … | I want to … | So that I can… |
+|----------|-------------|-------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
+| `* * *` | NUS student | view the list of tasks I need to complete | start implementing those tasks. |
+| `* * *` | NUS student | create the tasks in the tasklist | add the list of tasks that need to be completed |
+| `* * *` | NUS student | mark a task as complete | have a better idea of what I have completed. |
+| `* * *` | NUS student | delete the tasks in my tasklist | remove them if added wrongly. |
+| `* * *` | NUS student | delete the modules in my modulelist | remove them if added wrongly. |
+| `* * *` | NUS student | edit the modules in my modulelist | remove them if added wrongly. |
+| `* *` | NUS student | filter the task list by module, completion status and link status | easily look for a task. |
+| `* * *` | NUS student | delete the exams in my exam list | remove exams that I no longer want to track. |
+| `* * *` | NUS student | unlink a task from its exam | remove links that I linked wrongly. |
+| `* *` | NUS student | look at all tasks specific to an exam | easily track tasks of my next exam. |
+| `* *` | NUS student | clear all the tasks in my task list | quickly start with an empty task list. |
+| `* *` | NUS student | clear all tasks, exams and modules in the respective lists | quickly start with an empty task, module and exam list. |
+| `* * *` | NUS student | view a help guide on how to use the list of commands | refer to this guide when I forget some of the commands |
+| `* * *` | NUS student | indicate a task is completed | spend more time on other tasks. |
+| `* * *` | NUS student | add modules to my module list | add the modules that I am currently taking to the module list |
+| `* * *` | NUS student | link the task in the task list to the exam in the exam list | track the number of exam-related tasks |
+| `* * *` | NUS student | add my exams to the exam list | add my upcoming exams to the exam list to track my revision progress. |
+| `* * *` | NUS student | view the list of modules I have | see the modules I am taking and my study progress for the modules. |
+| `* * ` | NUS student | tag the priority status of a task in the task list | prioritise the task that I would like to complete first |
+| `* * ` | NUS student | tag the deadline of a task in the task list | track the date that the task should be completed |
+| `* * ` | NUS student | edit the priority status tagged to a task in the task list | change the priority of the task I would like to complete first |
+| `* * ` | NUS student | edit the deadline tagged to a task in the task list | change the deadline that I would like to complete the task |
+| `* * ` | NUS student | delete the priority status tagged to a task in the task list | remove the priority status of tasks which have been added wrongly |
+| `* * ` | NUS student | tag the priority status tagged to a task in the task list | remove deadlines which I no longer want to track. |
+| `* * ` | NUS student | sort the tasks in the task list | organise the tasks in the task list. |
+| `* *` | NUS student | find a task by task description through a command | quickly locate the task instead of having to go through the whole list of tasks just to find it. |
+| `* *` | NUS student | find a module by module code through a command | quickly locate the module instead of having to go through the whole list of modules just to find it. |
+| `* *` | NUS student | edit the exams in the exam list | change and correct the exam details easily if I input the wrong details. |
+| `* *` | NUS student | indicate a task is not completed | continue working on the task. |
+| `* *` | NUS student | edit the tasks in my task list | easily change and correct the details of my tasks. |
+| `* *` | NUS student | view my progress for each module | focus on the modules which currently have little progress. |
+| `* *` | NUS student | view my progress for each exam | focus on revising for the exams which currently have little progress. |
+
-*{More to be added}*
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `MODPRO` and the **Actor** is the `NUS student`, unless specified otherwise)
-**Use case: Delete a person**
+
+**Use case: Add a task into task list**
**MSS**
+1. NUS student requests to add a task.
+2. MODPRO shows the task added.
+3. MODPRO updates the progress bar to include the added task.
+ Use case ends.
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+**Extensions**
+* 1a. The command format is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The module code is invalid.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The given description is empty.
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+* 1d. The module does not exist in the module list.
+ * 1d1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: List tasks in task list**
- Use case ends.
+**MSS**
+1. NUS student requests to view all tasks in the stored task list
+2. MODPRO displays the list of all tasks and display a message of "Listed all tasks".
+
+ Use case ends.
**Extensions**
+* 1a. The provided command is in an invalid command format.
+
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+
-* 2a. The list is empty.
+**Use case: Indicate a task is completed**
+
+**MSS**
+1. NUS student requests to mark a specific task
+2. MODPRO ticks the specified task
+3. MODPRO updates the progress bar for the module and exam (if it exists) of the task
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The task specified is already marked
+ * 1c1. MODPRO shows an error message
+ Use case ends.
+
+**Use case: Indicate a task is not completed**
+**MSS**
+1. NUS student requests to unmark a specific task
+2. MODPRO unticks the specified task
+3. MODPRO updates the progress bar for the module and exam (if it exists) of the task
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The task specified is already unmarked
+ * 1c1. MODPRO shows an error message
+ Use case ends.
+
+**Use case: Edit a task**
+
+**MSS**
+1. NUS student requests to edit the module or description of a specific task
+2. MODPRO updates the specified task with the new values provided
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. Neither the module nor the description is provided.
+ * 1c1. MODPRO shows an error message
+ Use case ends.
+* 1d. The given module code is invalid
+ * 1d1. MODPRO shows an error message
+ Use case ends.
+* 1e. The given description is invalid
+ * 1e1. MODPRO shows an error message
+ Use case ends.
+* 1f. The given module does not exist in the module list
+ * 1f1. MODPRO shows an error message
+ Use case ends.
+* 1g. The module and description of the specified task are not changed.
+ * 1g1. MODPRO shows an error message
+ Use case ends.
+* 1h. The edited task is the same as another existing task in the task list
+ * 1h1. MODPRO shows an error message
+ Use case ends.
+* 2a. The module of the specified task is changed and the specified task is linked to an exam
+ * 2a1. MODPRO unlinks the task from its exam and updates the progress bar for the exam
+ * 2a2. MODPRO updates the progress bar for both the current and previous module of the task
+ Use case ends.
+* 2b. The module of the specified task is changed and the specified task is not linked to any exam
+ * 2b1. MODPRO updates the progress bar for both the current and previous module of the task
+ Use case ends.
+
+**Use Case: Add tags to a task**
+
+**MSS**
+1. NUS student requests to add a tag to a task in task list
+2. MODPRO adds the tag to the task in the task list
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The priority status provided is invalid
+ * 1c1. MODPRO shows an error message
+ Use case ends
+* 1d. The deadline provided is invalid
+ * 1d1. MODPRO shows an error message
+ Use case ends
+* 1e. The task already has a priority status
+ * 1e1. MODPRO shows an error message
+ Use case ends
+* 1f. The task already has a deadline
+ * 1f1. MODPRO shows an error message
+ Use case ends
+
+**Use Case: Edit the tags of a task**
+
+**MSS**
+1. NUS student requests to edit the tags of a task in task list
+2. MODPRO edits the tag of the task in the task list
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The priority status provided is invalid
+ * 1c1. MODPRO shows an error message
+ Use case ends
+* 1d. The deadline provided is invalid
+ * 1d1. MODPRO shows an error message
+ Use case ends
+* 1e. The task does not have a priority status
+ * 1e1. MODPRO shows an error message
+ Use case ends
+* 1f. The task does not have a deadline
+ * 1f1. MODPRO shows an error message
+ Use case ends
+* 1g. The priority status is the same as the priority status of the task
+ * 1g1. MODPRO shows an error message
+ Use case ends
+* 1h. The deadline is the same as the deadline of the task
+ * 1h1. MODPRO shows an error message
+ Use case ends
+
+**Use Case: Delete tags of a task**
+
+**MSS**
+1. NUS student requests to delete the tags of a task in task list
+2. MODPRO deletes the tag of the task in the task list
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The keywords provided is invalid
+ * 1c1. MODPRO shows an error message
+ Use case ends
+* 1d. The task does not have a priority status
+ * 1d1. MODPRO shows an error message
+ Use case ends
+* 1e. The task does not have a deadline
+ * 1e1. MODPRO shows an error message
+ Use case ends
+
+
+**Use case: Delete a task from the task list**
+
+**MSS**
+1. User requests to delete a specific task in the task list
+2. MODPRO deletes the task
+
+ Use case ends.
+
+**Extensions**
+* 1a. The given index is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Filter the task list**
-* 3a. The given index is invalid.
+**MSS**
+1. NUS student requests to filter the task list based on some conditions.
+2. MODPRO shows the list of tasks that fulfil the given conditions.
+ Use case ends.
+
+**Extensions**
+* 1a. The command format is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The module code is invalid.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The module does not exist in the task list.
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+* 1d. The completion status provided is invalid.
+ * 1d1. MODPRO shows an error message.
+ Use case ends.
+* 1e. The link status provided is invalid.
+ * 1e1. MODPRO shows an error message.
+ Use case ends.
+* 1f. There are no filter conditions stated.
+ * 1f1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Clear the task list**
+
+**MSS**
+1. NUS student requests to clear the task list.
+2. MODPRO clears the task list.
+3. MODPRO resets all exam and module progress bars.
+ Use case ends.
+
+**Extensions**
+* 1a. The task list is already empty
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+
+**Use Case: Add a module to the module list**
- * 3a1. AddressBook shows an error message.
+**MSS**
+1. NUS student requests to add a module to the module list
+2. MODPRO adds the module to the module list
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given module code is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The given module name is invalid
+ * 1c1. MODPRO shows an error message
+ Use case ends
+* 1d. The given module credit is invalid
+ * 1d1. MODPRO shows an error message
+ Use case ends
+* 1e. The given module code already exists in the module list
+ * 1e1. MODPRO shows an error message
+ Use case ends
+* 1f. The given module name already exists in the module list
+ * 1f1. MODPRO shows an error message
+ Use case ends
+* 1g. The given module credit already exists in the module list
+ * 1g1. MODPRO shows an error message
+ Use case ends
+
+**Use case: Delete a module from the module list**
- Use case resumes at step 2.
+**MSS**
+1. User requests to delete a specific module in the module list
+2. MODPRO deletes the module
+
+ Use case ends.
+
+**Extensions**
+* 1a. The given index is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The module at the given index is tied to multiple tasks thus cannot be deleted
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
-*{More to be added}*
+**Use case: Edit a module in the module list**
+
+**MSS**
+1. User requests to edit a specific module in the module list
+2. MODPRO edits the module
+
+ Use case ends.
+
+**Extensions**
+* 1a. The given index is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The module at the given index is tied to multiple tasks thus cannot be edited
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The given module code is invalid
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+
+
+**Use case: Delete an exam from the exam list**
+
+**MSS**
+1. NUS student requests to delete a specific exam in the exam list.
+2. MODPRO deletes the exam.
+3. MODPRO unlinks all tasks currently linked to the deleted exam.
+ Use case ends.
+
+**Extensions**
+* 1a. The command format is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The given index is non-positive or larger than 2147483647.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The given index is larger than the number of exams in the exam list.
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Unlink task from exam**
+
+**MSS**
+1. NUS student requests to unlink a task from its exam.
+2. MODPRO unlinks the task.
+3. MODPRO updates the exam progress bar.
+ Use case ends.
+
+**Extensions**
+* 1a. The command format is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The given index is non-positive or larger than 2147483647.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The given index is larger than the number of tasks in the task list.
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+* 1d. The task is not linked to any exam.
+ * 1d1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Showing the tasks of an exam**
+
+**MSS**
+1. NUS student requests to list all tasks of a specified exam.
+2. MODPRO shows list of all tasks of the specified exam.
+ Use case ends.
+
+**Extensions**
+* 1a. The command format is invalid.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The given index is non-positive or larger than 2147483647.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The given index is larger than the number of exams in the exam list.
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Clear all lists**
+
+**MSS**
+1. NUS student requests to clear all lists.
+2. MODPRO clears task, exam and module lists.
+ Use case ends.
+
+**Extensions**
+* 1a. All lists are already empty
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+
+
+**Use Case: Sort the task list**
+
+**MSS**
+1. NUS student requests to sort the task list
+2. MODPRO sorts the task in the task list
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given criteria is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+
+**Use Case: Link the exam to a task**
+
+**MSS**
+1. NUS student requests to link the exam in the exam list to a task in the task list
+2. MODPRO links the task to the exam
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The given index for the task is invalid
+ * 1b1. MODPRO shows an error message
+ Use case ends.
+* 1c. The given index for the exam is invalid
+ * 1c1. MODPRO shows an error message
+ Use case ends.
+* 1d. The task is already linked
+ * 1d1. MODPRO shows an error message
+ Use case ends.
+* 1e. The task and exam selected have a different module code
+ * 1e1. MODPRO shows an error message
+ Use case ends.
+
+
+
+
+**Use case: List modules in module list**
+
+**MSS**
+1. NUS student requests to view all modules in the stored module list.
+2. MODPRO displays the list of all modules and display a message of "Listed all modules".
+
+ Use case ends.
+
+**Extensions**
+* 1a. The provided command is in an invalid command format.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Find tasks in the task list**
+
+**MSS**
+1. NUS student requests to find tasks whose description matches the keyword inputted partially or fully.
+2. MODPRO show the list of tasks whose description matches the keyword partially or fully.
+
+ Use case ends.
+
+**Extensions**
+* 1a. The keyword inputted is empty
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The provided command is in an invalid command format.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Find modules in the module list**
+
+**MSS**
+1. NUS student requests to find modules whose module code matches the keyword inputted partially or fully.
+2. MODPRO show the list of modules whose module code matches the keyword partially or fully.
+
+ Use case ends.
+
+**Extensions**
+* 1a. The keyword inputted is empty
+ * 1a1. MODPRO shows an error message
+ Use case ends.
+* 1b. The provided command is in an invalid command format.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Add an exam into the exam list**
+
+**MSS**
+1. NUS student requests to add an exam.
+2. MODPRO adds the exam into the exam list.
+
+ Use case ends.
+
+**Extensions**
+* 1a. The description of the exam is empty
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The exam date provided is invalid.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. Exam module does not exist in MODPRO.
+ * 1c1. MODPRO shows an error message.
+ Use case ends.
+* 1d. The exam to be added is the same as another existing exam in the exam list.
+ * 1d1. MODPRO shows an error message.
+ Use case ends.
+* 1e. The module code provided is invalid.
+ * 1e1. MODPRO shows an error message.
+ Use case ends.
+* 1f. The provided command is in an invalid command format.
+ * 1f1. MODPRO shows an error message.
+ Use case ends.
+* 1g. Not all the fields(Exam description, Exam date, Module) are provided.
+ * 1g1. MODPRO shows an error message.
+ Use case ends.
+
+**Use case: Edit an exam in the exam list**
+
+**MSS**
+1. NUS student requests to edit a specific exam in the exam list by specifying the index number of the exam to be edited.
+2. MODPRO updates the specified exam with the new values provided.
+
+ Use case ends.
+
+**Extensions**
+* 1a. The given exam description is empty.
+ * 1a1. MODPRO shows an error message.
+ Use case ends.
+* 1b. The given exam date is invalid.
+ * 1b1. MODPRO shows an error message.
+ Use case ends.
+* 1c. The given exam module code is invalid.
+ * 1c1.MODPRO shows an error message.
+ Use case ends.
+* 1d. The given exam module does not exist in MODPRO.
+ * 1d1. MODPRO shows an error message.
+ Use case ends.
+* 1e. The given index is invalid.
+ * 1e1. MODPRO shows an error message.
+ Use case ends.
+* 1f. The edited exam is the same as another existing exam in the exam list.
+ * 1f1. MODPRO shows an error message.
+ Use case ends.
+* 1g. The module and description and date of the exam are not changed.
+ * 1g1. MODPRO shows an error message
+ Use case ends.
+* 1h. The provided command is in an invalid command format.
+ * 1h1. MODPRO shows an error message.
+ Use case ends.
+* 1i. No fields are provided to edit the exam.
+ * 1i1. MODPRO shows an error message.
+ Use case ends.
+* 2a. The module of the specified exam is changed and the exam is linked to some tasks previously.
+ * 2a1. MODPRO unlinks these tasks from the exam, and the updates the progress bar of the exam.
+ Use case ends.
### Non-Functional Requirements
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
+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 tasks without experiencing noticeable sluggishness in performance during 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.
-*{More to be added}*
### Glossary
* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **GUI**: Graphical User Interface
+* **UML** Unified Modeling Language
--------------------------------------------------------------------------------------------------------------------
@@ -338,40 +1408,386 @@ testers are expected to do more *exploratory* testing.
1. Initial launch
- 1. Download the jar file and copy into an empty folder
+ 1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Double-click the jar file Expected: Shows the GUI. The window size may not be optimum.
1. Saving window preferences
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+ 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
- 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
-
-### Deleting a person
-
-1. Deleting a person while all persons are being shown
-
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
-
- 1. Test case: `delete 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.
-
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
-
-1. _{ more test cases … }_
-
-### Saving data
-
-1. Dealing with missing/corrupted data files
-
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-1. _{ more test cases … }_
+### Adding a module
+1. Adding a module into the module list
+ 1. Test case: `m add c/cs2100 m/Computer Organisation mc/4`
+ Expected: Module with the name `Computer Organisation`, module credit `4` and
+module code `cs2100` is added to the module list.
+ 2. Test case: `m add c/cs2100`
+ Expected: An error message will be displayed. There are missing compulsory fields for
+ module name and module credit.
+ 3. Other incorrect add module commands to try: `m add`, `m add c/1111 m/module mc/4`
+ Expected: An error message will be displayed.
+
+### Adding a tag to a task
+1. Adding a tag to a task in the task list that is not tagged
+ 1. Prerequisites: The task selected must not be tagged with any tags.
+ 2. Test case: `t tagadd 1 p/HIGH`
+ Expected: The task located at the first index in the displayed task list is tagged with the priority status `HIGH`.
+ 3. Test case: `t tagadd 1 dl/25-11-2022`
+ Expected: The task located at the first index in the displayed task list is tagged with the deadline of `25-11-2022`.
+ 4. Test case: `t tagadd 1 p/random`
+ Expected: An error message will be displayed. An invalid priority status is chosen.
+ 5. Other incorrect add tag commands to try: `t tagadd`, `t tagadd 2 dl/39-13-2000`
+ Expected: An error message will be displayed.
+2. Adding a tag to a task in the task list that is tagged with only a priority status
+ 1. Prerequisites: The task selected must be tagged with a priority status.
+ 2. Test case: `t tagadd 1 p/HIGH`
+ Expected: An error message will be displayed. The task located at the first index in the displayed task list is already tagged with a priority status.
+ 3. Test case: `t tagadd 1 dl/25-11-2022'
+ Expected: The task located at the first index in the displayed task list is tagged with the deadline of `25-11-2022`.
+ 4. Other incorrect add tag commands to try: `t tagadd`, `t tagadd 2 dl/39-13-2000`
+ Expected: An error message will be displayed.
+3. Adding a tag to a task in the task list that it tagged with only a deadline
+ 1. Prerequisites: The task selected must be tagged with a deadline.
+ 2. Test case: `t tagadd 1 dl/25-11-2022`H`
+ Expected: An error message will be displayed. The task located at the first index in the displayed task list is already tagged with a deadline.
+ 3. Test case: `t tagadd 1 p/HIGH`
+ Expected: The task located at the first index in the displayed task list is tagged with the priority status `HIGH`.
+ 4. Other incorrect add tag commands to try: `t tagadd`, `t tagadd 2 dl/39-13-2000`
+ Expected: An error message will be displayed.
+
+### Editing a tag of a task
+1. Editing the tag of a task in the task list that is not tagged at all
+ 1. Prerequisite is that the task selected is not tagged with either the priority status or deadline
+ 2. Test case: `t tagedit 1 p/HIGH`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a priority status.
+ 3. Test case: `t tagedit 1 dl/25-11-2022`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a deadline.
+ 4. Test case: `t tagedit 1 p/random`
+ Expected: An error message will be displayed. An invalid priority status is chosen.
+ 5. Other incorrect edit tag commands to try: `t tagedit`, `t tagedit 2 dl/39-13-2000`
+ Expected: An error message will be displayed.
+2. Editing the tag of a task in the task list that is tagged with a priority status
+ 1. Prerequisites: The task selected is tagged with only the priority status `HIGH`
+ 2. Test case: `t tagedit 1 p/HIGH`
+ Expected: An error message will be displayed. The task selected is tagged with the same priority status.
+ 3. Test case: `t tagedit 1 p/LOW`
+ Expected: The task located at the first index of the displayed task list is tagged with the priority status `LOW`
+ 4. Other incorrect edit tag commands to try: `t tagedit`, `t tagedit 2 dl/39-13-2000`
+ Expected: An error message will be displayed.
+3. Editing the tag of a task in the task list that it tagged with a deadline
+ 1. Prerequisites: The task selected is tagged with only the deadline `25-11-2022`
+ 2. Test case: `t tagedit 1 dl/25-11-2022`H`
+ Expected: An error message will be displayed. The task selected is tagged with the same deadline.
+ 3. Test case: `t tagedit 1 dl/26-11-2022`
+ Expected: The task located at the first index in the displayed task list is tagged with the new deadline `26-11-2022`
+ 4. Other incorrect edit tag commands to try: `t tagedit`, `t tagedit 2 dl/39-13-2000`
+ Expected: An error message will be displayed.
+
+### Deleting a tag of a task
+1. Deleting the tag of a task in the task list that is not tagged at all
+ 1. Prerequisite is that the task selected is not tagged with either the priority status or deadline
+ 2. Test case: `t tagdel 1 t/priority`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a priority status.
+ 3. Test case: `t tagdel 1 t/deadline`
+ Expected: Error message is displayed. The task located at the first index in the displayed task list is not tagged with a deadline.
+ 4. Test case: `t tagdel 1 t/random`
+ Expected: An error message will be displayed. An invalid keyword is chosen.
+ 5. Other incorrect delete tag commands to try: `t tagdel`, `t tagdel -1 t/priority`
+ Expected: An error message will be displayed.
+2. Deleting the tag of a task in the task list that is tagged with a priority status
+ 1. Prerequisites: The task selected is tagged with only a priority status
+ 2. Test case: `t tagdel t/priority`
+ Expected: The task located at the first index in the displayed task list will have its priority status removed.
+ 3. Test case: `t tagdel t/deadline`
+ Expected: An error message is displayed. The task does not have a deadline.
+ 4. Other incorrect delete tag commands to try: `t tagdel`, `t tagdel -1 t/priority`
+ Expected: An error message will be displayed.
+3. Editing the tag of a task in the task list that it tagged with a deadline
+ 1. Prerequisites: The task selected is tagged with only a deadline
+ 2. Test case: `t tagdel t/priority`H`
+ Expected: An error message will be displayed. The task selected does not have a priority status
+ 3. Test case: `t tagdel t/deadline`
+ Expected: The task located at the first index in the displayed task list has its deadline removed.
+ 4. Other incorrect delete tag commands to try: `t tagdel`, `t tagdel -1 t/priority`
+ Expected: An error message will be displayed.
+
+### Sorting the task list
+1. Sorting the unfiltered task list
+ 1. Test case: `t sort c/priority`
+ Expected: Sorts all tasks in the unfiltered task list by priority status.
+ 2. Other incorrect sort task commands to try: `t sort`, `t sort c/low`
+ Expected: An error message will be displayed.
+2. Sorting the filtered task list
+ 1. Prerequisite: Perform a `t filter` operation on the task list
+ 2. Test case: `t sort c/priority`
+ Expected: Sorts all tasks in the filtered task list by priority status.
+ 3. Other incorrect sort task commands to try: `t sort`, `t sort c/low`
+ Expected: An error message will be displayed.
+
+### Linking the exam to a task
+1. Linking the exam to an unlinked task
+ 1. Prerequisite: The task selected is unlinked and the task and exam have the same module code
+ 2. Test case: `e link e/1 t/1`
+ Expected: Links the first task in the displayed task list with the first exam in the displayed exam list.
+ 3. Other incorrect link exam commands to try: `e link`, `e link e/-1 t/9999999999999999999`
+ Expected: An error message will be displayed.
+2. Linking the exam to a linked task
+ 1. Prerequisite: The task selected is linked and the task and exam selected have the same module code
+ 2. Test case: `e link e/1 t/1`
+ Expected: An error message is displayed. The first task in the displayed task list is already linked.
+ 3. Other incorrect link exam commands to try: `e link`, `e link e/-1 t/9999999999999999999`
+ Expected: An error message will be displayed.
+
+### Viewing the help window
+1. Viewing the help window
+ 1. Test case: `help`
+ Expected: The help window will display on the screen
+ 2. Incorrect help command to try: `help123`
+ Expected: An error message will be displayed
+
+
+
+### Adding an exam
+
+1. Adding an exam to the exam list
+ 1. Prerequisite: the module `cs2030s` is present in the module list, and `cs2040s` is not present in module list.
+ 2. Test case: `e add m/cs2030s ex/Finals ed/30-12-2023`
+ Expected: The exam with the module field as `cs2030s`, exam description as `Finals`, exam date as `30-12-2023` is added to the exam list.
+ A message is displayed to show that the exam is added successfully.
+ 3. Test case: `e add m/cs2030s ex/Finals ed/30-13-2023`
+ Expected: Exam will not be added, and error message will be shown to say that the date provided is not in DD-MM-YYYY format.
+ 4. Test case: `e add m/cs2040s ex/Finals ed/30-12-2023`
+ Expected: Exam will not be added, and error message will be shown to say that the module does not exist.
+ 5. Test case: `e add m/cs2030s ex/ ed/30-12-2023`
+ Expected: Exam will not be added, and error message will be shown to say that the description of the exam should not be empty.
+
+
+### Editing an exam
+1. Editing an exam in the exam list.
+ 1. Prerequisite: the module `cs2030s` is present in the module list, and `cs2040s` is not present in module list and exam list only has 2 exams.
+ 2. Test case: `e edit 1 m/cs2030s ex/Finals ed/30-12-2023`
+ Expected: The first exam in the exam list is edited by changing the module field to `cs2030s`, exam description to `Finals`, exam date to `30-12-2023`.
+ A message is displayed to show that the exam is edited successfully.
+ 3. Test case: `e edit 0 m/cs2030s ex/Finals ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the index should be greater than 0 and less than 2147483648 for the index of an exam.
+ 4. Test case: `e edit 1 m/cs2040s ex/Finals ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the module does not exist.
+ 5. Test case: `e edit 1 ex/ ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the description of the exam should not be empty.
+ 6. Test case: `e edit 1 ed/30-13-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the date provided is not in DD-MM-YYYY format.
+ 7. Test case: `e edit 4 ed/30-12-2023`
+ Expected: Exam will not be edited, and error message will be shown to say that the index should be more than 0 but less than 3.
+
+
+### Finding a task
+1. Finding a task in the task list
+ 1. Prerequisite: The tasks with task descriptions as "WORK", "homework 1", "homewoRK 2", "past year paper" are inside the task list.
+ 2. Test case: `t find work`
+ Expected: The task list should display the 3 tasks with task description of "WORK", "homework 1", "homewoRK 2",with a message saying "3 tasks listed"
+ 3. Test case: `t find paper`
+ Expected: The task list should only display the task with the task description "past year paper", with a message saying "1 tasks listed"
+ 4. Test case: `t find assignment`
+ Expected: The task list should show no tasks displayed, with a message saying "0 tasks listed"
+
+
+### Finding a module
+1. Finding a module in the module list
+ 1. Prerequisite: The modules with module code as "CS2030s", "CS2040S", "CS3333" are inside the module list.
+ 2. Test case: `m find cs`
+ Expected: The module list should display the 3 modules with module code of "CS2030s", "CS2040S", "CS3333",with a message saying "3 modules listed"
+ 3. Test case: `m find 20`
+ Expected: The module list should display the 2 modules with the module code of "CS2030s", "CS2040S",with a message saying "2 modules listed".
+ 4. Test case: `m find 5555`
+ Expected: The module list should show no module displayed, with a message saying "0 modules listed"
+
+
+### Listing modules
+1. Listing modules in the stored module list
+ 1. Prerequisite: The modules with module code as "CS2030s", "CS2040S", "CS3333" are inside the module list, and a `m find 20` command is done.
+ 2. Test case: `m list`
+ Expected: The module list should now display the all 3 modules with module code of "CS2030s", "CS2040S", "CS3333",with a message saying "Listed all modules"
+
+
+
+### Listing tasks
+1. Listing tasks in the stored tasks list
+ 1. Prerequisite: The tasks with task descriptions as "WORK", "homework 1", "homewoRK 2", "past year paper" are inside the task list, and a `t find paper` command is done.
+ 2. Test case: `t list`
+ Expected: The task list should now display the all 4 tasks with task descriptions of "WORK", "homework 1", "homewoRK 2", "past year paper",with a message saying "Listed all tasks"
+
+### Marking a task
+
+1. Marking a task while all tasks are being shown
+ * Prerequisites:
+ * List all tasks using the `t list` command.
+ * The task list displays multiple tasks.
+ * Test case: `t mark 1`
+ Expected:
+ * First task in the list is ticked.
+ * Details of the marked task shown in the feedback message.
+ * Progress bar for the module of the task is updated.
+ * Test case: `t mark 0`
+ Expected:
+ * No changes made to any tasks, exams or modules.
+ * Error details shown in the feedback message.
+ * Test case: `t mark INDEX` (where `INDEX` is the index of a task that is already marked)
+ Expected: Similar to the previous test case.
+ * Other invalid mark commands to try: `t mark `, `t mark asd`, `t mark INDEX` (where `INDEX` is larger than the list size)
+ Expected: Similar to the previous test case.
+
+2. Marking a task with only some tasks shown
+ * Prerequisites:
+ * Filter the tasks using the `t filter` command.
+ * The task list displays multiple tasks.
+ * Test case: `t mark 1`
+ Expected:
+ * First task in the list is ticked.
+ * Details of the marked task shown in the feedback message.
+ * Progress bar for the module of the task is updated.
+ * Test case: `t mark INDEX` (where `INDEX` is larger than the size of the displayed list but less than the size of the stored task list)
+ Expected:
+ * No changes made to any tasks, exams or modules.
+ * Error details shown in the feedback message.
+
+3. Marking a task linked to an exam
+ * Prerequisites:
+ * The list contains a task linked to an exam.
+ * The task shows the name of the exam.
+ * Test case: `t mark INDEX` (where `INDEX` is the index of the linked task)
+ Expected:
+ * The task specified is ticked.
+ * Details of the marked task shown in the feedback message.
+ * Progress bars for both the module of the task and the exam it is linked to, are updated.
+
+
+### Editing a task
+
+1. Editing a task while all tasks are being shown
+ * Prerequisites:
+ * List all tasks using the `t list` command.
+ * The task list displays multiple tasks.
+ * There are no tasks with the description 'task 1'.
+ * Test case: `t edit 1 d/task 1`
+ Expected:
+ * The description of the first task in the list is changed to 'task 1'.
+ * Details of the edited task is shown in the feedback message.
+ * Test case: `t edit 0 d/task 1`
+ Expected:
+ * No changes made to any tasks, exams or modules.
+ * Error details shown in the feedback message.
+ * Other incorrect edit commands to try: `t edit d/task 1`, `t edit 1`, `t edit asd d/task 1`, `t edit INDEX d/task 1` (where `INDEX` is larger than the list size), `t edit 1 d/DESCRIPTION` (where `DESCRIPTION` is the current description of the first task in the list)
+ Expected: Similar to the previous test case.
+
+2. Editing a task with only some tasks shown
+ * Prerequisites:
+ * Filter the tasks using the `t filter` command.
+ * The task list displays multiple tasks but not all the tasks in the stored task list.
+ * There are no tasks in the stored task list with the description 'task 1'.
+ * Test case: `t edit 1 d/task 1`
+ Expected:
+ * The description of the first task in the list is changed to 'task 1'.
+ * Details of the marked task shown in the feedback message.
+ * Test case: `t edit INDEX d/task 1` (where `INDEX` is larger than the size of the displayed list but less than the size of the stored task list)
+ Expected:
+ * No changes made to any tasks, exams or modules.
+ * Error details shown in the feedback message.
+
+3. Editing a task with invalid parameters
+ * Prerequisites:
+ * There are no modules in the stored module list with the module code 'cs2030'.
+ * The task list displays multiple tasks.
+ * Test cases: `t edit 1 m/cs2030`, `t edit 1 m/c`, `t edit 1 d/ `
+ Expected:
+ * No changes made to any tasks, exams or modules.
+ * Error details shown in the feedback message.
+
+4. Editing a task to be the same as another task
+ * Prerequisites:
+ * There are 2 modules in the stored module list.
+ * The first 2 tasks in the list have the same module.
+ * Test cases: `t edit 1 d/DESCRIPTION` (where DESCRIPTION is the description of the second task)
+ Expected:
+ * No changes made to any tasks, exams or modules.
+ * Error details shown in the feedback message.
+
+5. Editing the module of a task linked to an exam
+ * Prerequisites:
+ * List all tasks using the `t list` command.
+ * There are 2 modules in the stored module list, 1 exam in the exam list and 1 task in the task list.
+ * The module of both the exam and the task is the first module in the module list.
+ * The task is linked to the exam.
+ * Test cases: `t edit 1 m/MODULE` (where `MODULE` is the module code of the second module in the list)
+ Expected:
+ * The module of the task in the list is changed to `MODULE`.
+ * A warning and the details of the edited task are shown in the feedback message.
+ * Progress bars for the 2 modules are updated.
+
+### Adding a task
+1. Adding a task to the task list.
+ 1. Prerequisite: The module `cs2030s` is present in the module list, and `cs2040s` is not present in the module list.
+ 2. Test case: `t add m/cs2030s d/assignment`
+ Expected: A task with module `cs2030s` and description `assignment` is added to the task list. A message is displayed to show that task has been added successfully.
+ 3. Test case: `t add m/cs2040s d/assignment`
+ Expected: Task will not be added, and an error message will be shown to say that the module does not exist.
+ 4. Test case: `t add m/cs d/assignment`
+ Expected: Task will not be added, and an error message will be shown to say that the module code is invalid.
+ 5. Test case: `t add m/cs2040s d/`
+ Expected: Task will not be added, and an error message will be shown to say that the description should not be empty.
+
+### Filtering the task list
+1. Filtering the task list by module, completion status and link status.
+ 1. Prerequisite: First task with module `cs2030s`, which is `complete` and `linked`. Second task with module `cs2030s`, which is `incomplete` and `unlinked`.
+ 2. Test case: `t filter m/cs2030s`
+ Expected: Task list displays both tasks.
+ 3. Test case: `t filter m/cs2040s`
+ Expected: Task list does not display both tasks.
+ 4. Test case: `t filter c/y`
+ Expected: Task list displays first task only.
+ 5. Test case: `t filter l/n`
+ Expected: Task list displays second task only.
+ 6. Test case: `t filter m/cs`
+ Expected: Task list does not change, and an error message will be shown to say that the module code is invalid.
+ 7. Test case: `t filter c/yes`
+ Expected: Task list does not change, and an error message will be shown to say that response to condition is invalid.
+
+### Clearing the task list
+1. Clearing non-empty task list.
+ 1. Prerequisite: Task with module `cs2030s`, which is marked `complete`.
+ 2. Test case: `t clear`
+ Expected: Task list is cleared. `cs2030s` module progress bar resets.
+
+### Deleting an exam
+1. Deleting an exam from the exam list.
+ 1. Prerequisite: One exam in the exam list. One task in the task list linked to the exam.
+ 2. Test case: `e del 1`
+ Expected: Exam is deleted, task becomes unlinked.
+ 3. Test case: `e del 2`
+ Expected: Both lists remain unchanged, and an error message will be shown to say that the exam list index is invalid.
+
+### Unlinking an exam
+1. Unlink an exam from a task.
+ 1. Prerequisite: One exam in the exam list. First task in the task list linked to the exam. Second task is unlinked.
+ 2. Test case: `e unlink 1`
+ Expected: First task becomes unlinked.
+ 3. Test case: `e unlink 2`
+ Expected: Task list remains unchanged, and an error message will be shown to say that the task is already unlinked.
+ 4. Test case: `e unlink 3`
+ Expected: Both lists remain unchanged, and an error message will be shown to say that the task list index is invalid.
+
+### Showing tasks of an exam
+1. Show all tasks linked to an exam.
+ 1. Prerequisite: Two exams in the exam list and two tasks in the task list. Link first task to first exam and second task to second exam.
+ 2. Test case: `e showt 1`
+ Expected: Task list displays first task only.
+ 3. Test case: `e showt 3`
+ Expected: Both lists remain unchanged, and an error message will be shown to say that the exam list index is invalid.
+
+### Clearing all lists
+1. Clear all module, task and exam lists.
+ 1. Test case: `clearall`
+ Expected: Module, task and exam lists cleared.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..48fbc593c53 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,190 +3,912 @@ 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.
+## Introduction
-* Table of Contents
-{:toc}
+Welcome to the User Guide of MODPRO!
+
+MODPRO is a desktop application which helps NUS students track the progress of their modules.
+
+MODPRO helps you…
+* organise your tasks by modules and exams
+* track your progress for each module and exam with a progress bar
+* easily navigate through your tasks by tagging them, and filtering and sorting your task list
+
+It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. If you struggle to type fast, we also provide a Graphical User interface (GUI) to assist you in using MODPRO.
+
+------------------
+
+
+
+### The Graphical User Interface (GUI)
+
+To give you a quick overview of MODPRO, the following image shows you the main components of the GUI.
+
+|  |
+|:---------------------:|
+| GUI of MODPRO |
+
+The table below summarises the function of each component.
+
+| **Component** | **Function** |
+|---------------------------|--------------------------------------------------------------|
+| Menu Bar | To view the help window and to exit the application |
+| Command Input | To key in commands which will be executed |
+| Feedback Message Display | To view the feedback given after the execution of a command |
+| Task List | To view your tasks |
+| Module List | To view your modules |
+| Exam List | To view your exams |
+
+
+### Icons and formatting used in the guide
+
+
+This guide uses icons and formatting to differentiate between the different types of information so that it is comprehensible. The following table summarises the icons and formatting used, along with their meaning.
+
+| **Icon/Formatting** | **Meaning** |
+|--------------------------|--------------------------------------------------------------|
+| :information_source: | Extra information that is good to know |
+| :exclamation: | Warnings regarding the use of specific commands and features |
+| :bulb: | Tips on using MODPRO |
+| `WORDS HIGHLIGHTED GREY` | Words that you can type into the Command Input |
+
+
+### Purpose of the guide
+This document is to assist you in using MODPRO smoothly and effectively to track your tasks and progress.
+
+* For first-time users, you can proceed to [Quick Start](#quick-start) for a guide on how to set up MODPRO and a short tutorial on the basic commands. Once you are familiar with the interface, you can start exploring our extensive list of features [here](#features)
+* For the experienced users, you can learn more about these unique features that can help you manage your tasks better: [adding tags to your tasks](#adding-the-tags-to-a-task), [sorting your list](#sorting-the-task-list), [filtering your list](#filtering-the-task-list), [linking an exam to a task](#linking-an-exam)
--------------------------------------------------------------------------------------------------------------------
+
+
+
+
+## Table of Contents
+- [Quick Start](#quick-start)
+- [Features](#features)
+ - [Modules-Related Features](#modules-related-features)
+ - [Adding a module](#adding-a-module)
+ - [Listing the modules](#listing-the-modules)
+ - [Finding a module](#finding-a-module)
+ - [Deleting a module](#deleting-a-module)
+ - [Editing a module](#editing-a-module)
+ - [Tasks-Related Features](#tasks-related-features)
+ - [Adding a task](#adding-a-task)
+ - [Deleting a task](#deleting-a-task)
+ - [Editing a task](#editing-a-task)
+ - [Marking a task](#marking-a-task)
+ - [Unmarking a task](#unmarking-a-task)
+ - [Listing the tasks](#listing-the-tasks)
+ - [Filtering the task list](#filtering-the-task-list)
+ - [Finding a task](#finding-a-task)
+ - [Sorting the task list](#sorting-the-task-list)
+ - [Adding the tags to a task](#adding-the-tags-to-a-task)
+ - [Editing the tags of a task](#editing-the-tags-of-a-task)
+ - [Deleting the tags of a task](#deleting-the-tags-of-a-task)
+ - [Clearing the task list](#clearing-the-task-list)
+ - [Exams-Related Features](#exams-related-features)
+ - [Adding an exam](#adding-an-exam)
+ - [Editing an exam](#editing-an-exam)
+ - [Deleting an exam](#deleting-an-exam)
+ - [Linking an exam](#linking-an-exam)
+ - [Unlinking an exam](#unlinking-an-exam)
+ - [Showing the tasks of an exam](#showing-the-tasks-of-an-exam)
+ - [Other Features](#other-features)
+ - [Clearing all the lists](#clearing-all-the-lists)
+ - [Opening the help window](#opening-the-help-window)
+ - [Exiting the program](#exiting-the-program)
+- [Future Features](#1.1)
+- [General](#general)
+ - [Saving data to the data file](#saving-data-to-the-data-file)
+ - [Editing the data file](#editing-the-data-file)
+- [FAQ](#faq)
+- [Summary Of Commands](#summary-of-commands)
-## Quick start
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Quick start
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+2. Download the latest `modpro.jar` from [here](https://github.com/AY2223S1-CS2103T-F11-2/tp/releases/download/v1.4/modpro.jar).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+3. Copy the file to the folder you want to use as the _home folder_ for MODPRO.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Double-click the file to start the app. The GUI similar to the image below should appear in a few seconds.

-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
-
- * **`list`** : Lists all contacts.
+
+
+
- * **`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.
+5. Type the command in the command box and press Enter to execute it.
+ e.g. typing help and pressing Enter will open the help window.
+ Here are some commands you can try:
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ * `m add c/CS2100 m/Computer Organisation mc/4` : Adds the module called Computer Organisation with the module code CS2100 into the module list.
+ * `m list` : Lists all modules stored in the module list.
+ * `m delete 1` : Deletes the 1st module shown in the displayed module list.
+ * `exit` : Exits the app.
- * **`clear`** : Deletes all contacts.
+
+6. Refer to the [Features](#features) below for details of each command.
- * **`exit`** : Exits the app.
+--------------------------------------------------------------------------------------------------------------------
+
-1. Refer to the [Features](#features) below for details of each command.
---------------------------------------------------------------------------------------------------------------------
## Features
+**:information_source: How to read the command format:**
+
+* Command words are case-insensitive.
+ e.g. `t add` is the same as `T add` for command words.
-**: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`.
+* Prefixes are case-sensitive.
+ e.g. `t add m/CS2030 d/Programming` is not the same as `t add M/CS2030 D/Programming` and the latter command will throw an error message.
+* Words in UPPER_CASE are the parameters to be supplied by the user.
+ e.g. in `t add m/MODULE`, `MODULE` is a parameter which can be used as `t add m/CS2030`.
+
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+ e.g `t/KEYWORD [SECOND_KEYWORD]` can be used as `t/priority deadline` or as `t/priority`.
+
+* Items in square brackets with * are optional, but at least one of them is required.
+ e.g `[m/MODULE]* [d/DESCRIPTION]*` can be used as `m/cs2030` or `m/cs2030 d/assignment` but not ` ` (none provided).
+
+* Parameters can be in any order.
+ e.g. if the command specifies `m/MODULE d/DESCRIPTION`, `d/DESCRIPTION m/MODULE` is also acceptable.
+
+* If a parameter is expected only once in the command and it is specified multiple times, only the last occurrence of the parameter will be taken.
+ e.g. if you specify `m/cs2030 m/cs2040`, only `m/cs2040` will be taken.
+
+* Extraneous parameters for commands that do not take in parameters (such as help, exit and clearall) will be ignored.
+ e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+
-* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+--------------------------
+
-* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken.
-* 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`.
+## Modules-related Features
+
+### Adding a module
+Adds a module into the stored module list.
+
+Format: `m add c/MODULE_CODE m/MODULE_NAME mc/MODULE_CREDIT`
+
+Parameters:
+* `MODULE_CODE` refers to the module code of the module being added.
+* `MODULE_NAME` refers to the name of the module.
+* `MODULE_CREDIT` refers to the number of module credits that the module has.
+Restrictions:
+* `MODULE_CODE`
+ * `MODULE_CODE` should be at least 6 characters long.
+ * The first two characters of `MODULE_CODE` should be alphabetical and the remaining characters should be alphanumeric.
+* `MODULE_NAME` should not be empty.
+* `MODULE_CREDIT` should be an integer between 0 and 45 inclusive.
+
+
+
+:information_source: **Note:** `MODULE_CODE` is case-insensitive.
-### Viewing help : `help`
+Examples:
-Shows a message explaning how to access the help page.
+`m add c/cs2100 m/computer organisation mc/4` adds a module with the module code 'cs2100', the module name 'computer organisation' and the module credit '4'.
-
+`m add c/cs2105 m/networking mc/3` adds a module with the module code 'cs2105', the module name 'networking' and the module credit '3'.
-Format: `help`
+|  |
+|:-----------------------------------------------------------------------:|
+| Demonstration of Command: `m add c/cs2100 m/computer organisation mc/4` |
-### Adding a person: `add`
+### Listing the modules
+Lists all modules in the stored module list.
-Adds a person to the address book.
+Format: `m list`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+### Finding a module
+Finds modules in the stored module list whose module code matches the `KEYWORD` partially or fully.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+Format: `m find KEYWORD`
+
+Parameter:
+* `KEYWORD` refers to the keyword inputted by the user.
+
+
+
+:information_source: **Note:** `KEYWORD` is case-insensitive.
+------------------
+
+
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
-### Listing all persons : `list`
+`m find CS` finds modules whose module code contains the `KEYWORD` 'CS' such as 'CS2030S', 'CS2040S'.
-Shows a list of all persons in the address book.
+`m find 30` finds modules whose module code contains the `KEYWORD` '30' such as 'CS2030S'.
-Format: `list`
+### Deleting a module
+Deletes the specified module from the stored module list.
-### Editing a person : `edit`
+Format: `m del INDEX`
-Edits an existing person in the address book.
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed module list) of the module to be deleted.
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of modules in the displayed module list.
+
+Examples:
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+`m del 1` deletes the first module in the displayed module list.
-* 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.
+`m del 3` deletes the third module in the displayed module list.
+
+
+:exclamation: **Warning:** All tasks and exams related to the module will be deleted after the specified module is deleted.
+
+
+------------------
+
+
+
+### Editing a module
+Edits the specified module by updating the existing values to the input values.
+
+Format: `m edit INDEX [c/MODULE_CODE]* [m/MODULE_NAME]* [mc/MODULE_CREDIT]*`
+
+Parameters:
+* `INDEX` refers to the index number (shown in the displayed module list) of the module to be edited.
+* `MODULE_CODE` refers to the module code that will replace the existing module code of the module specified.
+* `MODULE_NAME` refers to the module name that will replace the existing module name of the module specified.
+* `MODULE_CREDIT` refers to the module credit that will replace the existing module credit of the module specified.
+
+
+:information_source: **Note:** `MODULE_CODE` is case-insensitive.
+
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of modules in the displayed module list.
+* `MODULE_CODE`
+ * `MODULE_CODE` should be at least 6 characters long.
+ * The first two characters of `MODULE_CODE` should be alphabetical and the remaining characters should be alphanumeric.
+ * `MODULE_CODE` should not be the module code of an existing module in the stored module list.
+* `MODULE_NAME` should not be empty.
+* `MODULE_CREDIT`
+ * `MODULE_CREDIT` should not be empty.
+ * `MODULE_CREDIT` should be an integer between 0 and 45 inclusive.
+* The input values should not be the same as existing values.
+* The edited module should not be the same as any existing module in the stored module list.
+
+
+:exclamation: **Warning:** If the module code of the module is edited, and the module is related to some tasks or exams, the module of these tasks and exams will be changed to this edited module.
+
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
-### Locating persons by name: `find`
+`m edit 1 m/Programming Methodology I mc/4` changes the module name of the first module in the displayed module list to 'Programming Methodology I' and the module credit to '4'.
+
+`m edit 2 c/cs2040 m/Data Structures and Algorithms mc/4` changes the module name of the second module in the displayed module list to ‘Data Structures and Algorithms’, the module code to ‘cs2040’ and the module credit to ‘4’.
+
+## Tasks-related Features
+
+### Adding a task
+Adds a task into the stored task list.
+
+Format: `t add m/MODULE d/DESCRIPTION`
+
+Parameters:
+* `MODULE` refers to the module code of the module which the task belongs to.
+* `DESCRIPTION` refers to the task description to be shown.
+
+
+
+:information_source: **Note:** `MODULE` is case-insensitive.
+
+
+Restrictions:
+* `MODULE`
+ * `MODULE` should be at least 6 characters long.
+ * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric.
+ * `MODULE` should be the module code of an existing module in the stored module list.
+* `DESCRIPTION` should not be empty.
+
+Example:
+`t add m/CS2105 d/Assignment 1` adds a task with the module as 'CS2105' and description as 'Assignment 1' into the stored task list.
-Finds persons whose names contain any of the given keywords.
+|  |
+|:---------------------------------------------------------:|
+| Demonstration of Command: `t add m/CS2105 d/Assignment 1` |
-Format: `find KEYWORD [MORE_KEYWORDS]`
+### Deleting a task
+Deletes the specified task from the stored task list.
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+Format: `t del INDEX`
+
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to be deleted.
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of tasks in the displayed task list.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- 
-### Deleting a person : `delete`
+`t del 1` deletes the first task in the displayed task list.
+
+`t del 3` deletes the third task in the displayed task list.
+
+
+:exclamation: **Warning:** When the task is deleted, if it has a link to an exam, the task will be unlinked from the exam.
+
+
+### Editing a task
+Edits the specified task by updating the existing values to the input values.
+
+Format: `t edit INDEX [m/MODULE]* [d/DESCRIPTION]*`
+
+Parameters:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to be edited.
+* `MODULE` refers to the module code of the module that will replace the existing module of the task specified.
+* `DESCRIPTION` refers to the description that will replace the existing description of the task specified.
-Deletes the specified person from the address book.
+
-Format: `delete INDEX`
+:information_source: **Note:** `MODULE` is case-insensitive.
+
-* 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, …
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of tasks in the displayed task list.
+* `MODULE`
+ * `MODULE` should be at least 6 characters long.
+ * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric.
+ * `MODULE` should be the module code of an existing module in the stored module list.
+* `DESCRIPTION` should not be empty.
+* The input values should not be the same as existing values.
+* The edited task should not be the same as any existing task in the stored task list.
+
+
+
+:exclamation: **Warning:** If a task is linked to an exam, and its module is changed, the task will be unlinked from the exam.
+
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.
-### Clearing all entries : `clear`
+`t edit 1 d/Assignment 2` changes the description of the first task in the displayed task list to 'Assignment 2'.
-Clears all entries from the address book.
+`t edit 2 m/CS2040 d/tutorial 2` changes the module and description of the second task in the displayed task list to 'CS2040' and 'tutorial 2' respectively.
-Format: `clear`
+### Marking a task
+Indicates the specified task is completed.
-### Exiting the program : `exit`
+Format: `t mark INDEX`
-Exits the program.
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to be marked.
-Format: `exit`
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of tasks in the displayed task list.
+* The task specified should not be already marked.
+
+Examples:
-### Saving the data
+`t mark 1` indicates the first task in the displayed task list is completed.
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+`t mark 3` indicates the third task in the displayed task list is completed.
-### Editing the data file
+
+
+:bulb: **Tip:** You can sort and filter tasks based on their completion status. The percentage of completed tasks are also shown for each exam and module.
+
+
+------------------
+
+
+|  |
+|:-----------------------------------------:|
+| Demonstration of Command: `t mark 1` |
+
+### Unmarking a task
+Indicates the specified task is not completed.
+
+Format: `t unmark INDEX`
+
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to be unmarked.
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of tasks in the displayed task list.
+* The task specified should not be already unmarked.
+
+Examples:
+
+`t unmark 1` indicates the first task in the displayed task list is not completed.
+
+`t unmark 3` indicates the third task in the displayed task list is not completed.
+
+
+### Listing the tasks
+Lists all tasks in the stored task list.
+
+Format: `t list`
+
+
+### Filtering the task list
+Filters the displayed task list to show only tasks that fulfil the module code, completion status, and/or link status conditions.
+
+Format: `t filter [m/MODULE/]* [c/COMPLETED]* [l/LINKED]*`
+
+Parameters:
+* `MODULE` refers to the module code of the module to be filtered out.
+* `COMPLETED` should be `y` to filter tasks that are complete or `n` to filter tasks that are incomplete.
+* `LINKED` should be `y` to filter tasks that are linked to an exam or `n` to filter tasks that are not linked to any exam.
+
+
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+:information_source: **Note:** `MODULE` is case-insensitive.
+
-
: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.
+Restrictions:
+* `MODULE`
+ * `MODULE` should be at least 6 characters long.
+ * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric.
+ * `MODULE` should be the module code of an existing module in the stored module list.
+* `COMPLETED` should be `y` or `n`.
+* `LINKED` should be `y` or `n`.
+
+Examples:
+
+`t filter l/n` filters out all tasks that are currently not linked to any exam.
+
+`t filter m/cs2030 c/y` filters out all completed tasks that are under the module 'cs2030'.
+
+|  |
+|:----------------------------------------------------------:|
+| Demonstration of Command: `t filter l/n` |
+
+### Finding a task
+Finds tasks in the stored task list whose task description matches the `KEYWORD` partially or fully.
+
+Format: `t find KEYWORD`
+
+Parameter:
+* `KEYWORD` refers to the keyword inputted by the user.
+
+
+
+:information_source: **Note:** `KEYWORD` is case-insensitive.
-### Archiving data files `[coming in v2.0]`
+Examples:
-_Details coming soon ..._
+`t find work` finds tasks that contain the `KEYWORD` 'work' such as 'homework', 'work to do'.
---------------------------------------------------------------------------------------------------------------------
+`t find do paper` finds tasks that contain the `KEYWORD` 'do paper', such as 'do paper one', 'do paper two'.
-## FAQ
+|  |
+|:---------------------------------------------:|
+| Demonstration of Command: `t find work` |
+
+### Sorting the task list
+Sorts tasks in the task list based on the criteria specified.
+
+Format: `t sort c/CRITERIA`
+
+Parameter:
+* `CRITERIA` refers to the criteria that is used for sorting the task list.
+
+Restrictions:
+* `CRITERIA` can be either `priority`, `deadline`, `module` or `description`.
+* When sorting by priority, tasks marked with priority status `HIGH` will appear at the top of the displayed task list, followed by tasks with priority status`MEDIUM`, and lastly tasks with priority status `LOW`.
+* When sorting by deadline, tasks with the earlier deadlines will appear at the top while tasks with no deadlines will appear at the bottom of the displayed task list.
+* When sorting by module, all tasks will be sorted by the module code in alphanumeric order.
+* When sorting by description, all tasks will be sorted by the task description in alphanumeric order.
+
+
+
+:information_source: **Note:** `CRITERIA` is case-insensitive.
+
+
+Examples:
+
+`t sort c/description` sorts all the tasks in the task list by task description.
+
+`t sort c/priority` sorts all the tasks in the task list by priority status.
+
+`t sort c/deadline` sorts all the tasks in the task list by deadline.
+
+`t sort c/module` sorts all the tasks in the task list by module code.
+
+|  |
+|:---------------------------------------------------:|
+| Demonstration of Command: `t sort c/description` |
+
+### Adding the tags to a task
+Tags the specified task with a priority status and/or the deadline to complete the task.
-**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.
+Format: `t tagadd INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*`
+
+Parameters:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to be tagged.
+* `PRIORITY_STATUS` refers to the priority status which is tagged to the task in the displayed task list.
+* `DEADLINE` refers to the deadline which is tagged to the task in the displayed task list.
+
+---------------------------------------------------------
+
+
+
+
+:information_source: **Note:** `PRIORITY_STATUS` is case-insensitive.
+
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of tasks in the displayed task list.
+* `PRIORITY STATUS` should be one of these three priorities: `HIGH`, `MEDIUM`, `LOW`.
+* `DEADLINE` should be in the format DD-MM-YYYY and not earlier than the current date.
+
+Examples:
+
+`t tagadd 1 p/HIGH` tags the first task in the displayed task list with the priority status of 'high'.
+
+`t tagadd 1 dl/31-12-2022` tags the first task in the displayed task list with the deadline of '31-12-2022'.
+
+`t tagadd 2 p/low dl/31-12-2022` tags the second task in the displayed task list with a priority status of
+'low' and a deadline of '31-12-2022'.
+
+|  |
+|:-----------------------------------------------:|
+| Demonstration of Command: `t tagadd 1 p/HIGH` |
+
+
+### Editing the tags of a task
+Edits the tags associated with the specified task.
+
+Format: `t tagedit INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*`
+
+Parameters:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to edit the tags.
+* `PRIORITY_STATUS` refers to the priority status which is tagged to the task in the displayed task list.
+* `DEADLINE` refers to the deadline which is tagged to the task in the displayed task list.
+
+
+
+:information_source: **Note:** `PRIORITY_STATUS` is case-insensitive.
+
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of tasks in the displayed task list.
+* `PRIORITY STATUS`
+ * `PRIORITY STATUS` should be one of these three priorities: `HIGH`, `MEDIUM`, `LOW`.
+ * `PRIORITY_STATUS` of the task should not be the same as the current priority status of the task.
+* `DEADLINE`
+ * `DEADLINE` should be in the format DD-MM-YYYY and not earlier than the current date.
+ * `DEADLINE` should not be the same as the current deadline of the task.
+* To edit the `PRIORITY_STATUS` of the task, the task should already have a priority status tagged to it.
+* To edit the `DEADLINE` of the task, the task should already have a deadline tagged to it.
+
+Examples:
+
+`t tagadd 1 p/HIGH` followed by `t tagedit 1 p/LOW` updates priority status of the task
+from 'high' to 'low'.
+
+`t tagadd 1 dl/31-12-2022` followed by `t tagedit 1 dl/31-11-2022` updates the deadline of the task
+from '31-12-2022' to '31-11-2022'.
+
+### Deleting the tags of a task
+Deletes the tags associated with the specified task.
+
+Format: `t tagdel INDEX t/KEYWORD [SECOND_KEYWORD]`
+
+Parameters:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to remove the tags.
+* `KEYWORD` refers to the first keyword which indicates the type of tag that can be removed.
+* `SECOND_KEYWORD` refers to second keyword which indicates the type of tag that can be removed.
+
+
+
+:information_source: **Note:** `KEYWORD` and `SECOND_KEYWORD` are case-insensitive.
+
+
+Restrictions:
+* `INDEX` should be an integer greater than 0 and less than 2147483648.
+* `INDEX` should not be greater than the number of tasks in the displayed task list.
+* The list of keywords which can be used for `KEYWORD` and `SECOND_KEYWORD` are `priority` and `deadline`.
+* If duplicate keywords are used, the duplicate keyword will be ignored.
+
+Examples:
+
+`t tagadd 1 p/HIGH` followed by `t tagdel 1 t/priority` deletes the priority status
+of the first task in the displayed task list.
+
+`t tagadd 1 dl/24-11-2022` followed by `t tagdel 1 t/deadline` deletes the deadline
+of the first task in the displayed task list.
+
+`t tagadd 2 p/LOW dl/31-12-2022` followed by `t tagdel 2 t/priority deadline` deletes
+the priority status and deadline of the second task in the displayed task list.
+
+### Clearing the task list
+Clears all tasks currently in the stored task list.
+
+Format: `t clear`
--------------------------------------------------------------------------------------------------------------------
-## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+## Exams-related Features
+
+### Adding an exam
+Adds an exam into the stored exam list.
+
+Format: `e add m/MODULE ex/EXAM_DESCRIPTION ed/EXAM_DATE`
+
+Parameters:
+* `MODULE` refers to the module code of the module of the exam to be added.
+* `EXAM_DESCRIPTION` refers to the description of the exam to be added.
+* `EXAM_DATE` refers to the date of the exam to be added.
+
+
+
+:information_source: **Note:** `MODULE` is case-insensitive.
+
+
+Restrictions:
+* `MODULE`
+ * `MODULE` should be at least 6 characters long.
+ * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric.
+ * `MODULE` should be the module code of an existing module in the stored module list.
+* `EXAM_DESCRIPTION` should not be empty.
+* `EXAM_DATE` should be in the format DD-MM-YYYY and not earlier than the current date.
+* The exam to be added should not be the same as any existing exam in the stored exam list.
+
+Example:
+
+`e add m/CS2100 ex/midterms ed/20-08-2023` adds the exam with the exam module as 'CS2100',
+exam description as 'midterms', exam date as '20-08-2023' into the stored exam list.
+
+|  |
+|:--------------------------------------------------------------------:|
+| Demonstration of Command: `e add m/CS2100 ex/midterms ed/20-08-2023` |
+
+### Editing an exam
+Edits the specified exam by updating the existing values to the input values.
+
+Format: `e edit INDEX [m/MODULE]* [ex/EXAM_DESCRIPTION]* [ed/EXAM_DATE]*`
+
+Parameters:
+* `INDEX` refers to the index number (shown in the displayed exam list) of the exam to be edited.
+* `MODULE` refers to the module code of the module that will replace the existing module of the exam specified.
+* `EXAM_DESCRIPTION` refers to the exam description that will replace the existing exam description of the exam specified.
+* `EXAM_DATE` refers to the exam date that will replace the existing exam date of the exam specified.
+
+
+
+:information_source: **Note:** `MODULE` is case-insensitive.
+
+
+Restrictions:
+* `INDEX`
+ * `INDEX` should be an integer greater than 0 and less than 2147483648.
+ * `INDEX` should not be greater than the number of exams in the displayed exam list.
+* `MODULE`
+ * `MODULE` should be at least 6 characters long.
+ * The first two characters of `MODULE` should be alphabetical and the remaining characters should be alphanumeric.
+ * `MODULE` should be the module code of an existing module in the stored module list.
+* `EXAM_DESCRIPTION` should not be empty.
+* `EXAM_DATE` should be in the format DD-MM-YYYY and not earlier than the current date.
+* The input values should not be the same as existing values.
+* The edited exam should not be the same as any existing exam in the stored exam list.
+
+
+
+:exclamation: **Warning:** If the exam is linked to some tasks, and the module of the exam is changed, the tasks will be unlinked from the exam.
+
+
+Examples:
+
+`e edit 1 ex/finals ed/20-12-2022` changes the exam description of the first exam in the displayed exam list to ‘finals’ and the exam date to ‘20-12-2022’.
+
+`e edit 2 m/CS2030S ex/midterms ed/22-12-2022` changes the exam description of the second exam in the displayed exam list to ‘midterms’, the exam module to ‘CS2030S’ and the exam date to ‘22-12-2022’.
+
+
+### Deleting an exam
+Deletes the specified exam from the stored exam list.
+
+Format: `e del INDEX`
+
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed exam list) of the exam to be deleted.
+
+Restrictions:
+* `INDEX` should be an integer greater than 0 and less than 2147483648.
+* `INDEX` should not be greater than the number of exams in the displayed exam list.
+
+Example:
+
+`e del 1` deletes the first exam in the displayed exam list.
+
+
+:exclamation: **Warning:** All tasks currently linked to the exam will be unlinked after the exam is deleted.
+
+
+### Linking an exam
+Links the specified task to the specified exam.
+
+Format `e link e/EXAM_INDEX t/TASK_INDEX`
+
+Parameters:
+* `EXAM_INDEX` refers to the index number (shown in the displayed exam list) of the exam to be linked.
+* `TASK_INDEX` refers to the index number (shown in the displayed task list) of the task to be linked.
+
+Restrictions:
+* `EXAM_INDEX` and `TASK_INDEX` should be an integer greater than 0 and less than 2147483648.
+* `EXAM_INDEX` should not be greater than the number of exams in the displayed exam list.
+* `TASK_INDEX` should not be greater than the number of tasks in the displayed task list.
+
+Examples:
+
+`e link e/1 t/2` links the second task in the displayed task list to the first exam in the displayed exam list.
+
+`e link e/2 t/3` links the third task in the displayed task list to the second exam in the displayed exam list.
+
+|  |
+|:--------------------------------------------------:|
+| Demonstration of Command: `e link e/1 t/2` |
+
+
+### Unlinking an exam
+Unlinks the exam from the specified task.
+
+Format: `e unlink INDEX`
+
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed task list) of the task to be unlinked.
+
+Restrictions:
+* `INDEX` should be an integer greater than 0 and less than 2147483648.
+* `INDEX` should not be greater than the number of tasks in the displayed task list.
+
+Example:
+
+`e unlink 1` unlinks the first task in the displayed task list from its current exam.
+
+### Showing the tasks of an exam
+Shows all tasks linked to the specified exam.
+
+Format: `e showt INDEX`
+
+Parameter:
+* `INDEX` refers to the index number (shown in the displayed exam list) of the exam.
+
+Restrictions:
+* `INDEX` should be an integer greater than 0 and less than 2147483648.
+* `INDEX` should not be greater than the number of exams in the displayed exam list.
+
+Example:
+
+`e showt 1` shows a list of all tasks linked to the first exam in the displayed exam list.
+
+|  |
+|:------------------------------------------------------------:|
+| Demonstration of Command: `e showt 1` |
+
+## Other Features
+
+### Clearing all the lists
+Clears all tasks, exams and modules currently in the respective stored lists.
+
+Format: `clearall`
+
+### Opening the help window
+Opens the help window which displays the list of commands.
+
+Format: `help`
+
+### Exiting the program
+Exits the program.
+
+Format: `exit`
+
+--------------------------------------------------------------
+## Future Features to be added (Coming Soon!!)
+
+To optimize your tracking process, MODPRO will let you:
+* Edit the case sensitivity of module code in tasks, modules and exams.
+* Tick checkboxes of tasks using the mouse.
+* Add time and venue for exams.
+* Undo commands.
+
+To provide you with a better user experience, MODPRO will:
+* Improve the handling of duplicate prefixes in commands, by recognizing and rejecting them.
+
+--------------------------------------------------------------------------
+
+
+## General
+
+
+
+
+### Saving data to the data file
+* All MODPRO data will be manually saved to the hard disk after the execution of each command.
+There is no need to manually save the data.
+
+### Editing the data file
+* MODPRO data is saved as a JSON file at `[JAR FILE LOCATION]/data/modpro.json`. Advanced users are allowed
+to modify the data at the JSON file.
+
+
+:exclamation: **Warning:**
+If changes made to the modpro.json makes the format invalid or invalid data is used,
+MODPRO will discard all data stored and start with an empty data file.
+
+
+
+
+--------------------------------------------------------------------------
+## FAQ
+
+1. What is the difference between the stored list and a displayed list? (e.g. stored task list vs displayed task list)
+ * The stored task list is the list containing all the tasks you created, whereas the displayed task list is the one shown on your screen.
+ * The stored task list and displayed task list could be different after the filter or find command.
+
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+
+## Summary of Commands
+
+| Command | Format and Examples |
+|---------------|----------------------------------------------------------------------------------------------------------------------------------|
+| **Module** | |
+| **m add** | **Format**: `m add c/MODULE_CODE m/MODULE_NAME mc/MODULE_CREDIT` **Example**: `m add c/cs2103t m/software engineering mc/4` |
+| **m del** | **Format**: `m del INDEX` **Example**: `m del 1` |
+| **m edit** | **Format**: `m edit INDEX [c/MODULE_CODE]* [m/MODULE_NAME]* [mc/MODULE_CREDIT]*` **Example**: `m edit 1 c/cs2040 mc/4` |
+| **m list** | **Format**: `m list` **Example**: `m list` |
+| **m find** | **Format**: `m find KEYWORD` **Example**: `m find cs` |
+| **Task** | |
+| **t add** | **Format**: `t add m/MODULE d/DESCRIPTION` **Example**: `t add m/CS2105 d/Assignment 1` |
+| **t del** | **Format**: `t del INDEX` **Example**: `t del 1` |
+| **t edit** | **Format**: `t edit INDEX [m/MODULE]* [d/DESCRIPTION]*` **Example**: `t edit 1 d/Assignment 2` |
+| **t mark** | **Format**: `t mark INDEX` **Example**: `t mark 1` |
+| **t unmark** | **Format**: `t unmark INDEX` **Example**: `t unmark 1` |
+| **t list** | **Format**: `t list` **Example**: `t list` |
+| **t sort** | **Format**: `t sort c/CRITERIA` **Example**: `t sort c/priority` |
+| **t filter** | **Format**: `t filter [m/MODULE]* [c/COMPLETED]* [l/LINKED]*` **Example**: `t filter m/cs2030 c/y` |
+| **t find** | **Format**: `t find KEYWORD` **Example**: `t find watch lecture rec` |
+| **t tagadd** | **Format**: `t tagadd INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*` **Example**: `t tagadd 1 p/high dl/29-12-2022` |
+| **t tagdel** | **Format**: `t tagdel INDEX t/KEYWORD [SECOND_KEYWORD]` **Example**:`t tagdel 1 t/priority` |
+| **t tagedit** | **Format**: `t tagedit INDEX [p/PRIORITY_STATUS]* [dl/DEADLINE]*` **Example**: `t tagedit 1 p/medium` |
+| **t clear** | **Format**: `t clear` **Example**: `t clear` |
+| **Exam** | |
+| **e add** | **Format**: `e add m/MODULE ex/EXAM_DESCRIPTION ed/EXAM_DATE` **Example**: `e add m/cs2013t ex/practical ed/29-10-2022` |
+| **e edit** | **Format**: `e edit INDEX [m/MODULE]* [ex/EXAM_DESCRIPTION]* [ed/EXAM_DATE]*` **Example**: `e edit 1 m/cs2040` |
+| **e del** | **Format**: `e del INDEX` **Example**: `e del 1` |
+| **e link** | **Format**: `e link e/EXAM_INDEX t/TASK_INDEX` **Example**: `e link e/1 t/2` |
+| **e unlink** | **Format**: `e unlink INDEX` **Example**: `e unlink 1` |
+| **e showt** | **Format**: `e showt INDEX` **Example**: `e showt 1` |
+| **Others** | |
+| **clearall** | **Format**: `clearall` **Example**: `clearall` |
+| **help** | **Format**: `help` **Example**: `help` |
+| **exit** | **Format**: `exit` **Example**: `exit` |
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..74d19a4e233 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "MODPRO"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2223S1-CS2103T-F11-2/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..9a0e41f47a6 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "MODPRO";
font-size: 32px;
}
}
diff --git a/docs/diagrams/AddExamActivityDiagram.puml b/docs/diagrams/AddExamActivityDiagram.puml
new file mode 100644
index 00000000000..8acf40f45ab
--- /dev/null
+++ b/docs/diagrams/AddExamActivityDiagram.puml
@@ -0,0 +1,28 @@
+@startuml
+start
+:User executes command;
+:Parse Command;
+if () then ([Fields to be edited are valid and command is valid])
+ if () then([The exam module exists in MODPRO])
+
+ if() then([The exam already exists in MODPRO])
+ :Display error message
+ saying that exam already exists in MODPRO.;
+ else([else])
+ :Add exam to exam list;
+ :Display message to show that
+ the exam is added successfully to the exam list.;
+ endif
+ else([else])
+ :Display error message;
+ endif
+else ([else])
+ :Display error message regarding
+ invalid fields or invalid command;
+
+endif
+
+stop
+
+@enduml
+
diff --git a/docs/diagrams/AddExamCommandSequenceDiagram.puml b/docs/diagrams/AddExamCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..c5ed103466d
--- /dev/null
+++ b/docs/diagrams/AddExamCommandSequenceDiagram.puml
@@ -0,0 +1,85 @@
+]]@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddExamCommandParser" as AddExamCommandParser LOGIC_COLOR
+participant "ex:AddExamCommand" as AddExamCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("e add m/cs2030s ex/midterm ed/20-12-2022")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("e add...")
+activate AddressBookParser
+
+create AddExamCommandParser
+AddressBookParser -> AddExamCommandParser
+activate AddExamCommandParser
+
+AddExamCommandParser --> AddressBookParser
+deactivate AddExamCommandParser
+
+AddressBookParser -> AddExamCommandParser: parse(m/cs2...)
+activate AddExamCommandParser
+
+create AddExamCommand
+AddExamCommandParser -> AddExamCommand
+activate AddExamCommand
+
+AddExamCommand --> AddExamCommandParser : ex
+deactivate AddExamCommand
+
+AddExamCommandParser --> AddressBookParser : ex
+deactivate AddExamCommandParser
+AddExamCommandParser [hidden]--> AddressBookParser
+destroy AddExamCommandParser
+
+AddressBookParser --> LogicManager : ex
+deactivate AddressBookParser
+
+LogicManager -> AddExamCommand : execute(model)
+activate AddExamCommand
+
+AddExamCommand -> Model: hasModule(exam)
+activate Model
+Model --> AddExamCommand
+deactivate Model
+
+
+
+AddExamCommand -> Model: hasExam(exam)
+activate Model
+Model --> AddExamCommand
+deactivate Model
+
+AddExamCommand -> Model: addExam(exam)
+activate Model
+
+Model --> AddExamCommand
+deactivate Model
+
+
+
+create CommandResult
+AddExamCommand -> CommandResult: CommandResult(MESSAGE_SUCCESS)
+
+activate CommandResult
+CommandResult --> AddExamCommand
+deactivate CommandResult
+
+AddExamCommand --> LogicManager: result
+deactivate AddExamCommand
+
+
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..dd2a6304dfc 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -7,13 +7,13 @@ Participant ":Logic" as logic LOGIC_COLOR
Participant ":Model" as model MODEL_COLOR
Participant ":Storage" as storage STORAGE_COLOR
-user -[USER_COLOR]> ui : "delete 1"
+user -[USER_COLOR]> ui : "t delete 3"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("t delete 3")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteTask(p)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
diff --git a/docs/diagrams/DeleteTaskActivityDiagram.puml b/docs/diagrams/DeleteTaskActivityDiagram.puml
new file mode 100644
index 00000000000..5e15238a420
--- /dev/null
+++ b/docs/diagrams/DeleteTaskActivityDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+start
+:User executes the delete exam command;
+if () then ([invalid command format])
+ :Display invalid command format error message;
+else ([else])
+ if () then ([invalid task index])
+ :Display invalid task index shown error message;
+
+ else ([else])
+ : delete task;
+ if () then ([deleted task is linked])
+ :Display message that task has been deleted successfully with link dropped;
+ else ([else])
+ :Display message that task has been deleted successfully;
+ endif
+ endif
+endif
+stop
+@enduml
diff --git a/docs/diagrams/DeleteTaskSequenceDiagram.puml b/docs/diagrams/DeleteTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..918ae466fa8
--- /dev/null
+++ b/docs/diagrams/DeleteTaskSequenceDiagram.puml
@@ -0,0 +1,80 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":DeleteTaskCommandParser" as DeleteTaskCommandParser LOGIC_COLOR
+participant ":DeleteTaskCommand" as DeleteTaskCommand LOGIC_COLOR
+participant ":MarkCommand" as MarkCommand LOGIC_COLOR
+participant "taskToDelete:Task" as TargetTask LOGIC_COLOR
+participant "markedTask:Task" as MarkedTask LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+
+[-> LogicManager : execute("t del 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("mark 1")
+activate AddressBookParser
+
+create DeleteTaskCommandParser
+AddressBookParser -> DeleteTaskCommandParser
+activate DeleteTaskCommandParser
+
+DeleteTaskCommandParser --> AddressBookParser
+deactivate DeleteTaskCommandParser
+
+AddressBookParser -> DeleteTaskCommandParser : parse("1")
+activate DeleteTaskCommandParser
+
+create DeleteTaskCommand
+DeleteTaskCommandParser -> DeleteTaskCommand
+activate DeleteTaskCommand
+
+DeleteTaskCommand --> DeleteTaskCommandParser
+deactivate DeleteTaskCommand
+
+DeleteTaskCommandParser --> AddressBookParser
+deactivate DeleteTaskCommandParser
+
+DeleteTaskCommandParser -[hidden]-> AddressBookParser
+destroy DeleteTaskCommandParser
+
+AddressBookParser --> LogicManager
+deactivate AddressBookParser
+
+LogicManager -> DeleteTaskCommand: execute()
+activate DeleteTaskCommand
+
+DeleteTaskCommand -> Model : getFilteredTaskList().get(targetIndex.getZeroBased())
+activate Model
+
+Model --> DeleteTaskCommand : taskToDelete
+deactivate Model
+
+DeleteTaskCommand -> Model : deleteTask(taskToDelete)
+activate Model
+
+Model --> DeleteTaskCommand
+deactivate Model
+
+create CommandResult
+DeleteTaskCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> DeleteTaskCommand
+deactivate CommandResult
+
+DeleteTaskCommand --> LogicManager: result
+deactivate DeleteTaskCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/EditTaskActivityDiagram.puml b/docs/diagrams/EditTaskActivityDiagram.puml
new file mode 100644
index 00000000000..db43e58a211
--- /dev/null
+++ b/docs/diagrams/EditTaskActivityDiagram.puml
@@ -0,0 +1,39 @@
+@startuml
+start
+:User executes the edit task command;
+
+switch ()
+case ( [invalid command] )
+ :Display error message
+ regarding invalid command;
+case ( [task is not edited] )
+ :Display error message
+ stating the fields provided
+ are the same as the current
+ values;
+case ( [duplicate task] )
+ :Display error message
+ stating the edited task
+ is the same as another
+ existing task in the task
+ list;
+case ( [module does not exist] )
+ :Display error message
+ stating the module
+ provided does not exist
+ in the module list;
+case ( [else] )
+ if () then ( [task is linked and its module is changed] )
+ :Unlink the task from its exam;
+ :Display message stating that
+ the task is successfully edited
+ and unlinked;
+ else ( [else] )
+ :Display message stating that
+ the task is successfully edited;
+ endif
+
+
+endswitch
+stop
+@enduml
diff --git a/docs/diagrams/EditTaskReferenceDiagram.puml b/docs/diagrams/EditTaskReferenceDiagram.puml
new file mode 100644
index 00000000000..6cf4218f40c
--- /dev/null
+++ b/docs/diagrams/EditTaskReferenceDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+!include style.puml
+mainframe **sd** get taskToEdit and create editedTask
+
+box Logic LOGIC_COLOR_T1
+participant "c:EditTaskCommand" as EditTaskCommand LOGIC_COLOR
+
+end box
+
+box Model MODEL_COLOR_T1
+participant "taskToEdit:Task" as TargetTask MODEL_COLOR
+participant "unlinkedEditedTask:Task" as UnlinkedEditedTask MODEL_COLOR
+participant "linkedEditedTask:Task" as LinkedEditedTask MODEL_COLOR
+participant ":Model" as Model MODEL_COLOR
+end box
+
+
+EditTaskCommand -> Model : get task at the specified index
+activate Model
+
+Model --> EditTaskCommand : taskToEdit
+deactivate Model
+
+EditTaskCommand -> TargetTask : edit()
+activate TargetTask
+
+alt "module is changed"
+
+create UnlinkedEditedTask
+TargetTask -> UnlinkedEditedTask
+note right
+unlinkedEditedTask has no
+linked exam, while
+linkedEditedTask keeps
+any linked exam of taskToEdit
+end note
+activate UnlinkedEditedTask
+
+UnlinkedEditedTask --> TargetTask
+deactivate UnlinkedEditedTask
+
+else "module not changed"
+create LinkedEditedTask
+TargetTask -> LinkedEditedTask
+activate LinkedEditedTask
+
+LinkedEditedTask --> TargetTask
+deactivate LinkedEditedTask
+end
+
+TargetTask --> EditTaskCommand : editedTask
+deactivate TargetTask
+autonumber
+
+@enduml
diff --git a/docs/diagrams/EditTaskSequenceDiagram.puml b/docs/diagrams/EditTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..78c52222a73
--- /dev/null
+++ b/docs/diagrams/EditTaskSequenceDiagram.puml
@@ -0,0 +1,86 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":EditTaskCommandParser" as EditTaskCommandParser LOGIC_COLOR
+participant ":EditTaskDescriptor" as EditTaskDescriptor LOGIC_COLOR
+participant "c:EditTaskCommand" as EditTaskCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+
+[-> LogicManager : execute("t edit d/task 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("t edit 2 d/task 1")
+activate AddressBookParser
+
+create EditTaskCommandParser
+AddressBookParser -> EditTaskCommandParser
+activate EditTaskCommandParser
+
+EditTaskCommandParser --> AddressBookParser
+deactivate EditTaskCommandParser
+
+AddressBookParser -> EditTaskCommandParser : parse("2 d/task 1")
+activate EditTaskCommandParser
+
+create EditTaskDescriptor
+EditTaskCommandParser -> EditTaskDescriptor
+activate EditTaskDescriptor
+
+EditTaskDescriptor --> EditTaskCommandParser
+deactivate EditTaskDescriptor
+
+create EditTaskCommand
+EditTaskCommandParser -> EditTaskCommand
+activate EditTaskCommand
+
+EditTaskCommand --> EditTaskCommandParser
+deactivate EditTaskCommand
+
+EditTaskCommandParser --> AddressBookParser : c
+deactivate EditTaskCommandParser
+
+EditTaskCommandParser -[hidden]-> AddressBookParser
+destroy EditTaskCommandParser
+
+AddressBookParser --> LogicManager : c
+deactivate AddressBookParser
+
+LogicManager -> EditTaskCommand: execute()
+activate EditTaskCommand
+
+ref over EditTaskCommand, Model
+get taskToEdit and create editedTask
+endref
+
+EditTaskCommand -> Model : replaceTask(taskToEdit, editedTask, false)
+activate Model
+
+Model --> EditTaskCommand
+deactivate Model
+
+create CommandResult
+EditTaskCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditTaskCommand
+deactivate CommandResult
+
+EditTaskCommand --> LogicManager : result
+deactivate EditTaskCommand
+
+EditTaskCommand -[hidden]-> LogicManager
+destroy EditTaskCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/FilterActivityDiagram.puml b/docs/diagrams/FilterActivityDiagram.puml
new file mode 100644
index 00000000000..21de4c010f9
--- /dev/null
+++ b/docs/diagrams/FilterActivityDiagram.puml
@@ -0,0 +1,26 @@
+@startuml
+
+start
+:User executes filter command;
+if () then ([command is invalid])
+ :Display invalid command
+ format error message and
+ filter message usage;
+else ([else])
+ if () then ([input fields are invalid])
+ if () then ([module does not exist])
+ :Display module not
+ found error message;
+ else ([else])
+ :Display response
+ constraints error
+ message;
+ endif
+ else ([else])
+ :Update filtered task list;
+ :Display filtered task list;
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/FilterSequenceDiagram.puml b/docs/diagrams/FilterSequenceDiagram.puml
new file mode 100644
index 00000000000..74db52bf491
--- /dev/null
+++ b/docs/diagrams/FilterSequenceDiagram.puml
@@ -0,0 +1,80 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FilterTasksCommandParser" as FilterTasksCommandParser LOGIC_COLOR
+participant "predicate:FilterPredicate" as FilterPredicate LOGIC_COLOR
+participant "f:FilterTasksCommand" as FilterTasksCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("t filter m/CS2103T c/y l/n")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("t filter m/CS2103T c/y l/n")
+activate AddressBookParser
+
+create FilterTasksCommandParser
+AddressBookParser -> FilterTasksCommandParser
+activate FilterTasksCommandParser
+
+FilterTasksCommandParser --> AddressBookParser
+deactivate FilterTasksCommandParser
+
+AddressBookParser -> FilterTasksCommandParser : parse("m/CS2103T c/y l/n")
+activate FilterTasksCommandParser
+
+create FilterPredicate
+FilterTasksCommandParser -> FilterPredicate
+activate FilterPredicate
+
+FilterPredicate --> FilterTasksCommandParser
+deactivate FilterPredicate
+
+create FilterTasksCommand
+FilterTasksCommandParser -> FilterTasksCommand
+activate FilterTasksCommand
+
+FilterTasksCommand --> FilterTasksCommandParser : f
+deactivate FilterTasksCommand
+
+FilterTasksCommandParser --> AddressBookParser : f
+deactivate FilterTasksCommandParser
+
+FilterTasksCommandParser [hidden]--> AddressBookParser
+destroy FilterTasksCommandParser
+
+AddressBookParser --> LogicManager : f
+deactivate AddressBookParser
+
+LogicManager -> FilterTasksCommand : execute()
+activate FilterTasksCommand
+
+FilterTasksCommand -> Model : updateFilteredTaskList(predicate)
+activate Model
+
+Model --> FilterTasksCommand
+deactivate Model
+
+create CommandResult
+FilterTasksCommand -> CommandResult : CommandResult(MESSAGE_SUCCESS)
+activate CommandResult
+
+CommandResult --> FilterTasksCommand
+deactivate CommandResult
+
+FilterTasksCommand --> LogicManager : result
+deactivate FilterTasksCommand
+
+FilterTasksCommand -[hidden]-> LogicManager
+destroy FilterTasksCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/FindTasksActivityDiagram.puml b/docs/diagrams/FindTasksActivityDiagram.puml
new file mode 100644
index 00000000000..ae99180c1fe
--- /dev/null
+++ b/docs/diagrams/FindTasksActivityDiagram.puml
@@ -0,0 +1,18 @@
+@startuml
+start
+:User execute keyword;
+:Parse Command;
+if () then ([keyword is not empty])
+ :Update filtered task list;
+ :Displays the tasks which matches the keyword
+ with a message that states
+ the number of tasks that are listed. ;
+
+else ([else])
+ :Display error message stating invalid format;
+
+endif
+
+stop
+
+@enduml
diff --git a/docs/diagrams/FindTasksCommandSequenceDiagram.puml b/docs/diagrams/FindTasksCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..b2392cf89fe
--- /dev/null
+++ b/docs/diagrams/FindTasksCommandSequenceDiagram.puml
@@ -0,0 +1,78 @@
+]]@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FindTasksCommandParser" as FindTasksCommandParser LOGIC_COLOR
+participant "ex:FindTasksCommand" as FindTasksCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+participant "pred: DescriptionContainsKeywordsPredicate" as DescriptionContainsKeywordsPredicate LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("t find task")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("t find task")
+activate AddressBookParser
+
+create FindTasksCommandParser
+AddressBookParser -> FindTasksCommandParser
+activate FindTasksCommandParser
+
+FindTasksCommandParser --> AddressBookParser
+deactivate FindTasksCommandParser
+
+AddressBookParser -> FindTasksCommandParser: parse(task)
+activate FindTasksCommandParser
+
+create DescriptionContainsKeywordsPredicate
+FindTasksCommandParser -> DescriptionContainsKeywordsPredicate
+activate DescriptionContainsKeywordsPredicate
+
+DescriptionContainsKeywordsPredicate --> FindTasksCommandParser
+deactivate DescriptionContainsKeywordsPredicate
+
+create FindTasksCommand
+FindTasksCommandParser -> FindTasksCommand: FindTasksCommand(pred)
+activate FindTasksCommand
+
+FindTasksCommand --> FindTasksCommandParser : ex
+deactivate FindTasksCommand
+
+
+FindTasksCommandParser --> AddressBookParser : ex
+deactivate FindTasksCommandParser
+FindTasksCommandParser [hidden]--> AddressBookParser
+destroy FindTasksCommandParser
+
+AddressBookParser --> LogicManager : ex
+deactivate AddressBookParser
+
+LogicManager -> FindTasksCommand : execute(model)
+activate FindTasksCommand
+
+FindTasksCommand -> Model: updateFilteredTaskList(predicate)
+activate Model
+Model --> FindTasksCommand
+deactivate Model
+
+create CommandResult
+FindTasksCommand -> CommandResult:
+
+activate CommandResult
+CommandResult --> FindTasksCommand
+deactivate CommandResult
+
+FindTasksCommand --> LogicManager: result
+deactivate FindTasksCommand
+
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/LinkExamCommandActivityDiagram.puml b/docs/diagrams/LinkExamCommandActivityDiagram.puml
new file mode 100644
index 00000000000..e37aab09312
--- /dev/null
+++ b/docs/diagrams/LinkExamCommandActivityDiagram.puml
@@ -0,0 +1,30 @@
+@startuml
+start
+:User executes LinkExamCommand;
+if () then ([invalid command format])
+ :Display invalid command format error message;
+else ([else])
+ if () then ([invalid task index])
+ :Display invalid task index shown error message;
+
+ else ([else])
+ if () then ([invalid exam index])
+ :Display invalid exam index shown error;
+ else ([else])
+ if () then ([task is already linked])
+ :Display task is task is already linked error;
+ else ([else])
+ :Link task to the exam;
+ :Display message that task has been linked successfully;
+ endif
+ endif
+ endif
+endif
+
+
+
+
+
+stop
+
+@enduml
diff --git a/docs/diagrams/LinkExamCommandSequenceDiagram.puml b/docs/diagrams/LinkExamCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..5878103f5de
--- /dev/null
+++ b/docs/diagrams/LinkExamCommandSequenceDiagram.puml
@@ -0,0 +1,117 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":LinkExamCommandParser" as LinkExamCommandParser LOGIC_COLOR
+participant "le:LinkExamCommand" as LinkExamCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "task:Task" as Task MODEL_COLOR
+participant "linkedTask:Task" as LinkedTask MODEL_COLOR
+participant "exam:Exam" as Exam MODEL_COLOR
+participant "Module" as Module MODEL_COLOR
+end box
+
+[-> LogicManager : execute("e link e/1 t/1")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("e link e/1 t/1")
+activate AddressBookParser
+
+create LinkExamCommandParser
+AddressBookParser -> LinkExamCommandParser
+activate LinkExamCommandParser
+
+LinkExamCommandParser --> AddressBookParser
+deactivate LinkExamCommandParser
+
+AddressBookParser -> LinkExamCommandParser: parse("e/1 t/1")
+activate LinkExamCommandParser
+
+create LinkExamCommand
+LinkExamCommandParser -> LinkExamCommand:
+activate LinkExamCommand
+
+LinkExamCommand --> LinkExamCommandParser : le
+deactivate LinkExamCommand
+
+LinkExamCommandParser --> AddressBookParser : le
+deactivate LinkExamCommandParser
+LinkExamCommandParser [hidden]--> AddressBookParser
+destroy LinkExamCommandParser
+
+AddressBookParser --> LogicManager : le
+deactivate AddressBookParser
+
+LogicManager -> LinkExamCommand : execute(model)
+activate LinkExamCommand
+
+LinkExamCommand -> Model: getFilteredTaskList()
+activate Model
+
+Model --> LinkExamCommand
+deactivate Model
+
+LinkExamCommand -> Model: getFilteredExamList()
+activate Model
+
+Model --> LinkExamCommand
+deactivate Model
+
+LinkExamCommand -> Task: isLinked()
+activate Task
+
+Task --> LinkExamCommand
+deactivate Task
+
+LinkExamCommand -> Task: getModule()
+activate Task
+
+Task --> LinkExamCommand
+deactivate Task
+
+LinkExamCommand -> Exam: getModule()
+activate Exam
+
+Exam --> LinkExamCommand
+deactivate Exam
+
+LinkExamCommand -> Module: isSameModule(task.module, exam.module)
+activate Module
+
+Module --> LinkExamCommand
+deactivate Module
+
+LinkExamCommand -> LinkedTask: linkTask(exam)
+activate LinkedTask
+
+LinkedTask --> LinkExamCommand
+deactivate LinkedTask
+
+LinkExamCommand -> Model: replaceTask(task, linkedTask, true)
+activate Model
+
+Model --> LinkExamCommand
+deactivate Model
+
+create CommandResult
+LinkExamCommand -> CommandResult: CommandResult(EXAM_LINKED_SUCCESS)
+activate CommandResult
+
+CommandResult --> LinkExamCommand
+deactivate CommandResult
+
+LinkExamCommand --> LogicManager
+deactivate LinkExamCommand
+LinkExamCommand [hidden]--> LogicManager
+destroy LinkExamCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/MarkTaskActivityDiagram.puml b/docs/diagrams/MarkTaskActivityDiagram.puml
new file mode 100644
index 00000000000..97f1188f097
--- /dev/null
+++ b/docs/diagrams/MarkTaskActivityDiagram.puml
@@ -0,0 +1,23 @@
+@startuml
+start
+:User executes the mark command;
+switch ()
+case ( [no valid number provided to indicate the index])
+ :Display error message
+ regarding invalid command format;
+case ( [invalid index])
+ :Display error message
+ regarding invalid index;
+case ( [task already marked] )
+ :Display error message
+ stating task is already marked;
+case ( [else] )
+ :Display message stating that
+ the task is successfully marked;
+ :Tick the task and update the
+ progress bar for the module
+ of the task;
+endswitch
+
+stop
+@enduml
diff --git a/docs/diagrams/MarkTaskReferenceDiagram.puml b/docs/diagrams/MarkTaskReferenceDiagram.puml
new file mode 100644
index 00000000000..0c5c825ff7f
--- /dev/null
+++ b/docs/diagrams/MarkTaskReferenceDiagram.puml
@@ -0,0 +1,37 @@
+@startuml
+!include style.puml
+mainframe **sd** get taskToMark and create markedTask
+
+box Logic LOGIC_COLOR_T1
+participant "c:MarkCommand" as MarkCommand LOGIC_COLOR
+
+end box
+
+box Model MODEL_COLOR_T1
+participant "taskToMark:Task" as TargetTask MODEL_COLOR
+participant "markedTask:Task" as MarkedTask MODEL_COLOR
+participant ":Model" as Model MODEL_COLOR
+end box
+
+
+MarkCommand -> Model : get task at the specified index
+activate Model
+
+Model --> MarkCommand : taskToMark
+deactivate Model
+
+MarkCommand -> TargetTask : mark()
+activate TargetTask
+
+create MarkedTask
+TargetTask -> MarkedTask
+activate MarkedTask
+
+MarkedTask --> TargetTask
+deactivate MarkedTask
+
+TargetTask --> MarkCommand : markedTask
+deactivate TargetTask
+autonumber
+
+@enduml
diff --git a/docs/diagrams/MarkTaskSequenceDiagram.puml b/docs/diagrams/MarkTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..3c3946e8eee
--- /dev/null
+++ b/docs/diagrams/MarkTaskSequenceDiagram.puml
@@ -0,0 +1,79 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":MarkCommandParser" as MarkCommandParser LOGIC_COLOR
+participant "c:MarkCommand" as MarkCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+
+[-> LogicManager : execute("t mark 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("t mark 1")
+activate AddressBookParser
+
+create MarkCommandParser
+AddressBookParser -> MarkCommandParser
+activate MarkCommandParser
+
+MarkCommandParser --> AddressBookParser
+deactivate MarkCommandParser
+
+AddressBookParser -> MarkCommandParser : parse("1")
+activate MarkCommandParser
+
+create MarkCommand
+MarkCommandParser -> MarkCommand
+activate MarkCommand
+
+MarkCommand --> MarkCommandParser
+deactivate MarkCommand
+
+MarkCommandParser --> AddressBookParser : c
+deactivate MarkCommandParser
+
+MarkCommandParser -[hidden]-> AddressBookParser
+destroy MarkCommandParser
+
+AddressBookParser --> LogicManager : c
+deactivate AddressBookParser
+
+LogicManager -> MarkCommand: execute()
+activate MarkCommand
+
+ref over MarkCommand, Model
+get taskToMark and create markedTask
+endref
+
+MarkCommand -> Model : replaceTask(taskToMark, markedTask, true)
+activate Model
+
+Model --> MarkCommand
+deactivate Model
+
+create CommandResult
+MarkCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> MarkCommand
+deactivate CommandResult
+
+MarkCommand --> LogicManager : result
+deactivate MarkCommand
+
+MarkCommand -[hidden]-> LogicManager
+destroy MarkCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 4439108973a..d58b47e4741 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -12,14 +12,21 @@ Class AddressBook
Class ModelManager
Class UserPrefs
-Class UniquePersonList
-Class Person
-Class Address
-Class Email
-Class Name
-Class Phone
-Class Tag
-
+Class DistinctTaskList
+Class DistinctExamList
+Class DistinctModuleList
+Class Task
+Class Module
+Class Exam
+Class ModuleCode
+Class ModuleCredit
+Class ModuleName
+Class TaskDescription
+Class TaskStatus
+Class PriorityTag
+Class DeadlineTag
+Class ExamDate
+Class ExamDescription
}
Class HiddenOutside #FFFFFF
@@ -34,17 +41,51 @@ 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
+AddressBook *--> "1" DistinctTaskList
+AddressBook *--> "1" DistinctModuleList
+AddressBook *--> "1" DistinctExamList
+DistinctTaskList --> "~* all" Task
+DistinctModuleList -> "~* all" Module
+DistinctExamList ---> "~* all" Exam
+
+ModelManager -->"~* filtered" Task
+ModelManager ---------->"~* filtered" Exam
+ModelManager ------>"~* filtered" Module
+
+Module *--> "1" ModuleCode
+Module *--> "0..1" ModuleName
+Module *--> "0..1" ModuleCredit
+
+Task --> "1" Module
+Task *---> "1" TaskDescription
+Task *-----> "1" TaskStatus
+Task --> "0..1" Exam
+Task *--> "0..1" PriorityTag
+Task *--> "0..1" DeadlineTag
+
+Exam ----> "1" Module
+Exam *--> "1" ExamDate
+Exam --> "1" ExamDescription
+
+DistinctTaskList -[hidden]up- AddressBook
+DistinctTaskList -[hidden]up- ModelManager
+DistinctModuleList -[hidden]up- AddressBook
+DistinctModuleList -[hidden]up- ModelManager
+DistinctExamList -[hidden]up- AddressBook
+DistinctExamList -[hidden]up- ModelManager
+
+DistinctTaskList -[hidden]left- DistinctModuleList
+DistinctModuleList -[hidden]left- DistinctExamList
+
+Task -[hidden]up- DistinctTaskList
+Module -[hidden]up- DistinctModuleList
+Exam -[hidden]up- DistinctExamList
+
+
+Module -[hidden]right- Task
+
+DistinctExamList -[hidden]left- DistinctTaskList
+DistinctTaskList -[hidden]left- DistinctModuleList
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
-ModelManager -->"~* filtered" Person
@enduml
diff --git a/docs/diagrams/SortTaskCommandActivityDiargam.puml b/docs/diagrams/SortTaskCommandActivityDiargam.puml
new file mode 100644
index 00000000000..012faeae895
--- /dev/null
+++ b/docs/diagrams/SortTaskCommandActivityDiargam.puml
@@ -0,0 +1,21 @@
+@startuml
+start
+:User executes the sort task command;
+if () then ([invalid command format])
+ :Display invalid command format message;
+else ([else])
+ if () then ([invalid criteria chosen])
+ :Display invalid criteria chosen error message;
+ else ([else])
+ if () then ([Task list is empty])
+ :Display there are no tasks to sort error message;
+ else ([else])
+ :Sort the task in the task list;
+ :Display the task sorted successfully message;
+ endif
+ endif
+endif
+
+stop
+
+@enduml
diff --git a/docs/diagrams/SortTaskCommandSequenceDiagram.puml b/docs/diagrams/SortTaskCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..0bad84ddd91
--- /dev/null
+++ b/docs/diagrams/SortTaskCommandSequenceDiagram.puml
@@ -0,0 +1,77 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":SortTaskCommandParser" as SortTaskCommandParser LOGIC_COLOR
+participant "st:SortTaskCommand" as SortTaskCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("t sort c/priority")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("t sort c/priority")
+activate AddressBookParser
+
+create SortTaskCommandParser
+AddressBookParser -> SortTaskCommandParser
+activate SortTaskCommandParser
+
+SortTaskCommandParser --> AddressBookParser
+deactivate SortTaskCommandParser
+
+AddressBookParser -> SortTaskCommandParser: parse("c/priority")
+activate SortTaskCommandParser
+
+create SortTaskCommand
+SortTaskCommandParser -> SortTaskCommand
+activate SortTaskCommand
+
+SortTaskCommand --> SortTaskCommandParser : st
+deactivate SortTaskCommand
+
+SortTaskCommandParser --> AddressBookParser : st
+deactivate SortTaskCommandParser
+SortTaskCommandParser [hidden]--> AddressBookParser
+destroy SortTaskCommandParser
+
+AddressBookParser --> LogicManager : st
+deactivate AddressBookParser
+
+LogicManager -> SortTaskCommand : execute(model)
+activate SortTaskCommand
+
+SortTaskCommand -> Model: sortTaskList(criteria)
+activate Model
+
+Model --> SortTaskCommand
+deactivate Model
+
+SortTaskCommand -> Model: getFilteredTaskList()
+activate Model
+
+Model --> SortTaskCommand
+deactivate Model
+
+create CommandResult
+SortTaskCommand -> CommandResult: CommandResult(TASK_SORTED_SUCCESSFULLY)
+activate CommandResult
+
+CommandResult --> SortTaskCommand
+deactivate CommandResult
+
+SortTaskCommand --> LogicManager
+deactivate SortTaskCommand
+SortTaskCommand [hidden]--> LogicManager
+destroy SortTaskCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 760305e0e58..3aa1b331cca 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -18,8 +18,9 @@ package "AddressBook Storage" #F4F6F6{
Class "<>\nAddressBookStorage" as AddressBookStorage
Class JsonAddressBookStorage
Class JsonSerializableAddressBook
-Class JsonAdaptedPerson
-Class JsonAdaptedTag
+Class JsonAdaptedTask
+Class JsonAdaptedModule
+Class JsonAdaptedExam
}
}
@@ -37,7 +38,9 @@ Storage -right-|> AddressBookStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
-JsonSerializableAddressBook --> "*" JsonAdaptedPerson
-JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonSerializableAddressBook --> "*" JsonAdaptedModule
+JsonSerializableAddressBook --> "*" JsonAdaptedExam
+JsonSerializableAddressBook --> "*" JsonAdaptedTask
+
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..97c30856db0 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -11,8 +11,13 @@ Class UiManager
Class MainWindow
Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
+
+Class ModuleListPanel
+Class ModuleCard
+Class TaskListPanel
+Class TaskCard
+Class ExamListPanel
+Class ExamCard
Class StatusBarFooter
Class CommandBox
}
@@ -32,26 +37,38 @@ UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "1" ModuleListPanel
+MainWindow *-down-> "1" TaskListPanel
+MainWindow *-down-> "1" ExamListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
-PersonListPanel -down-> "*" PersonCard
+TaskListPanel -down-> "*" TaskCard
+ModuleListPanel -down-> "*" ModuleCard
+ExamListPanel -down-> "*" ExamCard
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+ModuleListPanel --|> UiPart
+ModuleCard --|> UiPart
+TaskListPanel --|> UiPart
+TaskCard --|> UiPart
+ExamListPanel --|> UiPart
+ExamCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
-PersonCard ..> Model
+TaskCard ..> Model
+ModuleCard ..> Model
+ExamCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
-PersonListPanel -[hidden]left- HelpWindow
+ModuleListPanel -[hidden]left- HelpWindow
+TaskListPanel -[hidden]left- ModuleListPanel
+ExamListPanel -[hidden]left- TaskListPanel
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
ResultDisplay -[hidden]left- StatusBarFooter
diff --git a/docs/diagrams/UnlinkExamActivityDiagram.puml b/docs/diagrams/UnlinkExamActivityDiagram.puml
new file mode 100644
index 00000000000..06cbe31d19f
--- /dev/null
+++ b/docs/diagrams/UnlinkExamActivityDiagram.puml
@@ -0,0 +1,35 @@
+@startuml
+
+start
+:User executes unlink exam command;
+if () then ([command is invalid])
+ :Display invalid command
+ format error message and
+ unlink exam message usage;
+else ([else])
+ if () then ([index is invalid])
+ :Display error message
+ with valid integer range;
+ else ([else])
+ if () then ([index is outside range of task list])
+ :Display error message
+ with valid indexes for
+ task list;
+ else ([else])
+ if () then ([task is already unlinked])
+ :Display error message
+ that task is already unlinked;
+ else ([else])
+ :Create a copy of the task
+ at the specified index,
+ but unlinked;
+ :Replace the task at
+ the specified index with
+ the unlinked task;
+ endif
+ endif
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/UnlinkExamSequenceDiagram.puml b/docs/diagrams/UnlinkExamSequenceDiagram.puml
new file mode 100644
index 00000000000..1ca83f3cc1a
--- /dev/null
+++ b/docs/diagrams/UnlinkExamSequenceDiagram.puml
@@ -0,0 +1,94 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":UnlinkExamCommandParser" as UnlinkExamCommandParser LOGIC_COLOR
+participant "u:UnlinkExamCommand" as UnlinkExamCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "taskToUnlink:Task" as TaskToUnlink MODEL_COLOR
+participant "unlinkedTask:Task" as UnlinkedTask MODEL_COLOR
+end box
+
+[-> LogicManager : execute("e unlink 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("e unlink 1")
+activate AddressBookParser
+
+create UnlinkExamCommandParser
+AddressBookParser -> UnlinkExamCommandParser
+activate UnlinkExamCommandParser
+
+UnlinkExamCommandParser --> AddressBookParser
+deactivate UnlinkExamCommandParser
+
+AddressBookParser -> UnlinkExamCommandParser : parse("1")
+activate UnlinkExamCommandParser
+
+create UnlinkExamCommand
+UnlinkExamCommandParser -> UnlinkExamCommand
+activate UnlinkExamCommand
+
+UnlinkExamCommand --> UnlinkExamCommandParser
+deactivate UnlinkExamCommand
+
+UnlinkExamCommandParser --> AddressBookParser : u
+deactivate UnlinkExamCommandParser
+
+UnlinkExamCommandParser -[hidden]-> AddressBookParser
+destroy UnlinkExamCommandParser
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> UnlinkExamCommand : execute()
+activate UnlinkExamCommand
+
+UnlinkExamCommand -> Model : getFilteredTaskList()
+activate Model
+
+Model --> UnlinkExamCommand
+deactivate Model
+
+UnlinkExamCommand -> TaskToUnlink : unlinkTask()
+activate TaskToUnlink
+
+create UnlinkedTask
+TaskToUnlink -> UnlinkedTask
+activate UnlinkedTask
+
+UnlinkedTask --> TaskToUnlink
+deactivate UnlinkedTask
+
+TaskToUnlink --> UnlinkExamCommand : unlinkedTask
+deactivate TaskToUnlink
+
+UnlinkExamCommand -> Model : replaceTask(taskToUnlink, unlinkedTask, true)
+activate Model
+
+Model --> UnlinkExamCommand
+deactivate Model
+
+create CommandResult
+UnlinkExamCommand -> CommandResult : CommandResult(EXAM_UNLINKED_SUCCESS)
+activate CommandResult
+
+CommandResult --> UnlinkExamCommand
+deactivate CommandResult
+
+UnlinkExamCommand --> LogicManager : result
+deactivate UnlinkExamCommand
+
+UnlinkExamCommand -[hidden]-> LogicManager
+destroy UnlinkExamCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/images/AddExamActivityDiagram.png b/docs/images/AddExamActivityDiagram.png
new file mode 100644
index 00000000000..a06dc393f4a
Binary files /dev/null and b/docs/images/AddExamActivityDiagram.png differ
diff --git a/docs/images/AddExamCommandSequenceDiagram.png b/docs/images/AddExamCommandSequenceDiagram.png
new file mode 100644
index 00000000000..15c23a5a2e6
Binary files /dev/null and b/docs/images/AddExamCommandSequenceDiagram.png differ
diff --git a/docs/images/AddExamImage.png b/docs/images/AddExamImage.png
new file mode 100644
index 00000000000..4eef7ba49fd
Binary files /dev/null and b/docs/images/AddExamImage.png differ
diff --git a/docs/images/AddModuleCommandDemo.png b/docs/images/AddModuleCommandDemo.png
new file mode 100644
index 00000000000..3db4222116e
Binary files /dev/null and b/docs/images/AddModuleCommandDemo.png differ
diff --git a/docs/images/AddTagCommandDemo.png b/docs/images/AddTagCommandDemo.png
new file mode 100644
index 00000000000..c53826a8de7
Binary files /dev/null and b/docs/images/AddTagCommandDemo.png differ
diff --git a/docs/images/AddTaskCommandDemo.png b/docs/images/AddTaskCommandDemo.png
new file mode 100644
index 00000000000..99c5e8a5187
Binary files /dev/null and b/docs/images/AddTaskCommandDemo.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..d652f6cd3e4 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/DeleteTaskActivityDiagram.png b/docs/images/DeleteTaskActivityDiagram.png
new file mode 100644
index 00000000000..e037c52308f
Binary files /dev/null and b/docs/images/DeleteTaskActivityDiagram.png differ
diff --git a/docs/images/DeleteTaskSequenceDiagram.png b/docs/images/DeleteTaskSequenceDiagram.png
new file mode 100644
index 00000000000..60ef0c3c288
Binary files /dev/null and b/docs/images/DeleteTaskSequenceDiagram.png differ
diff --git a/docs/images/EditTaskActivityDiagram.png b/docs/images/EditTaskActivityDiagram.png
new file mode 100644
index 00000000000..435029767cf
Binary files /dev/null and b/docs/images/EditTaskActivityDiagram.png differ
diff --git a/docs/images/EditTaskReferenceDiagram.png b/docs/images/EditTaskReferenceDiagram.png
new file mode 100644
index 00000000000..8f858e22f87
Binary files /dev/null and b/docs/images/EditTaskReferenceDiagram.png differ
diff --git a/docs/images/EditTaskSequenceDiagram.png b/docs/images/EditTaskSequenceDiagram.png
new file mode 100644
index 00000000000..fa5fb83fb2b
Binary files /dev/null and b/docs/images/EditTaskSequenceDiagram.png differ
diff --git a/docs/images/FilterActivityDiagram.png b/docs/images/FilterActivityDiagram.png
new file mode 100644
index 00000000000..712c07b5dab
Binary files /dev/null and b/docs/images/FilterActivityDiagram.png differ
diff --git a/docs/images/FilterSequenceDiagram.png b/docs/images/FilterSequenceDiagram.png
new file mode 100644
index 00000000000..a9ed7cce1b7
Binary files /dev/null and b/docs/images/FilterSequenceDiagram.png differ
diff --git a/docs/images/FilterTasksCommandDemo.png b/docs/images/FilterTasksCommandDemo.png
new file mode 100644
index 00000000000..6c601ad6d03
Binary files /dev/null and b/docs/images/FilterTasksCommandDemo.png differ
diff --git a/docs/images/FindCommandImage.png b/docs/images/FindCommandImage.png
new file mode 100644
index 00000000000..da0cbf16496
Binary files /dev/null and b/docs/images/FindCommandImage.png differ
diff --git a/docs/images/FindTasksActivityDiagram.png b/docs/images/FindTasksActivityDiagram.png
new file mode 100644
index 00000000000..a1bbbd0fbed
Binary files /dev/null and b/docs/images/FindTasksActivityDiagram.png differ
diff --git a/docs/images/FindTasksCommandSequenceDiagram.png b/docs/images/FindTasksCommandSequenceDiagram.png
new file mode 100644
index 00000000000..67eb7597991
Binary files /dev/null and b/docs/images/FindTasksCommandSequenceDiagram.png differ
diff --git a/docs/images/GUI.png b/docs/images/GUI.png
new file mode 100644
index 00000000000..1240bc2db0f
Binary files /dev/null and b/docs/images/GUI.png differ
diff --git a/docs/images/LinkExamCommandActivityDiagram.png b/docs/images/LinkExamCommandActivityDiagram.png
new file mode 100644
index 00000000000..5a7f0d2657f
Binary files /dev/null and b/docs/images/LinkExamCommandActivityDiagram.png differ
diff --git a/docs/images/LinkExamCommandDemo.png b/docs/images/LinkExamCommandDemo.png
new file mode 100644
index 00000000000..0958f2a0ccc
Binary files /dev/null and b/docs/images/LinkExamCommandDemo.png differ
diff --git a/docs/images/LinkExamCommandSequenceDiagram.png b/docs/images/LinkExamCommandSequenceDiagram.png
new file mode 100644
index 00000000000..d49259f366e
Binary files /dev/null and b/docs/images/LinkExamCommandSequenceDiagram.png differ
diff --git a/docs/images/ListExamTasksCommandDemo.png b/docs/images/ListExamTasksCommandDemo.png
new file mode 100644
index 00000000000..ad61373c73f
Binary files /dev/null and b/docs/images/ListExamTasksCommandDemo.png differ
diff --git a/docs/images/MarkTask.png b/docs/images/MarkTask.png
new file mode 100644
index 00000000000..16660a9861a
Binary files /dev/null and b/docs/images/MarkTask.png differ
diff --git a/docs/images/MarkTaskActivityDiagram.png b/docs/images/MarkTaskActivityDiagram.png
new file mode 100644
index 00000000000..11979c64338
Binary files /dev/null and b/docs/images/MarkTaskActivityDiagram.png differ
diff --git a/docs/images/MarkTaskReferenceDiagram.png b/docs/images/MarkTaskReferenceDiagram.png
new file mode 100644
index 00000000000..7c7c383d81f
Binary files /dev/null and b/docs/images/MarkTaskReferenceDiagram.png differ
diff --git a/docs/images/MarkTaskSequenceDiagram-0.png b/docs/images/MarkTaskSequenceDiagram-0.png
new file mode 100644
index 00000000000..21ff34c309e
Binary files /dev/null and b/docs/images/MarkTaskSequenceDiagram-0.png differ
diff --git a/docs/images/MarkTaskSequenceDiagram.png b/docs/images/MarkTaskSequenceDiagram.png
new file mode 100644
index 00000000000..21ff34c309e
Binary files /dev/null and b/docs/images/MarkTaskSequenceDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 04070af60d8..58d190f0dfb 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/SortTaskCommandActivityDiagram.png b/docs/images/SortTaskCommandActivityDiagram.png
new file mode 100644
index 00000000000..d00151165f8
Binary files /dev/null and b/docs/images/SortTaskCommandActivityDiagram.png differ
diff --git a/docs/images/SortTaskCommandDemo.png b/docs/images/SortTaskCommandDemo.png
new file mode 100644
index 00000000000..b7c6ba14b53
Binary files /dev/null and b/docs/images/SortTaskCommandDemo.png differ
diff --git a/docs/images/SortTaskCommandSequenceDiagram.png b/docs/images/SortTaskCommandSequenceDiagram.png
new file mode 100644
index 00000000000..b0dc37b5e3d
Binary files /dev/null and b/docs/images/SortTaskCommandSequenceDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 2533a5c1af0..b22ec931ea5 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..9650a19dd7a 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 785e04dbab4..c5b448beadc 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UnlinkExamActivityDiagram.png b/docs/images/UnlinkExamActivityDiagram.png
new file mode 100644
index 00000000000..d9946fb0af9
Binary files /dev/null and b/docs/images/UnlinkExamActivityDiagram.png differ
diff --git a/docs/images/UnlinkExamSequenceDiagram.png b/docs/images/UnlinkExamSequenceDiagram.png
new file mode 100644
index 00000000000..6fd670bdc4e
Binary files /dev/null and b/docs/images/UnlinkExamSequenceDiagram.png differ
diff --git a/docs/images/dlimyy.png b/docs/images/dlimyy.png
new file mode 100644
index 00000000000..1582afd7dc4
Binary files /dev/null and b/docs/images/dlimyy.png differ
diff --git a/docs/images/phualiting.png b/docs/images/phualiting.png
new file mode 100644
index 00000000000..5f1fe367105
Binary files /dev/null and b/docs/images/phualiting.png differ
diff --git a/docs/images/rachelchua.png b/docs/images/rachelchua.png
new file mode 100644
index 00000000000..b63fcc02ac7
Binary files /dev/null and b/docs/images/rachelchua.png differ
diff --git a/docs/images/sampy147.png b/docs/images/sampy147.png
new file mode 100644
index 00000000000..ca4d434322d
Binary files /dev/null and b/docs/images/sampy147.png differ
diff --git a/docs/images/tlx02.png b/docs/images/tlx02.png
new file mode 100644
index 00000000000..bbfd771e159
Binary files /dev/null and b/docs/images/tlx02.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..c6e3a2d2f47 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -3,8 +3,7 @@ layout: page
title: AddressBook Level-3
---
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
+[](https://codecov.io/gh/AY2223S1-CS2103T-F11-2/tp)

diff --git a/docs/team/dlimyy.md b/docs/team/dlimyy.md
new file mode 100644
index 00000000000..2d813213317
--- /dev/null
+++ b/docs/team/dlimyy.md
@@ -0,0 +1,59 @@
+---
+layout: page
+title: Douglas Lim's Project Portfolio Page
+---
+
+### Project: MODPRO
+
+MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. 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**: Implemented `AddTagCommand`
+ * What it does: Adds the tag to a task in the task list.
+ * Justification: This feature allows users to add priority status or deadline to their task so
+ that they can track the priority and deadline to complete the task.
+ * Highlights: The implementation for `AddTagCommand` was slightly challenging as there were several cases
+ such the task already having the tag that needs to be considered. Moreover, given that the various tags added
+ interact with the `Task` classes when being added, some changes in `Task` methods have to be made.
+
+
+* **New Feature**: Implemented `LinkExamCommand` which allows users to link a task in the task list to an exam in the
+exam list.
+
+* **New Feature**: Partially implemented `AddModuleCommand` which allows users to add modules to the module list.
+
+* **New Feature**: Implemented the `EditTagCommand` which allows users to edit the tags linked to the task.
+
+* **New Feature**: Implemented the `DeleteTagCommand` which allows users to delete the tags linked to the task.
+
+* **New Feature**: Implemented the `SortTaskCommand` which allows users to sort the tasks stored in the task list.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=dlimyy)
+
+
+* **Project management**:
+ * Managed releases `v1.3.0` and `v1.3.1` on GitHub
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `AddModuleCommand`, `AddTagCommand`, `EditTagCommand`, `DeleteTagCommand`,
+ `LinkExamCommand` and `SortTaskCommand`.
+ * Updated the `Quick Start` portion of the UG to reflect change in application to `MODPRO`.
+ * Developer Guide:
+ * Added implementation details of `SortTaskCommand` and `LinkExamCommand`
+ * Added numerous use cases and user stories
+ * Updated class diagram for `Model` and `Storage` components
+
+* **Contribution to Team Tasks**
+ * Set up the GitHub Repo for `MODPRO`
+ * Added CodeCov coverage for our GitHub repo
+ * Enabled assertions in the build.gradle file for our team project
+ * Contributed to the design of UI image of our product
+
+* **Community**:
+ * Reviewed numerous PR
+ * Reported a total of 13 bugs for the group working on [MyInsuRec](https://github.com/AY2223S1-CS2103T-W16-4/tp)
+ (Examples of bugs include [1](https://github.com/dlimyy/ped/issues/13), [2](https://github.com/dlimyy/ped/issues/12),
+ [3](https://github.com/dlimyy/ped/issues/10))
+
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/phualiting.md b/docs/team/phualiting.md
new file mode 100644
index 00000000000..c7289b3922f
--- /dev/null
+++ b/docs/team/phualiting.md
@@ -0,0 +1,60 @@
+---
+layout: page
+title: Phua Li Ting's Project Portfolio Page
+---
+
+### Project: MODPRO
+
+MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. 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 add tasks.
+ * What it does: Allows users to add a task to the task list
+ * Justification: Allows users to track their progress of tasks
+ * Credits: Referenced AB3 implementation for adding a person
+
+* **New Feature**: Added the ability to filter tasks by module, completion status and link status.
+ * What it does: Allows users to see tasks that fulfil the one or more filter constraints
+ * Justification: Easier for users to look for a specific task
+
+* **New Feature**: Added the ability to clear all tasks.
+ * What it does: Allows users to clear the whole task list
+ * Justification: Easier for users to start from a fresh task list without manually deleting tasks one at a time
+
+* **New Feature**: Added the ability to delete exams.
+ * What it does: Allows users to delete an exam, which unlinks all tasks currently linked to it
+ * Justification: Allow users to remove exams they no longer wish to track
+ * Credit: Referenced AB3 implementation for deleting a person
+
+* **New Feature**: Added the ability to unlink exams.
+ * What it does: Allows users to unlink an exam from a task
+ * Justification: Allows users to unlink an exam that has been wrongly linked
+
+* **New Feature**: Added the ability to view tasks of an exam.
+ * What it does: Allows users to see all tasks that are linked to a specific exam
+ * Justification: Easier for users to look at tasks of one exam
+
+* **New Feature**: Added the ability to clear all task, module and exam lists.
+ * What it does: Allows users to clear the entire address book
+ * Justification: Easier for users to start from a fresh address book
+ * Credit: Referenced AB3 implementation of clear command
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=phualiting&breakdown=true)
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for all new features stated above
+ * Developer Guide:
+ * Added implementation details and diagrams for the filter tasks feature and unlink exam feature
+ * Added use cases, user stories and manual testing for all new features stated above
+
+* **Community**:
+ * PRs reviewed
+ * Reported bugs for other teams' User Guide and Developer Guide in class
+ * Reported bugs and suggestion for another team's product and User Guide
+
+* **Team Based**
+ * Contributed to UI design of the app
diff --git a/docs/team/rachelchua.md b/docs/team/rachelchua.md
new file mode 100644
index 00000000000..729adfb8192
--- /dev/null
+++ b/docs/team/rachelchua.md
@@ -0,0 +1,62 @@
+---
+layout: page
+title: Rachel Chua's Project Portfolio Page
+---
+
+### Project: MODPRO
+
+MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. 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**: To be added soon
+ * Added an exam class and its following fields classes to store the exams in an exam list.
+ * What it does: Implemented an exam column so that user can see their exam in one column.
+ * Justification: Allows students to track their revision progress for the exam so that they can stay on track for their revision despite their busy schedule.
+ * Added the AddExamCommand class together with other exam related fields classes.
+ * What it does: Add an exam to the exam list with the following fields of Exam Description, Exam Date, and Module
+ * Justification: Allows you to add an exam in exam list to track your exams.
+ * Credits: Referenced AB3 implementation for add person.
+ * Added the EditExamCommand class
+ * What it does: Edit an exam in the exam list
+ * Justification: If the user type a wrong exam into the exam list, he or she can easily change it using our edit exam command
+ * Credits: Referenced AB3 implementation for edit person.
+ * Added the FindTasksCommand class
+ * What it does: Find the tasks that contain the keyword inputted partially or fully by the user
+ * Justification: User can now easily find the tasks by keyword inputted from the long list of tasks to do as it is difficult to find a task manually by scrolling.
+ * Credits: Referenced AB3 implementation for find person.
+ * Added the ListTasksCommand
+ * What it does: List all the tasks in the task list
+ * Justification: Allows user to go back to the main list of tasks after keying commands such as find tasks or filter tasks command.
+ * Credits: Referenced AB3 implementation for list persons.
+ * Added the FindModulesCommand class
+ * What it does: Find the modules that contain the keyword inputted partially or fully by the user
+ * Justification: User can now easily find the modules.
+ * Credits: Referenced AB3 implementation for find person.
+ * Added the ListModulesCommand
+ * What it does: List all the modules in the task list
+ * Justification: Allows user to go back to the main list of modules after keying commands such as find module command.
+ * Credits: Referenced AB3 implementation for list persons.
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=rachelchua)
+
+* **Documentation**:
+ * User Guide:
+ Wrote documentation for
+ * FindTasks command,
+ * FindModules command,
+ * ListTasks command,
+ * ListModules command,
+ * AddExamCommand,
+ * EditExamCommand
+ * Developer Guide:
+ * Implementation details for AddExamCommand
+ * Implementation details for FindTasksCommand
+ * Manual Testing for commands that I implemented.
+ * Use Cases, User Stories for commands that I implemented.
+
+* **Community**:
+ * PRs reviewed.
+ * Reported bugs and suggestions for other teams in the class when reviewing their DG, and UG, as well as in PE dry run.
+* **Team Based**:
+ * Contributed to the UI design of the app.
+
diff --git a/docs/team/sampy147.md b/docs/team/sampy147.md
new file mode 100644
index 00000000000..1af3bc35044
--- /dev/null
+++ b/docs/team/sampy147.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: Samuel Pang's Project Portfolio Page
+---
+
+### Project: MODPRO
+
+MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. 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 delete a task / module
+ * What it does: allows the user to delete a task / module from the task / module list.
+ * 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. It gives the user the freedom to delete any task / module that the user deems unnecessary.
+ * Highlights: This enhancement required the development of non-trivial new methods across different classes. It required an in depth understanding of user requirements, as certain design decisions such as deleting exams and tasks related to a module when the module was deleted were based of a user-centric approach.
+ * Credits: Referenced AB3 implementation for delete command.
+
+
+* **New Feature**: Added the ability to edit a module
+ * What it does: allows the user to edit a module in the module list.
+ * Justification: This feature improves the product greatly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. It gives the user the freedom to edit any module that the user deems unnecessary.
+ * Highlights: This enhancement affects existing methods across different classes and required the creation of methods that required extensive understanding of the existing code base. It required complex implementation using multiple classes, as certain design decisions such as editing exams and tasks related to a module when the module was edited were non-trivial.
+ * Credits: Referenced AB3 implementation for edit command.
+
+
+* **New Feature**: Augmented the ability to add a module using module name, module credit fields on top of the existing module code field.
+ * What it does: allows the user to add a module using these fields.
+ * Justification: This feature improves the product greatly because a user can have a more detailed view of the modules. It provides added personalisation for the user.
+ * Highlights: The addition of these fields affects existing methods across different classes and required extensive understanding of the existing code base, especially since existing methods had to now account for these fields.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=sampy147&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `deleteTask`, `deleteModule` and `editModule` (links: [#43](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/43), [#201](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/201)).
+ * Developer Guide:
+ * Added implementation details of the `deleteTask` feature, and other details for `deleteModule` and `editModule` features (links: [#44](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/44), [#106](https://github.com/AY2223S1-CS2103T-F11-2/tp/pull/106)).
+
+
+* **Community**:
+ * Numerous PRs reviewed (with non-trivial review comments).
+ * Clarified questions in forum regarding feature bugs ([example](https://github.com/nus-cs2103-AY2223S1/forum/issues/407)).
+ * Reported bugs and suggestions for other teams (examples: [1](https://github.com/Sampy147/ped/issues/1), [2](https://github.com/Sampy147/ped/issues/2), [3](https://github.com/Sampy147/ped/issues/9), [4](https://github.com/Sampy147/ped/issues/10)).
+ * Served as the point of contact between the Professor, TA as well as the team especially when dealing with queries.
diff --git a/docs/team/tlx02.md b/docs/team/tlx02.md
new file mode 100644
index 00000000000..4bd6aeae05e
--- /dev/null
+++ b/docs/team/tlx02.md
@@ -0,0 +1,45 @@
+---
+layout: page
+title: Tan Li Xin's Project Portfolio Page
+---
+
+### Project: MODPRO
+
+MODPRO is a desktop application which helps NUS students in tracking the progress of their modules. It is highly optimised for students who prefer Command Line Interface (CLI) by allowing those who type fast to key in commands to track their modules. 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 progress bar for modules
+ * What it does: Calculates the percentage of tasks linked to the module, that are completed and shows the percentage on a progress bar. The percentage bar will update itself with each command from the user input.
+ * Justification: This feature improves the product significantly because it is a key feature of our product as a module tracker.
+ * Highlights: This enhancement is closely linked to existing commands since the percentage is affected by changes such as the editing and marking of tasks. The progress bar has to update itself to reflect these changes. Hence, it required analysis to find the most appropriate design structure and the implementation was difficult as it had to integrate well with other commands.
+ * Added the progress bar for exams
+ * What it does: Calculates the percentage of tasks linked to the exam, that are completed and shows the percentage on a progress bar. The percentage bar will update itself with each command from the user input.
+ * Justifications: This feature allows students to track their revision progress for their exams conveniently.
+ * Added the MarkTaskCommand
+ * What it does: Marks a task as completed. This is reflected on the GUI by adding a tick to the checkbox.
+ * Justifications: This features allows students to focus on tasks they have not completed. It is also key to the implementation of the progress bar.
+ * Added the UnmarkTaskCommand
+ * What it does: Unmarks (labels as not completed) a task. This is reflected on the GUI by removing the tick from the checkbox.
+ * Added the EditTaskCommand
+ * What it does: Edits the details of a task with the new values provided by the user.
+ * Justification: This feature allows students to correct their mistakes and make changes to the module and description of their tasks.
+ * Credits: Referenced AB3 implementation for editing persons.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=tlx02&breakdown=true)
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the Mark Task, Unmark Task, and Edit Task features
+ * Added the command summary
+ * Developer Guide:
+ * Added implementation details of the Mark Task and Edit Task features
+ * Added manual testing for Mark Task and Edit Task features
+ * Added user stories and use cases for the features I implemented
+ * **Community**:
+ * Reviewed PRs
+ * Reported bugs and suggestions for other teams in the class during the PE dry run ([1](https://github.com/tlx02/ped/issues/3), [2](https://github.com/tlx02/ped/issues/6), [3](https://github.com/tlx02/ped/issues/7))
+ * **Team based**:
+ * Contributed to the UI design of the app
+
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..7356b512aad 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -21,7 +21,6 @@
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;
@@ -78,10 +77,14 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
ReadOnlyAddressBook initialData;
try {
addressBookOptional = storage.readAddressBook();
+ //@@author dlimyy-reused
+ //Reused with minor modifications from
+ // https://github.com/se-edu/addressbook-level3/blob/master/src/main/java/seedu/address/MainApp.java
if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ logger.info("Data file not found. Will be starting with an empty AddressBook");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialData = addressBookOptional.orElseGet(AddressBook::new);
+ //@@author
} catch (DataConversionException e) {
logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
initialData = new AddressBook();
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..e4b9673cbb5 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -6,8 +6,33 @@
public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
+ public static final String MESSAGE_INVALID_FEATURE_TYPE_FORMAT = "Invalid %s command format!";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+ public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid";
+ public static final String MESSAGE_INVALID_TASK_INDEX_TOO_LARGE =
+ "Please provide a task index greater than 0 and less than %d";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
+ public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!";
+ public static final String MESSAGE_MODULES_LISTED_OVERVIEW = "%1$d modules listed!";
+ public static final String MESSAGE_INVALID_MODULE_INDEX =
+ "Please provide a positive unsigned integer less than 2147483648 for the index of a module.";
+ public static final String MESSAGE_INVALID_MODULE_DELETION_AS_TIED_WITH_TASK = "The module"
+ + " cannot be deleted as it is tied with an existing task";
+ public static final String MESSAGE_INVALID_MODULE_EDIT_AS_TIED_WITH_TASK = "The module"
+ + " cannot be edited as it is tied with an existing task";
+ public static final String MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE =
+ "Please provide a module index greater than 0 and less than %d";
+ public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task list";
+ public static final String MESSAGE_INVALID_TASK_INDEX =
+ "Please provide an integer greater than 0 and less than 2147483648 for the index of a task.";
+ public static final String MESSAGE_MODULE_NOT_FOUND = "This module does not exist";
+ public static final String MESSAGE_DUPLICATE_MODULE = "This module already exists in the module list";
+ public static final String MESSAGE_DUPLICATE_EXAM = "This exam already exists in the exam list";
+ public static final String MESSAGE_INVALID_EXAM_DISPLAYED_INDEX =
+ "The exam index provided is invalid";
+ public static final String MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE =
+ "Please provide an exam index greater than 0 and less than %d";
+ public static final String MESSAGE_INVALID_EXAM_INDEX =
+ "Please provide an integer greater than 0 and less than 2147483648 for the index of an exam.";
}
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java
index eafe4dfd681..2c5f69df335 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java
@@ -26,6 +26,16 @@ public static void requireAllNonNull(Collection> items) {
items.forEach(Objects::requireNonNull);
}
+ /**
+ * Throws NullPointerException if all of {@code items} is null.
+ */
+ public static void requireAnyNonNull(Object... items) {
+ requireNonNull(items);
+ if (!isAnyNonNull(items)) {
+ throw new NullPointerException();
+ }
+ }
+
/**
* Returns true if {@code items} contain any elements that are non-null.
*/
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..24a2307149b 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -37,6 +37,18 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
return Arrays.stream(wordsInPreppedSentence)
.anyMatch(preppedWord::equalsIgnoreCase);
}
+ /**
+ * Returns true if {@code sentence} contains the {@code partialWord}.
+ * @param sentence cannot be null
+ * @param partialWord cannot be null, cannot be empty
+ * @return true if {@code sentence} contains the {@code partialWord}, otherwise false
+ */
+ public static boolean containsPartialWord(String sentence, String partialWord) {
+ requireNonNull(sentence);
+ requireNonNull(partialWord);
+ String preppedWord = partialWord.trim();
+ return sentence.contains(preppedWord);
+ }
/**
* Returns a detailed message of the t, including the stack trace.
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..420c8365740 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -8,7 +8,9 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
/**
* API of the Logic component
@@ -30,8 +32,14 @@ public interface Logic {
*/
ReadOnlyAddressBook getAddressBook();
- /** Returns an unmodifiable view of the filtered list of persons */
- ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered list of modules */
+ ObservableList getFilteredModuleList();
+
+ /** Returns an unmodifiable view of the filtered list of tasks */
+ ObservableList getFilteredTaskList();
+
+ /** Returns an unmodifiable view of the filtered list of exams */
+ ObservableList getFilteredExamList();
/**
* Returns the user prefs' address book file path.
@@ -47,4 +55,5 @@ public interface Logic {
* 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
index 9d9c6d15bdc..6778db863bc 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -14,7 +14,9 @@
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.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
import seedu.address.storage.Storage;
/**
@@ -60,8 +62,17 @@ public ReadOnlyAddressBook getAddressBook() {
}
@Override
- public ObservableList getFilteredPersonList() {
- return model.getFilteredPersonList();
+ public ObservableList getFilteredModuleList() {
+ return model.getFilteredModuleList();
+ }
+
+ @Override
+ public ObservableList getFilteredTaskList() {
+ return model.getFilteredTaskList();
+ }
+ @Override
+ public ObservableList getFilteredExamList() {
+ return model.getFilteredExamList();
}
@Override
@@ -78,4 +89,5 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
}
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 71656d7c5c8..00000000000
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ /dev/null
@@ -1,67 +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.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, toAdd));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof AddCommand // instanceof handles nulls
- && toAdd.equals(((AddCommand) other).toAdd));
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/AddExamCommand.java b/src/main/java/seedu/address/logic/commands/AddExamCommand.java
new file mode 100644
index 00000000000..d479a7b35d1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddExamCommand.java
@@ -0,0 +1,64 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EXAM;
+import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+
+/**
+ * Adds an exam to the exam list.
+ */
+public class AddExamCommand extends Command {
+
+ public static final String COMMAND_WORD = "add";
+
+ public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + ": Adds an exam to the exam list. "
+ + "\n"
+ + "Parameters: "
+ + PREFIX_MODULE + "MODULE "
+ + PREFIX_EXAM_DESCRIPTION + "EXAMDESCRIPTION "
+ + PREFIX_EXAM_DATE + "EXAMDATE "
+ + "\n"
+ + "Example: e " + COMMAND_WORD + " "
+ + PREFIX_MODULE + "CS2103T "
+ + PREFIX_EXAM_DESCRIPTION + "Midterms "
+ + PREFIX_EXAM_DATE + "20-12-2022";
+
+ public static final String MESSAGE_SUCCESS = "New exam added: %1$s";
+
+ private final Exam toAdd;
+
+ /**
+ * Creates a AddExamCommand to add the specified {@code Exam}
+ */
+ public AddExamCommand(Exam exam) {
+ requireNonNull(exam);
+ toAdd = exam;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasModule(toAdd.getModule())) {
+ throw new CommandException(MESSAGE_MODULE_NOT_FOUND);
+ } else if (model.hasExam(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EXAM);
+ }
+ model.addExam(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddExamCommand // instanceof handles nulls
+ && toAdd.equals(((AddExamCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddModuleCommand.java b/src/main/java/seedu/address/logic/commands/AddModuleCommand.java
new file mode 100644
index 00000000000..de65bfdfaef
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddModuleCommand.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.module.Module;
+
+/**
+ * AddModuleCommand class represents an AddModuleCommand which adds the module.
+ */
+public class AddModuleCommand extends Command {
+ public static final String COMMAND_WORD = "add";
+ public static final String MESSAGE_USAGE = "m " + COMMAND_WORD + ": adds a module to the module list.\n"
+ + "Parameters: "
+ + PREFIX_MOD_CODE + "MODULE_CODE "
+ + PREFIX_MOD_NAME + "MODULE_NAME "
+ + PREFIX_MOD_CREDIT + "MODULE_CREDIT\n"
+ + "Example: " + "m " + COMMAND_WORD + " " + PREFIX_MOD_CODE + "CS2100 "
+ + PREFIX_MOD_NAME + "Computer organisation " + PREFIX_MOD_CREDIT + "4";
+ public static final String MODULE_ADDED_SUCCESS = "Module has been added successfully!";
+
+ public static final String DUPLICATE_MODULE_DETECTED = "This module already exists! "
+ + "Try to input a different module code along with your initial module name and module credit fields.";
+ private final Module moduleAdded;
+
+ /**
+ * Constructor of the AddModuleCommand class which
+ * helps to add a module.
+ *
+ * @param moduleAdded
+ */
+ public AddModuleCommand(Module moduleAdded) {
+ requireNonNull(moduleAdded);
+ this.moduleAdded = moduleAdded;
+
+ }
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasModule(moduleAdded)) {
+ throw new CommandException(DUPLICATE_MODULE_DETECTED);
+ }
+ model.addModule(moduleAdded);
+ model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES);
+ return new CommandResult(MODULE_ADDED_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object otherAddCommand) {
+ return otherAddCommand == this
+ || (otherAddCommand instanceof AddModuleCommand
+ && moduleAdded.equals(((AddModuleCommand) otherAddCommand).moduleAdded));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java
new file mode 100644
index 00000000000..35f5231be24
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java
@@ -0,0 +1,93 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElse;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS;
+
+import java.util.List;
+import java.util.Objects;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+import seedu.address.model.tag.exceptions.BothTagsCannotBeNullException;
+import seedu.address.model.task.Task;
+
+/**
+ * AddTagCommand represents a command which adds a tag to the existing task.
+ */
+public class AddTagCommand extends Command {
+ public static final String COMMAND_WORD = "tagadd";
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": tags a task in the task list.\n"
+ + "Parameters: INDEX " + "[" + PREFIX_PRIORITY_STATUS + "PRIORITY_STATUS]* "
+ + "[" + PREFIX_DEADLINE + "DEADLINE]*\n"
+ + "Example: " + "t " + COMMAND_WORD + " 1"
+ + " " + PREFIX_PRIORITY_STATUS + "HIGH";
+
+ public static final String TAG_ADDED_SUCCESS = "The tag(s) has/have been added successfully";
+
+ public static final String PRIORITY_TAG_ALREADY_EXIST = "The priority tag already exists";
+ public static final String DEADLINE_TAG_ALREADY_EXIST = "The deadline tag already exists";
+
+ private final PriorityTag priorityTag;
+ private final DeadlineTag deadlineTag;
+ private final Index index;
+
+ /**
+ * Constructor of the AddTagCommand. Sets the Priority tag, Deadline tag and
+ * the index to add the tag.
+ *
+ * @param priorityTag The priority tag to be added.
+ * @param deadlineTag The deadline tag to be added.
+ * @param index The index of the tag.
+ */
+ public AddTagCommand(PriorityTag priorityTag, DeadlineTag deadlineTag, Index index) {
+ requireNonNull(index);
+ if (priorityTag == null && deadlineTag == null) {
+ throw new BothTagsCannotBeNullException();
+ }
+ this.priorityTag = priorityTag;
+ this.deadlineTag = deadlineTag;
+ this.index = index;
+ }
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Task taggedTask = null;
+ List taskList = model.getFilteredTaskList();
+ if (index.getZeroBased() >= taskList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ Task currentTask = taskList.get(index.getZeroBased());
+ if (priorityTag != null) {
+ if (currentTask.hasPriorityTag()) {
+ throw new CommandException(PRIORITY_TAG_ALREADY_EXIST);
+ }
+ taggedTask = currentTask.setPriorityTag(priorityTag);
+ }
+
+ if (deadlineTag != null) {
+ if (currentTask.hasDeadlineTag()) {
+ throw new CommandException(DEADLINE_TAG_ALREADY_EXIST);
+ }
+ taggedTask = requireNonNullElse(taggedTask, currentTask).setDeadlineTag(deadlineTag);
+ }
+ model.replaceTask(currentTask, taggedTask, true);
+ return new CommandResult(TAG_ADDED_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object otherAddTagCommand) {
+ return otherAddTagCommand == this
+ || (otherAddTagCommand instanceof AddTagCommand
+ && Objects.equals(priorityTag, ((AddTagCommand) otherAddTagCommand).priorityTag)
+ && Objects.equals(deadlineTag, ((AddTagCommand) otherAddTagCommand).deadlineTag)
+ && index.equals(((AddTagCommand) otherAddTagCommand).index));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddTaskCommand.java b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java
new file mode 100644
index 00000000000..93101945b2b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java
@@ -0,0 +1,62 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_TASK;
+import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+
+
+/**
+ * Adds a task to the task list.
+ */
+public class AddTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "add";
+
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": Adds a task to the task list.\n"
+ + "Parameters: "
+ + PREFIX_MODULE + "MODULE "
+ + PREFIX_DESCRIPTION + "DESCRIPTION\n"
+ + "Example: t " + COMMAND_WORD + " "
+ + PREFIX_MODULE + "CS2103T "
+ + PREFIX_DESCRIPTION + "lecture quiz";
+
+ public static final String MESSAGE_SUCCESS = "New task added: %1$s";
+
+ private final Task toAdd;
+
+ /**
+ * Creates a AddTaskCommand to add the specified {@code Task}
+ */
+ public AddTaskCommand(Task toAdd) {
+ requireNonNull(toAdd);
+ this.toAdd = toAdd;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasModule(toAdd.getModule())) {
+ throw new CommandException(MESSAGE_MODULE_NOT_FOUND);
+ } else if (model.hasTask(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TASK);
+ }
+
+ model.addTask(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddTaskCommand // instanceof handles nulls
+ && toAdd.equals(((AddTaskCommand) other).toAdd));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearAllCommand.java b/src/main/java/seedu/address/logic/commands/ClearAllCommand.java
new file mode 100644
index 00000000000..77f6b2d9941
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ClearAllCommand.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import javafx.collections.ObservableList;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
+
+/**
+ * Clears task, exam and module lists.
+ */
+public class ClearAllCommand extends Command {
+
+ public static final String COMMAND_WORD = "clearall";
+ public static final String MESSAGE_SUCCESS = "Task, exam and module lists have been cleared!";
+ public static final String MESSAGE_ALL_LISTS_ALREADY_EMPTY = "Task, exam and module lists are already empty!";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ ObservableList taskList = model.getFilteredTaskList();
+ ObservableList moduleList = model.getFilteredModuleList();
+ ObservableList examList = model.getFilteredExamList();
+ if (taskList.isEmpty() && moduleList.isEmpty() && examList.isEmpty()) {
+ throw new CommandException(MESSAGE_ALL_LISTS_ALREADY_EMPTY);
+ }
+
+ model.setAddressBook(new AddressBook());
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
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/ClearTasksCommand.java b/src/main/java/seedu/address/logic/commands/ClearTasksCommand.java
new file mode 100644
index 00000000000..bc5ffc3be89
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ClearTasksCommand.java
@@ -0,0 +1,43 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import javafx.collections.ObservableList;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
+
+/**
+ * Clears the task list
+ */
+public class ClearTasksCommand extends Command {
+
+ public static final String COMMAND_WORD = "clear";
+ public static final String MESSAGE_SUCCESS = "Task list has been cleared!";
+ public static final String MESSAGE_TASK_LIST_ALREADY_EMPTY = "The task list is already empty!";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ ObservableList taskList = model.getFilteredTaskList();
+ if (taskList.isEmpty()) {
+ throw new CommandException(MESSAGE_TASK_LIST_ALREADY_EMPTY);
+ }
+
+ ObservableList moduleList = model.getFilteredModuleList();
+ ObservableList examList = model.getFilteredExamList();
+
+ AddressBook newAddressBook = new AddressBook();
+ newAddressBook.setModules(moduleList);
+ newAddressBook.setExams(examList);
+ newAddressBook.resetAllTaskCount();
+
+ model.setAddressBook(newAddressBook);
+
+ 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 02fd256acba..00000000000
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.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, personToDelete));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof DeleteCommand // instanceof handles nulls
- && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteExamCommand.java b/src/main/java/seedu/address/logic/commands/DeleteExamCommand.java
new file mode 100644
index 00000000000..0e2a1960d7c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteExamCommand.java
@@ -0,0 +1,65 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+
+/**
+ * Deletes an exam identified using it's displayed index from the address book. All tasks currently linked to that exam
+ * will become unlinked.
+ */
+public class DeleteExamCommand extends Command {
+
+ public static final String COMMAND_WORD = "del";
+
+ public static final String MESSAGE_USAGE = "e " + COMMAND_WORD
+ + ": Deletes the exam identified by the index number used in the displayed exam list.\n"
+ + "Parameters: INDEX\n"
+ + "Example: e " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_EXAM_SUCCESS = "Deleted Exam: %1$s";
+
+ public static final String MESSAGE_NO_EXAM_IN_LIST =
+ "There is no exam in the exam list so delete exam operation cannot be done!";
+
+ private final Index targetIndex;
+
+ public DeleteExamCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredExamList();
+
+ if (lastShownList.size() == 0) {
+ throw new CommandException(MESSAGE_NO_EXAM_IN_LIST);
+ }
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ Exam examToDelete = lastShownList.get(targetIndex.getZeroBased());
+
+ model.unlinkTasksFromExam(examToDelete);
+ model.deleteExam(examToDelete);
+
+ return new CommandResult(String.format(MESSAGE_DELETE_EXAM_SUCCESS, examToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteExamCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteExamCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteModuleCommand.java b/src/main/java/seedu/address/logic/commands/DeleteModuleCommand.java
new file mode 100644
index 00000000000..d7f718de7c5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteModuleCommand.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.module.Module;
+
+/**
+ * Deletes a module identified using it's displayed index from the module list.
+ */
+public class DeleteModuleCommand extends Command {
+
+ public static final String COMMAND_WORD = "del";
+
+ public static final String MESSAGE_USAGE = "m " + COMMAND_WORD
+ + ": Deletes the module identified by the index number used in the displayed module list.\n"
+ + "Parameters: INDEX\n"
+ + "Example: m " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_MODULE_SUCCESS = "Deleted Module: %1$s";
+ public static final String MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE =
+ "\nWarning! All the tasks and exams related to this module have been deleted.";
+
+ private final Index targetIndex;
+
+ public DeleteModuleCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredModuleList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ Module moduleToDelete = lastShownList.get(targetIndex.getZeroBased());
+
+ boolean areTasksOrExamsDeleted = false;
+
+ if (model.hasTaskWithModule(moduleToDelete)) {
+ model.deleteTasksWithModule(moduleToDelete);
+ areTasksOrExamsDeleted = true;
+ }
+ if (model.hasExamWithModule(moduleToDelete)) {
+ model.deleteExamsWithModule(moduleToDelete);
+ areTasksOrExamsDeleted = true;
+ }
+
+ model.deleteModule(moduleToDelete);
+
+ if (areTasksOrExamsDeleted) {
+ return new CommandResult(String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete)
+ + MESSAGE_DELETE_TASKS_AND_EXAMS_RELATED_TO_MODULE);
+ }
+ return new CommandResult(String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteModuleCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteModuleCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java
new file mode 100644
index 00000000000..58dd01566ed
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java
@@ -0,0 +1,81 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+
+/**
+ * DeleteTagCommand removes the tag linked to the task in the task list.
+ */
+public class DeleteTagCommand extends Command {
+ public static final String COMMAND_WORD = "tagdel";
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": deletes tags linked to the task.\n"
+ + "Parameters: " + "INDEX " + PREFIX_TAG + "KEYWORD [SECOND_KEYWORD]\n"
+ + "Example: " + "t " + COMMAND_WORD + " 1 " + "t/" + "priority";
+ public static final String NO_PRIORITY_TAG_TO_DELETE =
+ "There is no priority tag to delete from the task.";
+ public static final String NO_DEADLINE_TAG_TO_DELETE =
+ "There is no deadline tag to delete form the task.";
+ public static final String TAG_DELETED_SUCCESSFULLY =
+ "The tag(s) has/have been deleted from the task.";
+
+ private final Index index;
+ private final Set keywords;
+
+ /**
+ * The constructor of the DeleteTagCommand class. Sets the index and
+ * the keywords of the tags to be removed.
+ *
+ * @param index The index of the task
+ * @param keywords The keywords which identify the tags to be removed.
+ */
+ public DeleteTagCommand(Index index, Set keywords) {
+ requireAllNonNull(index, keywords);
+ this.index = index;
+ this.keywords = keywords;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List taskList = model.getFilteredTaskList();
+ if (index.getZeroBased() >= taskList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ Task currentTask = taskList.get(index.getZeroBased());
+ Task changedTask = currentTask;
+ boolean hasPriorityKeyword = keywords.contains("priority");
+ boolean hasDeadlineKeyword = keywords.contains("deadline");
+ if (hasPriorityKeyword) {
+ if (!currentTask.hasPriorityTag()) {
+ throw new CommandException(NO_PRIORITY_TAG_TO_DELETE);
+ }
+ changedTask = changedTask.deletePriorityTag();
+ }
+ if (hasDeadlineKeyword) {
+ if (!currentTask.hasDeadlineTag()) {
+ throw new CommandException(NO_DEADLINE_TAG_TO_DELETE);
+ }
+ changedTask = changedTask.deleteDeadlineTag();
+ }
+ model.replaceTask(currentTask, changedTask, true);
+ return new CommandResult(TAG_DELETED_SUCCESSFULLY);
+ }
+
+ @Override
+ public boolean equals(Object otherDeleteTagCommand) {
+ return otherDeleteTagCommand == this
+ || (otherDeleteTagCommand instanceof DeleteTagCommand
+ && keywords.equals(((DeleteTagCommand) otherDeleteTagCommand).keywords)
+ && index.equals(((DeleteTagCommand) otherDeleteTagCommand).index));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java
new file mode 100644
index 00000000000..5ca44253036
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java
@@ -0,0 +1,62 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+
+/**
+ * Deletes a task identified using it's displayed index from the task list.
+ */
+public class DeleteTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "del";
+
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD
+ + ": Deletes the task identified by the index number used in the displayed task list.\n"
+ + "Parameters: INDEX\n"
+ + "Example: t " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s";
+ public static final String MESSAGE_EXAM_LINK_DROPPED =
+ "\nWarning! The link between this task and its exam is dropped.";
+
+ private final Index targetIndex;
+
+ public DeleteTaskCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ Task taskToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteTask(taskToDelete);
+
+ if (taskToDelete.isLinked()) {
+ return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)
+ + MESSAGE_EXAM_LINK_DROPPED);
+ }
+
+ return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteTaskCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteTaskCommand) other).targetIndex)); // state check
+ }
+}
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 7e36114902f..00000000000
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ /dev/null
@@ -1,226 +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.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
-import seedu.address.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, 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) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- // state check
- EditCommand e = (EditCommand) other;
- return index.equals(e.index)
- && editPersonDescriptor.equals(e.editPersonDescriptor);
- }
-
- /**
- * 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) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditPersonDescriptor)) {
- return false;
- }
-
- // state check
- EditPersonDescriptor e = (EditPersonDescriptor) other;
-
- return getName().equals(e.getName())
- && getPhone().equals(e.getPhone())
- && getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
- }
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/EditExamCommand.java b/src/main/java/seedu/address/logic/commands/EditExamCommand.java
new file mode 100644
index 00000000000..0c0713af6bd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditExamCommand.java
@@ -0,0 +1,192 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_EXAM;
+import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EXAMS;
+
+import java.util.List;
+import java.util.Optional;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.exam.ExamDate;
+import seedu.address.model.exam.ExamDescription;
+import seedu.address.model.exam.exceptions.DuplicateExamException;
+import seedu.address.model.module.Module;
+
+/**
+ * Edits the exam with the specified index number in the displayed exam list.
+ */
+public class EditExamCommand extends Command {
+
+ public static final String COMMAND_WORD = "edit";
+
+ public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + ": Edits the details of the exam identified "
+ + "by the index number used in the displayed exam list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX "
+ + "[" + PREFIX_MODULE + "MODULE]* "
+ + "[" + PREFIX_EXAM_DESCRIPTION + "EXAM DESCRIPTION]* "
+ + "[" + PREFIX_EXAM_DATE + "EXAM DATE]* " + "\n"
+ + "Example: e " + COMMAND_WORD + " 1 "
+ + PREFIX_MODULE + "cs2030s "
+ + PREFIX_EXAM_DESCRIPTION + "finals "
+ + PREFIX_EXAM_DATE + "20-12-2022";
+
+ public static final String MESSAGE_EDIT_EXAM_SUCCESS = "Successfully Edited Exam: %1$s";
+ public static final String MESSAGE_EXAM_NOT_EDITED = "Please provide a module or exam description or exam date "
+ + "different from the exam's current module and description and exam date";;
+ public static final String MESSAGE_NO_FIELDS_PROVIDED =
+ "Please provide at least one of the fields to edit: m/MODULE, ex/EXAMDESCRIPTION, ed/EXAMDATE";
+ private final Index index;
+ private final EditExamDescriptor editExamDescriptor;
+
+ /**
+ * @param index of the exam in the filtered exam list to edit
+ * @param editExamDescriptor details to edit the exam with
+ */
+ public EditExamCommand(Index index, EditExamDescriptor editExamDescriptor) {
+ requireAllNonNull(index, editExamDescriptor);
+ this.index = index;
+ this.editExamDescriptor = editExamDescriptor;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredExamList();
+
+ if (lastShownList.size() == 0) {
+ throw new CommandException("There is no exam in the exam list so edit operation cannot be done");
+ }
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ if (editExamDescriptor.getModule().isPresent() && !model.hasModule(editExamDescriptor.module)) {
+ throw new CommandException(MESSAGE_MODULE_NOT_FOUND);
+ }
+
+ Exam examToEdit = lastShownList.get(index.getZeroBased());
+ Exam editedExam = examToEdit.edit(editExamDescriptor);
+
+ if (examToEdit.isSameExam(editedExam)) {
+ throw new CommandException(MESSAGE_EXAM_NOT_EDITED);
+ }
+
+ boolean isEditedExamOfSameModule = examToEdit.getModule().isSameModule(editedExam.getModule());
+
+ try {
+ model.replaceExam(examToEdit, editedExam, false);
+ if (!isEditedExamOfSameModule && model.isExamLinkedToTask(examToEdit)) {
+ model.unlinkTasksFromExam(examToEdit);
+ return new CommandResult(String.format(MESSAGE_EDIT_EXAM_SUCCESS, editedExam) + "\n"
+ + "Warning! All the tasks previously linked to this exam are now unlinked.");
+ } else {
+ model.updateExamFieldForTask(examToEdit, editedExam);
+ }
+ } catch (DuplicateExamException e) {
+ throw new CommandException(MESSAGE_DUPLICATE_EXAM);
+ }
+
+ model.updateFilteredExamList(PREDICATE_SHOW_ALL_EXAMS);
+ return new CommandResult(String.format(MESSAGE_EDIT_EXAM_SUCCESS, editedExam));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditExamCommand)) {
+ return false;
+ }
+
+ // state check
+ EditExamCommand e = (EditExamCommand) other;
+ return index.equals(e.index)
+ && editExamDescriptor.equals(e.editExamDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the exam with. Each non-empty field value will replace the
+ * corresponding field value of the exam.
+ */
+ public static class EditExamDescriptor {
+ private Module module;
+ private ExamDescription description;
+ private ExamDate examDate;
+
+ public EditExamDescriptor() {}
+
+ /**
+ * Copy constructor.
+ */
+ public EditExamDescriptor(EditExamDescriptor toCopy) {
+ setExamDate(toCopy.examDate);
+ setDescription(toCopy.description);
+ setModule(toCopy.module);
+ }
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(module, description, examDate);
+ }
+
+ public void setModule(Module module) {
+ this.module = module;
+ }
+
+ public Optional getModule() {
+ return Optional.ofNullable(module);
+ }
+
+ public void setDescription(ExamDescription description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setExamDate(ExamDate examDate) {
+ this.examDate = examDate;
+ }
+
+ public Optional getExamDate() {
+ return Optional.ofNullable(examDate);
+ }
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditExamDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditExamDescriptor e = (EditExamDescriptor) other;
+ return getModule().equals(e.getModule()) && getDescription().equals(e.getDescription())
+ && getExamDate().equals(e.getExamDate());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditModuleCommand.java b/src/main/java/seedu/address/logic/commands/EditModuleCommand.java
new file mode 100644
index 00000000000..7fd4d95a843
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditModuleCommand.java
@@ -0,0 +1,205 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES;
+
+import java.util.List;
+import java.util.Optional;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.module.ModuleCredit;
+import seedu.address.model.module.ModuleName;
+import seedu.address.model.module.exceptions.DuplicateModuleException;
+
+/**
+ * Edits the task with the specified index number in the displayed task list.
+ */
+public class EditModuleCommand extends Command {
+
+ public static final String COMMAND_WORD = "edit";
+
+ public static final String MESSAGE_USAGE = "m " + COMMAND_WORD
+ + ": Edits the module code, module name and module credit of the module identified "
+ + "by the index number used in the displayed module list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX "
+ + "[" + PREFIX_MOD_CODE + "MODULE CODE]* "
+ + "[" + PREFIX_MOD_NAME + "MODULE NAME]* "
+ + "[" + PREFIX_MOD_CREDIT + "MODULE CREDIT]*\n"
+ + "Example: m " + COMMAND_WORD + " 1 "
+ + PREFIX_MOD_CODE + "cs2040 "
+ + PREFIX_MOD_NAME + "Data Structures and Algorithms "
+ + PREFIX_MOD_CREDIT + "4";
+
+ public static final String MESSAGE_EDIT_MODULE_SUCCESS = "Edited Module: %1$s, Edited Name: %2$s, "
+ + "Edited Credit: %3$s";
+ public static final String MESSAGE_MODULE_NOT_EDITED = "The provided fields are the same as the current module";
+ public static final String MESSAGE_NO_FIELDS_PROVIDED =
+ String.format("Please provide at least one of the fields to edit: %1$sMODULE CODE, "
+ + "%2$sMODULE NAME, %3$sMODULE CREDIT", PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT);
+ public static final String MESSAGE_TASKS_EXAMS_RELATED_MODIFIED = "\n"
+ + "Warning! All the tasks and exams related to the initial module "
+ + "now have this edited Module as their new module.";
+
+ private final Index index;
+ private final EditModuleDescriptor editModuleDescriptor;
+
+ /**
+ * @param index of the task in the filtered module list to edit
+ * @param editModuleDescriptor details to edit the module with
+ */
+ public EditModuleCommand(Index index, EditModuleDescriptor editModuleDescriptor) {
+ requireAllNonNull(index, editModuleDescriptor);
+
+ this.index = index;
+ this.editModuleDescriptor = editModuleDescriptor;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredModuleList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_MODULE_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ Module moduleToEdit = lastShownList.get(index.getZeroBased());
+ Module editedModule = moduleToEdit.edit(editModuleDescriptor);
+
+ if (moduleToEdit.hasAllSameFields(editedModule)) {
+ throw new CommandException(MESSAGE_MODULE_NOT_EDITED);
+ }
+
+ try {
+ model.replaceModule(moduleToEdit, editedModule);
+
+ boolean isModuleFieldUpdated = false;
+ if (!moduleToEdit.isSameModule(editedModule)) {
+ if (model.hasTaskWithModule(moduleToEdit)) {
+ model.updateModuleFieldForTask(moduleToEdit, editedModule);
+ isModuleFieldUpdated = true;
+ }
+ if (model.hasExamWithModule(moduleToEdit)) {
+ model.updateModuleFieldForExam(moduleToEdit, editedModule);
+ isModuleFieldUpdated = true;
+ }
+ }
+
+ if (isModuleFieldUpdated) {
+ return new CommandResult(String.format(MESSAGE_EDIT_MODULE_SUCCESS, editedModule,
+ editedModule.getModuleName(), editedModule.getModuleCredit())
+ + MESSAGE_TASKS_EXAMS_RELATED_MODIFIED);
+ }
+
+ } catch (DuplicateModuleException e) {
+ throw new CommandException(Messages.MESSAGE_DUPLICATE_MODULE);
+ }
+
+ model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES);
+ return new CommandResult(String.format(MESSAGE_EDIT_MODULE_SUCCESS, editedModule,
+ editedModule.getModuleName(), editedModule.getModuleCredit()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditModuleCommand)) {
+ return false;
+ }
+
+ // state check
+ EditModuleCommand e = (EditModuleCommand) other;
+ return index.equals(e.index)
+ && editModuleDescriptor.equals(e.editModuleDescriptor);
+ }
+
+ /**
+ * Stores the module code, module name and module credit to edit the target module with.
+ * Each non-empty field value will replace the corresponding field value of the target module.
+ */
+ public static class EditModuleDescriptor {
+ private ModuleCode moduleCode;
+ private ModuleName moduleName;
+ private ModuleCredit moduleCredit;
+
+ public EditModuleDescriptor() {}
+
+ /**
+ * Copy constructor.
+ */
+ public EditModuleDescriptor(EditModuleCommand.EditModuleDescriptor toCopy) {
+ setModuleCode(toCopy.moduleCode);
+ setModuleName(toCopy.moduleName);
+ setModuleCredit(toCopy.moduleCredit);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(moduleCode, moduleName, moduleCredit);
+ }
+
+ public void setModuleCode(ModuleCode moduleCode) {
+ this.moduleCode = moduleCode;
+ }
+
+ public void setModuleName(ModuleName moduleName) {
+ this.moduleName = moduleName;
+ }
+
+ public void setModuleCredit(ModuleCredit moduleCredit) {
+ this.moduleCredit = moduleCredit;
+ }
+
+ public Optional getModuleCode() {
+ return Optional.ofNullable(moduleCode);
+ }
+
+ public Optional getModuleName() {
+ return Optional.ofNullable(moduleName);
+ }
+
+ public Optional getModuleCredit() {
+ return Optional.ofNullable(moduleCredit);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditModuleDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditModuleDescriptor e = (EditModuleDescriptor) other;
+
+ return getModuleCode().equals(e.getModuleCode())
+ && getModuleName().equals(e.getModuleName())
+ && getModuleCredit().equals(e.getModuleCredit());
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/EditTagCommand.java b/src/main/java/seedu/address/logic/commands/EditTagCommand.java
new file mode 100644
index 00000000000..8934fa27691
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditTagCommand.java
@@ -0,0 +1,113 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS;
+
+import java.util.List;
+import java.util.Objects;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+import seedu.address.model.tag.exceptions.BothTagsCannotBeNullException;
+import seedu.address.model.task.Task;
+
+/**
+ * EditTagCommand represents the edit tag command which edits the priority status of priority
+ * tag or the deadline of the deadline tag.
+ */
+public class EditTagCommand extends Command {
+ public static final String COMMAND_WORD = "tagedit";
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": edits the tags of a task "
+ + "in the task list.\n"
+ + "Parameters: INDEX " + "[" + PREFIX_PRIORITY_STATUS + "PRIORITY_STATUS]* "
+ + "[" + PREFIX_DEADLINE + "DEADLINE]*\n"
+ + "Example: " + "t " + COMMAND_WORD + " 1 " + PREFIX_PRIORITY_STATUS + "HIGH";
+ public static final String PRIORITY_TAG_DOES_NOT_EXIST = "The task does not have a priority tag.";
+ public static final String DEADLINE_TAG_DOES_NOT_EXIST = "The task does not have a deadline tag.";
+ public static final String PRIORITY_TAG_UNCHANGED = "The priority status provided is "
+ + "the same as the current priority status for the task.";
+ public static final String DEADLINE_TAG_UNCHANGED = "The deadline provided is"
+ + " the same as the current deadline for the task.";
+ public static final String TAG_EDITED_SUCCESSFULLY = "The tag(s) has/have been edited successfully.";
+
+ private final Index index;
+ private final PriorityTag priorityTag;
+ private final DeadlineTag deadlineTag;
+
+ /**
+ * The constructor of the EditTagCommand. Sets the index, the priority tag
+ * and the deadline tag.
+ *
+ * @param index The index of the task in the task list.
+ * @param priorityTag The priority status of the priority tag.
+ * @param deadlineTag The deadline of the deadline tag,.
+ */
+ public EditTagCommand(Index index, PriorityTag priorityTag,
+ DeadlineTag deadlineTag) {
+ requireNonNull(index);
+ if (priorityTag == null && deadlineTag == null) {
+ throw new BothTagsCannotBeNullException();
+ }
+ this.index = index;
+ this.deadlineTag = deadlineTag;
+ this.priorityTag = priorityTag;
+ }
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List tasks = model.getFilteredTaskList();
+ if (index.getZeroBased() >= tasks.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ Task currTask = tasks.get(index.getZeroBased());
+ checkTags(currTask);
+ Task taggedTask = currTask;
+ if (priorityTag != null) {
+ taggedTask = taggedTask.replacePriorityTag(priorityTag);
+ }
+ if (deadlineTag != null) {
+ taggedTask = taggedTask.replaceDeadlineTag(deadlineTag);
+ }
+ model.replaceTask(currTask, taggedTask, true);
+ return new CommandResult(TAG_EDITED_SUCCESSFULLY);
+ }
+
+ private void checkTags(Task task) throws CommandException {
+ boolean hasPriorityTag = task.hasPriorityTag();
+ boolean hasDeadlineTag = task.hasDeadlineTag();
+ PriorityTag currTaskPriorityTag = task.getPriorityTag();
+ DeadlineTag currTaskDeadlineTag = task.getDeadlineTag();
+ if (!hasPriorityTag && priorityTag != null) {
+ throw new CommandException(PRIORITY_TAG_DOES_NOT_EXIST);
+ }
+ if (!hasDeadlineTag && deadlineTag != null) {
+ throw new CommandException(DEADLINE_TAG_DOES_NOT_EXIST);
+ }
+ if (currTaskPriorityTag != null
+ && priorityTag != null
+ && currTaskPriorityTag.compareTo(priorityTag) == 0) {
+ throw new CommandException(PRIORITY_TAG_UNCHANGED);
+ }
+ if (currTaskDeadlineTag != null
+ && deadlineTag != null
+ && currTaskDeadlineTag.compareTo(deadlineTag) == 0) {
+ throw new CommandException(DEADLINE_TAG_UNCHANGED);
+ }
+ }
+
+ @Override
+ public boolean equals(Object otherEditTagCommand) {
+ return otherEditTagCommand == this
+ || (otherEditTagCommand instanceof EditTagCommand
+ && Objects.equals(priorityTag, ((EditTagCommand) otherEditTagCommand).priorityTag)
+ && Objects.equals(deadlineTag, ((EditTagCommand) otherEditTagCommand).deadlineTag)
+ && index.equals(((EditTagCommand) otherEditTagCommand).index));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditTaskCommand.java b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java
new file mode 100644
index 00000000000..29c39172e13
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java
@@ -0,0 +1,186 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+import java.util.List;
+import java.util.Optional;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.TaskDescription;
+import seedu.address.model.task.exceptions.DuplicateTaskException;
+
+/**
+ * Edits the task with the specified index number in the displayed task list.
+ */
+public class EditTaskCommand extends Command {
+
+ public static final String COMMAND_WORD = "edit";
+ public static final String FULL_COMMAND_WORD = "t edit";
+
+ public static final String MESSAGE_USAGE = FULL_COMMAND_WORD + ": Edits the details of the task at the specified "
+ + "INDEX in the displayed task list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX "
+ + "[" + PREFIX_MODULE + "MODULE]* "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]*\n"
+ + "Example: " + FULL_COMMAND_WORD + " 1 "
+ + PREFIX_MODULE + "cs2040 "
+ + PREFIX_DESCRIPTION + "task 3";
+
+ public static final String MESSAGE_EDIT_TASK_SUCCESS = "Successfully Edited Task: %1$s";
+ public static final String MESSAGE_NO_FIELDS_PROVIDED =
+ "Please provide at least one of the fields to edit: m/MODULE, d/DESCRIPTION";
+ public static final String MESSAGE_SAME_FIELDS_PROVIDED =
+ "Please provide a m/MODULE or d/DESCRIPTION different from the task's current module and description";
+ public static final String MESSAGE_EXAM_UNLINKED = "Warning: The task has been unlinked from its exam.\n";
+ public static final String MESSAGE_DUPLICATE_TASK =
+ "The edited task is the same as another task in the task list";
+ public static final String MESSAGE_NO_TASK_IN_THE_LIST =
+ "There is no task in the task list so edit operation cannot be done!";
+
+ private final Index index;
+ private final EditTaskDescriptor editTaskDescriptor;
+
+ /**
+ * @param index of the task in the filtered task list to edit
+ * @param editTaskDescriptor details to edit the task with
+ */
+ public EditTaskCommand(Index index, EditTaskDescriptor editTaskDescriptor) {
+ requireAllNonNull(index, editTaskDescriptor);
+
+ this.index = index;
+ this.editTaskDescriptor = editTaskDescriptor;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (lastShownList.size() == 0) {
+ throw new CommandException(MESSAGE_NO_TASK_IN_THE_LIST);
+ }
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ if (editTaskDescriptor.getModule().isPresent() && !model.hasModule(editTaskDescriptor.module)) {
+ throw new CommandException(MESSAGE_MODULE_NOT_FOUND);
+ }
+
+ Task taskToEdit = lastShownList.get(index.getZeroBased());
+ Task editedTask = taskToEdit.edit(editTaskDescriptor);
+
+ if (taskToEdit.isSameTask(editedTask)) {
+ throw new CommandException(MESSAGE_SAME_FIELDS_PROVIDED);
+ }
+
+ try {
+ model.replaceTask(taskToEdit, editedTask, false);
+ } catch (DuplicateTaskException e) {
+ throw new CommandException(MESSAGE_DUPLICATE_TASK);
+ }
+
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ if (!taskToEdit.getModule().isSameModule(editedTask.getModule())
+ && taskToEdit.isLinked()) {
+ return new CommandResult(
+ MESSAGE_EXAM_UNLINKED + String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask));
+ }
+ return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditTaskCommand)) {
+ return false;
+ }
+
+ // state check
+ EditTaskCommand e = (EditTaskCommand) other;
+ return index.equals(e.index)
+ && editTaskDescriptor.equals(e.editTaskDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the task with. Each non-empty field value will replace the
+ * corresponding field value of the task.
+ */
+ public static class EditTaskDescriptor {
+ private Module module;
+ private TaskDescription description;
+
+ public EditTaskDescriptor() {}
+
+ /**
+ * Copy constructor.
+ */
+ public EditTaskDescriptor(EditTaskDescriptor toCopy) {
+ setModule(toCopy.module);
+ setDescription(toCopy.description);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(module, description);
+ }
+
+ public void setModule(Module module) {
+ requireNonNull(module);
+ this.module = module;
+ }
+
+ public Optional getModule() {
+ return Optional.ofNullable(module);
+ }
+
+ public void setDescription(TaskDescription description) {
+ requireNonNull(description);
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditTaskDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditTaskDescriptor e = (EditTaskDescriptor) other;
+
+ return getModule().equals(e.getModule())
+ && getDescription().equals(e.getDescription());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FilterTasksCommand.java b/src/main/java/seedu/address/logic/commands/FilterTasksCommand.java
new file mode 100644
index 00000000000..20fb38115b3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FilterTasksCommand.java
@@ -0,0 +1,69 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_MODULE_NOT_FOUND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_COMPLETE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_LINKED;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.FilterPredicate;
+
+/**
+ * Filters the task list by module, completion status and link status.
+ */
+public class FilterTasksCommand extends Command {
+
+ public static final String COMMAND_WORD = "filter";
+
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD
+ + ": Filters the task list by module and/or completion status and/or link status.\n"
+ + "Parameters: "
+ + "[" + PREFIX_MODULE + "MODULE]* "
+ + "[" + PREFIX_IS_COMPLETE + "COMPLETED(y or n)]* "
+ + "[" + PREFIX_IS_LINKED + "LINKED(y or n)]*\n"
+ + "Example: t " + COMMAND_WORD + " "
+ + PREFIX_MODULE + "CS2103T "
+ + PREFIX_IS_COMPLETE + "y "
+ + PREFIX_IS_LINKED + "n";
+
+ public static final String MESSAGE_SUCCESS = "Listed all tasks with following constraints:%1$s";
+
+ public static final String MESSAGE_NO_RESULTS = "No tasks found with following constraints:%1$s";
+
+ private final FilterPredicate predicate;
+
+ /**
+ * Constructor of the FilterTaskCommand class which filters the task list by the given constraints.
+ *
+ * @param predicate conditions to check for each task
+ */
+ public FilterTasksCommand(FilterPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (predicate.hasModuleToCheck() && !model.hasModule(predicate.getModuleToCheck())) {
+ throw new CommandException(MESSAGE_MODULE_NOT_FOUND);
+ }
+
+ model.updateFilteredTaskList(predicate);
+
+ if (model.getFilteredTaskList().isEmpty()) {
+ return new CommandResult(String.format(MESSAGE_NO_RESULTS, predicate));
+ }
+ return new CommandResult(String.format(MESSAGE_SUCCESS, predicate));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FilterTasksCommand // instanceof handles nulls
+ && predicate.equals(((FilterTasksCommand) other).predicate)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
deleted file mode 100644
index d6b19b0a0de..00000000000
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
- */
-public class FindCommand extends Command {
-
- public static final String COMMAND_WORD = "find";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
-
- private final NameContainsKeywordsPredicate predicate;
-
- public FindCommand(NameContainsKeywordsPredicate predicate) {
- this.predicate = predicate;
- }
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof FindCommand // instanceof handles nulls
- && predicate.equals(((FindCommand) other).predicate)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/FindModulesCommand.java b/src/main/java/seedu/address/logic/commands/FindModulesCommand.java
new file mode 100644
index 00000000000..a2dd08dca4d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindModulesCommand.java
@@ -0,0 +1,48 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate;
+
+
+/**
+ * Finds and lists all modules in the module list whose module code contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+
+public class FindModulesCommand extends Command {
+ public static final String COMMAND_WORD = "find";
+
+ public static final String MESSAGE_USAGE = "m " + COMMAND_WORD + ": Finds all modules whose module code"
+ + " partially or fully contain any of "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD \n"
+ + "Example: m " + COMMAND_WORD + " cs2030s";
+
+ private final ModuleCodeContainsKeywordsPredicate predicate;
+
+ public FindModulesCommand(ModuleCodeContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.updateFilteredModuleList(predicate);
+
+ return new CommandResult(
+ String.format(Messages.MESSAGE_MODULES_LISTED_OVERVIEW, model.getFilteredModuleList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindModulesCommand // instanceof handles nulls
+ && predicate.equals(((FindModulesCommand) other).predicate)); // state check
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/logic/commands/FindTasksCommand.java b/src/main/java/seedu/address/logic/commands/FindTasksCommand.java
new file mode 100644
index 00000000000..a17a7fcff38
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindTasksCommand.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.task.DescriptionContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all tasks in the task list whose description contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindTasksCommand extends Command {
+ public static final String COMMAND_WORD = "find";
+
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": Finds all tasks whose task description "
+ + "contain partially or fully any of "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD \n"
+ + "Example: t " + COMMAND_WORD + " watch lecture rec";
+
+ private final DescriptionContainsKeywordsPredicate predicate;
+
+ public FindTasksCommand(DescriptionContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredTaskList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, model.getFilteredTaskList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindTasksCommand // instanceof handles nulls
+ && predicate.equals(((FindTasksCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/LinkExamCommand.java b/src/main/java/seedu/address/logic/commands/LinkExamCommand.java
new file mode 100644
index 00000000000..dc2acb48495
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LinkExamCommand.java
@@ -0,0 +1,78 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.task.Task;
+
+/**
+ * LinkExamCommand represents a command which links the exam to the task.
+ */
+public class LinkExamCommand extends Command {
+ public static final String COMMAND_WORD = "link";
+ public static final String MESSAGE_USAGE = "e " + COMMAND_WORD + ": links the exam to the task.\n"
+ + "Parameters: " + PREFIX_EXAM_INDEX + "EXAM_INDEX" + " "
+ + PREFIX_TASK_INDEX + "TASK_INDEX\n"
+ + "Example: " + "e " + COMMAND_WORD + " t/1 e/1";
+ public static final String EXAM_LINKED_SUCCESS = "The exam has been successfully linked to the task!";
+ public static final String TASK_ALREADY_LINKED = "This task is already linked to an exam";
+ public static final String DIFFERENT_MODULE_CODE = "This task has a different module code from the exam.";
+ private final Index examIndex;
+ private final Index taskIndex;
+
+ /**
+ * The constructor of the LinkExamCommand class. Sets
+ * the index in the exam list and the index in the task list.
+ *
+ * @param examIndex The index in the exam list.
+ * @param taskIndex The index in the task list.
+ */
+ public LinkExamCommand(Index examIndex, Index taskIndex) {
+ requireAllNonNull(examIndex, taskIndex);
+ this.examIndex = examIndex;
+ this.taskIndex = taskIndex;
+ }
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List tasks = model.getFilteredTaskList();
+ List exams = model.getFilteredExamList();
+ if (taskIndex.getZeroBased() >= tasks.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+ if (examIndex.getZeroBased() >= exams.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EXAM_DISPLAYED_INDEX);
+ }
+ Task task = tasks.get(taskIndex.getZeroBased());
+ if (task.isLinked()) {
+ throw new CommandException(TASK_ALREADY_LINKED);
+ }
+ Exam exam = exams.get(examIndex.getZeroBased());
+ if (!task.getModule().isSameModule(exam.getModule())) {
+ throw new CommandException(DIFFERENT_MODULE_CODE);
+ }
+ Task linkedTask = task.linkTask(exam);
+ model.replaceTask(task, linkedTask, true);
+ return new CommandResult(EXAM_LINKED_SUCCESS);
+ }
+
+ @Override
+
+ public boolean equals(Object otherLinkExamCommand) {
+ return otherLinkExamCommand == this
+ || (otherLinkExamCommand instanceof LinkExamCommand
+ && taskIndex.equals(((LinkExamCommand) otherLinkExamCommand).taskIndex))
+ && examIndex.equals(((LinkExamCommand) otherLinkExamCommand).examIndex);
+ }
+}
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/commands/ListExamTasksCommand.java b/src/main/java/seedu/address/logic/commands/ListExamTasksCommand.java
new file mode 100644
index 00000000000..ffd4b56fb1f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListExamTasksCommand.java
@@ -0,0 +1,76 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.task.TaskLinkedToExamPredicate;
+
+/**
+ * Shoes all tasks linked to a specific exam.
+ */
+public class ListExamTasksCommand extends Command {
+
+ public static final String COMMAND_WORD = "showt";
+
+ public static final String MESSAGE_USAGE = "e " + COMMAND_WORD
+ + ": Show all tasks linked to a specific exam identified by the index number used in the exam list.\n"
+ + "Parameter: INDEX\n"
+ + "Example: e " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Listed all tasks from exam: %1$s";
+
+ public static final String MESSAGE_NO_RESULTS = "No tasks found from exam: %1$s";
+
+ public static final String MESSAGE_NO_EXAM_IN_LIST =
+ "There is no exam in the exam list so showt operation cannot be done!";
+
+ private final Index examIndex;
+
+ /**
+ * Constructor of the ListExamTasksCommand class which shows all tasks linked to a specific exam.
+ *
+ * @param examIndex Index of exam to be checked if tasks are linked with.
+ */
+ public ListExamTasksCommand(Index examIndex) {
+ this.examIndex = examIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownExamList = model.getFilteredExamList();
+
+ if (lastShownExamList.size() == 0) {
+ throw new CommandException(MESSAGE_NO_EXAM_IN_LIST);
+ }
+
+ if (examIndex.getZeroBased() >= lastShownExamList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_EXAM_INDEX_TOO_LARGE, lastShownExamList.size() + 1));
+ }
+
+ Exam examToCheck = lastShownExamList.get(examIndex.getZeroBased());
+
+ TaskLinkedToExamPredicate predicate = new TaskLinkedToExamPredicate(examToCheck);
+
+ model.updateFilteredTaskList(predicate);
+
+ if (model.getFilteredTaskList().isEmpty()) {
+ return new CommandResult(String.format(MESSAGE_NO_RESULTS, examToCheck));
+ }
+ return new CommandResult(String.format(MESSAGE_SUCCESS, examToCheck));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ListExamTasksCommand // instanceof handles nulls
+ && examIndex.equals(((ListExamTasksCommand) other).examIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListModulesCommand.java b/src/main/java/seedu/address/logic/commands/ListModulesCommand.java
new file mode 100644
index 00000000000..dc034d95b10
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListModulesCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES;
+
+import seedu.address.model.Model;
+
+/**
+ * Lists all modules in the module list to the user.
+ */
+public class ListModulesCommand extends Command {
+
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all modules";
+
+ public static final String EMPTY_LIST = "No modules in list";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES);
+ if (model.getFilteredModuleList().size() == 0) {
+ return new CommandResult(EMPTY_LIST);
+ }
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListTasksCommand.java b/src/main/java/seedu/address/logic/commands/ListTasksCommand.java
new file mode 100644
index 00000000000..27545db88fe
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListTasksCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+import seedu.address.model.Model;
+
+/**
+ * Lists all tasks in the task list to the user.
+ */
+public class ListTasksCommand extends Command {
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all tasks";
+
+ public static final String EMPTY_LIST = "No tasks in list";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ if (model.getFilteredTaskList().size() == 0) {
+ return new CommandResult(EMPTY_LIST);
+ }
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/MarkCommand.java b/src/main/java/seedu/address/logic/commands/MarkCommand.java
new file mode 100644
index 00000000000..3416bb258b7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/MarkCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+
+/**
+ * Mark a task identified using it's displayed index from the task list as complete.
+ */
+public class MarkCommand extends Command {
+
+ public static final String COMMAND_WORD = "mark";
+ public static final String FULL_COMMAND_WORD = "t mark";
+
+ public static final String MESSAGE_USAGE = FULL_COMMAND_WORD
+ + ": Indicates the task at the specified INDEX in the displayed task list is not completed.\n"
+ + "Parameters: INDEX\n"
+ + "Example: " + FULL_COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_MARK_TASK_SUCCESS = "Successfully Marked Task: %1$s";
+ public static final String MESSAGE_TASK_ALREADY_MARKED = "The task is already marked!";
+ public static final String MESSAGE_NO_TASK_IN_THE_LIST =
+ "There is no task in the task list so mark operation cannot be done!";
+
+ private final Index targetIndex;
+
+ public MarkCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (lastShownList.size() == 0) {
+ throw new CommandException(MESSAGE_NO_TASK_IN_THE_LIST);
+ }
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ Task taskToMark = lastShownList.get(targetIndex.getZeroBased());
+ if (taskToMark.isComplete()) {
+ throw new CommandException(MESSAGE_TASK_ALREADY_MARKED);
+ }
+ Task markedTask = taskToMark.mark();
+
+ model.replaceTask(taskToMark, markedTask, true);
+
+ return new CommandResult(String.format(MESSAGE_MARK_TASK_SUCCESS, taskToMark));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof MarkCommand // instanceof handles nulls
+ && targetIndex.equals(((MarkCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/SortTaskCommand.java b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java
new file mode 100644
index 00000000000..a64215642c8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java
@@ -0,0 +1,51 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CRITERIA;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Criteria;
+import seedu.address.model.Model;
+
+/**
+ * SortTaskCommand represents a command which sorts the tasks in the task list.
+ */
+public class SortTaskCommand extends Command {
+ public static final String COMMAND_WORD = "sort";
+ public static final String MESSAGE_USAGE = "t " + COMMAND_WORD + ": sorts the task list.\n"
+ + "Parameters: " + PREFIX_CRITERIA + "CRITERIA\n"
+ + "Example: " + "t " + COMMAND_WORD + " " + PREFIX_CRITERIA + "priority";
+ public static final String TASK_SORTED_SUCCESSFULLY =
+ "Task list has been successfully sorted";
+ public static final String NO_TASK_TO_SORT =
+ "There are no tasks in the task list to sort!";
+ private final Criteria criteria;
+
+ /**
+ * The constructor of the SortTaskCommand. Sets the criteria
+ * that is used for sorting.
+ *
+ * @param criteria The criteria used for sorting.
+ */
+ public SortTaskCommand(Criteria criteria) {
+ requireNonNull(criteria);
+ this.criteria = criteria;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.sortTaskList(criteria);
+ if (model.getFilteredTaskList().size() == 0) {
+ throw new CommandException(NO_TASK_TO_SORT);
+ }
+ return new CommandResult(TASK_SORTED_SUCCESSFULLY);
+ }
+
+ @Override
+ public boolean equals(Object otherSortTaskCommand) {
+ return otherSortTaskCommand == this
+ || (otherSortTaskCommand instanceof SortTaskCommand
+ && criteria.equals(((SortTaskCommand) otherSortTaskCommand).criteria));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnlinkExamCommand.java b/src/main/java/seedu/address/logic/commands/UnlinkExamCommand.java
new file mode 100644
index 00000000000..3cb87584e91
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnlinkExamCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+
+/**
+ * UnlinkExamCommand represents a command which unlinks the exam from the task.
+ */
+public class UnlinkExamCommand extends Command {
+ public static final String COMMAND_WORD = "unlink";
+ public static final String MESSAGE_USAGE = "e " + COMMAND_WORD
+ + ": Unlinks the task identified by the index number used in the displayed task list.\n"
+ + "Parameters: INDEX\n"
+ + "Example: e " + COMMAND_WORD + " 1";
+ public static final String MESSAGE_EXAM_UNLINKED_SUCCESS = "The exam has been successfully unlinked from the task!";
+ public static final String MESSAGE_TASK_ALREADY_UNLINKED = "The task is already not linked to any exam.";
+ public static final String MESSAGE_NO_TASK_IN_LIST =
+ "There is no task in the task list so unlink operation cannot be done!";
+ private final Index taskIndex;
+
+ /**
+ * The constructor of the UnlinkExamCommand class.
+ *
+ * @param taskIndex The index of the task to be unlinked.
+ */
+ public UnlinkExamCommand(Index taskIndex) {
+ requireNonNull(taskIndex);
+ this.taskIndex = taskIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List tasks = model.getFilteredTaskList();
+
+ if (tasks.size() == 0) {
+ throw new CommandException(MESSAGE_NO_TASK_IN_LIST);
+ }
+
+ if (taskIndex.getZeroBased() >= tasks.size()) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, tasks.size() + 1));
+ }
+
+ Task toUnlink = tasks.get(taskIndex.getZeroBased());
+
+ if (!toUnlink.isLinked()) {
+ throw new CommandException(MESSAGE_TASK_ALREADY_UNLINKED);
+ }
+
+ Task unlinkedTask = toUnlink.unlinkTask();
+ model.replaceTask(toUnlink, unlinkedTask, true);
+ return new CommandResult(MESSAGE_EXAM_UNLINKED_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UnlinkExamCommand // instanceof handles nulls
+ && taskIndex.equals(((UnlinkExamCommand) other).taskIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnmarkCommand.java b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java
new file mode 100644
index 00000000000..83ba7626998
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnmarkCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+
+/**
+ * Unmark (label as incomplete) a task identified using it's displayed index from the task list.
+ */
+public class UnmarkCommand extends Command {
+
+ public static final String COMMAND_WORD = "unmark";
+ public static final String FULL_COMMAND_WORD = "t unmark";
+
+ public static final String MESSAGE_USAGE = FULL_COMMAND_WORD
+ + ": Indicates the task at the specified INDEX in the displayed task list is completed.\n"
+ + "Parameters: INDEX\n"
+ + "Example: " + FULL_COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_UNMARK_TASK_SUCCESS = "Successfully Unmarked Task: %1$s";
+ public static final String MESSAGE_TASK_ALREADY_UNMARKED = "The task is already unmarked!";
+ public static final String MESSAGE_NO_TASK_IN_THE_LIST =
+ "There is no task in the task list so unmark operation cannot be done!";
+
+ private final Index targetIndex;
+
+ public UnmarkCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (lastShownList.size() == 0) {
+ throw new CommandException(MESSAGE_NO_TASK_IN_THE_LIST);
+ }
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_INVALID_TASK_INDEX_TOO_LARGE, lastShownList.size() + 1));
+ }
+
+ Task taskToUnmark = lastShownList.get(targetIndex.getZeroBased());
+ if (!taskToUnmark.isComplete()) {
+ throw new CommandException(MESSAGE_TASK_ALREADY_UNMARKED);
+ }
+ Task unmarkedTask = taskToUnmark.unmark();
+
+ model.replaceTask(taskToUnmark, unmarkedTask, true);
+
+ return new CommandResult(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UnmarkCommand // instanceof handles nulls
+ && targetIndex.equals(((UnmarkCommand) other).targetIndex)); // state check
+ }
+}
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 3b8bfa035e8..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-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));
- }
-
- 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/AddExamCommandParser.java b/src/main/java/seedu/address/logic/parser/AddExamCommandParser.java
new file mode 100644
index 00000000000..3703d4d6c56
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddExamCommandParser.java
@@ -0,0 +1,53 @@
+package seedu.address.logic.parser;
+
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddExamCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.exam.ExamDate;
+import seedu.address.model.exam.ExamDescription;
+import seedu.address.model.module.Module;
+
+/**
+ * Parses arguments to create a new AddExamCommand Object.
+ */
+public class AddExamCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddExamCommand
+ * and returns an AddExamCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddExamCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_EXAM_DESCRIPTION, PREFIX_EXAM_DATE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_MODULE, PREFIX_EXAM_DESCRIPTION, PREFIX_EXAM_DATE)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddExamCommand.MESSAGE_USAGE));
+ }
+
+ Module module = new Module(ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get()));
+ ExamDescription description = ParserUtil.parseExamDescription(
+ argMultimap.getValue(PREFIX_EXAM_DESCRIPTION).get());
+ ExamDate examDate = ParserUtil.parseExamDate(argMultimap.getValue(PREFIX_EXAM_DATE).get());
+ Exam exam = new Exam(module, description, examDate);
+
+ return new AddExamCommand(exam);
+ }
+
+ /**
+ * 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/AddModuleCommandParser.java b/src/main/java/seedu/address/logic/parser/AddModuleCommandParser.java
new file mode 100644
index 00000000000..7bdb7e5b13a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddModuleCommandParser.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddModuleCommand;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.module.ModuleCredit;
+import seedu.address.model.module.ModuleName;
+
+/**
+ * Parses arguments to create a new AddModuleCommand Object.
+ */
+public class AddModuleCommandParser implements Parser {
+ @Override
+ public Command parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddModuleCommand.MESSAGE_USAGE));
+ }
+
+ ModuleCode modCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MOD_CODE)
+ .orElseThrow(NullPointerException::new));
+
+ ModuleName modName = ParserUtil.parseModuleName(argMultimap.getValue(PREFIX_MOD_NAME)
+ .orElseThrow(NullPointerException::new));
+
+ ModuleCredit modCredit = ParserUtil.parseModuleCredit(argMultimap.getValue(PREFIX_MOD_CREDIT)
+ .orElseThrow(NullPointerException::new));
+
+ Module module = new Module(modCode, modName, modCredit);
+ return new AddModuleCommand(module);
+ }
+
+ //@@author dlimyy-reused
+ //Reused from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+ //@@author
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java
new file mode 100644
index 00000000000..f3ac75d4510
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java
@@ -0,0 +1,56 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS;
+
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AddTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+
+/**
+ * AddTagCommandParser parses the arguments and supplies these arguments
+ * to the AddTagCommand.
+ */
+public class AddTagCommandParser implements Parser {
+ private final Logger logger = LogsCenter.getLogger(AddTagCommand.class);
+ @Override
+ public AddTagCommand parse(String args) throws ParseException {
+ logger.info("Parsing arguments for AddTagCommand");
+ requireNonNull(args);
+ String[] indexWithTags = args.strip().split("\\s", 2);
+ if (indexWithTags.length != 2 || !indexWithTags[0].matches("(-|\\+)?\\d+(\\.\\d+)?")) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ AddTagCommand.MESSAGE_USAGE));
+ }
+ Index index = ParserUtil.parseIndex(indexWithTags[0]);
+ String tags = " " + indexWithTags[1];
+ ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(tags, PREFIX_PRIORITY_STATUS, PREFIX_DEADLINE);
+ if (!areAnyPrefixesPresent(argMultiMap, PREFIX_PRIORITY_STATUS, PREFIX_DEADLINE)
+ || !argMultiMap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ AddTagCommand.MESSAGE_USAGE));
+ }
+ String priorityStatus = argMultiMap.getValue(PREFIX_PRIORITY_STATUS).orElse(null);
+ String deadline = argMultiMap.getValue(PREFIX_DEADLINE).orElse(null);
+ PriorityTag priorityTag = priorityStatus != null ? ParserUtil
+ .parsePriorityTag(priorityStatus) : null;
+ DeadlineTag deadlineTag = deadline != null ? ParserUtil
+ .parseDeadlineTag(deadline) : null;
+ return new AddTagCommand(priorityTag, deadlineTag, index);
+ }
+
+ //@@author dlimyy-reused
+ //Reused from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
+ private static boolean areAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+ //@@author
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java
new file mode 100644
index 00000000000..5234708e959
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.TaskDescription;
+
+/**
+ * Parses arguments to create a new AddTaskCommand Object.
+ */
+public class AddTaskCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddTaskCommand
+ * and returns an AddTaskCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddTaskCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_DESCRIPTION);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_MODULE, PREFIX_DESCRIPTION)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE));
+ }
+
+ ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get());
+ Module module = new Module(moduleCode);
+ TaskDescription description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get());
+
+ return new AddTaskCommand(new Task(module, description));
+ }
+
+ /**
+ * 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
index 1e466792b46..9e57ebf39bd 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -1,22 +1,44 @@
package seedu.address.logic.parser;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FEATURE_TYPE_FORMAT;
import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.commands.AddExamCommand;
+import seedu.address.logic.commands.AddModuleCommand;
+import seedu.address.logic.commands.AddTagCommand;
+import seedu.address.logic.commands.AddTaskCommand;
+import seedu.address.logic.commands.ClearAllCommand;
+import seedu.address.logic.commands.ClearTasksCommand;
import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.DeleteExamCommand;
+import seedu.address.logic.commands.DeleteModuleCommand;
+import seedu.address.logic.commands.DeleteTagCommand;
+import seedu.address.logic.commands.DeleteTaskCommand;
+import seedu.address.logic.commands.EditExamCommand;
+import seedu.address.logic.commands.EditModuleCommand;
+import seedu.address.logic.commands.EditTagCommand;
+import seedu.address.logic.commands.EditTaskCommand;
import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FilterTasksCommand;
+import seedu.address.logic.commands.FindModulesCommand;
+import seedu.address.logic.commands.FindTasksCommand;
import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LinkExamCommand;
+import seedu.address.logic.commands.ListExamTasksCommand;
+import seedu.address.logic.commands.ListModulesCommand;
+import seedu.address.logic.commands.ListTasksCommand;
+import seedu.address.logic.commands.MarkCommand;
+import seedu.address.logic.commands.SortTaskCommand;
+import seedu.address.logic.commands.UnlinkExamCommand;
+import seedu.address.logic.commands.UnmarkCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+
+
/**
* Parses user input.
*/
@@ -25,8 +47,11 @@ public class AddressBookParser {
/**
* Used for initial separation of command word and args.
*/
- private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
-
+ public static final String EXAM_FEATURE_TYPE = "e";
+ public static final String MODULE_FEATURE_TYPE = "m";
+ public static final String TASK_FEATURE_TYPE = "t";
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)"
+ + "(?\\s*\\S*)(?.*)");
/**
* Parses user input into command for execution.
*
@@ -40,34 +65,80 @@ public Command parseCommand(String userInput) throws ParseException {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
}
- final String commandWord = matcher.group("commandWord");
+ final String featureType = matcher.group("featureType");
+ final String commandWord = matcher.group("commandWord").strip();
final String arguments = matcher.group("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();
+ switch (featureType.toLowerCase()) {
+ case TASK_FEATURE_TYPE:
+ switch (commandWord.toLowerCase()) {
+ case AddTaskCommand.COMMAND_WORD:
+ return new AddTaskCommandParser().parse(arguments);
+ case DeleteTaskCommand.COMMAND_WORD:
+ return new DeleteTaskCommandParser().parse(arguments);
+ case EditTaskCommand.COMMAND_WORD:
+ return new EditTaskCommandParser().parse(arguments);
+ case MarkCommand.COMMAND_WORD:
+ return new MarkCommandParser().parse(arguments);
+ case UnmarkCommand.COMMAND_WORD:
+ return new UnmarkCommandParser().parse(arguments);
+ case ListTasksCommand.COMMAND_WORD:
+ return new ListTasksCommand();
+ case SortTaskCommand.COMMAND_WORD:
+ return new SortTaskCommandParser().parse(arguments);
+ case FilterTasksCommand.COMMAND_WORD:
+ return new FilterTasksCommandParser().parse(arguments);
+ case FindTasksCommand.COMMAND_WORD:
+ return new FindTaskCommandParser().parse(arguments);
+ case AddTagCommand.COMMAND_WORD:
+ return new AddTagCommandParser().parse(arguments);
+ case EditTagCommand.COMMAND_WORD:
+ return new EditTagCommandParser().parse(arguments);
+ case DeleteTagCommand.COMMAND_WORD:
+ return new DeleteTagCommandParser().parse(arguments);
+ case ClearTasksCommand.COMMAND_WORD:
+ return new ClearTasksCommand();
+ default:
+ throw new ParseException(String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "task"));
+ }
+ case MODULE_FEATURE_TYPE:
+ switch (commandWord.toLowerCase()) {
+ case AddModuleCommand.COMMAND_WORD:
+ return new AddModuleCommandParser().parse(arguments);
+ case EditModuleCommand.COMMAND_WORD:
+ return new EditModuleCommandParser().parse(arguments);
+ case DeleteModuleCommand.COMMAND_WORD:
+ return new DeleteModuleCommandParser().parse(arguments);
+ case ListModulesCommand.COMMAND_WORD:
+ return new ListModulesCommand();
+ case FindModulesCommand.COMMAND_WORD:
+ return new FindModulesCommandParser().parse(arguments);
+ default:
+ throw new ParseException(String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "module"));
+ }
+ case EXAM_FEATURE_TYPE:
+ switch (commandWord.toLowerCase()) {
+ case AddExamCommand.COMMAND_WORD:
+ return new AddExamCommandParser().parse(arguments);
+ case EditExamCommand.COMMAND_WORD:
+ return new EditExamCommandParser().parse(arguments);
+ case DeleteExamCommand.COMMAND_WORD:
+ return new DeleteExamCommandParser().parse(arguments);
+ case LinkExamCommand.COMMAND_WORD:
+ return new LinkExamCommandParser().parse(arguments);
+ case UnlinkExamCommand.COMMAND_WORD:
+ return new UnlinkExamCommandParser().parse(arguments);
+ case ListExamTasksCommand.COMMAND_WORD:
+ return new ListExamTasksCommandParser().parse(arguments);
+ default:
+ throw new ParseException(String.format(MESSAGE_INVALID_FEATURE_TYPE_FORMAT, "exam"));
+ }
+ case ClearAllCommand.COMMAND_WORD:
+ return new ClearAllCommand();
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
-
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..427fef9d7e7 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -6,10 +6,20 @@
public class CliSyntax {
/* Prefix definitions */
- public static final Prefix PREFIX_NAME = new Prefix("n/");
- public static final Prefix PREFIX_PHONE = new Prefix("p/");
- public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_MODULE = new Prefix("m/");
+ public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/");
+ public static final Prefix PREFIX_MOD_CODE = new Prefix("c/");
+ public static final Prefix PREFIX_PRIORITY_STATUS = new Prefix("p/");
+ public static final Prefix PREFIX_EXAM_DESCRIPTION = new Prefix("ex/");
+ public static final Prefix PREFIX_EXAM_DATE = new Prefix("ed/");
+ public static final Prefix PREFIX_DEADLINE = new Prefix("dl/");
+ public static final Prefix PREFIX_MOD_NAME = new Prefix("m/");
+ public static final Prefix PREFIX_MOD_CREDIT = new Prefix("mc/");
+ public static final Prefix PREFIX_EXAM_INDEX = new Prefix("e/");
+ public static final Prefix PREFIX_TASK_INDEX = new Prefix("t/");
+ public static final Prefix PREFIX_CRITERIA = new Prefix("c/");
+ public static final Prefix PREFIX_IS_COMPLETE = new Prefix("c/");
+ public static final Prefix PREFIX_IS_LINKED = new Prefix("l/");
}
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 522b93081cc..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.commons.core.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/DeleteExamCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteExamCommandParser.java
new file mode 100644
index 00000000000..0ca6cdcffee
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteExamCommandParser.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteExamCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteExamCommand object
+ */
+public class DeleteExamCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteExamCommand
+ * and returns a DeleteExamCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteExamCommand parse(String args) throws ParseException {
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteExamCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteExamCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_EXAM_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteModuleCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteModuleCommandParser.java
new file mode 100644
index 00000000000..79b055d0a60
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteModuleCommandParser.java
@@ -0,0 +1,39 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteModuleCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteModuleCommand object
+ */
+public class DeleteModuleCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteModuleCommand
+ * and returns a DeleteModuleCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteModuleCommand parse(String args) throws ParseException {
+
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteModuleCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteModuleCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_MODULE_INDEX);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java
new file mode 100644
index 00000000000..d52294e69a9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java
@@ -0,0 +1,51 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses the user input for the command to create a DeleteTagCommand object.
+ */
+public class DeleteTagCommandParser implements Parser {
+ public static final String INVALID_INDEX_FOR_DELETE_TAG = "The index for tagdel should"
+ + " be an unsigned positive integer greater than 0 and lesser than 2147483648.";
+ private final Logger logger = LogsCenter.getLogger(DeleteTagCommand.class);
+ @Override
+ public DeleteTagCommand parse(String userInput) throws ParseException {
+ logger.info("Parsing arguments for DeleteTagCommand.");
+ ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(userInput, PREFIX_TAG);
+ Index index;
+ if (!isTagPrefixPresent(argMultiMap)) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ DeleteTagCommand.MESSAGE_USAGE));
+ }
+
+ if (!argMultiMap.getPreamble().matches("(-|\\+)?\\d+(\\.\\d+)?")) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ DeleteTagCommand.MESSAGE_USAGE));
+ }
+ try {
+ index = ParserUtil.parseIndex(argMultiMap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(INVALID_INDEX_FOR_DELETE_TAG);
+ }
+ Set tags = ParserUtil.parseDeleteTagKeywords(argMultiMap.getValue(PREFIX_TAG)
+ .orElse(null));
+ return new DeleteTagCommand(index, tags);
+ }
+
+ //@@author dlimyy-reused
+ //Reused with minor modifications from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
+ private static boolean isTagPrefixPresent(ArgumentMultimap argumentMultimap) {
+ return argumentMultimap.getValue(PREFIX_TAG).isPresent();
+ }
+ //@@author
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java
new file mode 100644
index 00000000000..54c684746f5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteTaskCommandParser.java
@@ -0,0 +1,40 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+
+/**
+ * Parses input arguments and creates a new DeleteTaskCommand object
+ */
+public class DeleteTaskCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteTaskCommand
+ * and returns a DeleteTaskCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteTaskCommand parse(String args) throws ParseException {
+
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteTaskCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_INDEX);
+ }
+ }
+
+}
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 845644b7dea..00000000000
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package seedu.address.logic.parser;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_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);
- }
-
- 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/EditExamCommandParser.java b/src/main/java/seedu/address/logic/parser/EditExamCommandParser.java
new file mode 100644
index 00000000000..1de830d7c60
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditExamCommandParser.java
@@ -0,0 +1,69 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX;
+import static seedu.address.logic.commands.EditExamCommand.MESSAGE_NO_FIELDS_PROVIDED;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.logic.parser.ParserUtil.parseIndex;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditExamCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.Module;
+
+/**
+ * Parses input arguments and creates a new EditExamCommand object
+ */
+public class EditExamCommandParser implements Parser {
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditExamCommand
+ * and returns an EditExamCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditExamCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_EXAM_DESCRIPTION, PREFIX_EXAM_DATE);
+
+ Index index;
+
+ if (!pattern.matcher(argMultimap.getPreamble().strip()).matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditExamCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ index = parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_EXAM_INDEX);
+ }
+
+ EditExamCommand.EditExamDescriptor editExamDescriptor = new EditExamCommand.EditExamDescriptor();
+ if (argMultimap.getValue(PREFIX_EXAM_DESCRIPTION).isPresent()) {
+ editExamDescriptor.setDescription(ParserUtil
+ .parseExamDescription(argMultimap.getValue(PREFIX_EXAM_DESCRIPTION).get()));
+ }
+ if (argMultimap.getValue(PREFIX_MODULE).isPresent()) {
+ editExamDescriptor.setModule(new Module(ParserUtil
+ .parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get())));
+ }
+ if (argMultimap.getValue(PREFIX_EXAM_DATE).isPresent()) {
+ editExamDescriptor.setExamDate(ParserUtil
+ .parseExamDate(argMultimap.getValue(PREFIX_EXAM_DATE).get()));
+ }
+
+ if (!editExamDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(MESSAGE_NO_FIELDS_PROVIDED);
+ }
+
+ return new EditExamCommand(index, editExamDescriptor);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditModuleCommandParser.java b/src/main/java/seedu/address/logic/parser/EditModuleCommandParser.java
new file mode 100644
index 00000000000..a03947bea78
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditModuleCommandParser.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_INDEX;
+import static seedu.address.logic.commands.EditModuleCommand.MESSAGE_NO_FIELDS_PROVIDED;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditModuleCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.module.ModuleCredit;
+import seedu.address.model.module.ModuleName;
+
+/**
+ * Parses input arguments and creates a new EditModuleCommand object.
+ */
+public class EditModuleCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditModuleCommand
+ * and returns an EditModuleCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditModuleCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MOD_CODE, PREFIX_MOD_NAME, PREFIX_MOD_CREDIT);
+
+ if (!pattern.matcher(argMultimap.getPreamble()).matches()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditModuleCommand.MESSAGE_USAGE));
+ }
+
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_MODULE_INDEX);
+ }
+
+ EditModuleCommand.EditModuleDescriptor editModuleDescriptor = new EditModuleCommand.EditModuleDescriptor();
+
+ if (argMultimap.getValue(PREFIX_MOD_CODE).isPresent()) {
+ ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MOD_CODE).get());
+ editModuleDescriptor.setModuleCode(moduleCode);
+ }
+
+ if (argMultimap.getValue(PREFIX_MOD_NAME).isPresent()) {
+ ModuleName moduleName = ParserUtil.parseModuleName(argMultimap.getValue(PREFIX_MOD_NAME).get());
+ editModuleDescriptor.setModuleName(moduleName);
+ }
+
+ if (argMultimap.getValue(PREFIX_MOD_CREDIT).isPresent()) {
+ ModuleCredit moduleCredit = ParserUtil.parseModuleCredit(argMultimap.getValue(PREFIX_MOD_CREDIT).get());
+ editModuleDescriptor.setModuleCredit(moduleCredit);
+ }
+
+ if (!editModuleDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(MESSAGE_NO_FIELDS_PROVIDED);
+ }
+ return new EditModuleCommand(index, editModuleDescriptor);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java
new file mode 100644
index 00000000000..4c9fbd2c9bb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditTagCommandParser.java
@@ -0,0 +1,65 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY_STATUS;
+
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+
+/**
+ * EditTagCommandParser parses the prefix arguments given by the user to
+ * create a EditTagCommand object.
+ */
+public class EditTagCommandParser implements Parser {
+ public static final String INVALID_INDEX_EDIT_TAG = "The index for tagedit should be an unsigned "
+ + "positive integer greater than 0 "
+ + "and lesser than 2147483648.";
+ private final Logger logger = LogsCenter.getLogger(EditTagCommand.class);
+
+ @Override
+ public EditTagCommand parse(String args) throws ParseException {
+ logger.info("Parsing arguments for EditTagCommand.");
+ requireNonNull(args);
+ ArgumentMultimap argumentMultimap = ArgumentTokenizer
+ .tokenize(args, PREFIX_DEADLINE, PREFIX_PRIORITY_STATUS);
+ Index index;
+
+ if (!argumentMultimap.getPreamble().matches("(-|\\+)?\\d+(\\.\\d+)?")) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ EditTagCommand.MESSAGE_USAGE));
+ }
+
+ if (!areAnyPrefixesPresent(argumentMultimap, PREFIX_PRIORITY_STATUS, PREFIX_DEADLINE)) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ EditTagCommand.MESSAGE_USAGE));
+ }
+ try {
+ index = ParserUtil.parseIndex(argumentMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(INVALID_INDEX_EDIT_TAG);
+ }
+ String priorityStatus = argumentMultimap.getValue(PREFIX_PRIORITY_STATUS).orElse(null);
+ String deadline = argumentMultimap.getValue(PREFIX_DEADLINE).orElse(null);
+ PriorityTag priorityTag = priorityStatus != null ? ParserUtil
+ .parsePriorityTag(priorityStatus) : null;
+ DeadlineTag deadlineTag = deadline != null ? ParserUtil
+ .parseDeadlineTag(deadline) : null;
+ return new EditTagCommand(index, priorityTag, deadlineTag);
+ }
+
+ //@@author dlimyy-reused
+ //Reused from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
+ private static boolean areAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+ //@@author
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java
new file mode 100644
index 00000000000..bcb7f23ca84
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java
@@ -0,0 +1,64 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX;
+import static seedu.address.logic.commands.EditTaskCommand.MESSAGE_NO_FIELDS_PROVIDED;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+
+/**
+ * Parses input arguments and creates a new EditTaskCommand object.
+ */
+public class EditTaskCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditTaskCommand
+ * and returns an EditTaskCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditTaskCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_DESCRIPTION);
+
+ if (!pattern.matcher(argMultimap.getPreamble().strip()).matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE));
+ }
+
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_INDEX);
+ }
+
+ EditTaskCommand.EditTaskDescriptor editTaskDescriptor = new EditTaskCommand.EditTaskDescriptor();
+
+ if (argMultimap.getValue(PREFIX_MODULE).isPresent()) {
+ ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get());
+ editTaskDescriptor.setModule(new Module(moduleCode));
+ }
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editTaskDescriptor.setDescription(
+ ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()));
+ }
+
+ if (!editTaskDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(MESSAGE_NO_FIELDS_PROVIDED);
+ }
+ return new EditTaskCommand(index, editTaskDescriptor);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/FilterTasksCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterTasksCommandParser.java
new file mode 100644
index 00000000000..7e430f03573
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FilterTasksCommandParser.java
@@ -0,0 +1,96 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_COMPLETE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_IS_LINKED;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.FilterTasksCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.task.FilterPredicate;
+
+/**
+ * Parses input arguments and creates a new FilterTasksCommand object
+ */
+public class FilterTasksCommandParser implements Parser {
+ public static final String RESPONSE_CONSTRAINTS = "Response for filter criteria should be indicated as y or n";
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FilterTasksCommand
+ * and returns a FilterTasksCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FilterTasksCommand parse(String args) throws ParseException {
+ Optional module = Optional.empty();
+ Optional isCompleted = Optional.empty();
+ Optional isLinked = Optional.empty();
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_IS_COMPLETE, PREFIX_IS_LINKED);
+
+ if (!isPrefixPresent(argMultimap, PREFIX_MODULE, PREFIX_IS_COMPLETE, PREFIX_IS_LINKED)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterTasksCommand.MESSAGE_USAGE));
+ }
+
+ if (hasPrefix(argMultimap, PREFIX_MODULE)) {
+ ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE).get());
+ module = Optional.of(new Module(moduleCode));
+ }
+
+ if (hasPrefix(argMultimap, PREFIX_IS_COMPLETE)) {
+ isCompleted = Optional.of(parseYesNoResponse(argMultimap.getValue(PREFIX_IS_COMPLETE).get()));
+ }
+
+ if (hasPrefix(argMultimap, PREFIX_IS_LINKED)) {
+ isLinked = Optional.of(parseYesNoResponse(argMultimap.getValue(PREFIX_IS_LINKED).get()));
+ }
+
+ return new FilterTasksCommand(new FilterPredicate(module, isCompleted, isLinked));
+ }
+
+ /**
+ * Returns true if at least one prefix is not empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean isPrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Returns true if the prefix is not empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean hasPrefix(ArgumentMultimap argumentMultimap, Prefix prefix) {
+ return argumentMultimap.getValue(prefix).isPresent();
+ }
+
+ private static boolean isValidYesNoResponse(String response) {
+ return response.equals("y") || response.equals("n");
+ }
+
+ /**
+ * Parses a {@code String response} into a boolean.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code status} is invalid.
+ */
+ private static boolean parseYesNoResponse(String response) throws ParseException {
+ requireNonNull(response);
+ String lowerCaseTrimmedResponse = response.trim().toLowerCase();
+ if (!isValidYesNoResponse(lowerCaseTrimmedResponse)) {
+ throw new ParseException(RESPONSE_CONSTRAINTS);
+ }
+ if (lowerCaseTrimmedResponse.equals("y")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
deleted file mode 100644
index 4fb71f23103..00000000000
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import java.util.Arrays;
-
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.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.
- * @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));
- }
-
- String[] nameKeywords = trimmedArgs.split("\\s+");
-
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/FindModulesCommandParser.java b/src/main/java/seedu/address/logic/parser/FindModulesCommandParser.java
new file mode 100644
index 00000000000..addd2786a55
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FindModulesCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.FindModulesCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.module.ModuleCodeContainsKeywordsPredicate;
+
+
+
+/**
+ * Parses input arguments and creates a new FindModulesCommand object
+ */
+public class FindModulesCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindModulesCommand
+ * and returns a FindModulesCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindModulesCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim().toLowerCase();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindModulesCommand.MESSAGE_USAGE));
+ }
+ return new FindModulesCommand(new ModuleCodeContainsKeywordsPredicate(Arrays.asList(trimmedArgs)));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java
new file mode 100644
index 00000000000..af2688bd9d7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.FindTasksCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.task.DescriptionContainsKeywordsPredicate;
+
+
+
+/**
+ * Parses input arguments and creates a new FindTaskCommand object
+ */
+public class FindTaskCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindTaskCommand
+ * and returns a FindTaskCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindTasksCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim().toLowerCase();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTasksCommand.MESSAGE_USAGE));
+ }
+ return new FindTasksCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList(trimmedArgs)));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/LinkExamCommandParser.java b/src/main/java/seedu/address/logic/parser/LinkExamCommandParser.java
new file mode 100644
index 00000000000..6577f866159
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/LinkExamCommandParser.java
@@ -0,0 +1,54 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX;
+
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.LinkExamCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * LinkExamCommandParser parses the arguments provided by the user
+ * and creates a new LinkExamCommand object.
+ */
+public class LinkExamCommandParser implements Parser {
+
+ public static final String TASK_INDEX_INVALID = "The index for the task should be "
+ + "an unsigned positive integer greater than 0 and lesser than 2147483648.";
+ public static final String EXAM_INDEX_INVALID = "The index for the exam should be "
+ + "an unsigned positive integer greater than 0 and lesser than 2147483648.";
+
+ @Override
+ public LinkExamCommand parse(String userArgs) throws ParseException {
+ ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(userArgs,
+ PREFIX_EXAM_INDEX, PREFIX_TASK_INDEX);
+ Index examIndex;
+ Index taskIndex;
+ if (!areAllPrefixesPresent(argumentMultimap, PREFIX_TASK_INDEX, PREFIX_EXAM_INDEX)
+ || !argumentMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ LinkExamCommand.MESSAGE_USAGE));
+ }
+ try {
+ examIndex = ParserUtil.parseIndex(argumentMultimap.getValue(PREFIX_EXAM_INDEX).get());
+ } catch (ParseException pe) {
+ throw new ParseException(EXAM_INDEX_INVALID);
+ }
+ try {
+ taskIndex = ParserUtil.parseIndex(argumentMultimap.getValue(PREFIX_TASK_INDEX).get());
+ } catch (ParseException pe) {
+ throw new ParseException(TASK_INDEX_INVALID);
+ }
+ return new LinkExamCommand(examIndex, taskIndex);
+ }
+
+ //@@author dlimyy-reused
+ //Reused with minor modifications from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
+ private static boolean areAllPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+ //@@author
+}
diff --git a/src/main/java/seedu/address/logic/parser/ListExamTasksCommandParser.java b/src/main/java/seedu/address/logic/parser/ListExamTasksCommandParser.java
new file mode 100644
index 00000000000..6b2078bdef9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListExamTasksCommandParser.java
@@ -0,0 +1,37 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EXAM_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ListExamTasksCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ListExamTasksCommand object.
+ */
+public class ListExamTasksCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListExamTasksCommand
+ * and returns ListExamTasksCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ListExamTasksCommand parse(String args) throws ParseException {
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListExamTasksCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ListExamTasksCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_EXAM_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/MarkCommandParser.java b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java
new file mode 100644
index 00000000000..1b1f3458214
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/MarkCommandParser.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.MarkCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new MarkCommand object.
+ */
+public class MarkCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the MarkCommand
+ * and returns a MarkCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public MarkCommand parse(String args) throws ParseException {
+
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new MarkCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_INDEX);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..4bbf252dbba 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -2,25 +2,38 @@
import static java.util.Objects.requireNonNull;
-import java.util.Collection;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
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;
+import seedu.address.model.Criteria;
+import seedu.address.model.exam.ExamDate;
+import seedu.address.model.exam.ExamDescription;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.module.ModuleCredit;
+import seedu.address.model.module.ModuleName;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+import seedu.address.model.task.TaskDescription;
/**
* 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.";
+ public static final String MESSAGE_INVALID_INDEX = "Index should be an unsigned integer that is"
+ + " greater than 0 and less than 2147483648.";
+ public static final String MESSAGE_INVALID_KEYWORDS = "The keywords for tagdel must be priority"
+ + " or deadline or both.";
+ public static final String MESSAGE_INVALID_NUMBER_OF_KEYWORDS = "The number of keywords used for tag"
+ + "del should be either 1 or 2";
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
@@ -36,89 +49,196 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
}
/**
- * Parses a {@code String name} into a {@code Name}.
- * Leading and trailing whitespaces will be trimmed.
+ * Parses the {@code moduleCode} String into a {@code ModuleCode} object.
*
- * @throws ParseException if the given {@code name} is invalid.
+ * @param moduleCode The module code of the module.
+ * @return The ModuleCode object created from the moduleCode string.
+ * @throws ParseException if the given {@code moduleCode} is not valid.
*/
- public static Name parseName(String name) throws ParseException {
- requireNonNull(name);
- String trimmedName = name.trim();
- if (!Name.isValidName(trimmedName)) {
- throw new ParseException(Name.MESSAGE_CONSTRAINTS);
+ public static ModuleCode parseModuleCode(String moduleCode) throws ParseException {
+ requireNonNull(moduleCode);
+ String trimmedModuleCode = moduleCode.strip();
+ if (!ModuleCode.isValidModuleCode(trimmedModuleCode)) {
+ throw new ParseException(ModuleCode.MODULE_CODE_CONSTRAINTS);
}
- return new Name(trimmedName);
+ return new ModuleCode(trimmedModuleCode);
}
/**
- * Parses a {@code String phone} into a {@code Phone}.
- * Leading and trailing whitespaces will be trimmed.
+ * Parses the {@code moduleName} String into a {@code ModuleName} object.
*
- * @throws ParseException if the given {@code phone} is invalid.
+ * @param moduleName The name of the module.
+ * @return The ModuleName object created from the moduleName string.
+ * @throws ParseException if the given {@code moduleName} is not valid.
*/
- public static Phone parsePhone(String phone) throws ParseException {
- requireNonNull(phone);
- String trimmedPhone = phone.trim();
- if (!Phone.isValidPhone(trimmedPhone)) {
- throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
+ public static ModuleName parseModuleName(String moduleName) throws ParseException {
+ requireNonNull(moduleName);
+ String trimmedModuleName = moduleName.strip();
+ if (!ModuleName.isValidModuleName(trimmedModuleName)) {
+ throw new ParseException(ModuleName.MODULE_NAME_CONSTRAINTS);
}
- return new Phone(trimmedPhone);
+ return new ModuleName(trimmedModuleName);
}
/**
- * Parses a {@code String address} into an {@code Address}.
- * Leading and trailing whitespaces will be trimmed.
+ * Parses the {@code moduleCredit} String into a {@code ModuleCredit} object.
+ *
+ * @param moduleCredit The module credit of the module.
+ * @return The ModuleCredit object created from the moduleCredit string.
+ * @throws ParseException if the given {@code moduleCredit} is not valid.
+ */
+ public static ModuleCredit parseModuleCredit(String moduleCredit) throws ParseException {
+ requireNonNull(moduleCredit);
+ final int integerModuleCredit;
+ String trimmedModuleCredit = moduleCredit.strip();
+ try {
+ integerModuleCredit = Integer.parseInt(trimmedModuleCredit);
+ } catch (NumberFormatException nfe) {
+ throw new ParseException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS);
+ }
+ if (!ModuleCredit.isValidModuleCredit(integerModuleCredit)) {
+ throw new ParseException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS);
+ }
+ return new ModuleCredit(integerModuleCredit);
+ }
+
+ /**
+ * Parses delete tag keywords from a String into a Set containing each keyword.
+ *
+ * @param keywords The keywords used to specify which tag to delete
+ * @return The set of string containing the keywords specifying the tags to delete.
+ * @throws ParseException if the keywords given are invalid or are duplicated.
+ */
+ public static Set parseDeleteTagKeywords(String keywords) throws ParseException {
+ requireNonNull(keywords);
+ String trimmedKeywords = keywords.strip();
+ String[] keywordsList = trimmedKeywords.split("\\s+");
+ final Set keywordSet = new HashSet<>();
+ if (keywordsList.length > 2 || keywordsList.length < 1) {
+ throw new ParseException(MESSAGE_INVALID_NUMBER_OF_KEYWORDS);
+ }
+ for (String keyword : keywordsList) {
+ if (!(keyword.equalsIgnoreCase("priority")
+ || keyword.equalsIgnoreCase("deadline"))) {
+ throw new ParseException(MESSAGE_INVALID_KEYWORDS);
+ }
+ keywordSet.add(keyword.toLowerCase());
+ }
+ return keywordSet;
+ }
+
+ /**
+ * Parses the priority status into a PriorityTag.
+ *
+ * @param priorityTag The priority status added to the tag.
+ * @return The priorityTag object containing the priority status.
+ * @throws ParseException if the priority status is not valid.
+ */
+ public static PriorityTag parsePriorityTag(String priorityTag) throws ParseException {
+ requireNonNull(priorityTag);
+ String trimmedPriorityStatus = priorityTag.strip();
+ if (!PriorityTag.isValidTag(trimmedPriorityStatus)) {
+ throw new ParseException(PriorityTag.PRIORITY_TAG_CONSTRAINTS);
+ }
+ return new PriorityTag(trimmedPriorityStatus);
+ }
+
+ /**
+ * Parses the deadline into a DeadlineTag.
*
- * @throws ParseException if the given {@code address} is invalid.
+ * @param deadline The deadline which is added to the DeadlineTag.
+ * @return The deadlineTag containing the deadline status.
+ * @throws ParseException if the deadline is in an invalid format.
*/
- public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
- String trimmedAddress = address.trim();
- if (!Address.isValidAddress(trimmedAddress)) {
- throw new ParseException(Address.MESSAGE_CONSTRAINTS);
+ public static DeadlineTag parseDeadlineTag(String deadline) throws ParseException {
+ requireNonNull(deadline);
+ final LocalDate date;
+ if (!DeadlineTag.checkDateFormat(deadline)) {
+ throw new ParseException(DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS);
}
- return new Address(trimmedAddress);
+ //@@author dlimyy-reused
+ //Reused from https://stackoverflow.com/questions/32823368/
+ //with minor modifications.
+ final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-uuuu")
+ .withResolverStyle(ResolverStyle.STRICT);
+ //@@author
+ try {
+ date = LocalDate.parse(deadline, dtf);
+ } catch (DateTimeParseException dtp) {
+ throw new ParseException(DeadlineTag.DEADLINE_TAG_INVALID_DATE);
+ }
+ if (!DeadlineTag.isValidDeadline(date)) {
+ throw new ParseException(DeadlineTag.DEADLINE_TAG_DATE_HAS_PASSED);
+ }
+ return new DeadlineTag(date);
}
/**
- * Parses a {@code String email} into an {@code Email}.
+ * Parses a {@code String description} into a {@code Description}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code email} is invalid.
+ * @throws ParseException if the given {@code description} is invalid.
*/
- public static Email parseEmail(String email) throws ParseException {
- requireNonNull(email);
- String trimmedEmail = email.trim();
- if (!Email.isValidEmail(trimmedEmail)) {
- throw new ParseException(Email.MESSAGE_CONSTRAINTS);
+ public static TaskDescription parseDescription(String description) throws ParseException {
+ requireNonNull(description);
+ String trimmedDescription = description.trim();
+ if (!TaskDescription.isValidDescription(trimmedDescription)) {
+ throw new ParseException(TaskDescription.DESCRIPTION_CONSTRAINTS);
}
- return new Email(trimmedEmail);
+ return new TaskDescription(trimmedDescription);
}
/**
- * Parses a {@code String tag} into a {@code Tag}.
+ * Parses the criteria given to create a new Criteria object.
+ *
+ * @param criteria The criteria given for sorting.
+ * @return The criteria object which contains the criteria used for sorting.
+ * @throws ParseException if the given {@code criteria} is invalid.
+ */
+ public static Criteria parseCriteria(String criteria) throws ParseException {
+ requireNonNull(criteria);
+ String strippedCriteria = criteria.strip();
+ if (!Criteria.isValidCriteria(strippedCriteria)) {
+ throw new ParseException(Criteria.CRITERIA_CONSTRAINTS);
+ }
+ return new Criteria(strippedCriteria);
+ }
+
+ /**
+ * Parses a {@code String description} into a {@code ExamDescription}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code tag} is invalid.
+ * @throws ParseException if the given {@code description} 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);
+ public static ExamDescription parseExamDescription(String description) throws ParseException {
+ requireNonNull(description);
+ String trimmedDescription = description.trim();
+ if (!ExamDescription.isValidDescription(trimmedDescription)) {
+ throw new ParseException(ExamDescription.DESCRIPTION_CONSTRAINTS);
}
- return new Tag(trimmedTag);
+ return new ExamDescription(trimmedDescription);
}
/**
- * Parses {@code Collection tags} into a {@code Set}.
+ * Parses a {@code String examDate} into a {@code ExamDate}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code examDate} is invalid.
*/
- public static Set parseTags(Collection tags) throws ParseException {
- requireNonNull(tags);
- final Set tagSet = new HashSet<>();
- for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
+ public static ExamDate parseExamDate(String examDate) throws ParseException {
+ requireNonNull(examDate);
+ String trimmedDate = examDate.trim();
+
+ if (!ExamDate.isCorrectDateFormat(trimmedDate)) {
+ throw new ParseException(ExamDate.DATE_FORMAT_CONSTRAINTS);
+ }
+ if (!ExamDate.isExistingDate(trimmedDate)) {
+ throw new ParseException(ExamDate.VALID_DATE_CONSTRAINTS);
}
- return tagSet;
+ if (!ExamDate.isNotAPastDate(trimmedDate)) {
+ throw new ParseException(ExamDate.NOT_A_PAST_DATE_CONSTRAINTS);
+ }
+ return new ExamDate(trimmedDate);
}
+
}
diff --git a/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java
new file mode 100644
index 00000000000..f2257e76b17
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CRITERIA;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.SortTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.Criteria;
+
+
+/**
+ * SortTaskCommandParser represents a parser which parses the arguments
+ * given by the user to create a SortTaskCommand object.
+ */
+public class SortTaskCommandParser implements Parser {
+ private final Logger logger = LogsCenter.getLogger(SortTaskCommand.class);
+ @Override
+ public SortTaskCommand parse(String args) throws ParseException {
+ logger.info("Parsing arguments for SortTaskCommand.");
+ ArgumentMultimap argumentMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_CRITERIA);
+ if (!isPrefixPresent(argumentMultimap)
+ || !argumentMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT,
+ SortTaskCommand.MESSAGE_USAGE));
+ }
+
+ Criteria criteria = ParserUtil.parseCriteria(argumentMultimap.getValue(PREFIX_CRITERIA).get());
+ return new SortTaskCommand(criteria);
+ }
+
+ private static boolean isPrefixPresent(ArgumentMultimap argumentMultimap) {
+ return argumentMultimap.getValue(PREFIX_CRITERIA).isPresent();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/UnlinkExamCommandParser.java b/src/main/java/seedu/address/logic/parser/UnlinkExamCommandParser.java
new file mode 100644
index 00000000000..c277d985260
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnlinkExamCommandParser.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnlinkExamCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * UnlinkExamCommandParser parses the arguments provided by the user and creates a new UnlinkExamCommand object.
+ */
+public class UnlinkExamCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the UnlinkExamCommand
+ * and returns a UnlinkedExamCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnlinkExamCommand parse(String args) throws ParseException {
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnlinkExamCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new UnlinkExamCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java
new file mode 100644
index 00000000000..5fd8ff596d8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnmarkCommandParser.java
@@ -0,0 +1,39 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TASK_INDEX;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnmarkCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new UnmarkCommand object.
+ */
+public class UnmarkCommandParser implements Parser {
+
+ private final Pattern pattern = Pattern.compile("(-|\\+)?\\d+(\\.\\d+)?");
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the UnmarkCommand
+ * and returns an UnmarkCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnmarkCommand parse(String args) throws ParseException {
+
+ if (!pattern.matcher(args.strip()).matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new UnmarkCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TASK_INDEX);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 1a943a0781a..3e556bf06e3 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -1,12 +1,20 @@
package seedu.address.model;
import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.util.List;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.exam.DistinctExamList;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.exam.exceptions.DuplicateExamException;
+import seedu.address.model.module.DistinctModuleList;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.exceptions.DuplicateModuleException;
+import seedu.address.model.task.DistinctTaskList;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.exceptions.DuplicateTaskException;
/**
* Wraps all data at the address-book level
@@ -14,7 +22,10 @@
*/
public class AddressBook implements ReadOnlyAddressBook {
- private final UniquePersonList persons;
+ private final DistinctModuleList modules;
+ private final DistinctTaskList tasks;
+ private final DistinctExamList exams;
+
/*
* The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
@@ -24,7 +35,9 @@ public class AddressBook implements ReadOnlyAddressBook {
* among constructors.
*/
{
- persons = new UniquePersonList();
+ modules = new DistinctModuleList();
+ tasks = new DistinctTaskList();
+ exams = new DistinctExamList();
}
public AddressBook() {}
@@ -39,12 +52,17 @@ public AddressBook(ReadOnlyAddressBook 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);
+
+ public void setModules(List modules) {
+ this.modules.setModules(modules);
+ }
+
+ public void setExams(List exams) {
+ this.exams.setExams(exams);
+ }
+
+ public void setTasks(List tasks) {
+ this.tasks.setTasks(tasks);
}
/**
@@ -52,69 +70,298 @@ public void setPersons(List persons) {
*/
public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
+ setTasks(newData.getTaskList());
+ setModules(newData.getModuleList());
+ setExams(newData.getExamList());
+ }
+
+ //// module-level operations
- setPersons(newData.getPersonList());
+ public void addModule(Module mod) {
+ modules.addModule(mod);
+ }
+
+ /**
+ * Checks whether the module list contains the module.
+ *
+ * @param module The module that is being checked.
+ * @return true if the module list contains the module; else returns false.
+ */
+ public boolean hasModule(Module module) {
+ requireNonNull(module);
+ return modules.containsModule(module);
}
- //// person-level operations
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ * {@code key} must not be tied to any tasks in the address book.
+ */
+ public void removeModule(Module key) {
+ modules.remove(key);
+ }
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Replaces the given Module {@code target} with {@code editedModule}.
+ * {@code target} must exist in the module list.
+ *
+ * @throws DuplicateModuleException if module identity of {@code editedModule} is the same as another module
+ * in the list (other than {@code target}).
*/
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return persons.contains(person);
+ public void replaceModule(Module target, Module editedModule) throws DuplicateModuleException {
+ requireAllNonNull(target, editedModule);
+
+ modules.replaceModule(target, editedModule);
}
+ //// exam-level operations
+
/**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
+ * Returns true if an exam with the same module and exam description and exam date
+ * as {@code exam} exists in the exam list.
*/
- public void addPerson(Person p) {
- persons.add(p);
+ public boolean hasExam(Exam exam) {
+ requireNonNull(exam);
+ return exams.contains(exam);
+ }
+
+ public boolean hasExamWithModule(Module module) {
+ return exams.containsModule(module);
}
/**
- * 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.
+ * Adds an exam to the exam list.
+ * The exam must not already exist in the exam list.
*/
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
+ public void addExam(Exam exam) {
+ exams.addExam(exam);
+ }
- persons.setPerson(target, editedPerson);
+
+ /**
+ * Replaces the given exam {@code target} with {@code editedExam}.
+ * {@code target} must exist in the exam list.
+ *
+ * @throws DuplicateExamException if task identity of {@code editedExam} is the same as another exam
+ * in the list (other than {@code target}).
+ */
+ public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException {
+ requireAllNonNull(target, editedExam);
+ exams.replaceExam(target, editedExam, isSameExam);
}
/**
* Removes {@code key} from this {@code AddressBook}.
* {@code key} must exist in the address book.
*/
- public void removePerson(Person key) {
- persons.remove(key);
+ public void removeExam(Exam key) {
+ exams.remove(key);
+ }
+
+
+ //// task-level operations
+
+ /**
+ * Returns true if a task with the same module and description as {@code task} exists in the task list.
+ */
+ public boolean hasTask(Task task) {
+ requireNonNull(task);
+ return tasks.contains(task);
+ }
+
+ public boolean hasTaskWithModule(Module module) {
+ return tasks.containsModule(module);
+ }
+
+ /**
+ * Adds a task to the task list.
+ * The task must not already exist in the task list.
+ */
+ public void addTask(Task task) {
+ tasks.addTask(task);
+ modules.updateTotalNumOfTasks(task.getModule(), tasks);
+ modules.updateNumOfCompletedTasks(task.getModule(), tasks);
+ if (task.isLinked()) {
+ exams.updateTotalNumOfTasks(task.getExam(), tasks);
+ exams.updateNumOfCompletedTasks(task.getExam(), tasks);
+ }
+ }
+
+ /**
+ * Replaces the given task {@code target} with {@code editedTask}.
+ * {@code target} must exist in the task list.
+ * If {@code isSameTask} is true, the task identity of {@code editedTask} should be the same as {@code target}.
+ *
+ * @param target the task to be replaced.
+ * @param editedTask the edited task to replace {@code target}.
+ * @param isSameTask true if {@code target} has the same task identity as {@code editedTask}, false otherwise.
+ * @throws DuplicateTaskException if {@code isSameTask} is false but task identity of {@code editedTask}
+ * is the same as another task in the list (other than {@code target}).
+ */
+ public void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException {
+ requireAllNonNull(target, editedTask);
+ tasks.replaceTask(target, editedTask, isSameTask);
+ modules.updateNumOfCompletedTasks(target.getModule(), tasks);
+ modules.updateTotalNumOfTasks(target.getModule(), tasks);
+ modules.updateNumOfCompletedTasks(editedTask.getModule(), tasks);
+ modules.updateTotalNumOfTasks(editedTask.getModule(), tasks);
+
+ if (target.isLinked() && !editedTask.isLinked()) {
+ // to update exam when a task is unlinked from an exam
+ exams.updateTotalNumOfTasks(target.getExam(), tasks);
+ exams.updateNumOfCompletedTasks(target.getExam(), tasks);
+ }
+
+ if (editedTask.isLinked()) {
+ // to update exam for linked tasks
+ exams.updateTotalNumOfTasks(editedTask.getExam(), tasks);
+ exams.updateNumOfCompletedTasks(editedTask.getExam(), tasks);
+ }
+ }
+
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeTask(Task key) {
+ tasks.remove(key);
+ modules.updateNumOfCompletedTasks(key.getModule(), tasks);
+ modules.updateTotalNumOfTasks(key.getModule(), tasks);
+ if (key.isLinked()) {
+ exams.updateNumOfCompletedTasks(key.getExam(), tasks);
+ exams.updateTotalNumOfTasks(key.getExam(), tasks);
+ }
}
//// util methods
+ /**
+ * Resets number of tasks and number of completed tasks of all modules and exams to 0.
+ */
+ public void resetAllTaskCount() {
+ modules.resetAllTaskCount();
+ exams.resetAllTaskCount();
+ }
+
@Override
- public String toString() {
- return persons.asUnmodifiableObservableList().size() + " persons";
- // TODO: refine later
+ public ObservableList getModuleList() {
+ return modules.getUnmodifiableModuleList();
}
@Override
- public ObservableList getPersonList() {
- return persons.asUnmodifiableObservableList();
+ public ObservableList getTaskList() {
+ return tasks.getUnmodifiableTaskList();
+ }
+
+ @Override
+ public ObservableList getExamList() {
+ return exams.getUnmodifiableExamList();
+ }
+
+ /**
+ * Sorts the task list in the address book.
+ *
+ * @param criteria The criteria used for sorting the task list.
+ */
+ public void sortTaskList(Criteria criteria) {
+ requireNonNull(criteria);
+ tasks.sortTasks(criteria);
+ }
+
+ /**
+ * Unlinks all tasks that are currently linked to {@code exam}.
+ * @param exam The exam to unlink all tasks from.
+ */
+ public void unlinkTasksFromExam(Exam exam) {
+ tasks.unlinkTasksFromExam(exam);
+ }
+
+ /**
+ * Replaces task by changing its given exam field from {@code previousExam}
+ * to {@code newExam} for tasks that have their exam field as {@code previousExam}.
+ * @param previousExam The exam in the task's exam field.
+ * @param newExam The new exam which will replace the previous exam in the task's exam field.
+ */
+ public void updateExamFieldForTask(Exam previousExam, Exam newExam) {
+ requireAllNonNull(previousExam, newExam);
+ tasks.updateExamFieldForTask(previousExam, newExam);
+ }
+
+ /**
+ * Returns true if {@code examToEdit} is linked to any task, otherwise false.
+ */
+ public boolean isExamLinkedToTask(Exam examToEdit) {
+ requireNonNull(examToEdit);
+ return tasks.isExamLinkedToTask(examToEdit);
+ }
+ /**
+ * Replaces task by changing its given module field from {@code previousModule}
+ * to {@code newModule} for tasks that have their module field as {@code previousModule}.
+ * @param previousModule The module in the task's module field.
+ * @param newModule The new module which will replace the previous module in the task's module field.
+ */
+ public void updateModuleFieldForTask(Module previousModule, Module newModule) {
+ requireAllNonNull(previousModule, newModule);
+ tasks.updateModuleFieldForTask(previousModule, newModule);
+ modules.updateTotalNumOfTasks(newModule, tasks);
+ modules.updateNumOfCompletedTasks(newModule, tasks);
+ }
+
+ /**
+ * Replaces exam by changing its given module field from {@code previousModule}
+ * to {@code newModule} for all exams that have their module field as {@code previousModule}. It also links
+ * the replaced exam to tasks previously linked to the exam with {code previousModule}.
+ *
+ * @param previousModule The module in the exam's module field.
+ * @param newModule The new module which will replace the previous module in the exam's module field.
+ */
+ public void updateModuleFieldForExam(Module previousModule, Module newModule) {
+ requireAllNonNull(previousModule, newModule);
+ exams.updateModuleFieldForExam(tasks, previousModule, newModule);
+ exams.updateTotalNumOfTasksForAllExams(tasks);
+ exams.updateNumOfCompletedTasksForAllExams(tasks);
+ }
+
+ /**
+ * Deletes tasks that have their module field as {@code module}.
+ * @param module The module in the task's module field.
+ */
+ public void deleteTasksWithModule(Module module) {
+ requireNonNull(module);
+ tasks.deleteTasksWithModule(module);
+ }
+
+ /**
+ * Deletes exams that have their module field as {@code module}.
+ * @param module The module in the exam's module field.
+ */
+ public void deleteExamsWithModule(Module module) {
+ requireNonNull(module);
+ exams.deleteExamsWithModule(module);
+ }
+
+ @Override
+ public String toString() {
+ return modules.getUnmodifiableModuleList().size() + " modules"
+ + "\n" + tasks.getUnmodifiableTaskList().size() + "tasks"
+ + "\n" + exams.getUnmodifiableExamList().size() + "exams";
+ // TODO: refine later
}
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
|| (other instanceof AddressBook // instanceof handles nulls
- && persons.equals(((AddressBook) other).persons));
+ && tasks.equals(((AddressBook) other).tasks)
+ && exams.equals(((AddressBook) other).exams)
+ && modules.equals(((AddressBook) other).modules));
}
+ //@@author
@Override
public int hashCode() {
- return persons.hashCode();
+ return modules.hashCode();
}
+ //@@author
+
}
diff --git a/src/main/java/seedu/address/model/Criteria.java b/src/main/java/seedu/address/model/Criteria.java
new file mode 100644
index 00000000000..17faf1b0a6b
--- /dev/null
+++ b/src/main/java/seedu/address/model/Criteria.java
@@ -0,0 +1,51 @@
+package seedu.address.model;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Criteria class which represents the criteria that the user is sorting
+ * the task list by.
+ */
+public class Criteria {
+ public static final String CRITERIA_CONSTRAINTS =
+ "The sorting criteria should be either priority, deadline, module or description";
+ private final String criteria;
+ /**
+ * The constructor of the criteria class. Sets the criteria which
+ * will be used for sorting.
+ *
+ * @param criteria The criteria which is used for sorting.
+ */
+ public Criteria(String criteria) {
+ requireNonNull(criteria);
+ checkArgument(isValidCriteria(criteria), CRITERIA_CONSTRAINTS);
+ this.criteria = criteria;
+ }
+
+ /**
+ * Checks whether the criteria given by the user is valid.
+ *
+ * @param criteria The criteria that is being checked for validity.
+ * @return true if the criteria is valid; else return false.
+ */
+ public static boolean isValidCriteria(String criteria) {
+ requireNonNull(criteria);
+ return criteria.equalsIgnoreCase("priority")
+ || criteria.equalsIgnoreCase("deadline")
+ || criteria.equalsIgnoreCase("module")
+ || criteria.equalsIgnoreCase("description");
+ }
+
+ public String getCriteria() {
+ return criteria;
+ }
+
+
+
+ @Override
+ public boolean equals(Object otherCriteria) {
+ return otherCriteria instanceof Criteria
+ && criteria.equalsIgnoreCase(((Criteria) otherCriteria).criteria);
+ }
+}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..34b335d97eb 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,14 +5,27 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.exam.exceptions.DuplicateExamException;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.exceptions.DuplicateTaskException;
/**
* The API of the Model component.
*/
public interface Model {
+
+
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true;
+
/** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_MODULES = unused -> true;
+
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_EXAMS = unused -> true;
+
/**
* Replaces user prefs data with the data in {@code userPrefs}.
@@ -52,36 +65,146 @@ public interface Model {
/** Returns the AddressBook */
ReadOnlyAddressBook getAddressBook();
+ void addModule(Module module);
+
+ ObservableList getFilteredModuleList();
+
+ /**
+ * Returns true if a task with the same description and module as {@code task} exists in the task list.
+ */
+ boolean hasTask(Task task);
+
+ /**
+ * Returns true if a task with {@code module} exists in the task list.
+ */
+ boolean hasTaskWithModule(Module module);
+
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Adds the given task.
+ * {@code task} must not already exist in the task list.
*/
- boolean hasPerson(Person person);
+ void addTask(Task task);
/**
- * Deletes the given person.
- * The person must exist in the address book.
+ * Replaces the given task {@code target} with {@code editedTask}.
+ * {@code target} must exist in the task list.
+ * If {@code isSameTask} is true, the task identity of {@code editedTask} should be the same as {@code target}.
+ *
+ * @param target the task to be replaced.
+ * @param editedTask the edited task to replace {@code target}.
+ * @param isSameTask true if {@code target} has the same task identity as {@code editedTask}, false otherwise.
+ * @throws DuplicateTaskException if {@code isSameTask} is false but task identity of {@code editedTask}
+ * is the same as another task in the list (other than {@code target}).
*/
- void deletePerson(Person target);
+ void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException;
+ /** Returns an unmodifiable view of the filtered task list */
+ ObservableList getFilteredTaskList();
+
+ boolean hasModule(Module module);
+ void updateFilteredModuleList(Predicatepredicate);
/**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
+ * Deletes the given task.
+ * The task must exist in the address book.
*/
- void addPerson(Person person);
+ void deleteTask(Task target);
/**
- * 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.
+ * Deletes the given module.
+ * The module must exist in the address book.
*/
- void setPerson(Person target, Person editedPerson);
+ void deleteModule(Module target);
+
+ void replaceModule(Module target, Module editedModule);
- /** 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}.
+ * Updates the filter of the filtered task list to filter by the given {@code predicate}.
* @throws NullPointerException if {@code predicate} is null.
*/
- void updateFilteredPersonList(Predicate predicate);
+ void updateFilteredTaskList(Predicate predicate);
+
+
+ void sortTaskList(Criteria criteria);
+
+ /**
+ * Updates the task list to unlink all tasks that are currently linked to the give {@code exam}.
+ * @param exam
+ */
+ void unlinkTasksFromExam(Exam exam);
+
+ /**
+ * Returns true if an exam with the same description and module and exam date
+ * as {@code exam} exists in the exam list.
+ */
+ boolean hasExam(Exam exam);
+
+ void deleteExam(Exam target);
+
+ boolean hasExamWithModule(Module module);
+
+ /**
+ * Adds the given exam.
+ * {@code exam} must not already exist in the exam list.
+ */
+ void addExam(Exam exam);
+
+
+ /**
+ * Replaces the given exam {@code target} with {@code editedExam}.
+ * {@code target} must exist in the exam list.
+ *
+ * @throws DuplicateExamException if task identity of {@code editedExam} is the same as another exam
+ * in the list (other than {@code target}).
+ */
+ void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException;
+
+ /** Returns an unmodifiable view of the filtered exam list */
+ ObservableList getFilteredExamList();
+
+ /**
+ * Updates the filter of the filtered exam list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredExamList(Predicatepredicate);
+
+
+ /**
+ * Updates the exam field in task by replacing the previous exam with the new exam.
+ * @param previousExam The exam in the task's exam field.
+ * @param newExam The new exam which will replace the previous exam in the task's exam field.
+ */
+ void updateExamFieldForTask(Exam previousExam, Exam newExam);
+
+ /**
+ * Returns true if {@code examToEdit} is linked to any task, otherwise false.
+ */
+ boolean isExamLinkedToTask(Exam examToEdit);
+
+ /**
+ * Updates the module field in task by replacing the previous module with the new module.
+ * @param previousModule The module in the task's module field.
+ * @param newModule The new module which will replace the previous module in the task's module field.
+ */
+ void updateModuleFieldForTask(Module previousModule, Module newModule);
+
+ /**
+ * Updates the module field in exam by replacing the previous module with the new module.
+ * @param previousModule The module in the exam's module field.
+ * @param newModule The new module which will replace the previous module in the exam's module field.
+ */
+ void updateModuleFieldForExam(Module previousModule, Module newModule);
+
+ /**
+ * Deletes tasks that have their module field as {@code module}.
+ * @param module The module in the task's module field.
+ */
+ void deleteTasksWithModule(Module module);
+
+ /**
+ * Deletes exams that have their module field as {@code module}.
+ * @param module The module in the exam's module field.
+ */
+ void deleteExamsWithModule(Module module);
+
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 86c1df298d7..0e27e3cad26 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,7 +11,11 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
+
+
/**
* Represents the in-memory model of the address book data.
@@ -21,7 +25,13 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
+
+ private final FilteredList moduleFilteredList;
+
+ private final FilteredList taskFilteredList;
+
+ private final FilteredList examFilteredList;
+
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -33,7 +43,9 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ moduleFilteredList = new FilteredList<>(this.addressBook.getModuleList());
+ taskFilteredList = new FilteredList<>(this.addressBook.getTaskList());
+ examFilteredList = new FilteredList<>(this.addressBook.getExamList());
}
public ModelManager() {
@@ -87,64 +99,182 @@ public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}
+ //========== Task List ==================================================================================
+
@Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
+ public boolean hasTask(Task task) {
+ requireNonNull(task);
+ return addressBook.hasTask(task);
}
@Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
+ public boolean hasTaskWithModule(Module module) {
+ requireNonNull(module);
+ return addressBook.hasTaskWithModule(module);
}
+
@Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ public void addTask(Task task) {
+ addressBook.addTask(task);
+ updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
}
@Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
+ public void replaceTask(Task target, Task editedTask, boolean isSameTask) {
+ requireAllNonNull(target, editedTask, isSameTask);
- addressBook.setPerson(target, editedPerson);
+ addressBook.replaceTask(target, editedTask, isSameTask);
}
- //=========== Filtered Person List Accessors =============================================================
+ @Override
+ public void deleteTask(Task target) {
+ addressBook.removeTask(target);
+ }
+
+ //========== Exam List ==================================================================================
+ @Override
+ public boolean hasExam(Exam exam) {
+ requireNonNull(exam);
+ return addressBook.hasExam(exam);
+ }
+
+ @Override
+ public boolean hasExamWithModule(Module module) {
+ requireNonNull(module);
+ return addressBook.hasExamWithModule(module);
+ }
+
+
+ @Override
+ public void addExam(Exam exam) {
+ addressBook.addExam(exam);
+ updateFilteredExamList(PREDICATE_SHOW_ALL_EXAMS);
+ }
+
+ @Override
+ public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) {
+ requireAllNonNull(target, editedExam);
+ addressBook.replaceExam(target, editedExam, isSameExam);
+ }
+
+ @Override
+ public void deleteExam(Exam target) {
+ addressBook.removeExam(target);
+ }
+
+ //=============================Module Commands================================
+ @Override
+ public void addModule(Module module) {
+ addressBook.addModule(module);
+ updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES);
+ }
+
+ @Override
+ public boolean hasModule(Module module) {
+ requireNonNull(module);
+ return addressBook.hasModule(module);
+ }
+
+ @Override
+ public ObservableList getFilteredModuleList() {
+ return moduleFilteredList;
+ }
+
+ @Override
+ public void updateFilteredModuleList(Predicate predicate) {
+ requireNonNull(predicate);
+ moduleFilteredList.setPredicate(predicate);
+ }
+
+ @Override
+ public void deleteModule(Module target) {
+ addressBook.removeModule(target);
+ }
+
+ @Override
+ public void replaceModule(Module target, Module editedModule) {
+ requireAllNonNull(target, editedModule);
+
+ addressBook.replaceModule(target, editedModule);
+ }
+
+ //================================Task Commands=====================================
+ @Override
+ public ObservableList getFilteredTaskList() {
+ return taskFilteredList;
+ }
+
+ @Override
+ public void updateFilteredTaskList(Predicate predicate) {
+ requireNonNull(predicate);
+ taskFilteredList.setPredicate(predicate);
+ }
+
+ @Override
+ public void sortTaskList(Criteria criteria) {
+ requireNonNull(criteria);
+ addressBook.sortTaskList(criteria);
+ }
+
+ @Override
+ public void unlinkTasksFromExam(Exam exam) {
+ requireNonNull(exam);
+ addressBook.unlinkTasksFromExam(exam);
+ }
+
+ @Override
+ public void updateExamFieldForTask(Exam previousExam, Exam newExam) {
+ requireAllNonNull(previousExam, newExam);
+ addressBook.updateExamFieldForTask(previousExam, newExam);
+ }
+
+ @Override
+ public void updateModuleFieldForTask(Module previousModule, Module newModule) {
+ requireAllNonNull(previousModule, newModule);
+ addressBook.updateModuleFieldForTask(previousModule, newModule);
+ }
- /**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
- * {@code versionedAddressBook}
- */
@Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
+ public void deleteTasksWithModule(Module module) {
+ requireNonNull(module);
+ addressBook.deleteTasksWithModule(module);
}
+ //================================Exam Commands=====================================
@Override
- public void updateFilteredPersonList(Predicate predicate) {
+ public ObservableList getFilteredExamList() {
+ return examFilteredList;
+ }
+
+ @Override
+ public void updateFilteredExamList(Predicate predicate) {
requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
+ examFilteredList.setPredicate(predicate);
}
@Override
- public boolean equals(Object obj) {
- // short circuit if same object
- if (obj == this) {
- return true;
- }
+ public boolean isExamLinkedToTask(Exam examToEdit) {
+ requireNonNull(examToEdit);
+ return addressBook.isExamLinkedToTask(examToEdit);
+ }
- // instanceof handles nulls
- if (!(obj instanceof ModelManager)) {
- return false;
- }
+ @Override
+ public void updateModuleFieldForExam(Module previousModule, Module newModule) {
+ requireAllNonNull(previousModule, newModule);
+ addressBook.updateModuleFieldForExam(previousModule, newModule);
+ }
- // state check
- ModelManager other = (ModelManager) obj;
- return addressBook.equals(other.addressBook)
- && userPrefs.equals(other.userPrefs)
- && filteredPersons.equals(other.filteredPersons);
+ @Override
+ public void deleteExamsWithModule(Module module) {
+ requireNonNull(module);
+ addressBook.deleteExamsWithModule(module);
}
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof ModelManager
+ && this.addressBook.equals(((ModelManager) other).addressBook));
+ }
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
index 6ddc2cd9a29..379e658f9b7 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
@@ -1,7 +1,10 @@
package seedu.address.model;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
+
/**
* Unmodifiable view of an address book
@@ -12,6 +15,19 @@ public interface ReadOnlyAddressBook {
* Returns an unmodifiable view of the persons list.
* This list will not contain any duplicate persons.
*/
- ObservableList getPersonList();
+
+ ObservableList getModuleList();
+
+ /**
+ * Returns an unmodifiable view of the task list.
+ * This list will not contain any duplicate tasks.
+ */
+ ObservableList getTaskList();
+
+ /**
+ * Returns an unmodifiable view of the exam list.
+ * This list will not contain any duplicate exams.
+ */
+ ObservableList getExamList();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..6aa52b3f131 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -14,7 +14,7 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path addressBookFilePath = Paths.get("data" , "modpro.json");
/**
* Creates a {@code UserPrefs} with default values.
diff --git a/src/main/java/seedu/address/model/exam/DistinctExamList.java b/src/main/java/seedu/address/model/exam/DistinctExamList.java
new file mode 100644
index 00000000000..fe8bd137b66
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/DistinctExamList.java
@@ -0,0 +1,239 @@
+package seedu.address.model.exam;
+
+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.exam.exceptions.DuplicateExamException;
+import seedu.address.model.exam.exceptions.ExamIdentityModifiedException;
+import seedu.address.model.exam.exceptions.ExamNotFoundException;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.DistinctTaskList;
+import seedu.address.model.task.Task;
+
+/**
+ * This class represents a list which contains Exam objects which are distinct from
+ * each other. Exam objects are distinct from each other when they have different module
+ * codes and different description and different dates.
+ */
+public class DistinctExamList implements Iterable {
+
+ public final ObservableList examList = FXCollections.observableArrayList();
+ public final ObservableList unmodifiableExamList = FXCollections
+ .unmodifiableObservableList(examList);
+
+ /**
+ * Returns true if the exam list contains an equivalent exam as the given argument.
+ */
+ public boolean contains(Exam toCheck) {
+ requireNonNull(toCheck);
+ return examList.stream().anyMatch(toCheck::isSameExam);
+ }
+
+ /**
+ * Returns true if the exam list contains an exam with an equivalent module as the given argument.
+ */
+ public boolean containsModule(Module toCheck) {
+ requireNonNull(toCheck);
+
+ return examList.stream().map(Exam::getModule).anyMatch(toCheck::isSameModule);
+ }
+
+ /**
+ * Adds the exam to the examList.
+ * The exam must not already exist in the list.
+ *
+ * @param examAdded The exam to be added.
+ */
+ public void addExam(Exam examAdded) {
+ requireNonNull(examAdded);
+ if (contains(examAdded)) {
+ throw new DuplicateExamException();
+ }
+ examList.add(examAdded);
+ }
+
+ /**
+ * Replaces the given exam {@code target} with {@code editedExam}.
+ * {@code target} must exist in the exam list.
+ *
+ * @throws DuplicateExamException if task identity of {@code editedExam} is the same as another exam
+ * in the exam list (other than {@code target}).
+ */
+ public void replaceExam(Exam target, Exam editedExam, boolean isSameExam) throws DuplicateExamException {
+ requireAllNonNull(target, editedExam);
+
+ int index = examList.indexOf(target);
+ if (index == -1) {
+ throw new ExamNotFoundException();
+ }
+
+ if (isSameExam && !target.isSameExam(editedExam)) {
+ throw new ExamIdentityModifiedException();
+ }
+
+ if (!isSameExam && contains(editedExam) && !editedExam.isSameExam(target)) {
+ throw new DuplicateExamException();
+ }
+ examList.set(index, editedExam);
+ }
+
+ /**
+ * Removes the equivalent exam from the exam list.
+ * The exam must exist in the list.
+ */
+ public void remove(Exam toRemove) {
+ requireNonNull(toRemove);
+ if (!examList.remove(toRemove)) {
+ throw new ExamNotFoundException();
+ }
+ }
+
+ /**
+ * Counts the number of tasks in {@code tasks} linked to {@code exam},
+ * and updates this number in {@code exam}.
+ * {@code exam} must exist in the exam list.
+ *
+ * @param exam The exam to check for number of tasks.
+ * @param tasks The list of tasks to check with the exam.
+ */
+ public void updateTotalNumOfTasks(Exam exam, DistinctTaskList tasks) {
+ requireAllNonNull(exam, tasks);
+ int totalNumOfTasks = tasks.getTotalNumOfExamTasks(exam);
+
+ int index = examList.indexOf(exam);
+ if (index == -1) {
+ throw new ExamNotFoundException();
+ }
+
+ Exam examToEdit = examList.get(index);
+ Exam updatedExam = examToEdit.setTotalNumOfTasks(totalNumOfTasks);
+ examList.set(index, updatedExam);
+ }
+
+ /**
+ * Counts the number of tasks in {@code tasks} linked to each exam in the exam list
+ * and updates this number in each exam.
+ *
+ * @param tasks The list of tasks to check with the exam.
+ */
+ public void updateTotalNumOfTasksForAllExams(DistinctTaskList tasks) {
+ examList.forEach(exam -> updateTotalNumOfTasks(exam, tasks));
+ }
+
+ /**
+ * Counts the number of completed tasks in {@code tasks} linked to {@code exam},
+ * and updates this number in {@code exam}.
+ * {@code exam} must exist in the exam list.
+ *
+ * @param exam The exam to check for number of completed tasks.
+ * @param tasks The list of tasks to check with the exam.
+ */
+ public void updateNumOfCompletedTasks(Exam exam, DistinctTaskList tasks) {
+ requireAllNonNull(exam, tasks);
+ int numOfCompletedTasks = tasks.getNumOfCompletedExamTasks(exam);
+
+ int index = examList.indexOf(exam);
+ if (index == -1) {
+ throw new ExamNotFoundException();
+ }
+ Exam examToEdit = examList.get(index);
+ Exam updatedExam = examToEdit.setNumOfCompletedTasks(numOfCompletedTasks);
+ examList.set(index, updatedExam);
+ }
+
+ /**
+ * Counts the number of completed tasks in {@code tasks} linked to each exam in the exam list,
+ * and updates this number in each exam.
+ *
+ * @param tasks The list of tasks to check with the exam.
+ */
+ public void updateNumOfCompletedTasksForAllExams(DistinctTaskList tasks) {
+ examList.forEach(exam -> updateNumOfCompletedTasks(exam, tasks));
+ }
+
+ /**
+ * Resets number of tasks and number of completed tasks of all exams to 0.
+ */
+ public void resetAllTaskCount() {
+ examList.forEach(exam -> {
+ int index = examList.indexOf(exam);
+ Exam updatedExam = exam.setNumOfCompletedTasks(0);
+ updatedExam = updatedExam.setTotalNumOfTasks(0);
+ examList.set(index, updatedExam);
+ });
+ }
+
+ /**
+ * Replaces all exams that have their module field as {@code previousModule} by changing its given module field
+ * to {@code newModule}. It also links all tasks in {@code taskList} which has an exam having
+ * {@code previousModule} to the corresponding exam with {@code newModule}.
+ *
+ * @param taskList The list of tasks.
+ * @param previousModule The module in the exam's module field.
+ * @param newModule The new module which will replace the previous module in the exams's module field.
+ */
+ public void updateModuleFieldForExam(DistinctTaskList taskList, Module previousModule, Module newModule) {
+ requireAllNonNull(taskList, previousModule, newModule);
+ examList.forEach(exam -> {
+ if (exam.getModule().equals(previousModule)) {
+ Exam editedExam = exam.edit(newModule, null, null);
+ updateExamFieldForTask(taskList, exam, editedExam);
+ }
+ });
+ }
+
+ /**
+ * Unlinks all tasks from {@code previousExam} and links these tasks to {@code newExam}. It also replaces
+ * the {@code previousExam} with {@code newExam}.
+ *
+ * @param taskList The list of tasks.
+ * @param previousExam The exam to be replaced and linked to.
+ * @param newExam The exam which will replace {@code previousExam} and will be linked to.
+ */
+ private void updateExamFieldForTask(DistinctTaskList taskList, Exam previousExam, Exam newExam) {
+ List unlinkedTasks = taskList.unlinkTasksFromExam(previousExam);
+ replaceExam(previousExam, newExam, false);
+ taskList.linkTasksToExam(newExam, unlinkedTasks);
+ }
+
+ /**
+ * Remove exams that have their module field as {@code module} from the examlist.
+ * @param module The module in the exam's module field.
+ */
+ public void deleteExamsWithModule(Module module) {
+ requireAllNonNull(module);
+ for (int i = 0; i < examList.size(); i++) {
+ Exam exam = examList.get(i);
+ if (exam.getModule().equals(module)) {
+ remove(exam);
+ --i;
+ }
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ return examList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object otherExam) {
+ return otherExam == this
+ || (otherExam instanceof DistinctExamList
+ && examList.equals(((DistinctExamList) otherExam).examList));
+ }
+
+ public ObservableList getUnmodifiableExamList() {
+ return unmodifiableExamList;
+ }
+
+ public void setExams(List exams) {
+ requireNonNull(exams);
+ examList.setAll(exams);
+ }
+}
diff --git a/src/main/java/seedu/address/model/exam/Exam.java b/src/main/java/seedu/address/model/exam/Exam.java
new file mode 100644
index 00000000000..de13064c1fa
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/Exam.java
@@ -0,0 +1,209 @@
+package seedu.address.model.exam;
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAnyNonNull;
+
+import seedu.address.logic.commands.EditExamCommand;
+import seedu.address.model.exam.exceptions.NoLinkedTaskForExamException;
+import seedu.address.model.module.Module;
+
+/**
+ * Exam class represents an exam which stores the module code, the
+ * description and the date of the exam.
+ */
+public class Exam {
+
+ public static final String MESSAGE_NO_TASKS_FOR_EXAM = "You have no tasks for this exam";
+
+ private final Module module;
+ private final ExamDescription examDescription;
+ private final ExamDate examDate;
+ private int totalNumOfTasks;
+ private int numOfCompletedTasks;
+
+ /**
+ * The constructor of the Exam class. Sets the module,
+ * exam description and exam date.
+ *
+ * @param module The module associated with the exam.
+ * @param examDescription The description of the exam.
+ * @param examDate The date of the exam.
+ *
+ */
+ public Exam(Module module, ExamDescription examDescription, ExamDate examDate) {
+ this.module = module;
+ this.examDescription = examDescription;
+ this.examDate = examDate;
+ this.totalNumOfTasks = 0;
+ this.numOfCompletedTasks = 0;
+ }
+
+ /**
+ * The constructor of the Exam class. Sets the module,
+ * exam description, exam date, the total number of tasks
+ * and number of completed tasks linked to the exam.
+ *
+ * @param module The module being added.
+ * @param examDescription The description of the exam.
+ * @param examDate The date of the exam.
+ * @param totalNumOfTasks The total number of tasks linked to the exam.
+ * @param numOfCompletedTasks The number of completed tasks linked to the exam.
+ *
+ */
+ public Exam(Module module, ExamDescription examDescription, ExamDate examDate,
+ int totalNumOfTasks, int numOfCompletedTasks) {
+ this.module = module;
+ this.examDescription = examDescription;
+ this.examDate = examDate;
+ this.totalNumOfTasks = totalNumOfTasks;
+ this.numOfCompletedTasks = numOfCompletedTasks;
+ }
+
+ public ExamDescription getDescription() {
+ return examDescription;
+ }
+
+ public Module getModule() {
+ return module;
+ }
+
+ public ExamDate getExamDate() {
+ return examDate;
+ }
+
+ public int getNumOfCompletedTasks() {
+ return numOfCompletedTasks;
+ }
+ public int getTotalNumOfTasks() {
+ return totalNumOfTasks;
+ }
+
+ /**
+ * Returns true if both exams have the same data fields.
+ */
+ public boolean isSameExam(Exam otherExam) {
+ return this.equals(otherExam);
+ }
+
+ public Exam setTotalNumOfTasks(Integer totalNumOfTasks) {
+ return new Exam(this.module, this.examDescription, this.examDate, totalNumOfTasks,
+ this.numOfCompletedTasks);
+ }
+
+ public Exam setNumOfCompletedTasks(Integer numOfCompletedTasks) {
+ return new Exam(this.module, this.examDescription, this.examDate, this.totalNumOfTasks,
+ numOfCompletedTasks);
+ }
+
+ /**
+ * Returns the percentage of tasks completed for the exam.
+ */
+ public double getPercentageCompleted() {
+ assert(numOfCompletedTasks >= 0);
+ assert(totalNumOfTasks >= 0);
+ if (totalNumOfTasks == 0) {
+ throw new NoLinkedTaskForExamException();
+ }
+ return (double) numOfCompletedTasks / (double) totalNumOfTasks;
+ }
+
+ /**
+ * Returns a string representation of the number of completed tasks and number of total tasks linked to the exam.
+ */
+ public String generateProgressMessage() {
+ if (totalNumOfTasks == 0) {
+ return MESSAGE_NO_TASKS_FOR_EXAM;
+ } else {
+ return numOfCompletedTasks + " / " + totalNumOfTasks + " task(s) completed";
+ }
+ }
+
+ public boolean hasTasks() {
+ return !(totalNumOfTasks == 0);
+ }
+
+ /**
+ * Creates and returns a {@code Exam} with the details of {@code this}
+ * edited with {@code editExamDescriptor}.
+ */
+ public Exam edit(EditExamCommand.EditExamDescriptor editExamDescriptor) {
+ requireNonNull(editExamDescriptor);
+ Module updatedModule = editExamDescriptor.getModule().orElse(module);
+ ExamDescription updatedDescription = editExamDescriptor.getDescription().orElse(examDescription);
+ ExamDate updatedExamDate = editExamDescriptor.getExamDate().orElse(examDate);
+
+ if (!module.isSameModule(updatedModule)) {
+ return new Exam(updatedModule, updatedDescription, updatedExamDate);
+ }
+ return new Exam(updatedModule, updatedDescription, updatedExamDate, totalNumOfTasks, numOfCompletedTasks);
+ }
+
+ /**
+ * Creates and returns a {@code Exam} with the details of {@code this}
+ * edited with {@code newModule}, {@code newDescription}, {@code newExamDate}.
+ */
+ public Exam edit(Module newModule, ExamDescription newDescription, ExamDate newExamDate) {
+ requireAnyNonNull(newModule, newDescription, newExamDate);
+ Module updatedModule = module;
+ ExamDescription updatedDescription = examDescription;
+ ExamDate updatedExamDate = examDate;
+ if (newModule != null) {
+ updatedModule = newModule;
+ }
+ if (newDescription != null) {
+ updatedDescription = newDescription;
+ }
+ if (newExamDate != null) {
+ updatedExamDate = newExamDate;
+ }
+ if (!module.isSameModule(updatedModule)) {
+ return new Exam(updatedModule, updatedDescription, updatedExamDate);
+ }
+ return new Exam(updatedModule, updatedDescription, updatedExamDate, totalNumOfTasks, numOfCompletedTasks);
+ }
+
+ /**
+ * Checks whether the two exams have the exact same fields.
+ *
+ * @param otherExam The other exam being compared against.
+ * @return true if the two Exam objects have the same module, exam description, exam date, number of completed tasks
+ * and total number of tasks.
+ */
+ public boolean hasAllSameFields(Exam otherExam) {
+ requireNonNull(otherExam);
+ return this.module.equals(otherExam.module)
+ && this.examDescription.equals(otherExam.examDescription)
+ && this.examDate.equals(otherExam.examDate)
+ && this.numOfCompletedTasks == otherExam.numOfCompletedTasks
+ && this.totalNumOfTasks == otherExam.totalNumOfTasks;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Exam)) {
+ return false;
+ }
+
+ Exam otherExam = (Exam) other;
+ return otherExam.getDescription().equals(getDescription())
+ && otherExam.getModule().equals(getModule())
+ && otherExam.getExamDate().equals(getExamDate());
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Module: ")
+ .append(getModule())
+ .append("; ExamDescription: ")
+ .append(getDescription())
+ .append("; ExamDate: ")
+ .append(getExamDate());
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/exam/ExamDate.java b/src/main/java/seedu/address/model/exam/ExamDate.java
new file mode 100644
index 00000000000..4496aea1cd0
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/ExamDate.java
@@ -0,0 +1,135 @@
+package seedu.address.model.exam;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
+
+
+/**
+ * ExamDate class represents the date of the exam.
+ */
+public class ExamDate {
+ public static final String DATE_CONSTRAINTS =
+ "Exam Date should be in the format DD-MM-YYYY and a valid date. DD should be between "
+ + "1 and 31(both inclusive)\nand MM "
+ + "should be between 1 and 12(both inclusive)";
+ public static final String DATE_FORMAT_CONSTRAINTS =
+ "Exam Date should be in the format DD-MM-YYYY. DD should be between "
+ + "1 and 31(both inclusive)\nand MM "
+ + "should be between 1 and 12(both inclusive)";
+ public static final String NOT_A_PAST_DATE_CONSTRAINTS =
+ "Exam Date should not be earlier than today's date.";
+ public static final String VALID_DATE_CONSTRAINTS =
+ "Exam Date should be a valid date";
+ public static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("dd-MM-uuuu").withResolverStyle(ResolverStyle.STRICT);
+ public final String examDate;
+
+
+ /**
+ * Constructs a {@code ExamDate}.
+ *
+ * @param date A valid exam date.
+ */
+ public ExamDate(String date) {
+ requireNonNull(date);
+ checkArgument(isExistingDate(date), DATE_CONSTRAINTS);
+ checkArgument(isCorrectDateFormat(date), DATE_CONSTRAINTS);
+ examDate = date;
+ }
+
+
+ /**
+ * Checks if the format given for the date is valid.
+ *
+ * @param date The date provided.
+ * @return true if the date has the correct format; else return false.
+ */
+ public static boolean isValidDateFormat(String date) {
+ try {
+ LocalDate.parse(date, DATE_TIME_FORMATTER);
+ return true;
+ } catch (DateTimeParseException e) {
+ return false;
+ }
+ }
+
+ public static boolean isValidDate(String date) {
+ return isCorrectDateFormat(date) && isExistingDate(date) && isNotAPastDate(date);
+ }
+
+ /**
+ * Checks if format is in DD-MM-YYYY where DD is between 1 to 31(both inclusive),
+ * MM is between 1 to 12(both inclusive), yyyy is between 0 to 9999(both inclusive).
+ * @param date The date provided.
+ * @return true if date is in DD-MM-YYYY format.
+ */
+ public static boolean isCorrectDateFormat(String date) {
+ try {
+ String[] a = date.split("-");
+ Integer days = Integer.parseInt(a[0]);
+ Integer month = Integer.parseInt(a[1]);
+ Integer year = Integer.parseInt(a[2]);
+ if (year >= 0000 && year <= 9999 && month >= 01 && month
+ <= 12 && days >= 01 && days <= 31 && isValidDateLength(date)) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (NumberFormatException | ArrayIndexOutOfBoundsException ex) {
+ return false;
+ }
+ }
+
+
+ public static boolean isValidDateLength(String date) {
+ return date.length() == 10;
+ }
+
+ /**
+ * Returns true if date inputted exists.
+ * @param date A String that represents a date that is in the DD-MM-YYYY format.
+ * @return true if date exist, otherwise false.
+ */
+ public static boolean isExistingDate(String date) {
+ try {
+ LocalDate.parse(date, DATE_TIME_FORMATTER).format(DateTimeFormatter.ofPattern("dd MMM yyyy"));
+ return true;
+ } catch (DateTimeException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if date inputted is not a date before the current date.
+ * @param date A String that represents a date that is in the DD-MM-YYYY format.
+ * @return true if date is not a past date, otherwise false.
+ */
+ public static boolean isNotAPastDate(String date) {
+ LocalDate d = LocalDate.parse(date, DATE_TIME_FORMATTER);
+ return d.compareTo(LocalDate.now()) >= 0;
+ }
+
+
+ @Override
+ public String toString() {
+ return examDate;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ExamDate // instanceof handles nulls
+ && examDate.equals(((ExamDate) other).examDate)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return examDate.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/exam/ExamDescription.java b/src/main/java/seedu/address/model/exam/ExamDescription.java
new file mode 100644
index 00000000000..ce8955a0245
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/ExamDescription.java
@@ -0,0 +1,49 @@
+package seedu.address.model.exam;
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * ExamDescription class represents the description of the exam.
+ */
+public class ExamDescription {
+ public static final String DESCRIPTION_CONSTRAINTS =
+ "The description of the exam should not be empty";
+
+ public final String description;
+
+ /**
+ * The constructor of the ExamDescription class. Sets the
+ * description of the class.
+ *
+ * @param description The description of the class.
+ */
+ public ExamDescription(String description) {
+ requireNonNull(description);
+ checkArgument(isValidDescription(description), DESCRIPTION_CONSTRAINTS);
+ this.description = description;
+ }
+
+ public static boolean isValidDescription(String description) {
+ return description.strip().length() > 0;
+ }
+
+ @Override
+ public boolean equals(Object otherDescription) {
+ return otherDescription == this || (otherDescription instanceof ExamDescription
+ && description.equalsIgnoreCase((((ExamDescription) otherDescription).description)));
+ }
+
+ @Override
+ public int hashCode() {
+ return description.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/model/exam/exceptions/DuplicateExamException.java b/src/main/java/seedu/address/model/exam/exceptions/DuplicateExamException.java
new file mode 100644
index 00000000000..3bebf72bdd2
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/exceptions/DuplicateExamException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.exam.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Exams (Exams are considered duplicates if they have the same
+ * module and exam description and exam date).
+ */
+public class DuplicateExamException extends RuntimeException {
+ public DuplicateExamException() {
+ super("Operation would result in duplicate exams.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/exam/exceptions/ExamIdentityModifiedException.java b/src/main/java/seedu/address/model/exam/exceptions/ExamIdentityModifiedException.java
new file mode 100644
index 00000000000..abd8a9dc259
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/exceptions/ExamIdentityModifiedException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.exam.exceptions;
+
+/**
+ * Signals that the operation will modify the identity of the exam.
+ */
+public class ExamIdentityModifiedException extends RuntimeException {
+ public ExamIdentityModifiedException() {
+ super("Operation would change exam identity");
+ }
+}
diff --git a/src/main/java/seedu/address/model/exam/exceptions/ExamNotFoundException.java b/src/main/java/seedu/address/model/exam/exceptions/ExamNotFoundException.java
new file mode 100644
index 00000000000..5244ab6ade4
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/exceptions/ExamNotFoundException.java
@@ -0,0 +1,7 @@
+package seedu.address.model.exam.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified exam.
+ */
+public class ExamNotFoundException extends RuntimeException {
+}
diff --git a/src/main/java/seedu/address/model/exam/exceptions/NoLinkedTaskForExamException.java b/src/main/java/seedu/address/model/exam/exceptions/NoLinkedTaskForExamException.java
new file mode 100644
index 00000000000..004bd52e29f
--- /dev/null
+++ b/src/main/java/seedu/address/model/exam/exceptions/NoLinkedTaskForExamException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.exam.exceptions;
+
+/**
+ * Signals that the operation is unable to find linked tasks for the specified exam.
+ */
+public class NoLinkedTaskForExamException extends RuntimeException {
+ public NoLinkedTaskForExamException() {
+ super("Operation cannot find any linked tasks for the specified exam");
+ }
+}
diff --git a/src/main/java/seedu/address/model/module/DistinctModuleList.java b/src/main/java/seedu/address/model/module/DistinctModuleList.java
new file mode 100644
index 00000000000..9d8d26ec0ea
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/DistinctModuleList.java
@@ -0,0 +1,163 @@
+package seedu.address.model.module;
+
+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.module.exceptions.DuplicateModuleException;
+import seedu.address.model.module.exceptions.ModuleNotFoundException;
+import seedu.address.model.task.DistinctTaskList;
+
+/**
+ * This class represents a list which contains Module objects which are distinct from
+ * each other. Module Objects are distinct from each other when they have different module
+ * codes and different module names.
+ */
+public class DistinctModuleList implements Iterable {
+
+ private final ObservableList moduleList = FXCollections.observableArrayList();
+ private final ObservableList unmodifiableModuleList = FXCollections
+ .unmodifiableObservableList(moduleList);
+
+ /**
+ * Checks whether the moduleList contains a Module with the same module code.
+ *
+ * @param module The Module object being compared against.
+ * @return true if the Module object has the same module code as one of the
+ * Module objects in the list; else return false
+ */
+ public boolean containsModule(Module module) {
+ requireNonNull(module);
+ return moduleList.stream().anyMatch(module::isSameModule);
+ }
+
+ /**
+ * Adds modules to the moduleList.
+ *
+ * @param moduleAdded The module which is being added.
+ */
+ public void addModule(Module moduleAdded) {
+ requireNonNull(moduleAdded);
+ if (containsModule(moduleAdded)) {
+ throw new DuplicateModuleException();
+ }
+ moduleList.add(moduleAdded);
+ }
+
+ public void setModules(List modules) {
+ requireAllNonNull(modules);
+ moduleList.setAll(modules);
+ }
+
+ /**
+ * Counts the number of tasks in {@code tasks} that belong to {@code module},
+ * and updates this number in {@code module}.
+ * {@code module} must exist in the module list.
+ *
+ * @param module The module to check for number of tasks.
+ * @param tasks the list of tasks to check with the module.
+ */
+ public void updateTotalNumOfTasks(Module module, DistinctTaskList tasks) {
+ requireAllNonNull(module, tasks);
+ int totalNumOfTasks = tasks.getTotalNumOfModuleTasks(module);
+
+ int index = moduleList.indexOf(module);
+ if (index == -1) {
+ throw new ModuleNotFoundException();
+ }
+
+ Module moduleToEdit = moduleList.get(index);
+ Module updatedModule = moduleToEdit.setTotalNumOfTasks(totalNumOfTasks);
+ moduleList.set(index, updatedModule);
+ }
+
+ /**
+ * Counts the number of completed tasks in {@code tasks} that belong to {@code module},
+ * and updates this number in {@code module}.
+ * {@code module} must exist in the module list.
+ * @param module The module to check for number of completed tasks.
+ * @param tasks the list of tasks to check with the module.
+ */
+ public void updateNumOfCompletedTasks(Module module, DistinctTaskList tasks) {
+ requireAllNonNull(module, tasks);
+ int numOfCompletedTasks = tasks.getNumOfCompletedModuleTasks(module);
+
+ int index = moduleList.indexOf(module);
+ if (index == -1) {
+ throw new ModuleNotFoundException();
+ }
+ Module moduleToEdit = moduleList.get(index);
+ Module updatedModule = moduleToEdit.setNumOfCompletedTasks(numOfCompletedTasks);
+ moduleList.set(index, updatedModule);
+ }
+
+ /**
+ * Resets number of tasks and number of completed tasks of all modules to 0.
+ */
+ public void resetAllTaskCount() {
+ moduleList.forEach(module -> {
+ int index = moduleList.indexOf(module);
+ Module updatedModule = module.setNumOfCompletedTasks(0);
+ updatedModule = updatedModule.setTotalNumOfTasks(0);
+ moduleList.set(index, updatedModule);
+ });
+ }
+
+ /**
+ * Removes the equivalent module from the module list.
+ * The module must exist in the list.
+ */
+ public void remove(Module toRemove) {
+ requireNonNull(toRemove);
+ if (!moduleList.remove(toRemove)) {
+ throw new ModuleNotFoundException();
+ }
+ }
+
+ /**
+ * Replaces the given task {@code target} with {@code editedModule}.
+ * {@code target} must exist in the module list.
+ *
+ * @throws DuplicateModuleException if module {@code editedModule} is the same as another module
+ * in the list (other than {@code target}).
+ * @throws ModuleNotFoundException if module {@code target} does not exist in the list.
+ */
+ public void replaceModule(Module target, Module editedModule) throws DuplicateModuleException,
+ ModuleNotFoundException {
+ requireAllNonNull(target, editedModule);
+
+ int index = moduleList.indexOf(target);
+ if (index == -1) {
+ throw new ModuleNotFoundException();
+ }
+ if (containsModule(editedModule) && !editedModule.isSameModule(target)) {
+ throw new DuplicateModuleException();
+ }
+ moduleList.set(index, editedModule);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return moduleList.iterator();
+ }
+
+ @Override
+ public int hashCode() {
+ return moduleList.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object otherMod) {
+ return otherMod == this
+ || (otherMod instanceof DistinctModuleList
+ && moduleList.equals(((DistinctModuleList) otherMod).moduleList));
+ }
+
+ public ObservableList getUnmodifiableModuleList() {
+ return unmodifiableModuleList;
+ }
+}
diff --git a/src/main/java/seedu/address/model/module/Module.java b/src/main/java/seedu/address/model/module/Module.java
new file mode 100644
index 00000000000..f1c25aaa1f3
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/Module.java
@@ -0,0 +1,232 @@
+package seedu.address.model.module;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import seedu.address.logic.commands.EditModuleCommand;
+import seedu.address.model.module.exceptions.NullModuleCodeException;
+
+/**
+ * Module class represents a Module being taken.
+ */
+public class Module implements Comparable {
+
+ public static final String MESSAGE_NO_TASKS_FOR_MODULE = "You have no tasks for this module";
+
+ private final ModuleCode moduleCode;
+ private int totalNumOfTasks;
+ private int numOfCompletedTasks;
+ private final ModuleName moduleName;
+ private final ModuleCredit moduleCredit;
+
+ /**
+ * Constructor of the Module class.
+ * Module code must be present.
+ *
+ * @param moduleCode The module code of the module.
+ * @param moduleName The module name of the module.
+ * @param moduleCredit The module credit of the module.
+ */
+ public Module(ModuleCode moduleCode, ModuleName moduleName, ModuleCredit moduleCredit) {
+ requireAllNonNull(moduleCode, moduleName, moduleCredit);
+ this.moduleCode = moduleCode;
+ this.moduleName = moduleName;
+ this.moduleCredit = moduleCredit;
+ this.totalNumOfTasks = 0;
+ this.numOfCompletedTasks = 0;
+ }
+
+ /**
+ * Constructor of the Module class.
+ * Module code must be present.
+ *
+ * @param moduleCode The module code of the module.
+ */
+ public Module(ModuleCode moduleCode) {
+ requireNonNull(moduleCode);
+ this.moduleCode = moduleCode;
+ this.moduleName = null;
+ this.moduleCredit = null;
+ this.numOfCompletedTasks = 0;
+ this.totalNumOfTasks = 0;
+ }
+
+ /**
+ * Constructor of the Module class.
+ * Module code, number of completed tasks and total number of tasks must be specified.
+ *
+ * @param moduleCode The module code of the module.
+ * @param moduleName The name of the module.
+ * @param moduleCredit The number of module credit of the module.
+ * @param numOfCompletedTasks The number of completed tasks the module has.
+ * @param totalNumOfTasks The total number of tasks the module has.
+ */
+ private Module(ModuleCode moduleCode, ModuleName moduleName, ModuleCredit moduleCredit,
+ int numOfCompletedTasks, int totalNumOfTasks) {
+ requireAllNonNull(moduleCode, moduleName, moduleCredit);
+ this.moduleCode = moduleCode;
+ this.moduleName = moduleName;
+ this.moduleCredit = moduleCredit;
+ this.numOfCompletedTasks = numOfCompletedTasks;
+ this.totalNumOfTasks = totalNumOfTasks;
+ }
+
+ public ModuleCode getModuleCode() {
+ return moduleCode;
+ }
+
+ public ModuleName getModuleName() {
+ return moduleName;
+ }
+
+ public ModuleCredit getModuleCredit() {
+ return moduleCredit;
+ }
+
+ public int getNumOfCompletedTasks() {
+ return numOfCompletedTasks;
+ }
+
+ public int getTotalNumOfTasks() {
+ return totalNumOfTasks;
+ }
+
+
+ /**
+ * Checks whether two modules have the same module code.
+ *
+ * @param otherModule The other module being compared against.
+ * @return true if the two Module objects have the same module code;
+ * else return false
+ */
+ public boolean isSameModule(Module otherModule) {
+ return this.equals(otherModule);
+ }
+
+ /**
+ * Checks whether the two modules has the exact same fields.
+ *
+ * @param otherModule The other module being compared against.
+ * @return true if the two Module objects have the same module code, module name and module credit;
+ * else return false
+ */
+ public boolean hasAllSameFields(Module otherModule) {
+ return hasSameModuleCode(otherModule) && hasSameModuleName(otherModule) && hasSameModuleCredit(otherModule);
+ }
+
+ /**
+ * Checks whether the two modules has the same module code.
+ *
+ * @param otherModule The other module being compared against.
+ * @return true if the two Module objects have the same module code;
+ * else return false
+ * @throws NullModuleCodeException if the current module or other module {@code otherModule}
+ * has a null module code field.
+ */
+ private boolean hasSameModuleCode(Module otherModule) throws NullModuleCodeException {
+ requireNonNull(otherModule);
+ if (moduleCode == null || otherModule.moduleCode == null) {
+ throw new NullModuleCodeException();
+ }
+ return moduleCode.equals(otherModule.moduleCode);
+ }
+
+ /**
+ * Checks whether the two modules has the same module name.
+ *
+ * @param otherModule The other module being compared against.
+ * @return true if the two Module objects have the same module name;
+ * else return false
+ */
+ private boolean hasSameModuleName(Module otherModule) {
+ if (moduleName != null && otherModule.moduleName != null) {
+ return moduleName.equals(otherModule.moduleName);
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the two modules has the same module credit.
+ *
+ * @param otherModule The other module being compared against.
+ * @return true if the two Module objects have the same module credit;
+ * else return false
+ */
+ private boolean hasSameModuleCredit(Module otherModule) {
+ if (moduleCredit != null && otherModule.moduleCredit != null) {
+ return moduleCredit.equals(otherModule.moduleCredit);
+ }
+ return false;
+ }
+
+ /**
+ * Creates and returns a {@code Module} with the details of {@code this}
+ * edited with {@code editModuleDescriptor}.
+ */
+ public Module edit(EditModuleCommand.EditModuleDescriptor editModuleDescriptor) {
+ requireNonNull(editModuleDescriptor);
+
+ ModuleCode updatedModuleCode = editModuleDescriptor.getModuleCode().orElse(this.moduleCode);
+ ModuleName updatedModuleName = editModuleDescriptor.getModuleName().orElse(this.moduleName);
+ ModuleCredit updatedModuleCredit = editModuleDescriptor.getModuleCredit().orElse(this.moduleCredit);
+
+ return new Module(updatedModuleCode, updatedModuleName, updatedModuleCredit,
+ this.numOfCompletedTasks, this.totalNumOfTasks);
+ }
+
+ public Module setTotalNumOfTasks(Integer numOfTasks) {
+ return new Module(this.moduleCode, this.moduleName, this.moduleCredit, this.numOfCompletedTasks, numOfTasks);
+ }
+
+ public Module setNumOfCompletedTasks(Integer numOfCompletedTasks) {
+ return new Module(this.moduleCode, this.moduleName, this.moduleCredit,
+ numOfCompletedTasks, this.totalNumOfTasks);
+ }
+
+ /**
+ * Returns the percentage of tasks completed for the module.
+ */
+ public double getPercentageCompleted() {
+ return (double) numOfCompletedTasks / (double) totalNumOfTasks;
+ }
+
+ /**
+ * Returns a string representation of the number of completed tasks and number of total tasks.
+ */
+ public String generateProgressMessage() {
+ if (totalNumOfTasks == 0) {
+ return MESSAGE_NO_TASKS_FOR_MODULE;
+ } else {
+ return numOfCompletedTasks + " / " + totalNumOfTasks + " task(s) completed";
+ }
+ }
+
+ public boolean hasTasks() {
+ return totalNumOfTasks > 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Module)) {
+ return false;
+ }
+
+ Module otherModule = (Module) other;
+
+ return hasSameModuleCode(otherModule);
+ }
+
+ @Override
+ public String toString() {
+ return getModuleCode().toString();
+ }
+
+ @Override
+ public int compareTo(Module mod) {
+ return this.getModuleCode().moduleCode.compareTo(mod.getModuleCode().moduleCode);
+ }
+}
diff --git a/src/main/java/seedu/address/model/module/ModuleCode.java b/src/main/java/seedu/address/model/module/ModuleCode.java
new file mode 100644
index 00000000000..7ca86e6db0b
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/ModuleCode.java
@@ -0,0 +1,49 @@
+package seedu.address.model.module;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * ModuleCode represents the module code of the module.
+ */
+public class ModuleCode {
+ public static final String MODULE_CODE_CONSTRAINTS = "Module code should be at least 6 "
+ + "characters long, with first two characters "
+ + "being alphabetical characters and the rest of the characters being alphanumeric characters";
+ public static final String VALIDATION_REGEX = "^[A-Za-z]{2}[A-Za-z0-9]*";
+
+ public final String moduleCode;
+
+ /**
+ * The constructor of the ModuleCode class which stores
+ * the module code of the module.
+ *
+ * @param moduleCode The module code of the module.
+ */
+ public ModuleCode(String moduleCode) {
+ requireNonNull(moduleCode);
+ checkArgument(isValidModuleCode(moduleCode), MODULE_CODE_CONSTRAINTS);
+ this.moduleCode = moduleCode;
+ }
+
+ public static boolean isValidModuleCode(String testModuleCode) {
+ return testModuleCode.matches(VALIDATION_REGEX) && testModuleCode.length() >= 6;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this || (obj instanceof ModuleCode
+ && moduleCode.equalsIgnoreCase((((ModuleCode) obj).moduleCode)));
+ }
+
+ @Override
+ public String toString() {
+ return moduleCode;
+ }
+
+ @Override
+ public int hashCode() {
+ return moduleCode.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..2ab23c7ef1a
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/ModuleCodeContainsKeywordsPredicate.java
@@ -0,0 +1,33 @@
+package seedu.address.model.module;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+
+/**
+ * Tests that a {@code Module}'s {@code ModuleCode} matches any of the keywords given.
+ */
+public class ModuleCodeContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public ModuleCodeContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Module module) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsPartialWord(module.getModuleCode()
+ .moduleCode.toLowerCase(), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ModuleCodeContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((ModuleCodeContainsKeywordsPredicate) other).keywords)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/module/ModuleCredit.java b/src/main/java/seedu/address/model/module/ModuleCredit.java
new file mode 100644
index 00000000000..abd62047916
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/ModuleCredit.java
@@ -0,0 +1,49 @@
+package seedu.address.model.module;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * ModuleCredit class represents the number of module credit of the module.
+ */
+public class ModuleCredit {
+ public static final String MODULE_CREDIT_CONSTRAINTS =
+ "The module credit of a module should be a integer from 0 to 45";
+
+ public final int moduleCredit;
+
+ /**
+ * The constructor of the ModuleCredit class. Sets the
+ * number of module credit of the module.
+ *
+ * @param moduleCredit The number of module credit the class has.
+ */
+ public ModuleCredit(int moduleCredit) {
+ requireNonNull(moduleCredit);
+ checkArgument(isValidModuleCredit(moduleCredit), MODULE_CREDIT_CONSTRAINTS);
+ this.moduleCredit = moduleCredit;
+ }
+
+ public static boolean isValidModuleCredit(int moduleCredit) {
+ return moduleCredit >= 0 && moduleCredit <= 45;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || (other instanceof ModuleCredit
+ && moduleCredit == ((ModuleCredit) other).moduleCredit);
+ }
+
+ @Override
+ public int hashCode() {
+ return moduleCredit;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(moduleCredit);
+ }
+}
+
+
+
diff --git a/src/main/java/seedu/address/model/module/ModuleName.java b/src/main/java/seedu/address/model/module/ModuleName.java
new file mode 100644
index 00000000000..8d5abfe8904
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/ModuleName.java
@@ -0,0 +1,48 @@
+package seedu.address.model.module;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * ModuleName class represents the name of the module.
+ */
+public class ModuleName {
+ public static final String MODULE_NAME_CONSTRAINTS =
+ "The name of the module should not be empty.";
+
+ public final String moduleName;
+
+ /**
+ * The constructor of the ModuleName class. Sets the
+ * string name of the module.
+ *
+ * @param moduleName The string name of the module.
+ */
+ public ModuleName(String moduleName) {
+ requireNonNull(moduleName);
+ checkArgument(isValidModuleName(moduleName), MODULE_NAME_CONSTRAINTS);
+ this.moduleName = moduleName;
+ }
+
+ public static boolean isValidModuleName(String moduleName) {
+ return moduleName.strip().length() > 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || (other instanceof ModuleName
+ && moduleName.equalsIgnoreCase((((ModuleName) other).moduleName)));
+ }
+
+ @Override
+ public int hashCode() {
+ return moduleName.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return moduleName;
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java b/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java
new file mode 100644
index 00000000000..3faa3293759
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.module.exceptions;
+
+/**
+ * DuplicateModuleException class represents an exception that there
+ * are duplicate modules being added.
+ */
+public class DuplicateModuleException extends RuntimeException {
+ public DuplicateModuleException() {
+ super("This would result in duplicate modules being added.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java b/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java
new file mode 100644
index 00000000000..52abdad64ea
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.module.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified module.
+ */
+public class ModuleNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/module/exceptions/NullModuleCodeException.java b/src/main/java/seedu/address/model/module/exceptions/NullModuleCodeException.java
new file mode 100644
index 00000000000..3adeb07021b
--- /dev/null
+++ b/src/main/java/seedu/address/model/module/exceptions/NullModuleCodeException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.module.exceptions;
+
+/**
+ * Signals that a module object was instantiated without a module code field.
+ */
+public class NullModuleCodeException extends RuntimeException {
+ public NullModuleCodeException() {
+ super("A module should have been instantiated with a ModuleCode field.");
+ }
+}
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 60472ca22a0..00000000000
--- a/src/main/java/seedu/address/model/person/Address.java
+++ /dev/null
@@ -1,57 +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) {
- return other == this // short circuit if same object
- || (other instanceof Address // instanceof handles nulls
- && value.equals(((Address) other).value)); // state check
- }
-
- @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 f866e7133de..00000000000
--- a/src/main/java/seedu/address/model/person/Email.java
+++ /dev/null
@@ -1,71 +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) {
- return other == this // short circuit if same object
- || (other instanceof Email // instanceof handles nulls
- && value.equals(((Email) other).value)); // state check
- }
-
- @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 79244d71cf7..00000000000
--- a/src/main/java/seedu/address/model/person/Name.java
+++ /dev/null
@@ -1,59 +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) {
- return other == this // short circuit if same object
- || (other instanceof Name // instanceof handles nulls
- && fullName.equals(((Name) other).fullName)); // state check
- }
-
- @Override
- public int hashCode() {
- return fullName.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
deleted file mode 100644
index c9b5868427c..00000000000
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package seedu.address.model.person;
-
-import java.util.List;
-import java.util.function.Predicate;
-
-import seedu.address.commons.util.StringUtil;
-
-/**
- * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
- */
-public class NameContainsKeywordsPredicate implements Predicate {
- private final List keywords;
-
- public NameContainsKeywordsPredicate(List keywords) {
- this.keywords = keywords;
- }
-
- @Override
- public boolean test(Person person) {
- return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls
- && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check
- }
-
-}
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 8ff1d83fe89..00000000000
--- a/src/main/java/seedu/address/model/person/Person.java
+++ /dev/null
@@ -1,123 +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.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;
- }
-
- if (!(other instanceof Person)) {
- return false;
- }
-
- Person otherPerson = (Person) other;
- return otherPerson.getName().equals(getName())
- && otherPerson.getPhone().equals(getPhone())
- && otherPerson.getEmail().equals(getEmail())
- && otherPerson.getAddress().equals(getAddress())
- && otherPerson.getTags().equals(getTags());
- }
-
- @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() {
- final StringBuilder builder = new StringBuilder();
- builder.append(getName())
- .append("; Phone: ")
- .append(getPhone())
- .append("; Email: ")
- .append(getEmail())
- .append("; Address: ")
- .append(getAddress());
-
- Set tags = getTags();
- if (!tags.isEmpty()) {
- builder.append("; Tags: ");
- tags.forEach(builder::append);
- }
- return builder.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 872c76b382f..00000000000
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ /dev/null
@@ -1,53 +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) {
- return other == this // short circuit if same object
- || (other instanceof Phone // instanceof handles nulls
- && value.equals(((Phone) other).value)); // state check
- }
-
- @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 0fee4fe57e6..00000000000
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ /dev/null
@@ -1,137 +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) {
- return other == this // short circuit if same object
- || (other instanceof UniquePersonList // instanceof handles nulls
- && internalList.equals(((UniquePersonList) other).internalList));
- }
-
- @Override
- public int hashCode() {
- return internalList.hashCode();
- }
-
- /**
- * 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/DeadlineTag.java b/src/main/java/seedu/address/model/tag/DeadlineTag.java
new file mode 100644
index 00000000000..8976df1478f
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/DeadlineTag.java
@@ -0,0 +1,80 @@
+package seedu.address.model.tag;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Deadline Tag represents a tag which contains the deadline for the task
+ */
+public class DeadlineTag implements Comparable {
+ public static final String DEADLINE_TAG_FORMAT_CONSTRAINTS =
+ "The deadline should be in the format DD-MM-YYYY. DD should be between "
+ + "1 and 31(both inclusive)\nand MM "
+ + "should be between 1 and 12(both inclusive)";
+ public static final String DEADLINE_TAG_INVALID_DATE =
+ "The deadline should be a valid date";
+ public static final String DEADLINE_TAG_DATE_HAS_PASSED =
+ "The deadline should not be earlier than today's date.";
+ private static final String dateFormat = "^(?:0[1-9]|[1-2][0-9]|3[0-1])[-]"
+ + "(?:0[1-9]|1[0-2])[-][0-9]{4}";
+ private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy");
+
+ public final LocalDate deadline;
+
+ /**
+ * Constructor of the DeadlineTag. Sets the deadline of the
+ * tag.
+ *
+ * @param deadline The deadline of the tag.
+ */
+ public DeadlineTag(LocalDate deadline) {
+ requireNonNull(deadline);
+ this.deadline = deadline;
+ }
+
+ /**
+ * Checks that the format of the date is correct
+ *
+ * @param date The date string being checked
+ * @return true if the format of string is correct; else return false;
+ */
+ public static boolean checkDateFormat(String date) {
+ return date.matches(dateFormat);
+ }
+
+ /**
+ * Checks whether the deadline given is valid.
+ *
+ * @param testDateAdded The date being checked for validity.
+ * @return true if the date is valid; else return false.
+ */
+ public static boolean isValidDeadline(LocalDate testDateAdded) {
+ if (testDateAdded == null) {
+ return false;
+ }
+ return testDateAdded.compareTo(LocalDate.now()) >= 0;
+ }
+
+ @Override
+ public boolean equals(Object otherDate) {
+ return otherDate == this || (otherDate instanceof DeadlineTag
+ && deadline.equals(((DeadlineTag) otherDate).deadline));
+ }
+
+ @Override
+ public String toString() {
+ return deadline.format(dtf);
+ }
+
+ @Override
+ public int hashCode() {
+ return deadline.hashCode();
+ }
+
+ @Override
+ public int compareTo(DeadlineTag otherDeadlineTag) {
+ return this.deadline.compareTo(otherDeadlineTag.deadline);
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/PriorityTag.java b/src/main/java/seedu/address/model/tag/PriorityTag.java
new file mode 100644
index 00000000000..dd31e2a4e82
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/PriorityTag.java
@@ -0,0 +1,84 @@
+package seedu.address.model.tag;
+
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Priority Tag represents a tag which contains the priority status of the task.
+ */
+public class PriorityTag implements Comparable {
+ public static final String PRIORITY_TAG_CONSTRAINTS =
+ "Priority status of tag should only be HIGH, MEDIUM or LOW";
+ public static final String VALIDATION_REGEX = "high|low|medium";
+ public final String status;
+
+ /**
+ * Constructor of the priority tag. Sets the priority
+ * status to the tag.
+ *
+ * @param status The priority status of the tag.
+ */
+ public PriorityTag(String status) {
+ requireNonNull(status);
+ checkArgument(isValidTag(status), PRIORITY_TAG_CONSTRAINTS);
+ this.status = status;
+ }
+
+ /**
+ * Checks whether the priority status in the tag is valid.
+ *
+ * @param testTag The tag that is being checked.
+ * @return true if the priority status is valid; else return false.
+ */
+ public static boolean isValidTag(String testTag) {
+ if (testTag == null) {
+ return false;
+ }
+ String testTagStatus = testTag.toLowerCase();
+ return testTagStatus.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public int compareTo(PriorityTag otherPriorityTag) {
+ if (this.status.equalsIgnoreCase("high")) {
+ if (otherPriorityTag.status.equalsIgnoreCase("high")) {
+ return 0;
+ }
+ return -1;
+ }
+ if (this.status.equalsIgnoreCase("medium")) {
+ if (otherPriorityTag.status.equalsIgnoreCase("high")) {
+ return 1;
+ }
+ if (otherPriorityTag.status.equalsIgnoreCase("medium")) {
+ return 0;
+ }
+ return -1;
+ }
+ if (this.status.equalsIgnoreCase("low")) {
+ if (otherPriorityTag.status.equalsIgnoreCase("low")) {
+ return 0;
+ }
+ return 1;
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean equals(Object otherTag) {
+ return otherTag == this || (otherTag instanceof PriorityTag
+ && status.equalsIgnoreCase((((PriorityTag) otherTag).status)));
+ }
+
+ @Override
+ public String toString() {
+ return status;
+ }
+
+ @Override
+ public int hashCode() {
+ return status.hashCode();
+ }
+}
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 b0ea7e7dad7..00000000000
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ /dev/null
@@ -1,54 +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) {
- return other == this // short circuit if same object
- || (other instanceof Tag // instanceof handles nulls
- && tagName.equals(((Tag) other).tagName)); // state check
- }
-
- @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/tag/exceptions/BothTagsCannotBeNullException.java b/src/main/java/seedu/address/model/tag/exceptions/BothTagsCannotBeNullException.java
new file mode 100644
index 00000000000..fe4a754897f
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/BothTagsCannotBeNullException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * BothTagsCannotBeNullException is an exception which is thrown when both priority tag
+ * and the deadline tag are null in AddTagCommand method
+ */
+public class BothTagsCannotBeNullException extends RuntimeException {
+ public BothTagsCannotBeNullException() {
+ super("Both the priority tag and the deadline tag cannot be null!");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagAlreadyExistsException.java b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagAlreadyExistsException.java
new file mode 100644
index 00000000000..1a524970cfe
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagAlreadyExistsException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * DeadlineTagAlreadyExistsException is an exception which is
+ * thrown when the task already contains the deadline.
+ */
+public class DeadlineTagAlreadyExistsException extends RuntimeException {
+ public DeadlineTagAlreadyExistsException() {
+ super("There is already a deadline for the task.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagDoesNotExistException.java b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagDoesNotExistException.java
new file mode 100644
index 00000000000..4af61e083b0
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagDoesNotExistException.java
@@ -0,0 +1,15 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * DeadlineTagDoesNotExist is an exception which is thrown when the deadline tag
+ * does not exist.
+ */
+public class DeadlineTagDoesNotExistException extends RuntimeException {
+ /**
+ * The constructor of DeadlineTagDoesNotExist. Sets the message that
+ * the deadline tag does not exist.
+ */
+ public DeadlineTagDoesNotExistException() {
+ super("The deadline tag does not exist.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagUnchangedException.java b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagUnchangedException.java
new file mode 100644
index 00000000000..0b107619bca
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/DeadlineTagUnchangedException.java
@@ -0,0 +1,16 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * DeadlineTagUnchangedException is an exception which is thrown when the
+ * newly provided deadline tag is the same as the existing deadline tag.
+ */
+public class DeadlineTagUnchangedException extends RuntimeException {
+ /**
+ * The constructor of the DeadlineTagUnchangedException. Sets the message for the
+ * exception.
+ */
+ public DeadlineTagUnchangedException() {
+ super("The deadline provided is"
+ + " the same as the current deadline for the task.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/PriorityTagAlreadyExistsException.java b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagAlreadyExistsException.java
new file mode 100644
index 00000000000..ce27c39b89b
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagAlreadyExistsException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * PriorityTagAlreadyExistsException is an exception which is
+ * thrown when the task already contains the priority status.
+ */
+public class PriorityTagAlreadyExistsException extends RuntimeException {
+ public PriorityTagAlreadyExistsException() {
+ super("The priority status already exists for the task.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/PriorityTagDoesNotExistException.java b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagDoesNotExistException.java
new file mode 100644
index 00000000000..7b6efe7d087
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagDoesNotExistException.java
@@ -0,0 +1,15 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * PriorityTagDoesNotExistException is an exception which is thrown when the priority
+ * tag does not exist.
+ */
+public class PriorityTagDoesNotExistException extends RuntimeException {
+ /**
+ * The constructor of PriorityTagDoesNotExist. Sets the message of priority tag
+ * does not exist.
+ */
+ public PriorityTagDoesNotExistException() {
+ super("The priority tag does not exist.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/PriorityTagUnchangedException.java b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagUnchangedException.java
new file mode 100644
index 00000000000..192c1577810
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/PriorityTagUnchangedException.java
@@ -0,0 +1,16 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * PriorityTagUnchangedException is an exception which is thrown when the
+ * newly provided priority tag is the same as the current priority tag.
+ */
+public class PriorityTagUnchangedException extends RuntimeException {
+ /**
+ * The constructor of the PriorityTagUnchangedException. Sets the message for the
+ * exception.
+ */
+ public PriorityTagUnchangedException() {
+ super("The priority status provided is "
+ + "the same as the current priority status for the task.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..e0b075f1b1a
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java
@@ -0,0 +1,31 @@
+package seedu.address.model.task;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+/**
+ * Tests that a {@code Task}'s {@code TaskDescription} matches any of the keywords given.
+ */
+public class DescriptionContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public DescriptionContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsPartialWord(task.getDescription()
+ .description.toLowerCase(), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DescriptionContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((DescriptionContainsKeywordsPredicate) other).keywords)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/task/DistinctTaskList.java b/src/main/java/seedu/address/model/task/DistinctTaskList.java
new file mode 100644
index 00000000000..0381f7004d1
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/DistinctTaskList.java
@@ -0,0 +1,268 @@
+package seedu.address.model.task;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.Criteria;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.exceptions.DuplicateTaskException;
+import seedu.address.model.task.exceptions.TaskNotFoundException;
+import seedu.address.model.task.exceptions.WrongTaskModifiedException;
+
+/**
+ * This class represents a list which contains Tasks objects which are distinct from
+ * each other. Tasks are distinct from each other when they have different descriptions and
+ * module codes.
+ */
+public class DistinctTaskList implements Iterable {
+
+ public final ObservableList taskList = FXCollections.observableArrayList();
+ public final ObservableList unmodifiableTaskList = FXCollections
+ .unmodifiableObservableList(taskList);
+
+ /**
+ * Returns true if the list contains an equivalent task as the given argument.
+ */
+ public boolean contains(Task toCheck) {
+ requireNonNull(toCheck);
+ return taskList.stream().anyMatch(toCheck::isSameTask);
+ }
+
+ /**
+ * Returns true if the list contains a task with an equivalent module as the given argument.
+ */
+ public boolean containsModule(Module toCheck) {
+ requireNonNull(toCheck);
+ return taskList.stream().map(Task::getModule).anyMatch(toCheck::isSameModule);
+ }
+
+ /**
+ * Adds the task to the taskList.
+ * The task must not already exist in the list.
+ *
+ * @param taskAdded The task to be added.
+ */
+ public void addTask(Task taskAdded) {
+ requireNonNull(taskAdded);
+ //Might have to add address book as param to check if addressBook already has module
+ if (contains(taskAdded)) {
+ throw new DuplicateTaskException();
+ }
+ taskList.add(taskAdded);
+ }
+
+ /**
+ * Replaces the given task {@code target} with {@code editedTask}.
+ * {@code target} must exist in the task list.
+ * If {@code isSameTask} is true, the task identity of {@code editedTask} should be the same as {@code target}.
+ *
+ * @param target the task to be replaced.
+ * @param editedTask the edited task to replace {@code target}.
+ * @param isSameTask true if {@code target} has the same task identity as {@code editedTask}, false otherwise.
+ * @throws DuplicateTaskException if {@code isSameTask} is false but task identity of {@code editedTask}
+ * is the same as another task in the list (other than {@code target}).
+ */
+ public void replaceTask(Task target, Task editedTask, boolean isSameTask) throws DuplicateTaskException {
+ requireAllNonNull(target, editedTask, isSameTask);
+
+ int index = taskList.indexOf(target);
+ if (index == -1) {
+ throw new TaskNotFoundException();
+ }
+
+ if (isSameTask && !target.isSameTask(editedTask)) {
+ throw new WrongTaskModifiedException();
+ }
+
+ boolean isDuplicateTask = contains(editedTask) && !editedTask.isSameTask(target);
+ if (!isSameTask && isDuplicateTask) {
+ throw new DuplicateTaskException();
+ }
+
+ taskList.set(index, editedTask);
+ }
+
+ /**
+ * Unlinks all tasks that are currently linked to {@code exam} and returns a list of these tasks.
+ * @param exam the exam for the tasks to be unlinked from
+ */
+ public List unlinkTasksFromExam(Exam exam) {
+ requireNonNull(exam);
+ List matchedTasks = new ArrayList();
+ taskList.forEach(task-> {
+ if (task.isLinked() && task.getExam().equals(exam)) {
+ Task unlinkedTask = task.unlinkTask();
+ matchedTasks.add(unlinkedTask);
+ replaceTask(task, unlinkedTask, true);
+ }
+ });
+ return matchedTasks;
+ }
+
+ /**
+ * Returns true if {@code examToEdit} is linked to any task, otherwise false.
+ */
+ public boolean isExamLinkedToTask(Exam exam) {
+ requireNonNull(exam);
+ for (int i = 0; i < taskList.size(); i++) {
+ if (taskList.get(i).isLinked() && taskList.get(i).getExam().equals(exam)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Replaces task by changing its given exam field from {@code previousExam}
+ * to {@code newExam} for tasks that have their exam field as {@code previousExam}.
+ * @param previousExam The exam in the task's exam field.
+ * @param newExam The new exam which will replace the previous exam in the task's exam field.
+ */
+ public void updateExamFieldForTask(Exam previousExam, Exam newExam) {
+ requireAllNonNull(previousExam, newExam);
+ taskList.forEach(task-> {
+ if (task.isLinked() && task.getExam().equals(previousExam)) {
+ Task editedTask = task.linkTask(newExam);
+ replaceTask(task, editedTask, true);
+ }
+ });
+ }
+
+ /**
+ * Replaces task by changing its given module field from {@code previousModule}
+ * to {@code newModule} for tasks that have their module field as {@code previousModule}.
+ * @param previousModule The module in the task's module field.
+ * @param newModule The new module which will replace the previous module in the task's module field.
+ */
+ public void updateModuleFieldForTask(Module previousModule, Module newModule) {
+ requireAllNonNull(previousModule, newModule);
+ taskList.forEach(task-> {
+ if (task.getModule().equals(previousModule)) {
+ Task editedTask = task.editWithoutUnlinkingExam(newModule, null);
+ replaceTask(task, editedTask, false);
+ }
+ });
+ }
+
+ /**
+ * Links all tasks in {@code tasks} to {@code exam}.
+ * @param exam The exam to link to.
+ * @param tasks The list of tasks to link each task to {@code exam}.
+ */
+ public void linkTasksToExam(Exam exam, List tasks) {
+ tasks.forEach(task-> {
+ Task linkedTask = task.linkTask(exam);
+ replaceTask(task, linkedTask, true);
+ });
+ }
+
+ /**
+ * Removes the equivalent task from the tasklist.
+ * The task must exist in the list.
+ */
+ public void remove(Task toRemove) {
+ requireNonNull(toRemove);
+ if (!taskList.remove(toRemove)) {
+ throw new TaskNotFoundException();
+ }
+ }
+
+ /**
+ * Removes tasks that have their module field as {@code module} from the tasklist.
+ * @param module The module in the task's module field.
+ */
+ public void deleteTasksWithModule(Module module) {
+ requireAllNonNull(module);
+ for (int i = 0; i < taskList.size(); i++) {
+ Task task = taskList.get(i);
+ if (task.getModule().equals(module)) {
+ remove(task);
+ --i;
+ }
+ }
+ }
+
+ public int getNumOfCompletedModuleTasks(Module module) {
+ requireNonNull(module);
+ return (int) taskList.stream().filter(Task::isComplete).map(Task::getModule)
+ .filter(module::isSameModule).count();
+ }
+
+ public int getTotalNumOfModuleTasks(Module module) {
+ requireNonNull(module);
+ return (int) taskList.stream().map(Task::getModule).filter(module::isSameModule).count();
+ }
+
+ public int getNumOfCompletedExamTasks(Exam exam) {
+ requireNonNull(exam);
+ return (int) taskList.stream().filter(Task::isComplete).map(Task::getExam)
+ .filter(exam::isSameExam).count();
+ }
+
+ public int getTotalNumOfExamTasks(Exam exam) {
+ requireNonNull(exam);
+ return (int) taskList.stream().map(Task::getExam).filter(exam::isSameExam).count();
+ }
+
+ /**
+ * Sorts the tasks stored in the task list.
+ *
+ * @param criteria The criteria used for sorting.
+ */
+ public void sortTasks(Criteria criteria) {
+
+ //@@author dlimyy-reused
+ //Reused from https://stackoverflow.com/questions/51186174/
+ //with slight modifications
+ switch (criteria.getCriteria().toLowerCase()) {
+ case "priority":
+ FXCollections.sort(taskList, Comparator.comparing(Task::getPriorityTag,
+ Comparator.nullsLast(Comparator.naturalOrder())));
+ break;
+ case "deadline":
+ FXCollections.sort(taskList, Comparator.comparing(Task::getDeadlineTag,
+ Comparator.nullsLast(Comparator.naturalOrder())));
+ break;
+ case "module":
+ FXCollections.sort(taskList, Comparator.comparing(Task::getModule,
+ Comparator.naturalOrder()));
+ break;
+ case "description":
+ FXCollections.sort(taskList, Comparator.comparing(Task::getDescription,
+ Comparator.naturalOrder()));
+ break;
+ default:
+ break;
+ }
+ //@@author
+ }
+
+ @Override
+ public Iterator iterator() {
+ return taskList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object otherTask) {
+ return otherTask == this
+ || (otherTask instanceof DistinctTaskList
+ && taskList.equals(((DistinctTaskList) otherTask).taskList));
+ }
+
+ public ObservableList getUnmodifiableTaskList() {
+ return unmodifiableTaskList;
+ }
+
+ public void setTasks(List tasks) {
+ requireNonNull(tasks);
+ taskList.setAll(tasks);
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/FilterPredicate.java b/src/main/java/seedu/address/model/task/FilterPredicate.java
new file mode 100644
index 00000000000..6e104ededdf
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/FilterPredicate.java
@@ -0,0 +1,90 @@
+package seedu.address.model.task;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import seedu.address.model.module.Module;
+
+/**
+ * Tests that a {@code Task} fulfils the given predicate.
+ */
+public class FilterPredicate implements Predicate {
+ private final Optional moduleToCheck;
+ private final Optional isCompleted;
+ private final Optional isLinked;
+
+ /**
+ * Tests that a {@code Task} matches all the module and status constraints given.
+ * @param module check if task has same module code.
+ * @param isCompleted check if task has same completion status.
+ * @param isLinked check if task has same link status.
+ */
+ public FilterPredicate(Optional module, Optional isCompleted, Optional isLinked) {
+ this.moduleToCheck = module;
+ this.isCompleted = isCompleted;
+ this.isLinked = isLinked;
+ }
+
+ public Module getModuleToCheck() {
+ return moduleToCheck.get();
+ }
+
+ public boolean hasModuleToCheck() {
+ return moduleToCheck.isPresent();
+ }
+
+ public String getCompleteCondition() {
+ if (isCompleted.get()) {
+ return "Complete";
+ } else {
+ return "Incomplete";
+ }
+ }
+
+ public String getLinkCondition() {
+ if (isLinked.get()) {
+ return "Linked";
+ } else {
+ return "Unlinked";
+ }
+ }
+
+ @Override
+ public boolean test(Task task) {
+ boolean result = true;
+ if (moduleToCheck.isPresent()) {
+ result &= task.getModule().equals(moduleToCheck.get());
+ }
+ if (isCompleted.isPresent()) {
+ result &= task.getStatus().isComplete() == isCompleted.get();
+ }
+ if (isLinked.isPresent()) {
+ result &= task.isLinked() == isLinked.get();
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FilterPredicate // instanceof handles nulls
+ && moduleToCheck.equals(((FilterPredicate) other).moduleToCheck)
+ && isCompleted.equals(((FilterPredicate) other).isCompleted)
+ && isLinked.equals(((FilterPredicate) other).isLinked)); // state check
+ }
+
+ @Override
+ public String toString() {
+ String result = "";
+ if (moduleToCheck.isPresent()) {
+ result += " Module: " + moduleToCheck.get();
+ }
+ if (isCompleted.isPresent()) {
+ result += " " + getCompleteCondition();
+ }
+ if (isLinked.isPresent()) {
+ result += " " + getLinkCondition();
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java
new file mode 100644
index 00000000000..59646b6f618
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Task.java
@@ -0,0 +1,358 @@
+package seedu.address.model.task;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAnyNonNull;
+
+import java.util.Objects;
+
+import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+import seedu.address.model.tag.exceptions.DeadlineTagAlreadyExistsException;
+import seedu.address.model.tag.exceptions.DeadlineTagDoesNotExistException;
+import seedu.address.model.tag.exceptions.DeadlineTagUnchangedException;
+import seedu.address.model.tag.exceptions.PriorityTagAlreadyExistsException;
+import seedu.address.model.tag.exceptions.PriorityTagDoesNotExistException;
+import seedu.address.model.tag.exceptions.PriorityTagUnchangedException;
+import seedu.address.model.task.exceptions.TaskAlreadyMarkedException;
+import seedu.address.model.task.exceptions.TaskAlreadyUnmarkedException;
+
+/**
+ * Task class represents a task which stores the module code and the
+ * description of the task.
+ */
+public class Task {
+ private final Module module;
+ private final TaskDescription description;
+ private final PriorityTag priorityTag;
+ private final DeadlineTag deadlineTag;
+ private final TaskStatus status;
+ private final Exam linkedExam;
+
+ /**
+ * The constructor of the Task class. Sets the module and
+ * description of the task.
+ *
+ * @param module The module being added.
+ * @param description The description of the task.
+ */
+ public Task(Module module, TaskDescription description) {
+ this.module = module;
+ this.description = description;
+ status = TaskStatus.INCOMPLETE;
+ priorityTag = null;
+ deadlineTag = null;
+ linkedExam = null;
+ }
+
+ /**
+ * The constructor of the Task class. Sets the module and
+ * description of the task.
+ *
+ * @param module The module being added.
+ * @param description The description of the task.
+ */
+ public Task(Module module, TaskDescription description, TaskStatus status) {
+ this.module = module;
+ this.description = description;
+ this.status = status;
+ priorityTag = null;
+ deadlineTag = null;
+ linkedExam = null;
+ }
+
+ /**
+ * The constructor of the Task class. Sets the module, description,
+ * completion status, the priority status of the task and the deadline
+ * of the task.
+ *
+ * @param module The module being set.
+ * @param description The description being set.
+ * @param status The completion status of the task.
+ * @param priorityTag The tag marking the priority status of the task.
+ * @param deadlineTag The tag marking the deadline of the task.
+ */
+ public Task(Module module, TaskDescription description, TaskStatus status, PriorityTag priorityTag,
+ DeadlineTag deadlineTag) {
+ this.module = module;
+ this.description = description;
+ this.status = status;
+ this.priorityTag = priorityTag;
+ this.deadlineTag = deadlineTag;
+ linkedExam = null;
+ }
+
+ /**
+ * The constructor of the Task class. Sets the module, description,
+ * completion status, the priority status of the task, the deadline
+ * of the task and the exam description of the task.
+ *
+ * @param module The module being set.
+ * @param description The description being set.
+ * @param status The completion status of the task.
+ * @param priorityTag The tag marking the priority status of the task.
+ * @param deadlineTag The tag marking the deadline of the task.
+ * @param linkedExam The exam the task is linked to.
+ */
+ public Task(Module module, TaskDescription description, TaskStatus status, PriorityTag priorityTag,
+ DeadlineTag deadlineTag, Exam linkedExam) {
+ this.module = module;
+ this.description = description;
+ this.status = status;
+ this.priorityTag = priorityTag;
+ this.deadlineTag = deadlineTag;
+ this.linkedExam = linkedExam;
+ }
+
+ public TaskDescription getDescription() {
+ return description;
+ }
+
+ public Module getModule() {
+ return module;
+ }
+
+ public TaskStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * Returns true if both tasks have the same data fields.
+ */
+ public boolean isSameTask(Task otherTask) {
+ if (otherTask == null) {
+ return false;
+ }
+ return otherTask == this || (this.getModule().equals(otherTask.getModule())
+ && this.getDescription().equals(otherTask.getDescription()));
+ }
+
+ /**
+ * Returns true if task is complete.
+ */
+ public boolean isComplete() {
+ return this.status.isComplete();
+ }
+
+ /**
+ * Marks the task as complete and returns the task.
+ *
+ * @return a task with identical fields as {@this}, but labelled as complete.
+ * @throws TaskAlreadyMarkedException if the task is already marked.
+ */
+ public Task mark() {
+ if (isComplete()) {
+ throw new TaskAlreadyMarkedException();
+ }
+ return new Task(module, description, TaskStatus.COMPLETE, priorityTag, deadlineTag, linkedExam);
+ }
+
+ public Task setPriorityTag(PriorityTag tag) {
+ requireNonNull(tag);
+ if (priorityTag != null) {
+ throw new PriorityTagAlreadyExistsException();
+ }
+ return new Task(module, description, status, tag, deadlineTag, linkedExam);
+ }
+
+ /**
+ * Replaces the priority tag stored in the task.
+ *
+ * @param tag The new priority tag.
+ * @return The task which contains the new priority tag.
+ */
+ public Task replacePriorityTag(PriorityTag tag) {
+ requireNonNull(tag);
+ if (priorityTag != null && priorityTag.compareTo(tag) == 0) {
+ throw new PriorityTagUnchangedException();
+ }
+ return new Task(module, description, status, tag, deadlineTag, linkedExam);
+ }
+
+ /**
+ * Deletes the deadline tag stored in the task.
+ *
+ * @return The task which contains no deadline tag.
+ */
+ public Task deletePriorityTag() {
+ if (priorityTag == null) {
+ throw new PriorityTagDoesNotExistException();
+ }
+ return new Task(module, description, status, null, deadlineTag, linkedExam);
+ }
+
+ public boolean hasPriorityTag() {
+ return priorityTag != null;
+ }
+
+ public PriorityTag getPriorityTag() {
+ return priorityTag;
+ }
+
+ public boolean hasDeadlineTag() {
+ return deadlineTag != null;
+ }
+
+ public DeadlineTag getDeadlineTag() {
+ return deadlineTag;
+ }
+
+ public Task setDeadlineTag(DeadlineTag tag) {
+ requireNonNull(tag);
+ if (deadlineTag != null) {
+ throw new DeadlineTagAlreadyExistsException();
+ }
+ return new Task(module, description, status, priorityTag, tag, linkedExam);
+ }
+
+ /**
+ * Replaces the deadline tag stored in the task.
+ *
+ * @param tag The new deadline tag.
+ * @return The task which contains the new deadline tag.
+ */
+ public Task replaceDeadlineTag(DeadlineTag tag) {
+ requireNonNull(tag);
+ if (deadlineTag != null && deadlineTag.compareTo(tag) == 0) {
+ throw new DeadlineTagUnchangedException();
+ }
+ return new Task(module, description, status, priorityTag, tag, linkedExam);
+ }
+
+ /**
+ * Deletes the priority tag stored in the task.
+ *
+ * @return The task which contains no deadline tag.
+ */
+ public Task deleteDeadlineTag() {
+ if (deadlineTag == null) {
+ throw new DeadlineTagDoesNotExistException();
+ }
+ return new Task(module, description, status, priorityTag, null, linkedExam);
+ }
+
+ public boolean isLinked() {
+ return linkedExam != null;
+ }
+
+ public Exam getExam() {
+ return linkedExam;
+ }
+
+ /**
+ * Unmarks (labels as incomplete) the task and returns the task.
+ *
+ * @return a task with identical fields as {@this} but labelled as incomplete.
+ * @throws TaskAlreadyUnmarkedException if the task is already unmarked.
+ */
+ public Task unmark() {
+ if (!isComplete()) {
+ throw new TaskAlreadyUnmarkedException();
+ }
+ return new Task(module, description, TaskStatus.INCOMPLETE, priorityTag, deadlineTag, linkedExam);
+ }
+
+ /**
+ * Creates and returns a {@code Task} with the details of {@code this}
+ * edited with {@code editTaskDescriptor}.
+ */
+ public Task edit(EditTaskDescriptor editTaskDescriptor) {
+ requireNonNull(editTaskDescriptor);
+ Module updatedModule = editTaskDescriptor.getModule().orElse(module);
+ TaskDescription updatedDescription = editTaskDescriptor.getDescription().orElse(description);
+
+ if (editTaskDescriptor.getModule().isPresent()
+ && !editTaskDescriptor.getModule().get().equals(module)) {
+ return new Task(updatedModule, updatedDescription, status, priorityTag, deadlineTag);
+ }
+
+ return new Task(updatedModule, updatedDescription, status, priorityTag, deadlineTag, linkedExam);
+ }
+
+ /**
+ * Creates and returns a {@code Task} with the details of {@code this}
+ * edited with {@code newModule} and {@code newTaskDescription}. Preserves the link between the task
+ * and its corresponding exam even if the module is changed.
+ */
+ public Task editWithoutUnlinkingExam(Module newModule, TaskDescription newTaskDescription) {
+ requireAnyNonNull(newModule, newTaskDescription);
+ Module updatedModule = module;
+ TaskDescription updatedDescription = description;
+ if (newModule != null) {
+ updatedModule = newModule;
+ }
+ if (newTaskDescription != null) {
+ updatedDescription = newTaskDescription;
+ }
+ return new Task(updatedModule, updatedDescription, status, priorityTag, deadlineTag, linkedExam);
+ }
+
+ /**
+ * Links the task to the exam in the exam list.
+ *
+ * @param exam The exam which the task will be linked to.
+ * @return Task object which now contains the linked exam.
+ */
+ public Task linkTask(Exam exam) {
+ requireNonNull(exam);
+ return new Task(module, description, status, priorityTag, deadlineTag, exam);
+ }
+
+ public Task unlinkTask() {
+ return new Task(module, description, status, priorityTag, deadlineTag, null);
+ }
+
+ /**
+ * Checks whether the two tasks have the exact same fields.
+ *
+ * @param otherTask The other task being compared against.
+ * @return true if the two Task objects have the same module, description, status, priority tag, deadline tag and
+ * linked exam, else return false.
+ */
+ public boolean hasAllSameFields(Task otherTask) {
+ return this.module.equals(otherTask.module)
+ && this.description.equals(otherTask.description)
+ && this.status.equals(otherTask.status)
+ && (!hasPriorityTag() && !otherTask.hasPriorityTag()
+ || this.priorityTag.equals(otherTask.priorityTag))
+ && (!hasDeadlineTag() && !otherTask.hasDeadlineTag()
+ || this.deadlineTag.equals(otherTask.deadlineTag))
+ && (!isLinked() && !otherTask.isLinked()
+ || this.linkedExam.equals(otherTask.linkedExam));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Task)) {
+ return false;
+ }
+
+ Task otherTask = (Task) other;
+ return otherTask.getDescription().equals(getDescription())
+ && otherTask.getModule().equals(getModule())
+ && otherTask.getStatus().equals(getStatus())
+ && Objects.equals(otherTask.getDeadlineTag(), getDeadlineTag())
+ && Objects.equals(otherTask.getPriorityTag(), getPriorityTag());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(description, module);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Description: ")
+ .append(getDescription())
+ .append("; Module: ")
+ .append(getModule());
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/TaskDescription.java b/src/main/java/seedu/address/model/task/TaskDescription.java
new file mode 100644
index 00000000000..2d24ea2c823
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskDescription.java
@@ -0,0 +1,53 @@
+package seedu.address.model.task;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * TaskDescription class represents the description of the task.
+ */
+public class TaskDescription implements Comparable {
+ public static final String DESCRIPTION_CONSTRAINTS =
+ "The description of the task should not be empty";
+
+ public final String description;
+
+ /**
+ * The constructor of the TaskDescription class. Sets the
+ * description of the class.
+ *
+ * @param description The description of the class.
+ */
+ public TaskDescription(String description) {
+ requireNonNull(description);
+ checkArgument(isValidDescription(description), DESCRIPTION_CONSTRAINTS);
+ this.description = description;
+ }
+
+ public static boolean isValidDescription(String description) {
+ return description.strip().length() > 0;
+ }
+
+ @Override
+ public boolean equals(Object otherDescription) {
+ return otherDescription == this || (otherDescription instanceof TaskDescription
+ && description.equalsIgnoreCase((((TaskDescription) otherDescription).description)));
+ }
+
+ @Override
+ public int hashCode() {
+ return description.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+
+ @Override
+ public int compareTo(TaskDescription otherDescription) {
+ return this.description.compareTo(otherDescription.description);
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/model/task/TaskLinkedToExamPredicate.java b/src/main/java/seedu/address/model/task/TaskLinkedToExamPredicate.java
new file mode 100644
index 00000000000..eb4600e4a00
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskLinkedToExamPredicate.java
@@ -0,0 +1,32 @@
+package seedu.address.model.task;
+
+import java.util.function.Predicate;
+
+import seedu.address.model.exam.Exam;
+
+/**
+ * Tests that a {@code Task} fulfils the given predicate.
+ */
+public class TaskLinkedToExamPredicate implements Predicate {
+ private final Exam examToCheck;
+
+ /**
+ * Tests that a {@code Task} is linked to a specific exam.
+ * @param exam Exam to be checked if task is linked with.
+ */
+ public TaskLinkedToExamPredicate(Exam exam) {
+ this.examToCheck = exam;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ return task.isLinked() && task.getExam().equals(examToCheck);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskLinkedToExamPredicate // instanceof handles nulls
+ && examToCheck.equals(((TaskLinkedToExamPredicate) other).examToCheck)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/TaskStatus.java b/src/main/java/seedu/address/model/task/TaskStatus.java
new file mode 100644
index 00000000000..0421a1bf054
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskStatus.java
@@ -0,0 +1,82 @@
+package seedu.address.model.task;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * TaskStatus class represents the status of the task - whether it is complete.
+ */
+public class TaskStatus {
+
+ private static final String COMPLETE_STRING = "complete";
+ private static final String INCOMPLETE_STRING = "incomplete";
+
+ public static final TaskStatus COMPLETE = new TaskStatus(COMPLETE_STRING);
+ public static final TaskStatus INCOMPLETE = new TaskStatus(INCOMPLETE_STRING);
+
+ public static final String STATUS_CONSTRAINTS =
+ "The status of the task should be " + COMPLETE + " or " + INCOMPLETE;
+
+ public final String status;
+
+ private TaskStatus(String status) {
+ requireNonNull(status);
+ checkArgument(isValidStatus(status), STATUS_CONSTRAINTS);
+ this.status = status;
+ }
+
+ /**
+ * Constructs a {@code TaskStatus}.
+ * It is either complete or incomplete.
+ *
+ * @param status A valid status.
+ */
+ public static TaskStatus of(String status) {
+ requireNonNull(status);
+ checkArgument(isValidStatus(status), STATUS_CONSTRAINTS);
+ if (status.equalsIgnoreCase(COMPLETE_STRING)) {
+ return COMPLETE;
+ } else {
+ return INCOMPLETE;
+ }
+ }
+
+ /**
+ * Returns true if the given string is "complete" or "incomplete",
+ * false otherwise.
+ * This comparison is not case-sensitive.
+ *
+ * @param status string representation of a status.
+ * @return true if the given string is a valid status, false otherwise.
+ */
+ public static boolean isValidStatus(String status) {
+ return status.equalsIgnoreCase(COMPLETE_STRING) || status.equalsIgnoreCase(INCOMPLETE_STRING);
+ }
+
+ /**
+ * Returns true if task status is "complete",
+ * false otherwise.
+ *
+ * @return true if task status is "complete".
+ */
+ public boolean isComplete() {
+ return this == COMPLETE;
+ }
+
+ @Override
+ public boolean equals(Object otherStatus) {
+ return otherStatus == this || (otherStatus instanceof TaskStatus
+ && status.equalsIgnoreCase((((TaskStatus) otherStatus).status)));
+ }
+
+ @Override
+ public int hashCode() {
+ return status.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return status;
+ }
+}
+
diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java
new file mode 100644
index 00000000000..5262c173db3
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.task.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Task (Tasks are considered duplicates if they have the same
+ * module and description).
+ */
+public class DuplicateTaskException extends RuntimeException {
+ public DuplicateTaskException() {
+ super("Operation would result in duplicate tasks");
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyMarkedException.java b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyMarkedException.java
new file mode 100644
index 00000000000..af05e46e2f3
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyMarkedException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.task.exceptions;
+
+/**
+ * Signals that the operation marks a task that is already indicated as completed.
+ */
+public class TaskAlreadyMarkedException extends RuntimeException {
+ public TaskAlreadyMarkedException() {
+ super("The operation marks a task that is already unmarked");
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyUnmarkedException.java b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyUnmarkedException.java
new file mode 100644
index 00000000000..02600e50712
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/exceptions/TaskAlreadyUnmarkedException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.task.exceptions;
+
+/**
+ * Signals that the operation unmarks a task that is already indicated as not completed.
+ */
+public class TaskAlreadyUnmarkedException extends RuntimeException {
+ public TaskAlreadyUnmarkedException() {
+ super("The operation unmarks a task that is already unmarked");
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java
new file mode 100644
index 00000000000..3c84ae73c35
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java
@@ -0,0 +1,8 @@
+package seedu.address.model.task.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified task.
+ */
+public class TaskNotFoundException extends RuntimeException {
+}
+
diff --git a/src/main/java/seedu/address/model/task/exceptions/WrongTaskModifiedException.java b/src/main/java/seedu/address/model/task/exceptions/WrongTaskModifiedException.java
new file mode 100644
index 00000000000..08457793f72
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/exceptions/WrongTaskModifiedException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.task.exceptions;
+
+/**
+ * Signals that the operation modifies the wrong task.
+ */
+public class WrongTaskModifiedException extends RuntimeException {
+ public WrongTaskModifiedException() {
+ super("Operation modifies the wrong task");
+ }
+}
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/JsonAdaptedExam.java b/src/main/java/seedu/address/storage/JsonAdaptedExam.java
new file mode 100644
index 00000000000..f0c40ca6161
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedExam.java
@@ -0,0 +1,81 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.exam.ExamDate;
+import seedu.address.model.exam.ExamDescription;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+
+/**
+ * This class represents a Jackson friendly version of the Exam.
+ */
+public class JsonAdaptedExam {
+ public static final String MISSING_EXAM_DESCRIPTION = "Exam description is not present";
+ public static final String MISSING_MODULE_CODE = "Module code is not present";
+ public static final String MISSING_EXAM_DATE = "Exam date is not present";
+ private final String description;
+ private final String moduleCode;
+ private final String date;
+
+ /**
+ * Builds a {@code JsonAdaptedExam} with the description and module code and date.
+ *
+ * @param description The description of the exam.
+ * @param moduleCode The module code of the exam.
+ * @param date The date of the exam.
+ */
+ public JsonAdaptedExam(@JsonProperty("description") String description,
+ @JsonProperty("modCode") String moduleCode,
+ @JsonProperty("date") String date) {
+ this.description = description;
+ this.moduleCode = moduleCode;
+ this.date = date;
+ }
+
+ /**
+ * Converts an existing exam into {@code JsonAdaptedExam} object
+ *
+ * @param exam The exam object being converted.
+ */
+ public JsonAdaptedExam(Exam exam) {
+ description = exam.getDescription().description;
+ moduleCode = exam.getModule().getModuleCode().moduleCode;
+ date = exam.getExamDate().examDate;
+ }
+
+ /**
+ * Converts this Jackson-friendly exam object into a Exam object for the model.
+ *
+ * @return The Exam object which has been created.
+ * @throws IllegalValueException if the exam has invalid fields.
+ */
+ public Exam toModelType() throws IllegalValueException {
+ if (description == null) {
+ throw new IllegalValueException(MISSING_EXAM_DESCRIPTION);
+ }
+ if (moduleCode == null) {
+ throw new IllegalValueException(MISSING_MODULE_CODE);
+ }
+ if (date == null) {
+ throw new IllegalValueException(MISSING_EXAM_DATE);
+ }
+ if (!ExamDescription.isValidDescription(description)) {
+ throw new IllegalValueException(ExamDescription.DESCRIPTION_CONSTRAINTS);
+ }
+ if (!ModuleCode.isValidModuleCode(moduleCode)) {
+ throw new IllegalValueException(ModuleCode.MODULE_CODE_CONSTRAINTS);
+ }
+ if (!ExamDate.isValidDateFormat(date)) {
+ throw new IllegalValueException(ExamDate.DATE_CONSTRAINTS);
+ }
+ final ExamDescription examDescription = new ExamDescription(description);
+ final ModuleCode modCode = new ModuleCode(moduleCode);
+ final Module module = new Module(modCode);
+ final ExamDate examDate = new ExamDate(date);
+ return new Exam(module, examDescription, examDate);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedModule.java b/src/main/java/seedu/address/storage/JsonAdaptedModule.java
new file mode 100644
index 00000000000..783bccd0f63
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedModule.java
@@ -0,0 +1,76 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.module.ModuleCredit;
+import seedu.address.model.module.ModuleName;
+
+/**
+ * This class represents a Jackson friendly version of {@link Module}.
+ */
+public class JsonAdaptedModule {
+ public static final String MISSING_MODULE_MESSAGE_FORMAT =
+ "Module's fields are not present";
+ private final String moduleCode;
+ private final String moduleName;
+ private final String moduleCredit;
+
+ /**
+ * Builds a {@code JsonAdaptedModule} with the module code, module name and module credit.
+ *
+ * @param moduleCode The module code of the module,
+ * @param moduleName The module name of the module.
+ * @param moduleCredit The module credit of the module.
+ */
+ public JsonAdaptedModule(@JsonProperty("modCode") String moduleCode, @JsonProperty("modName") String moduleName,
+ @JsonProperty("modCredit") String moduleCredit) {
+ this.moduleCode = moduleCode;
+ this.moduleName = moduleName;
+ this.moduleCredit = moduleCredit;
+ }
+
+ /**
+ * Converts an existing module into {@code JsonAdaptedModule} object
+ *
+ * @param mod The module object being converted.
+ */
+ public JsonAdaptedModule(Module mod) {
+ moduleCode = mod.getModuleCode().moduleCode;
+ moduleName = mod.getModuleName().moduleName;
+ moduleCredit = String.valueOf(mod.getModuleCredit().moduleCredit);
+ }
+
+ /**
+ * Converts this Jackson-friendly module object into a Module Object.
+ *
+ * @return The Module Object which is created.
+ * @throws IllegalValueException If moduleCode is null.
+ */
+ public Module toModelType() throws IllegalValueException {
+ if (moduleCode == null || moduleName == null || moduleCredit == null) {
+ throw new IllegalValueException(MISSING_MODULE_MESSAGE_FORMAT);
+ }
+ if (!ModuleCode.isValidModuleCode(moduleCode)) {
+ throw new IllegalValueException(ModuleCode.MODULE_CODE_CONSTRAINTS);
+ }
+ if (!ModuleName.isValidModuleName(moduleName)) {
+ throw new IllegalValueException(ModuleName.MODULE_NAME_CONSTRAINTS);
+ }
+ int integerModuleCredit;
+ try {
+ integerModuleCredit = Integer.parseInt(moduleCredit);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalValueException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS);
+ }
+ if (!ModuleCredit.isValidModuleCredit(integerModuleCredit)) {
+ throw new IllegalValueException(ModuleCredit.MODULE_CREDIT_CONSTRAINTS);
+ }
+ final ModuleCode modCode = new ModuleCode(moduleCode);
+ final ModuleName modName = new ModuleName(moduleName);
+ final ModuleCredit modCredit = new ModuleCredit(integerModuleCredit);
+ return new Module(modCode, modName, modCredit);
+ }
+}
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 a6321cec2ea..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 tagged = 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("tagged") List tagged) {
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- if (tagged != null) {
- this.tagged.addAll(tagged);
- }
- }
-
- /**
- * 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;
- tagged.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 : tagged) {
- 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/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java
new file mode 100644
index 00000000000..c86cd4e78c5
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java
@@ -0,0 +1,153 @@
+package seedu.address.storage;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.exam.ExamDate;
+import seedu.address.model.exam.ExamDescription;
+import seedu.address.model.module.Module;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.tag.DeadlineTag;
+import seedu.address.model.tag.PriorityTag;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.TaskDescription;
+import seedu.address.model.task.TaskStatus;
+
+/**
+ * This class represents a Jackson friendly version of the Task.
+ */
+public class JsonAdaptedTask {
+ public static final String MISSING_TASK_DESCRIPTION = "Description of the task is not present";
+ public static final String MISSING_TASK_MODULE = "Module of the task is not present";
+ public static final String MISSING_TASK_STATUS = "Completion status of the task is not present";
+ public static final String WRONG_EXAM_FORMAT = "The task must either have both exam description"
+ + " and exam date or have no exam description and exam date.";
+ private final String taskDescription;
+ private final String moduleCode;
+ private final String status;
+ private final String priority;
+ private final String deadline;
+ private final String examDescription;
+ private final String examDate;
+
+ /**
+ * Builds a {@code JsonAdaptedTask} with the description and module code.
+ *
+ * @param taskDescription The description of the task.
+ * @param moduleCode The module code of the task.
+ * @param status The completion status of the task.
+ * @param priority The tag which carries the priority status of the task.
+ * @param deadline The deadline to complete the task.
+ * @param examDate The date of the exam.
+ * @param examDescription The description of the exam.
+ */
+ public JsonAdaptedTask(@JsonProperty("taskDescription") String taskDescription,
+ @JsonProperty("modCode") String moduleCode, @JsonProperty("status") String status,
+ @JsonProperty("priority") String priority, @JsonProperty("deadline") String deadline,
+ @JsonProperty("examDate") String examDate,
+ @JsonProperty("examDescription") String examDescription) {
+ this.taskDescription = taskDescription;
+ this.moduleCode = moduleCode;
+ this.status = status;
+ this.priority = priority;
+ this.deadline = deadline;
+ this.examDate = examDate;
+ this.examDescription = examDescription;
+ }
+
+ /**
+ * Converts an existing task into {@code JsonAdaptedTask} object
+ *
+ * @param task The task object being converted.
+ */
+ public JsonAdaptedTask(Task task) {
+ taskDescription = task.getDescription().description;
+ moduleCode = task.getModule().getModuleCode().moduleCode;
+ status = task.getStatus().status;
+ priority = task.getPriorityTag() == null ? null : task.getPriorityTag().status;
+ deadline = task.getDeadlineTag() == null ? null : task.getDeadlineTag().toString();
+ examDate = task.getExam() == null ? null : task.getExam().getExamDate().examDate;
+ examDescription = task.getExam() == null ? null : task.getExam().getDescription().description;
+ }
+
+ /**
+ * Converts this Jackson-friendly task object into a Task object for the model.
+ *
+ * @return The Task object which has been created.
+ * @throws IllegalValueException if the task has invalid fields.
+ */
+ public Task toModelType() throws IllegalValueException {
+ if (taskDescription == null) {
+ throw new IllegalValueException(MISSING_TASK_DESCRIPTION);
+ }
+ if (moduleCode == null) {
+ throw new IllegalValueException(MISSING_TASK_MODULE);
+ }
+ if (status == null) {
+ throw new IllegalValueException(MISSING_TASK_STATUS);
+ }
+ if (!TaskDescription.isValidDescription(taskDescription)) {
+ throw new IllegalValueException(TaskDescription.DESCRIPTION_CONSTRAINTS);
+ }
+ if (!ModuleCode.isValidModuleCode(moduleCode)) {
+ throw new IllegalValueException(ModuleCode.MODULE_CODE_CONSTRAINTS);
+ }
+ if (!TaskStatus.isValidStatus(status)) {
+ throw new IllegalValueException(TaskStatus.STATUS_CONSTRAINTS);
+ }
+ if (priority != null && !PriorityTag.isValidTag(priority)) {
+ throw new IllegalValueException(PriorityTag.PRIORITY_TAG_CONSTRAINTS);
+ }
+
+ final LocalDate date;
+ if (deadline != null && !DeadlineTag.checkDateFormat(deadline)) {
+ throw new IllegalValueException(DeadlineTag.DEADLINE_TAG_FORMAT_CONSTRAINTS);
+ }
+ try {
+ if (deadline != null) {
+ //@@author dlimyy-reused
+ //Reused from https://stackoverflow.com/questions/32823368/
+ //with minor modifications.
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-uuuu")
+ .withResolverStyle(ResolverStyle.STRICT);
+ //@@author
+ date = LocalDate.parse(deadline, dtf);
+ } else {
+ date = null;
+ }
+ } catch (DateTimeParseException dtp) {
+ throw new IllegalValueException(DeadlineTag.DEADLINE_TAG_INVALID_DATE);
+ }
+ if (!((examDate == null && examDescription == null)
+ || (examDate != null && examDescription != null))) {
+ throw new IllegalValueException(WRONG_EXAM_FORMAT);
+ }
+
+ if (examDate != null && !ExamDate.isValidDateFormat(examDate)) {
+ throw new IllegalValueException(ExamDate.DATE_CONSTRAINTS);
+ }
+ if (examDescription != null && !ExamDescription.isValidDescription(examDescription)) {
+ throw new IllegalValueException(ExamDescription.DESCRIPTION_CONSTRAINTS);
+ }
+ final ExamDescription descriptionOfExam = examDescription == null
+ ? null : new ExamDescription(this.examDescription);
+ final ExamDate dateOfExam = examDate == null ? null : new ExamDate(examDate);
+
+ final DeadlineTag deadlineTag = date == null ? null : new DeadlineTag(date);
+ final TaskDescription descriptionOfTask = new TaskDescription(taskDescription);
+ final ModuleCode modCode = new ModuleCode(moduleCode);
+ final Module module = new Module(modCode);
+ final Exam exam = examDate == null ? null
+ : new Exam(module, descriptionOfExam, dateOfExam);
+ final TaskStatus taskStatus = TaskStatus.of(status);
+ final PriorityTag priorityTag = priority == null ? null : new PriorityTag(priority);
+ return new Task(module, descriptionOfTask, taskStatus, priorityTag, deadlineTag, exam);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..33cca11ac05 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -11,7 +11,9 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.exam.Exam;
+import seedu.address.model.module.Module;
+import seedu.address.model.task.Task;
/**
* An Immutable AddressBook that is serializable to JSON format.
@@ -19,26 +21,43 @@
@JsonRootName(value = "addressbook")
class JsonSerializableAddressBook {
- public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
-
- private final List persons = new ArrayList<>();
+ public static final String MESSAGE_MODULE_NOT_PRESENT = "This module does not exist";
+ public static final String MESSAGE_DUPLICATE_MODULE = "There is/are duplicate module(s) "
+ + "present in the module list";
+ public static final String MESSAGE_DUPLICATE_EXAM = "There is/are duplicate exam(s) "
+ + "present in the exam list";
+ public static final String INVALID_EXAM_LINKED = "Invalid exam is linked to the task";
+ public static final String MESSAGE_DUPLICATE_TASK = "There is/are duplicate task(s) "
+ + "present in the task list";
+ private final List modules = new ArrayList<>();
+ private final List tasks = new ArrayList<>();
+ private final List exams = new ArrayList<>();
/**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
+ * Constructs a {@code JsonSerializableAddressBook} with the given tasks and modules.
*/
@JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
- this.persons.addAll(persons);
+ public JsonSerializableAddressBook(@JsonProperty("tasks") List tasks,
+ @JsonProperty("modules") List modules,
+ @JsonProperty("exams") List exams) {
+ this.tasks.addAll(tasks);
+ this.modules.addAll(modules);
+ this.exams.addAll(exams);
}
+ //@@author dlimyy-reused
+ //Reused with minor modifications from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
/**
* 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()));
+ modules.addAll(source.getModuleList().stream().map(JsonAdaptedModule::new).collect(Collectors.toList()));
+ tasks.addAll(source.getTaskList().stream().map(JsonAdaptedTask::new).collect(Collectors.toList()));
+ exams.addAll(source.getExamList().stream().map(JsonAdaptedExam::new).collect(Collectors.toList()));
}
+ //@@author
/**
* Converts this address book into the model's {@code AddressBook} object.
@@ -47,12 +66,35 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
*/
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);
+ for (JsonAdaptedModule jsonAdaptedModule: modules) {
+ Module module = jsonAdaptedModule.toModelType();
+ if (addressBook.hasModule(module)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_MODULE);
+ }
+ addressBook.addModule(module);
+ }
+ for (JsonAdaptedExam jsonAdaptedExam: exams) {
+ Exam exam = jsonAdaptedExam.toModelType();
+ if (addressBook.hasExam(exam)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_EXAM);
+ }
+ if (!addressBook.hasModule(exam.getModule())) {
+ throw new IllegalValueException(MESSAGE_MODULE_NOT_PRESENT);
+ }
+ addressBook.addExam(exam);
+ }
+ for (JsonAdaptedTask jsonAdaptedTask: tasks) {
+ Task task = jsonAdaptedTask.toModelType();
+ if (addressBook.hasTask(task)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_TASK);
+ }
+ if (!addressBook.hasModule(task.getModule())) {
+ throw new IllegalValueException(MESSAGE_MODULE_NOT_PRESENT);
+ }
+ if (task.getExam() != null && !addressBook.hasExam(task.getExam())) {
+ throw new IllegalValueException(INVALID_EXAM_LINKED);
}
- addressBook.addPerson(person);
+ addressBook.addTask(task);
}
return addressBook;
}
diff --git a/src/main/java/seedu/address/ui/ExamCard.java b/src/main/java/seedu/address/ui/ExamCard.java
new file mode 100644
index 00000000000..17a3bb4e173
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ExamCard.java
@@ -0,0 +1,66 @@
+package seedu.address.ui;
+
+
+import javafx.fxml.FXML;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.layout.Region;
+import seedu.address.model.exam.Exam;
+
+
+/**
+ * ExamCard represents an Exam Card which contains details of the Exam and the position in the
+ * listView.
+ */
+public class ExamCard extends UiPart {
+ private static final String FXML = "ExamListCard.fxml";
+
+ public final Exam exam;
+
+ @FXML
+ private Label id;
+
+ @FXML
+ private Label examDescription;
+
+ @FXML
+ private Label examDate;
+
+ @FXML
+ private Label moduleCode;
+
+ @FXML
+ private ProgressBar percentageCompleted;
+
+ @FXML
+ private Label progressMessage;
+
+
+ /**
+ * Constructor of the ExamCard. Sets the exam and the position.
+ *
+ * @param exam The exam being set.
+ * @param position The position of the exam in the listView.
+ */
+ public ExamCard(Exam exam, int position) {
+ super(FXML);
+ this.exam = exam;
+ id.setText(position + ". ");
+ moduleCode.setText("Module Code: " + exam.getModule().getModuleCode().moduleCode);
+ examDescription.setText(exam.getDescription().description);
+ examDate.setText("Date: " + exam.getExamDate().examDate);
+ if (!exam.hasTasks()) {
+ percentageCompleted.setPrefWidth(0);
+ } else {
+ percentageCompleted.setPadding(new Insets(0, 5, 0, 0));
+ }
+
+ if (exam.hasTasks()) {
+ percentageCompleted.setProgress(exam.getPercentageCompleted());
+ }
+ percentageCompleted.setStyle("-fx-accent:limegreen");
+ progressMessage.setText(exam.generateProgressMessage());
+
+ }
+}
diff --git a/src/main/java/seedu/address/ui/ExamListPanel.java b/src/main/java/seedu/address/ui/ExamListPanel.java
new file mode 100644
index 00000000000..8eb05f435a9
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ExamListPanel.java
@@ -0,0 +1,47 @@
+package seedu.address.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 seedu.address.model.exam.Exam;
+
+
+/**
+ * ExamListPanel class represents a panel which contains a list of all
+ * the exams.
+ */
+public class ExamListPanel extends UiPart {
+ private static final String FXML = "ExamListPanel.fxml";
+
+ @FXML
+ private ListView examListView;
+
+ /**
+ * The constructor of the ExamListPanel. Sets the list of exams
+ * to the ListView.
+ *
+ * @param examList
+ */
+ public ExamListPanel(ObservableList examList) {
+ super(FXML);
+ examListView.setItems(examList);
+ examListView.setCellFactory(listView -> new ExamListViewCell());
+ }
+
+ static class ExamListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Exam exam, boolean empty) {
+ super.updateItem(exam, empty);
+
+ if (empty || exam == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new ExamCard(exam, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..d6399372802 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-f11-2.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..c1163835277 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -31,7 +31,10 @@ public class MainWindow extends UiPart {
private Logic logic;
// Independent Ui parts residing in this Ui container
- private PersonListPanel personListPanel;
+
+ private TaskListPanel taskListPanel;
+ private ModuleListPanel moduleListPanel;
+ private ExamListPanel examListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
@@ -50,6 +53,15 @@ public class MainWindow extends UiPart {
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private StackPane moduleListPanelPlaceholder;
+
+ @FXML
+ private StackPane taskListPanelPlaceholder;
+
+ @FXML
+ private StackPane examListPanelPlaceholder;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -59,7 +71,6 @@ public MainWindow(Stage primaryStage, Logic logic) {
// Set dependencies
this.primaryStage = primaryStage;
this.logic = logic;
-
// Configure the UI
setWindowDefaultSize(logic.getGuiSettings());
@@ -110,8 +121,18 @@ 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());
+ //@@author dlimyy-reused
+ //Reused with modifications
+ // from existing AB3 (https://github.com/nus-cs2103-AY2223S1/tp)
+ taskListPanel = new TaskListPanel(logic.getFilteredTaskList());
+ taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot());
+
+ moduleListPanel = new ModuleListPanel(logic.getFilteredModuleList());
+ moduleListPanelPlaceholder.getChildren().add(moduleListPanel.getRoot());
+ //@@author
+
+ examListPanel = new ExamListPanel(logic.getFilteredExamList());
+ examListPanelPlaceholder.getChildren().add(examListPanel.getRoot());
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
@@ -163,10 +184,6 @@ private void handleExit() {
primaryStage.hide();
}
- public PersonListPanel getPersonListPanel() {
- return personListPanel;
- }
-
/**
* Executes the command and returns the result.
*
@@ -177,15 +194,12 @@ private CommandResult executeCommand(String commandText) throws CommandException
CommandResult commandResult = logic.execute(commandText);
logger.info("Result: " + commandResult.getFeedbackToUser());
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
-
if (commandResult.isShowHelp()) {
handleHelp();
}
-
if (commandResult.isExit()) {
handleExit();
}
-
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("Invalid command: " + commandText);
diff --git a/src/main/java/seedu/address/ui/ModuleCard.java b/src/main/java/seedu/address/ui/ModuleCard.java
new file mode 100644
index 00000000000..043d0f45753
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ModuleCard.java
@@ -0,0 +1,62 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.layout.Region;
+import seedu.address.model.module.Module;
+
+/**
+ * ModuleCard class represents a module card which contains the details of
+ * the module card.
+ */
+public class ModuleCard extends UiPart {
+ //@@author dlimyy-reused
+ //Reused with minor modifications from existing AddressBook 3
+
+ private static final String FXML = "ModuleListCard.fxml";
+ @FXML
+ private Label id;
+
+ @FXML
+ private Label moduleCode;
+
+ @FXML
+ private Label moduleName;
+
+ @FXML
+ private Label moduleCredit;
+
+ @FXML
+ private ProgressBar percentageCompleted;
+
+ @FXML
+ private Label progressString;
+
+ /**
+ * The constructor of ModuleCard. Sets the id and module
+ * code fields with their values.
+ *
+ * @param module The module whose module code is being set.
+ * @param position The position of the module in the module list.
+ */
+ public ModuleCard(Module module, int position) {
+ super(FXML);
+ id.setText(position + ". ");
+ moduleCode.setText(module.getModuleCode().moduleCode);
+ moduleName.setText("Name: " + module.getModuleName().moduleName);
+ moduleCredit.setText("Module Credit: " + module.getModuleCredit().moduleCredit);
+
+ if (!module.hasTasks()) {
+ percentageCompleted.setPrefWidth(0);
+ } else {
+ percentageCompleted.setPadding(new Insets(0, 5, 0, 0));
+ }
+
+ percentageCompleted.setProgress(module.getPercentageCompleted());
+ percentageCompleted.setStyle("-fx-accent: limegreen");
+ progressString.setText(module.generateProgressMessage());
+ }
+ //@@author
+}
diff --git a/src/main/java/seedu/address/ui/ModuleListPanel.java b/src/main/java/seedu/address/ui/ModuleListPanel.java
new file mode 100644
index 00000000000..7c6243bf55d
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ModuleListPanel.java
@@ -0,0 +1,49 @@
+package seedu.address.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 seedu.address.model.module.Module;
+
+/**
+ * ModuleListPanel class represents a panel which contains a list of all
+ * the module card.
+ */
+public class ModuleListPanel extends UiPart {
+ //@@author dlimyy-reused
+ //Reused with minor modifications from existing AddressBook 3
+ private static final String FXML = "ModuleListPanel.fxml";
+
+ @FXML
+ private ListView moduleListView;
+
+ /**
+ * The constructor of the ModuleListPanel. Sets the list of modules
+ * to the ListView.
+ *
+ * @param moduleList
+ */
+ public ModuleListPanel(ObservableList moduleList) {
+ super(FXML);
+ moduleListView.setItems(moduleList);
+ moduleListView.setCellFactory(listView -> new ModuleListViewCell());
+ }
+
+ static class ModuleListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Module module, boolean empty) {
+ super.updateItem(module, empty);
+
+ if (empty || module == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new ModuleCard(module, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+ //@@author
+}
diff --git a/src/main/java/seedu/address/ui/ModuleUtil.java b/src/main/java/seedu/address/ui/ModuleUtil.java
new file mode 100644
index 00000000000..01b72893091
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ModuleUtil.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CODE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_CREDIT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MOD_NAME;
+
+import seedu.address.logic.commands.AddModuleCommand;
+import seedu.address.logic.commands.EditModuleCommand;
+import seedu.address.model.module.Module;
+
+
+/**
+ * A utility class for Module.
+ */
+public class ModuleUtil {
+
+ /**
+ * Returns an add module command string for adding the {@code module}.
+ */
+ public static String getAddModuleCommand(Module module) {
+ return "m " + AddModuleCommand.COMMAND_WORD + " " + getModuleDetails(module);
+ }
+
+ /**
+ * Returns the part of command string for the given {@code module}'s details.
+ */
+ public static String getModuleDetails(Module module) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(PREFIX_MOD_CODE + module.getModuleCode().moduleCode + " ");
+ sb.append(PREFIX_MOD_NAME + module.getModuleName().moduleName + " ");
+ sb.append(PREFIX_MOD_CREDIT + module.getModuleCredit().toString() + " ");
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the part of command string for the given {@code EditModuleDescriptor}'s details.
+ */
+ public static String getEditModuleDescriptorDetails(EditModuleCommand.EditModuleDescriptor descriptor) {
+ StringBuilder sb = new StringBuilder();
+ descriptor.getModuleCode().ifPresent(moduleCode
+ -> sb.append(PREFIX_MOD_CODE).append(moduleCode).append(" "));
+ descriptor.getModuleName().ifPresent(moduleName
+ -> sb.append(PREFIX_MOD_NAME).append(moduleName).append(" "));
+ descriptor.getModuleCredit().ifPresent(moduleCredit
+ -> sb.append(PREFIX_MOD_CREDIT).append(moduleCredit).append(" "));
+ return sb.toString();
+ }
+}
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 7fc927bc5d9..00000000000
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ /dev/null
@@ -1,77 +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)));
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof PersonCard)) {
- return false;
- }
-
- // state check
- PersonCard card = (PersonCard) other;
- return id.getText().equals(card.id.getText())
- && person.equals(card.person);
- }
-}
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/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java
new file mode 100644
index 00000000000..1843780f0ff
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TaskCard.java
@@ -0,0 +1,74 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
+import seedu.address.model.task.Task;
+
+/**
+ * TaskCard represents a Task Card which contains details of the Task and the position in the
+ * listView.
+ */
+public class TaskCard extends UiPart {
+ //@@author dlimyy-reused
+ //Reused with minor modifications from AddressBook 3
+ private static final String FXML = "TaskListCard.fxml";
+
+ public final Task task;
+
+ @FXML
+ private Label id;
+
+ @FXML
+ private Label description;
+
+ @FXML
+ private Label moduleCode;
+
+ @FXML
+ private CheckBox isComplete;
+
+ @FXML
+ private Button priorityTag;
+
+ @FXML
+ private Button deadlineTag;
+
+ @FXML
+ private Label examDescription;
+
+ /**
+ * Constructor of the TaskCard. Sets the task and the position.
+ *
+ * @param task The task being set.
+ * @param position The position of the task in the listView.
+ */
+ public TaskCard(Task task, int position) {
+ super(FXML);
+ this.task = task;
+ id.setText(position + ". ");
+ moduleCode.setText("Module Code: " + task.getModule().getModuleCode());
+ description.setText(task.getDescription().description);
+ isComplete.setSelected(task.isComplete());
+ if (task.getPriorityTag() != null) {
+ priorityTag.setText(task.getPriorityTag().status.toUpperCase());
+ } else {
+ priorityTag.setManaged(false);
+ }
+
+ if (task.getDeadlineTag() != null) {
+ deadlineTag.setText(task.getDeadlineTag().toString());
+ } else {
+ deadlineTag.setManaged(false);
+ }
+
+ if (task.getExam() != null) {
+ examDescription.setText("Exam Description: " + task.getExam().getDescription().description);
+ } else {
+ examDescription.setManaged(false);
+ }
+ }
+ //@@author
+}
diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java
new file mode 100644
index 00000000000..397bbe54453
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TaskListPanel.java
@@ -0,0 +1,50 @@
+package seedu.address.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 seedu.address.model.task.Task;
+
+/**
+ * TaskListPanel is a panel which represents the list of tasks.
+ *
+ */
+public class TaskListPanel extends UiPart {
+ //@@author dlimyy-reused
+ //Reused with minor modifications from AddressBook 3
+ private static final String FXML = "TaskListPanel.fxml";
+
+ @FXML
+ private ListView taskListView;
+
+ /**
+ * Constructor of the TaskListPanel. Sets the list of tasks
+ * to the ListView
+ *
+ * @param tasks The list of tasks which will be set.
+ */
+ public TaskListPanel(ObservableList tasks) {
+ super(FXML);
+ taskListView.setItems(tasks);
+ taskListView.setCellFactory(listView -> new TaskListViewCell());
+ }
+
+
+ static class TaskListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Task task, boolean empty) {
+ super.updateItem(task, empty);
+
+ if (empty || task == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new TaskCard(task, getIndex() + 1).getRoot());
+ }
+ }
+ }
+ //@@author
+}
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..7fceabb62d6 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -77,14 +77,14 @@
}
.split-pane:horizontal .split-pane-divider {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: transparent transparent transparent #4d4d4d;
+ -fx-background-color: derive(#0D187B, 20%);
+ -fx-border-color: transparent;
}
.split-pane {
-fx-border-radius: 1;
-fx-border-width: 1;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#0D187B, 20%);
}
.list-view {
@@ -121,36 +121,58 @@
}
.cell_big_label {
- -fx-font-family: "Segoe UI Semibold";
+ -fx-font-family: "PT Mono";
-fx-font-size: 16px;
- -fx-text-fill: #010504;
+ -fx-text-fill: black;
}
.cell_small_label {
- -fx-font-family: "Segoe UI";
- -fx-font-size: 13px;
- -fx-text-fill: #010504;
+ -fx-font-family: "PT Sans";
+ -fx-font-size: 15px;
+ -fx-text-fill: black;
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#0D187B, 20%);
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
- -fx-border-top-width: 1px;
+ -fx-background-color: derive(#0D187B, 20%);
+}
+
+.exams-pane-with-border {
+ -fx-background-color: derive(#2A61EF, 20%);
+ -fx-border-color: black;
+ -fx-border-width: 2 2 2 2;
+ -fx-background-radius: 11px;
+ -fx-border-radius: 10px;
+}
+
+.modules-pane-with-border {
+ -fx-background-color: derive(#329BFF, 20%);
+ -fx-border-color: black;
+ -fx-border-width: 2 2 2 2;
+ -fx-background-radius: 11px;
+ -fx-border-radius: 10px;
+}
+
+.tasks-pane-with-border {
+ -fx-background-color: derive(#303EBD, 20%);
+ -fx-border-color: black;
+ -fx-border-width: 2 2 2 2;
+ -fx-background-radius: 11px;
+ -fx-border-radius: 10px;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#0D187B, 20%);
}
.result-display {
-fx-background-color: transparent;
- -fx-font-family: "Segoe UI Light";
+ -fx-font-weight: bold;
-fx-font-size: 13pt;
- -fx-text-fill: white;
+ -fx-text-fill: black;
}
.result-display .label {
@@ -158,16 +180,14 @@
}
.status-bar .label {
- -fx-font-family: "Segoe UI Light";
+ -fx-font-weight: bold;
-fx-text-fill: white;
-fx-padding: 4px;
-fx-pref-height: 30px;
}
.status-bar-with-border {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 25%);
- -fx-border-width: 1px;
+ -fx-background-color: derive(#0D187B, 20%);
}
.status-bar-with-border .label {
@@ -193,14 +213,16 @@
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#050D55, 20%);
+ -fx-border-color: black;
+ -fx-border-width: 0 0 2 0;
}
.menu-bar .label {
-fx-font-size: 14pt;
- -fx-font-family: "Segoe UI Light";
-fx-text-fill: white;
-fx-opacity: 0.9;
+ -fx-font-weight: bold;
}
.menu .left-container {
@@ -213,14 +235,14 @@
* http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
*/
.button {
- -fx-padding: 5 22 5 22;
+ -fx-padding: 5 10 5 10;
-fx-border-color: #e2e2e2;
-fx-border-width: 2;
-fx-background-radius: 0;
-fx-background-color: #1d1d1d;
- -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
- -fx-font-size: 11pt;
- -fx-text-fill: #d8d8d8;
+ -fx-font-family: "PT Mono", Helvetica, Arial, sans-serif;
+ -fx-font-size: 10pt;
+ -fx-text-fill: black;
-fx-background-insets: 0 0 0 0, 0, 1, 2;
}
@@ -307,6 +329,10 @@
-fx-padding: 8 1 8 1;
}
+.check-box:disabled {
+ -fx-opacity: 1;
+}
+
#cardPane {
-fx-background-color: transparent;
-fx-border-width: 0;
@@ -318,12 +344,12 @@
}
#commandTextField {
- -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-color: transparent;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-color: derive(#0D187B, 20%) derive(#0D187B, 20%) #ffffff derive(#0D187B, 20%);
-fx-border-insets: 0;
- -fx-border-width: 1;
- -fx-font-family: "Segoe UI Light";
+ -fx-border-width: 2;
+ -fx-font-weight: bold;
-fx-font-size: 13pt;
-fx-text-fill: white;
}
@@ -333,8 +359,7 @@
}
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
- -fx-background-radius: 0;
+ -fx-background-color: #B3C1D0;
}
#tags {
@@ -350,3 +375,76 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#titleBox {
+ -fx-background-color: derive(#000533, 15%);
+ -fx-border-color: black;
+ -fx-border-width: 0 0 2 0;
+ -fx-alignment: center;
+}
+
+#title {
+ -fx-font-size: 15pt;
+ -fx-text-fill: white;
+}
+
+#examsHeaderBox {
+ -fx-background-color: derive(#2A61EF, 20%);
+ -fx-alignment: top-center;
+}
+
+#examsHeader {
+ -fx-font-size: 15pt;
+ -fx-text-fill: white;
+}
+
+#modulesHeaderBox {
+ -fx-background-color: derive(#329BFF, 20%);
+ -fx-alignment: top-center;
+}
+
+#modulesHeader {
+ -fx-font-size: 15pt;
+ -fx-text-fill: white;
+}
+
+#tasksHeaderBox {
+ -fx-background-color: derive(#303EBD, 20%);
+ -fx-alignment: top-center;
+}
+
+#tasksHeader {
+ -fx-font-size: 15pt;
+ -fx-text-fill: white;
+}
+
+#examListView .list-cell {
+ -fx-background-color: derive(#2A61EF, 20%);
+}
+
+#examListView {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(#2A61EF, 20%);
+}
+
+#moduleListView .list-cell {
+ -fx-background-color: derive(#329BFF, 20%);
+}
+
+#moduleListView {
+ -fx-background-color: derive(#329BFF, 20%);
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+}
+
+#taskListView .list-cell {
+ -fx-background-color: derive(#303EBD, 20%);
+}
+
+#taskListView {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(#303EBD, 20%);
+}
+
diff --git a/src/main/resources/view/ExamListCard.fxml b/src/main/resources/view/ExamListCard.fxml
new file mode 100644
index 00000000000..b1132413ace
--- /dev/null
+++ b/src/main/resources/view/ExamListCard.fxml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+