diff --git a/Collate-TUI.jar b/Collate-TUI.jar new file mode 100644 index 000000000000..50bdfe6902f8 Binary files /dev/null and b/Collate-TUI.jar differ diff --git a/build.gradle b/build.gradle index 32fccab6bc4a..2414c91a4931 100644 --- a/build.gradle +++ b/build.gradle @@ -102,7 +102,7 @@ allprojects { } shadowJar { - archiveName = "taskmaster.jar" + archiveName = "happyjimtaskmaster.jar" manifest { attributes "Main-Class": "seedu.address.MainApp" diff --git a/collate.bat b/collate.bat new file mode 100644 index 000000000000..fe9495364100 --- /dev/null +++ b/collate.bat @@ -0,0 +1,5 @@ +java -jar Collate-TUI.jar collate from src/main to collated/main include java, fxml, css + +java -jar Collate-TUI.jar collate from src/test to collated/test include java + +java -jar Collate-TUI.jar collate from docs to collated/docs include md, html \ No newline at end of file diff --git a/collated/docs/A0135782Y.md b/collated/docs/A0135782Y.md new file mode 100644 index 000000000000..dc3e3397bf8b --- /dev/null +++ b/collated/docs/A0135782Y.md @@ -0,0 +1,101 @@ +# A0135782Y +###### \DeveloperGuide.md +``` md + +``` +###### \DeveloperGuide.md +``` md + +``` +###### \DeveloperGuide.md +``` md +The _Sequence Diagram_ below show how recurring tasks are handled when they are first added by the user into Happy Jim Task Master. + +
+ +> Note task is a Task reference from the Model and thus any changes made in the RecurringTaskManager will mutate the values of the task. + +The _Sequence Diagram_ below show how recurring tasks have dates appended to them every startup of Happy Jim Task Master + +
+ +> Note that repeatingTasks is a reference to the UniqueTaskList from the TaskMaster. Any changes made to repeatingTasks in RecurringTaskManager will affect TaskMaster's version of UniqueTaskList. +``` +###### \DeveloperGuide.md +``` md +
+``` +###### \DeveloperGuide.md +``` md + _Unit tests_ targeting the lowest level methods/classes. Below are some snippets,
+ + _Task.java_
+
+ + _RecurringTaskManager.java_
+
+ + _Integration tests_ that are checking the integration of multiple code units + (those code units are assumed to be working). Below are some snippets,
+ + _XmlTaskListStorage.java_
+
+``` +###### \DeveloperGuide.md +``` md + _LogicManagerTest.java_ +
+``` +###### \DeveloperGuide.md +``` md +## Appendix E : Product Survey +Product Name | Strengths | Weaknesses +---|---|--- +**Remember the Milk**|
  • Allows for recurring tasks
  • Allows floating tasks
  • Allows for location
  • Allows for estimate
  • Allows priority
  • |
  • Requires an accounr
  • Not really command line input friendly
  • Requires internet connection
  • +**Google Calendar**|
  • Generally suitable for target audience's requirements
  • Has a database to store tasks that can be synced
  • |
  • Not command line input friendly
  • Requires internet connection
  • +**Any.do**|
  • Can sync across platforms
  • Provide convenience service for scheduling
  • |
  • Not command line input friendly
  • Requires an account
  • Requires internet connection`
  • +**Calendar Iphone App**|
  • Separates tasks and calendar into one app
  • Able to add task and tag them
  • Able to add recurring task
  • Able to add in tasks to calendar in one line using auto detect
  • Able to view completed tasks
  • |
  • Not really command line input friendly, use touch input heavily
  • + + +``` +###### \UserGuide.md +``` md +#### Adding a floating task: `add` +Adds a task to the todo list
    +Format:`add TASK_NAME [t/TAG]...` + +Examples: +* `add Homework` +* `add Homework t/CS1231`
    + + + +#### Adding a task with deadline: `add` +Format: `add TASK_NAME by DATE TIME [RECURRING_TYPE] [t/TAG]...` + +> `RECURRING_TYPE` consists of daily, weekly, monthly and yearly case insensitive. +> Tasks can have only 1 `RECURRING_TYPE`. +> If multiple `RECURRING_TYPE` are used, only the first instance will be accepted. + +Examples:
    +* `add Homework by 24 sep 8pm t/CS1231` +* `add Homework by 24 sep 6pm daily t/CS1231` + + + +#### Adding a task with start time and end time: `add` +Format: `add TASK_NAME from DATE TIME to DATE TIME [RECURRING_TYPE] [t/TAG]...` + +> `RECURRING_TYPE` consists of daily, weekly, monthly and yearly case insensitive. +> Tasks can have only 1 `RECURRING_TYPE`. +> If multiple `RECURRING_TYPE` are used, only the first instance will be accepted. + +Examples: +* `add Homework from 24 sep 8pm to 25 sep 9pm tag/CS1231` +* `add Homework from today 8.03pm to today 8.15pm t/CS1231` + + +* `add Homework from 26 oct 10am to 26 oct 11am daily` + + +``` diff --git a/collated/docs/A0135784W.md b/collated/docs/A0135784W.md new file mode 100644 index 000000000000..01533ca717f1 --- /dev/null +++ b/collated/docs/A0135784W.md @@ -0,0 +1,56 @@ +# A0135784W +###### \UserGuide.md +``` md +## HappyJimTaskMaster's GUI + +1. Command box. This is where the commands are entered. Simply type in the command and press enter to execute it. +2. Result display. This is where the results of commands are shown. +3. Agenda. This where the agenda of this week is shown. +4. Tasklist panel. This is where the tasks are displayed. +5. Navigation bar panel. This is where the navigation categories are displayed. +``` +###### \UserGuide.md +``` md +## Features +### Command Format +> * Each command consists of a command word (such as add or delete), followed by other options such as DATE,TIME or [t/TAG] +> * Words in `UPPER_CASE` are the description of what kind data to input. +> * Items in `SQUARE_BRACKETS` `[items]` are optional. +> * Items with `...` after them can have multiple instances. +> * The order of parameters is fixed. + +### Date and Time Format +HappyJimTaskMaster uses Natty date parser to parse date and time options.
    + +Some examples of acceptable format include: +* 21 nov 2005 +* 24 sep 8pm +* jan 1st +* next thursday +* 3 days from now + +For a full list of acceptable formats, please refer to http://natty.joestelmach.com/doc.jsp +``` +###### \UserGuide.md +``` md +## Command Summary + +Command | Format +-------- | :-------- +Add | `add TASK_NAME [t/TAG]...` +Add | `add TASK_NAME by DATE TIME [RECURRING_TYPE] [t/TAG]...` +Add | `add TASK_NAME from DATE TIME to DATE TIME [RECURRING_TYPE] [t/TAG]...` +Edit | `edit TASK_ID [from EDIT_START_DATE EDIT_START_TIME to EDIT_END_DATE EDIT_END_TIME] [by EDIT_END_DATE EDIT_END_TIME] [t/EDIT_TAG]...` +Delete | `delete TASK_ID` +Complete | `done TASK_ID` +Block | `block TASK_NAME from [START_DATE] START_TIME to [START_DATE] START_TIME [t/TAG]...` +Redo | `r` +Undo | `u` +Find | `find [KEY_WORD] [from DATE_TIME to DATE_TIME | by DATE_TIME] [t/TAG]...` +View | `view DATE [TIME]` +Clear | `clear` +Change directory | `cd FILE_PATH` +Exit | `exit` + +----- +``` diff --git a/collated/docs/A0147967J.md b/collated/docs/A0147967J.md new file mode 100644 index 000000000000..9d2c9dc8fd6f --- /dev/null +++ b/collated/docs/A0147967J.md @@ -0,0 +1,167 @@ +# A0147967J +###### \DeveloperGuide.md +``` md +The _Sequence Diagram_ below shows how Happy Jim Task Master handles undo request from user. + +
    + +> Note that the context is a class that stores previous task master in the previous model before the target command executes. + +The _Class Diagram_ below shows the structure of how Happy Jim Task Master implements undo and redo operations. + +
    + +> Note that LogicManager maintains an URManager. UR manager contains two ArrayDeque, one for undo and the other for redo, +> to store the command and its context, specifically, the model before the command executes. +> To undo/redo a command, it is just to restore the previous model (specifically, the data, which is TaskMaster). +> As a result, as the task master grows, the consumption of memory to store the context grows. +> To maintain a good performance regarding to memory consumption, we restrict maximum undo/redo number to 3. +> (Noted that it is possible to reach unlimited undo/redo by simply wiping off the limit number.) + +``` +###### \DeveloperGuide.md +``` md + +1. **GUI Tests** - These are _System Tests_ that test the entire App by simulating user actions on the GUI. + These are in the `guitests` package. + + Currently, _Systems Tests_ have covered the basic functionalities of Happy Jim Task Master v0.4. + Following form shows the some of the essential commands and corresponding testcases. + + 1. _AddCommandTest_ + + + | Case# | Event | Basis Path | Output | + | :---: | --- | --- | --- | + | 1 | add floating task to existing task list `add eat with Hoon Meier` | 1 -> 2 | `New floating task added: eat with Hoon Meier Tags: ` | + | 2 | add floating task to existing task list `add play with Ida Mueller` | 1 -> 2 | `New floating task added: play with Ida Mueller Tags: ` | + | 3 | add duplicate floating task to existing task master `add eat with Hoon Meier` | 1 | `This task already exists in the task list` | + | 4 | clear existing task list `clear` | 1 -> 2 | `Task list has been cleared!` | + | 5 | add to empty task list `add take trash t/notUrgent` | 1 -> 2 | `New floating task added: take trash Tags: [notUrgent]` | + | 6 | invalid add command `adds Johnny` | 1 | `Unknown command` | + + 2. _ClearCommandTest_ + + + | Case# | Event | Basis Path | Output | + | :---: | --- | :---: | --- | + | 1 | clear existing non-empty task list `clear` | 1 -> 2 | `Task list has been cleared!` | + | 2 | verify other commands can work after task list cleared `add eat with Hoon Meier` | 1 -> 2 | `New floating task added: eat with Hoon Meier Tags: ` | + | 3 | add duplicate floating task `delete 1` | 1 -> 2| `Deleted Task: eat with Hoon Meier Tags: ` | + | 4 | verify clear command works when the list is empty `clear` | 1 -> 2 | `Task list has been cleared!` | + + 3. _CommandBoxTest_ + + + | Case# | Event | Basis Path | Output | + | :---: | --- | :---: | --- | + | 1 | command succeeds text cleared `add read book t/textBook t/weekly` | 1 -> 2 | `This task already exists in the task list` | + | 2 | command fails text stays `invalid command` | 1 | `Unknown Command` | + + 4. _DeleteCommandTest_ + + + | Case# | Event | Basis Path | Output | + | :---: | --- | :---: | --- | + | 1 | delete the first in the list `delete 1` | 1 -> 2 | `Deleted Task: take trash Tags: [notUrgent]` | + | 2 | delete the last in the list `delete 6` | 1 -> 2 | `Deleted Task: visit George Best Tags: ` | + | 3 | delete from the middle of the list `delete 2` | 1 -> 2 | `Deleted Task: do homework Tags: ` | + | 4 | delete with invalid index `delete 51` | 1 | `The task index provided is invalid` | + + 5. _FindCommandTest_ + + + | Case# | Event | Basis Path | Output | + | :---: | --- | :---: | --- | + | 1 | find in non-empty list with no results `find Mark` | 1 -> 2 | `0 tasks listed!` | + | 2 | find in non-empty list with multiple results `find read` | 1 -> 2 | `2 tasks listed!` | + | 3 | delete one result `delete 1` | 1 -> 2 | `Deleted Task: read book Tags: [textBook][weekly]` | + | 4 | find in non-empty list with one result `find read` | 1 -> 2 | `1 tasks listed!` | + | 5 | find in empty list `find Jean` | 1 -> 2 | `0 tasks listed!` | + | 6 | invalid find command `findgeorge` | 1 | `Unknown command` | + +``` +###### \DeveloperGuide.md +``` md + Hybrids of unit and integration tests. These test are checking multiple code units as well as + how the are connected together. Below are some snippets,
    + e.g. `seedu.taskmaster.logic.LogicManagerTest`
    + In the `LogicManagerTest`, Happy Jim Task Master tests the logic it uses.
    + Typically, Happy Jim Task Master focuses on some boundary tests.
    + e.g. To `find` a task, for instance, `Test Task 1 by 20 oct 11am `,
    + try execute
    + *`find by 20 oct 11am` --> exact boundary, task found;
    + *`find by 20 oct 10.59am` --> smaller boundary, lists nothing;
    + *`find by 20 oct 11.01am` --> lax boundary, task found.
    + > Note that this is a test not merely for `logic`, but also `parser` and `model`.
    +``` +###### \UserGuide.md +``` md + +#### Archive completed tasks : `done` +Format: done TASK_ID + +Examples: +* `done 5` + + + + + >Completed tasks can be viewed from navigation bar on the side. + +#### Block out timeslot : `block` +Format: block from [START_DATE] START_TIME to [START_DATE] START_TIME [t/TAG] + +Examples: +* `block from tomorrow 3pm to tomorrow 5pm t/meeting` + + > + > + +#### Undo tasks : `undo` +Format: u + +> Maximum 3 undo + +Examples: +* `u` + + + + +#### Redo tasks : `redo` +Format: r + +> Maximum 3 redo + +Examples: +* `r` + + + + +#### View agenda of a day: `view` +Format: view DATE [TIME] + +Examples: +* `view next monday` + + + + + +``` +###### \UserGuide.md +``` md + +#### Change directory: `cd` +Format: cd FILE_PATH + +Examples: +* `cd data\newlist.xml` + + + + + +``` diff --git a/collated/docs/A0147995H.md b/collated/docs/A0147995H.md new file mode 100644 index 000000000000..7ab7ee23164a --- /dev/null +++ b/collated/docs/A0147995H.md @@ -0,0 +1,72 @@ +# A0147995H +###### \UserGuide.md +``` md + +#### Edit tasks : `edit` +Format: `edit TASK_ID [NEW_TASK_NAME] [from DATE_TIME to DATE_TIME | by DATE_TIME [daily | weekly | monthly | yearly] ] [tag/EDIT_TAG]...` + +> Every field in edit is optional. After you specify the task that you are going to edit, +> you are able to change its name, date time and tag. +> For editing date time of a task, you have the following restrictions: +> 1. You cannot change a non-floating task to a floating task. +> 2. You cannot directly change recurring type of a task (need to specify time first). + +Examples: +* `edit 1 cs2103 webcast`
    + + +* `edit 1 t/study`
    + +* `edit 1 from today 4pm to today 5pm`
    + +* `edit 2 by today 7pm`
    + +* `edit 1 from today 4pm to today 5pm daily`
    + + +#### Delete tasks : `delete` +Format: delete TASK_ID + +Examples: +* `delete 2` + +``` +###### \UserGuide.md +``` md + +#### Find tasks : `find` +Format: `find [KEY_WORD] [from DATE_TIME to DATE_TIME | by DATE_TIME] [t/TAG]...` + +> For find command, all parameters optional. +> You are able to search by key words of a particular task, +> or search by a particular time period, search by deadline, +> or search by particular tags. +> (You can have more than one tags to search) + +Examples:
    + * `find cs2103`
    + + + + + * `find from today 5am to today 6am`
    + + + + * `find by today 10am`
    + + + + * `find cs2103 tag/lolo`
    + + + +#### Undo tasks : `clear` +Format: clear + +> clears all the tasks + +Examples: +* `clear` + +``` diff --git a/collated/main/A0135782Y.md b/collated/main/A0135782Y.md new file mode 100644 index 000000000000..86860cc90f2e --- /dev/null +++ b/collated/main/A0135782Y.md @@ -0,0 +1,1063 @@ +# A0135782Y +###### \java\seedu\address\logic\RecurringTaskManager.java +``` java +/** + * Handles the behaviour of recurring tasks + * Dictates when should the recurring tasks be shown + * This class is using a singleton pattern. + * Use RecurringTaskManager.getInstance() to get the instance of the class + */ +public class RecurringTaskManager { + private static final int APPEND_INCREMENT = 1; + private static final double NUM_MONTHS_IN_YEAR = 12.0; + private static final double NUM_WEEKS_IN_MONTH = 4.0; + private static final double NUM_DAYS_IN_WEEK = 7.0; + private static final int NUMBER_OF_DAYS_IN_A_WEEK = 7; + private static RecurringTaskManager instance; + private static final Logger logger = LogsCenter.getLogger(MainApp.class); + + private UniqueTaskList repeatingTasks; + private RecurringTaskManager() {} + + public void setTaskList(UniqueTaskList referenceList) { + assert referenceList != null : "Reference Task list cannot be null"; + logger.fine("Initializing with RecurringTaskManager to manage: " + referenceList.toString()); + repeatingTasks = referenceList; + } + + public void updateAnyRecurringTasks() { + assert repeatingTasks != null : "Repeating Task list reference cannot be null"; + logger.info("=============================[ RecurringTaskManager Updating ]==========================="); + for(ReadOnlyTask task : repeatingTasks){ + if (task.getRecurringType().equals(RecurringType.NONE)) { + continue; + } + updateRecurringTask(task); + } + } + + /** + * Corrects the recurring task that are overdued to reflect the present and future recurring dates + * + * @param task Added task that might have dates that are overdued at the start + */ + public void correctAddingOverdueTasks(Task task) { + assert task != null : "task that needs correcting cannot be null!"; + correctAddingOverdueTasks(task, LocalDate.now()); + } + + /** + * Helps to correct date till a certain date. + * Helps with testing of the correcting of date. + * + * @param task Task to be correct, cannot be null + * @param currentDate LocalDate that we are correcting towards, cannot be null + */ + public void correctAddingOverdueTasks(Task task, LocalDate currentDate) { + assert !CollectionUtil.isAnyNull(task,currentDate); + if (task.getRecurringType().equals(RecurringType.NONE)) { + return; + } + LocalDate localDateCurrently = currentDate; + LocalDate startDateInLocalDate = null; + if (!task.getComponentForNonRecurringType().hasOnlyEndDate()) { + startDateInLocalDate = task.getComponentForNonRecurringType().getStartDate().getDate() + .toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + LocalDate endDateInLocalDate = task.getComponentForNonRecurringType().getEndDate().getDate() + .toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + switch(task.getRecurringType()) { + case DAILY: + attemptCorrectDailyRecurringTask(task, localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + case WEEKLY: + attemptCorrectWeeklyRecurringTask(task, localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + case MONTHLY: + attemptCorrectMonthlyRecurringTask(task, localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + case YEARLY: + attempCorrectYearlyRecurringTask(task, localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + default: + assert false : "Recurring Type must always be specified"; + break; + } + } + + /** + * Corrects recurring tasks that are overdued to the next possible recurring slot. + * Does not correct the tasks if it is not overdued. + * + * @param task Task that we are interested in correcting + * @param localDateCurrently Local date should not be null and should be the date that we are interested in. + * @param startDateInLocalDate Converted form of start date. + * @param endDateInLocalDate Converted form of end date. + */ + private void attempCorrectYearlyRecurringTask(Task task, LocalDate localDateCurrently, + LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedYear; + if (startDateInLocalDate != null) { + elapsedYear = (int) Math.ceil(ChronoUnit.MONTHS.between(startDateInLocalDate, localDateCurrently) / NUM_MONTHS_IN_YEAR); + } else { + elapsedYear = (int) Math.ceil(ChronoUnit.MONTHS.between(endDateInLocalDate, localDateCurrently) / NUM_MONTHS_IN_YEAR); + } + if (elapsedYear > 0) { + correctYearlyRecurringTask(task, elapsedYear); + } else { + final int elapsedDay = (int) ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently); + if (elapsedDay > 0) { + correctYearlyRecurringTask(task, 1); + } + } + } + + /** + * Corrects recurring tasks that are overdued to the next possible recurring slot. + * Does not correct the tasks if it is not overdued. + * + * @param task Task that we are interested in correcting + * @param localDateCurrently Local date should not be null and should be the date that we are interested in. + * @param startDateInLocalDate Converted form of start date. + * @param endDateInLocalDate Converted form of end date. + */ + private void attemptCorrectMonthlyRecurringTask(Task task, LocalDate localDateCurrently, + LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedMonth; + if (startDateInLocalDate != null) { + elapsedMonth = (int) Math.ceil(ChronoUnit.WEEKS.between(startDateInLocalDate, localDateCurrently) / NUM_WEEKS_IN_MONTH); + } else { + elapsedMonth = (int) Math.ceil(ChronoUnit.WEEKS.between(endDateInLocalDate, localDateCurrently) / NUM_WEEKS_IN_MONTH); + } + if(elapsedMonth > 0) { + correctMonthlyRecurringTask(task, elapsedMonth); + } else { + final int elapsedDay = (int) ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently); + if (elapsedDay > 0) { + correctMonthlyRecurringTask(task, 1); + } + } + } + + /** + * Corrects recurring tasks that are overdued to the next possible recurring slot. + * Does not correct the tasks if it is not overdued. + * + * @param task Task that we are interested in correcting + * @param localDateCurrently Local date should not be null and should be the date that we are interested in. + * @param startDateInLocalDate Converted form of start date. + * @param endDateInLocalDate Converted form of end date. + */ + private void attemptCorrectWeeklyRecurringTask(Task task, LocalDate localDateCurrently, + LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedWeek; + if (startDateInLocalDate != null) { + elapsedWeek = (int) (ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently) / NUM_DAYS_IN_WEEK); + } else { + elapsedWeek = (int) (ChronoUnit.DAYS.between(endDateInLocalDate, localDateCurrently) / NUM_DAYS_IN_WEEK); + } + if(elapsedWeek > 0) { + correctWeeklyRecurringTask(task, elapsedWeek); + } else { + final int elapsedDay = (int) ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently); + if (elapsedDay > 0) { + correctWeeklyRecurringTask(task, 1); + } + } + } + + /** + * Corrects recurring tasks that are overdued to the next possible recurring slot. + * Does not correct the tasks if it is not overdued. + * + * @param task Task that we are interested in correcting + * @param localDateCurrently Local date should not be null and should be the date that we are interested in. + * @param startDateInLocalDate Converted form of start date. + * @param endDateInLocalDate Converted form of end date. + */ + private void attemptCorrectDailyRecurringTask(Task task, LocalDate localDateCurrently, + LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedDay; + if (startDateInLocalDate != null) { + elapsedDay = (int) ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently); + } else { + elapsedDay = (int) ChronoUnit.DAYS.between(endDateInLocalDate, localDateCurrently); + } + if(elapsedDay > 0) { + correctDailyRecurringTask(task, elapsedDay); + } + } + + /** + * Corrects the overdued yearly tasks. + * + * @param task Task that we are correcting. + * @param elapsedYear How many years to correct to. + */ + private void correctYearlyRecurringTask(ReadOnlyTask task, int elapsedYear) { + Calendar calendar = Calendar.getInstance(); + TaskDate correctedStartDate = new TaskDate(); + TaskDate correctedEndDate = new TaskDate(); + TaskDate startDate = task.getComponentForNonRecurringType().getStartDate(); + TaskDate endDate = task.getComponentForNonRecurringType().getEndDate(); + + if(!task.getComponentForNonRecurringType().hasOnlyEndDate()) { + calendar.setTime(startDate.getDate()); + calendar.add(Calendar.YEAR, elapsedYear); + correctedStartDate.setDateInLong(calendar.getTime().getTime()); + }else { + correctedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getDate()); + calendar.add(Calendar.YEAR, elapsedYear); + correctedEndDate.setDateInLong(calendar.getTime().getTime()); + + task.getComponentForNonRecurringType().setStartDate(correctedStartDate); + task.getComponentForNonRecurringType().setEndDate(correctedEndDate); + } + + /** + * Corrects the overdued monthly tasks. + * + * @param task Task that we are correcting. + * @param elapsedMonth How many months to correct to. + */ + private void correctMonthlyRecurringTask(ReadOnlyTask task, int elapsedMonth) { + Calendar calendar = Calendar.getInstance(); + TaskDate correctedStartDate = new TaskDate(); + TaskDate correctedEndDate = new TaskDate(); + TaskDate startDate = task.getComponentForNonRecurringType().getStartDate(); + TaskDate endDate = task.getComponentForNonRecurringType().getEndDate(); + + if(!task.getComponentForNonRecurringType().hasOnlyEndDate()) { + calendar.setTime(startDate.getDate()); + calendar.add(Calendar.MONTH, elapsedMonth); + correctedStartDate.setDateInLong(calendar.getTime().getTime()); + }else { + correctedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getDate()); + calendar.add(Calendar.MONTH, elapsedMonth); + correctedEndDate.setDateInLong(calendar.getTime().getTime()); + + task.getComponentForNonRecurringType().setStartDate(correctedStartDate); + task.getComponentForNonRecurringType().setEndDate(correctedEndDate); + } + + /** + * Corrects the overdued weekly tasks. + * + * @param task Task that we are correcting. + * @param elapsedWeek How many weeks to correct to. + */ + private void correctWeeklyRecurringTask(ReadOnlyTask task, int elapsedWeek) { + Calendar calendar = Calendar.getInstance(); + TaskDate correctedStartDate = new TaskDate(); + TaskDate correctedEndDate = new TaskDate(); + TaskDate startDate = task.getComponentForNonRecurringType().getStartDate(); + TaskDate endDate = task.getComponentForNonRecurringType().getEndDate(); + + if(!task.getComponentForNonRecurringType().hasOnlyEndDate()) { + calendar.setTime(startDate.getDate()); + calendar.add(Calendar.DAY_OF_MONTH, elapsedWeek * NUMBER_OF_DAYS_IN_A_WEEK); + correctedStartDate.setDateInLong(calendar.getTime().getTime()); + }else { + correctedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getDate()); + calendar.add(Calendar.DAY_OF_MONTH, elapsedWeek * NUMBER_OF_DAYS_IN_A_WEEK); + correctedEndDate.setDateInLong(calendar.getTime().getTime()); + + task.getComponentForNonRecurringType().setStartDate(correctedStartDate); + task.getComponentForNonRecurringType().setEndDate(correctedEndDate); + } + + /** + * Corrects the overdued daily tasks. + * + * @param task Task that we are correcting. + * @param elapsedDays How many days to correct to. + */ + private void correctDailyRecurringTask(Task task, int elapsedDay) { + Calendar calendar = Calendar.getInstance(); + TaskDate correctedStartDate = new TaskDate(); + TaskDate correctedEndDate = new TaskDate(); + TaskDate startDate = task.getComponentForNonRecurringType().getStartDate(); + TaskDate endDate = task.getComponentForNonRecurringType().getEndDate(); + + if(!task.getComponentForNonRecurringType().hasOnlyEndDate()) { + calendar.setTime(startDate.getDate()); + calendar.add(Calendar.DAY_OF_MONTH, elapsedDay); + correctedStartDate.setDateInLong(calendar.getTime().getTime()); + }else { + correctedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getDate()); + calendar.add(Calendar.DAY_OF_MONTH, elapsedDay); + correctedEndDate.setDateInLong(calendar.getTime().getTime()); + + task.getComponentForNonRecurringType().setStartDate(correctedStartDate); + task.getComponentForNonRecurringType().setEndDate(correctedEndDate); + } + + /** + * @param Updates recurring tasks to append a new date when their recurring period has elapsed + * @return True if the recurring task has been updated + * False if the recurring tasks has not been updated; + */ + private void updateRecurringTask(ReadOnlyTask task) { + Calendar startDate = new GregorianCalendar(); + Calendar endDate = new GregorianCalendar(); + + List dateComponents = task.getTaskDateComponent(); + TaskComponent lastAddedComponent = dateComponents.get(dateComponents.size()-1); + startDate.setTime(lastAddedComponent.getStartDate().getDate()); + endDate.setTime(lastAddedComponent.getEndDate().getDate()); + + if(!lastAddedComponent.getStartDate().isValid()) { + startDate = null; + } + appendRecurringTasks(task, startDate, endDate); + } + + /** + * Appends new task when the it is time to add the recurring task. + * Recurring tasks that pass their recurring dates will be appended to show the next task date. + * The LocalDate is assumed to be the current system date + * + * @param task Task that we are interested in + * @param startDate Start date of the task in Calendar form to account for time zone. + * @param endDate Start date of the task in Calendar form to account for time zone. + */ + private void appendRecurringTasks(ReadOnlyTask task, Calendar startDate, Calendar endDate) { + appendRecurringTasks(task,startDate,endDate,LocalDate.now()); + } + + + /** + * Appends new task when the it is time to add the recurring task. + * Recurring tasks that pass their recurring dates will be appended to show the next task date. + * + * @param task Task that we are interested in + * @param startDate Start date of the task in Calendar form to account for time zone. + * @param endDate Start date of the task in Calendar form to account for time zone. + * @param currentDate Current date to measure the amount of tasks that have not been done. + */ + public void appendRecurringTasks(ReadOnlyTask task, Calendar startDate, Calendar endDate, LocalDate currentDate) { + assert !CollectionUtil.isAnyNull(task, startDate, endDate, currentDate); + LocalDate localDateCurrently = currentDate; + LocalDate startDateInLocalDate = null; + if (startDate != null) { + startDateInLocalDate = startDate.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + LocalDate endDateInLocalDate = endDate.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + switch (task.getRecurringType()) { + case DAILY: + attemptAppendDailyRecurringTasks(task, startDate, endDate, + localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + case WEEKLY: + attempAppendWeeklyRecurringTasks(task, startDate, endDate, + localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + case MONTHLY: + attemptAppendMonthlyRecurringTasks(task, startDate, endDate, + localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + case YEARLY: + attemptAppendYearlyRecurringTasks(task, startDate, endDate, + localDateCurrently, startDateInLocalDate, endDateInLocalDate); + break; + default: + assert true : "Failed to set recurring type"; + } + } + + /** + * Appends yearly recurring tasks if the task has crossed over to a new year + * + * @param task Task that we are interest in + * @param startDate Start date of the task + * @param endDate End date of the ask + * @param localDateCurrently Current date that we are at now + * @param startDateInLocalDate Converted start date as a LocalDate + * @param endDateInLocalDate Converted end date as a LocalDate + */ + private void attemptAppendYearlyRecurringTasks(ReadOnlyTask task, Calendar startDate, Calendar endDate, + LocalDate localDateCurrently, LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedYear; + if (startDateInLocalDate != null) { + elapsedYear = (int) Math.ceil(ChronoUnit.MONTHS.between(startDateInLocalDate, localDateCurrently) / NUM_MONTHS_IN_YEAR); + } else { + elapsedYear = (int) Math.ceil(ChronoUnit.MONTHS.between(endDateInLocalDate, localDateCurrently) / NUM_MONTHS_IN_YEAR); + } + for (int i = 0; i < elapsedYear; i++) { + appendYearlyRecurringTask(task, startDate, endDate, elapsedYear); + } + } + + /** + * Appends monthly recurring tasks if the task has crossed over to a new month + * + * @param task Task that we are interest in + * @param startDate Start date of the task + * @param endDate End date of the ask + * @param localDateCurrently Current date that we are at now + * @param startDateInLocalDate Converted start date as a LocalDate + * @param endDateInLocalDate Converted end date as a LocalDate + */ + private void attemptAppendMonthlyRecurringTasks(ReadOnlyTask task, Calendar startDate, Calendar endDate, + LocalDate localDateCurrently, LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedMonth; + if (startDateInLocalDate != null) { + elapsedMonth = (int) Math.ceil(ChronoUnit.WEEKS.between(startDateInLocalDate, localDateCurrently) / NUM_WEEKS_IN_MONTH); + } else { + elapsedMonth = (int) Math.ceil(ChronoUnit.WEEKS.between(endDateInLocalDate, localDateCurrently) / NUM_WEEKS_IN_MONTH); + } + for (int i = 0; i < elapsedMonth; i++) { + appendMonthlyRecurringTask(task, startDate, endDate, elapsedMonth); + } + } + + /** + * Appends weekly recurring tasks if the task has crossed over to a new week + * + * @param task Task that we are interest in + * @param startDate Start date of the task + * @param endDate End date of the ask + * @param localDateCurrently Current date that we are at now + * @param startDateInLocalDate Converted start date as a LocalDate + * @param endDateInLocalDate Converted end date as a LocalDate + */ + private void attempAppendWeeklyRecurringTasks(ReadOnlyTask task, Calendar startDate, Calendar endDate, + LocalDate localDateCurrently, LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedWeek; + if ( startDateInLocalDate != null) { + elapsedWeek = (int) Math.ceil((ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently) / NUM_DAYS_IN_WEEK)); + } else { + elapsedWeek = (int) Math.ceil((ChronoUnit.DAYS.between(endDateInLocalDate, localDateCurrently) / NUM_DAYS_IN_WEEK)); + } + for (int i = 0; i < elapsedWeek; i++) { + appendWeeklyRecurringTask(task, startDate, endDate, elapsedWeek); + } + } + + /** + * Appends daily recurring tasks if the task has crossed over to a new day + * + * @param task Task that we are interest in + * @param startDate Start date of the task + * @param endDate End date of the ask + * @param localDateCurrently Current date that we are at now + * @param startDateInLocalDate Converted start date as a LocalDate + * @param endDateInLocalDate Converted end date as a LocalDate + */ + private void attemptAppendDailyRecurringTasks(ReadOnlyTask task, Calendar startDate, Calendar endDate, + LocalDate localDateCurrently, LocalDate startDateInLocalDate, LocalDate endDateInLocalDate) { + final int elapsedDay; + if (startDateInLocalDate != null) { + elapsedDay = (int) ChronoUnit.DAYS.between(startDateInLocalDate, localDateCurrently); + } else { + elapsedDay = (int) ChronoUnit.DAYS.between(endDateInLocalDate, localDateCurrently); + } + for (int i = 0; i < elapsedDay; i++) { + appendDailyRecurringTask(task, startDate, endDate, elapsedDay); + } + } + + /** + * Updates Yearly recurring tasks to the their latest date slot. + * + * @param task Recurring task to be considered. + * @param startDate The start date of this task if any. Null values represents that start date is not present. + * @param endDate The end date of the is task. + * @param elapsedYear The years that have elapsed. + */ + private void appendYearlyRecurringTask(ReadOnlyTask task, Calendar startDate, + Calendar endDate, int elapsedYear) { + // Append a new date to the current task + Calendar calendar = Calendar.getInstance(); + TaskDate editedStartDate = new TaskDate(); + TaskDate editedEndDate = new TaskDate(); + if(startDate != null) { + calendar.setTime(startDate.getTime()); + calendar.add(Calendar.YEAR, APPEND_INCREMENT); + editedStartDate.setDateInLong(calendar.getTime().getTime()); + startDate.setTime(editedStartDate.getDate()); + }else { + editedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getTime()); + calendar.add(Calendar.YEAR, APPEND_INCREMENT); + editedEndDate.setDateInLong(calendar.getTime().getTime()); + endDate.setTime(editedEndDate.getDate()); + + TaskComponent newAppendedDate = new TaskComponent((Task) task, editedStartDate, editedEndDate); + task.appendRecurringDate(newAppendedDate); + repeatingTasks.appendTaskComponent(newAppendedDate); + } + + /** + * Updates Monthly recurring tasks to the their latest date slot. + * + * @param task Recurring task to be considered. + * @param startDate The start date of this task if any. Null values represents that start date is not present. + * @param endDate The end date of the is task. + * @param elapsedYear The months that have elapsed. + */ + private void appendMonthlyRecurringTask(ReadOnlyTask task, Calendar startDate, + Calendar endDate, int elapsedMonth) { + // Append a new date to the current task + // Append a new date to the current task + Calendar calendar = Calendar.getInstance(); + TaskDate editedStartDate = new TaskDate(); + TaskDate editedEndDate = new TaskDate(); + if(startDate != null) { + calendar.setTime(startDate.getTime()); + calendar.add(Calendar.MONTH, APPEND_INCREMENT); + editedStartDate.setDateInLong(calendar.getTime().getTime()); + startDate.setTime(editedStartDate.getDate()); + }else { + editedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getTime()); + calendar.add(Calendar.MONTH, APPEND_INCREMENT); + editedEndDate.setDateInLong(calendar.getTime().getTime()); + endDate.setTime(editedEndDate.getDate()); + + TaskComponent newAppendedDate = new TaskComponent((Task) task, editedStartDate, editedEndDate); + task.appendRecurringDate(newAppendedDate); + repeatingTasks.appendTaskComponent(newAppendedDate); + } + + /** + * Updates Weekly recurring tasks to the their latest date slot. + * + * @param task Recurring task to be considered. + * @param startDate The start date of this task if any. Null values represents that start date is not present. + * @param endDate The end date of the is task. + * @param elapsedYear The weeks that have elapsed. + */ + private void appendWeeklyRecurringTask(ReadOnlyTask task, Calendar startDate, + Calendar endDate, int elapsedWeek) { + // Append a new date to the current task + Calendar calendar = Calendar.getInstance(); + TaskDate editedStartDate = new TaskDate(); + TaskDate editedEndDate = new TaskDate(); + if(startDate != null) { + calendar.setTime(startDate.getTime()); + calendar.add(Calendar.DAY_OF_MONTH, NUMBER_OF_DAYS_IN_A_WEEK); + editedStartDate.setDateInLong(calendar.getTime().getTime()); + startDate.setTime(editedStartDate.getDate()); + }else { + editedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getTime()); + calendar.add(Calendar.DAY_OF_MONTH, NUMBER_OF_DAYS_IN_A_WEEK); + editedEndDate.setDateInLong(calendar.getTime().getTime()); + endDate.setTime(editedEndDate.getDate()); + + TaskComponent newAppendedDate = new TaskComponent((Task) task, editedStartDate, editedEndDate); + task.appendRecurringDate(newAppendedDate); + repeatingTasks.appendTaskComponent(newAppendedDate); + } + + /** + * Updates Daily recurring tasks to the their latest date slot. + * + * @param task Recurring task to be considered. + * @param startDate The start date of this task if any. Null values represents that start date is not present. + * @param endDate The end date of the is task. + * @param elapsedYear The days that have elapsed. + */ + private void appendDailyRecurringTask(ReadOnlyTask task, Calendar startDate, + Calendar endDate, int elapsedDay) { + // Append a new date to the current task + Calendar calendar = Calendar.getInstance(); + TaskDate editedStartDate = new TaskDate(); + TaskDate editedEndDate = new TaskDate(); + if(startDate != null) { + calendar.setTime(startDate.getTime()); + calendar.add(Calendar.DAY_OF_MONTH, APPEND_INCREMENT); + editedStartDate.setDateInLong(calendar.getTime().getTime()); + startDate.setTime(editedStartDate.getDate()); + }else { + editedStartDate.setDateInLong((new TaskDate()).getDateInLong()); + } + + calendar.setTime(endDate.getTime()); + calendar.add(Calendar.DAY_OF_MONTH, APPEND_INCREMENT); + editedEndDate.setDateInLong(calendar.getTime().getTime()); + endDate.setTime(editedEndDate.getDate()); + + TaskComponent newAppendedDate = new TaskComponent((Task) task, editedStartDate, editedEndDate); + task.appendRecurringDate(newAppendedDate); + repeatingTasks.appendTaskComponent(newAppendedDate); + } + + + public static RecurringTaskManager getInstance() { + if (instance == null) { + instance = new RecurringTaskManager(); + } + return instance; + } +} +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Initializes a ModelManager with the given TaskList + * TaskList and its variables should not be null + */ + public ModelManager(TaskMaster src, UserPrefs userPrefs) { + super(); + assert src != null; + assert userPrefs != null; + logger.fine("Initializing with address book: " + src + " and user prefs " + userPrefs); + + taskMaster = new TaskMaster(src); + tasks = taskMaster.getTasks(); + filteredTaskComponents = new FilteredList<>(taskMaster.getTaskComponentList()); + RecurringTaskManager.getInstance().setTaskList(taskMaster.getUniqueTaskList()); + RecurringTaskManager.getInstance().updateAnyRecurringTasks(); + + } +``` +###### \java\seedu\address\model\ModelManager.java +``` java + public ModelManager(ReadOnlyTaskMaster initialData, UserPrefs userPrefs) { + taskMaster = new TaskMaster(initialData); + tasks = taskMaster.getTasks(); + + filteredTaskComponents = new FilteredList<>(taskMaster.getTaskComponentList()); + RecurringTaskManager.getInstance().setTaskList(taskMaster.getUniqueTaskList()); + RecurringTaskManager.getInstance().updateAnyRecurringTasks(); + + } +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public synchronized void addTask(Task task) throws UniqueTaskList.DuplicateTaskException, TimeslotOverlapException { + taskMaster.addTask(task); + RecurringTaskManager.getInstance().correctAddingOverdueTasks(task); + updateFilteredListToShowAll(); + indicateTaskListChanged(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + private class ArchiveQualifier implements Qualifier { + private boolean isArchived; + + ArchiveQualifier(boolean isItArchive) { + this.isArchived= isItArchive; + } + + @Override + public boolean run(TaskComponent task) { + return task.isArchived() == isArchived; + } + + @Override + public String toString() { + return "type=" + isArchived; + } + } +``` +###### \java\seedu\address\model\task\RecurringType.java +``` java +public enum RecurringType { + NONE, + DAILY, + WEEKLY, + MONTHLY, + YEARLY, + IGNORED //Merely for parsing +} +``` +###### \java\seedu\address\model\task\Task.java +``` java + @Override + public List getTaskDateComponent() { + return recurringDates; + } + + @Override + public TaskType getTaskType() { + return taskType; + } + @Override + public RecurringType getRecurringType() { + return recurringType; + } + + public void setTaskType(TaskType type) { + this.taskType = type; + } + + public void setRecurringType(RecurringType type) { + if (taskType == TaskType.FLOATING) { + assert (!type.equals(RecurringType.NONE)) : "Floating Task cannot be a recurring task"; + } + this.recurringType = type; + } +``` +###### \java\seedu\address\model\task\Task.java +``` java + @Override + public void completeTaskWhenAllComponentArchived() { + for (TaskComponent c : recurringDates) { + if (c.isArchived() == false || c.getTaskReference().getRecurringType() != RecurringType.NONE) { + return; + } + } + taskType = TaskType.COMPLETED; + } +``` +###### \java\seedu\address\model\task\Task.java +``` java + @Override + public TaskComponent getComponentForNonRecurringType() { + assert recurringDates.size() == 1 : "This method should only be used for non recurring tasks"; + return recurringDates.get(0); + } + + @Override + public TaskComponent getLastAppendedComponent() { + return recurringDates.get(recurringDates.size()-1); + } + + @Override + public void appendRecurringDate(TaskComponent componentToBeAdded) { + assert !recurringType.equals(RecurringType.NONE) : "You cannot append new dates to non recurring tasks"; + recurringDates.add(componentToBeAdded); + recurringDates.get(recurringDates.size()-1).setTaskReferrence(this); + } +``` +###### \java\seedu\address\model\task\TaskComponent.java +``` java +/** +* This class served as the occurrence portion in an abstraction occurrence pattern. +* The abstraction is the Task and the occurrence is the TaskDateComponent. +* +*/ +public class TaskComponent { + + private Task taskReference; + private TaskDate startDate, endDate; + private boolean isArchived; + + public TaskComponent(Task taskReference, TaskDate startDate, TaskDate endDate) { + assert !CollectionUtil.isAnyNull(startDate, endDate); + this.startDate = new TaskDate(startDate); + this.endDate = new TaskDate(endDate); + this.taskReference = taskReference; + } + + public TaskComponent(TaskComponent taskDateComponent) { + assert taskDateComponent != null : "Cannot pass in null values"; + this.taskReference = taskDateComponent.taskReference; + this.startDate = taskDateComponent.startDate; + this.endDate = taskDateComponent.endDate; + this.isArchived = taskDateComponent.isArchived; + } + + public void setStartDate(TaskDate startDate) { + this.startDate = startDate; + } + + public void setEndDate(TaskDate endDate) { + this.endDate = endDate; + } + + public void setTaskReferrence(Task task) { + this.taskReference = task; + } + + public TaskDate getStartDate() { + return startDate; + } + + public TaskDate getEndDate() { + return endDate; + } + + /** + * Checks if TaskDateComponent is in a valid time slot + * + * @return True if it is in a valid time slot + */ + public boolean isValidTimeSlot(){ + if(startDate!=null && endDate!=null){ + return (endDate.getDate()).after(startDate.getDate()); + }else{ + return true; + } + } + + /** + * Checks if the TaskDate has only end date + * + * @return True if it has only an end date. + * False if it also contains a start date. + */ + public boolean hasOnlyEndDate() { + if (startDate.getDateInLong() != TaskDate.DATE_NOT_PRESENT){ + return false; + } + return true; + } + + + public ReadOnlyTask getTaskReference() { + return taskReference; + } + + /** + * Archive this task component + */ + public void archive() { + isArchived = true; + } + +``` +###### \java\seedu\address\model\task\TaskDate.java +``` java +/** + * Helper class for storing date for the Task + */ +public class TaskDate { + public static final int DATE_NOT_PRESENT = -1; + private long date; + + /** + * Date is not present by default if nothing is specified + * Convenience and defensive + */ + public TaskDate() { + this.date = DATE_NOT_PRESENT; + } + + public TaskDate(Date date) { + this.date = date.getTime(); + } + + public TaskDate(long date) { + this.date = date; + } + + public TaskDate(TaskDate copy) { + this.date = copy.date; + } + + //For sake of testing, not implemented in main app + public TaskDate(String inputDate) { + this.date = new com.joestelmach.natty.Parser().parse(inputDate).get(0).getDates().get(0).getTime(); + } + + public void setDateInLong(long date) { + this.date = date; + } + /** + * Formats the date in (EEE, MMM d hh.mma) format which will give MON, Oct 20 10.00PM + * If there is no date present return empty string + * @return Empty string if there is no date present + * Formatted date if there is date + */ + public String getFormattedDate() { + if (date == DATE_NOT_PRESENT) { + return ""; + } + SimpleDateFormat formatter = new SimpleDateFormat("EEE, MMM d hh.mma", Locale.ENGLISH); + return formatter.format(new Date(date)); + } + + //For sake of testing + public String getInputDate() { + if (date == DATE_NOT_PRESENT) { + return ""; + } + SimpleDateFormat formatter = new SimpleDateFormat("dd MMM hha", Locale.ENGLISH); + return formatter.format(new Date(date)); + } + + public long getDateInLong() { + return date; + } + + /** + * Parses the date in Long and provides it in the Date class format + * @return + */ + public Date getDate() { + return new Date(date); + } + + @Override + public boolean equals(Object other){ + return other == this || + (other instanceof TaskDate // instanceof handles nulls + && this.getDate().equals(((TaskDate) other).getDate())); + } + + public boolean isValid() { + return date != DATE_NOT_PRESENT; + } + + @Override + public String toString() { + return getFormattedDate(); + } + +} +``` +###### \java\seedu\address\model\task\TaskType.java +``` java +public enum TaskType { + FLOATING, + NON_FLOATING, + COMPLETED +} +``` +###### \java\seedu\address\model\task\UniqueTaskList.java +``` java + /** + * Adds a task to the list. + * + * @throws DuplicateTaskException if the task to add is a duplicate of an existing task in the list. + * @throws TimeslotOverlapException + */ + public void add(Task toAdd) throws DuplicateTaskException, TimeslotOverlapException { + assert toAdd != null; + if (contains(toAdd)) { + if (!toAdd.getRecurringType().equals(RecurringType.NONE)) { + // append this "task" as date component to the task + appendDuplicateRecurringDatesToTask(toAdd); + return; + } + throw new DuplicateTaskException(); + } + if(overlaps(toAdd)){ + throw new TimeslotOverlapException(); + } + internalList.add(toAdd); + internalComponentList.addAll(toAdd.getTaskDateComponent()); + } + + private void appendDuplicateRecurringDatesToTask(Task toAdd) { + int idx = internalList.indexOf(toAdd); + Task toBeAppendedOn = internalList.get(idx); + internalComponentList.add(toAdd.getComponentForNonRecurringType()); + toBeAppendedOn.appendRecurringDate(toAdd.getComponentForNonRecurringType()); + } +``` +###### \java\seedu\address\storage\XmlAdaptedTaskComponent.java +``` java + @XmlElement + private long startDate; + @XmlElement + private long endDate; + @XmlElement + private String recurringType; + @XmlElement + private boolean isArchived; +``` +###### \java\seedu\address\storage\XmlAdaptedTaskComponent.java +``` java + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedTaskComponent(TaskComponent source) { + name = source.getTaskReference().getName().fullName; + tagged = new ArrayList<>(); + for (Tag tag : source.getTaskReference().getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + + if (source.getTaskReference().getTaskType() == TaskType.NON_FLOATING) { + startDate = source.getStartDate().getDateInLong(); + endDate = source.getEndDate().getDateInLong(); + } else if (source.getTaskReference().getTaskType() == TaskType.FLOATING) { + startDate = TaskDate.DATE_NOT_PRESENT; + endDate = TaskDate.DATE_NOT_PRESENT; + } else if(source.getTaskReference().getTaskType() == TaskType.COMPLETED){ + startDate = source.getStartDate().getDateInLong(); + endDate = source.getEndDate().getDateInLong(); + } + if (source.getTaskReference().getRecurringType() != RecurringType.NONE && source.isArchived()) { + TaskDate startCopy = new TaskDate(source.getStartDate()); + TaskDate endCopy = new TaskDate(source.getEndDate()); + startDate = startCopy.getDateInLong(); + endDate = endCopy.getDateInLong(); + } + recurringType = source.getTaskReference().getRecurringType().name(); + isArchived = source.isArchived(); + } + + /** + * Converts this jaxb-friendly adapted task object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + final Name name = new Name(this.name); + final UniqueTagList tags = new UniqueTagList(taskTags); + + if (endDate != TaskDate.DATE_NOT_PRESENT) { + return toModelTypeNonFloating(name, tags); + } + return toModelTypeFloating(name, tags); + } + + private Task toModelTypeFloating(final Name name, final UniqueTagList tags) { + Task task = new Task(name, tags); + + if(isArchived){ + task.setTaskType(TaskType.COMPLETED); + for(TaskComponent t: task.getTaskDateComponent()){ + t.archive(); + } + } + + return task; + } + + private Task toModelTypeNonFloating(final Name name, final UniqueTagList tags) { + final TaskDate taskStartDate = new TaskDate(startDate); + final TaskDate taskEndDate = new TaskDate(endDate); + RecurringType toBeAdded = RecurringType.NONE; + if (recurringType != null ) { + toBeAdded = RecurringType.valueOf(recurringType); + } + + Task task = new Task(name, tags, taskStartDate, taskEndDate, toBeAdded); + if(isArchived){ + task.setTaskType(TaskType.COMPLETED); + for(TaskComponent t: task.getTaskDateComponent()){ + t.archive(); + } + } + + return task; + } +``` +###### \resources\view\TaskListCard.fxml +``` fxml +