diff --git a/.project b/.project index 1c9339c5f927..6692e9c722f6 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - addressbook-level4 + main Project addressbook-level4 created by Buildship. diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 4e0fc71ac89f..58b9567e0ca2 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -1,7 +1,6 @@ build.commands=org.eclipse.jdt.core.javabuilder connection.arguments= connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) -connection.gradle.user.home=null connection.java.home=null connection.jvm.arguments= connection.project.dir= 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/README.md b/README.md index 249a00b3899c..69c764924f7e 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,13 @@ -[![Build Status](https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master)](https://travis-ci.org/se-edu/addressbook-level4) -[![Coverage Status](https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master)](https://coveralls.io/github/se-edu/addressbook-level4?branch=master) +[![Build Status](https://travis-ci.org/CS2103AUG2016-T14-C2/main.svg?branch=master)](https://travis-ci.org/CS2103AUG2016-T14-C2/main) +[![Coverage Status](https://coveralls.io/repos/github/CS2103AUG2016-T14-C2/main/badge.svg?branch=master)](https://coveralls.io/github/CS2103AUG2016-T14-C2/main?branch=master) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/91232e72b4c645dc9d988942651ca906)](https://www.codacy.com/app/xefrog/main?utm_source=github.com&utm_medium=referral&utm_content=CS2103AUG2016-T14-C2/main&utm_campaign=Badge_Grade) -# Address Book (Level 4) +# Savvy Tasker
-* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using +* This is a desktop Task Tracker application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as - the main programming language. -* It is **written in OOP fashion**. It provides a **reasonably well-written** code example that is - **significantly bigger** (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from [level 3](https://github.com/se-edu/addressbook-level3): - * A more sophisticated GUI that includes a list panel and an in-built Browser. - * More test cases, including automated GUI testing. - * Support for *Build Automation* using Gradle and for *Continuous Integration* using Travis CI. #### Site Map @@ -29,6 +22,8 @@ * Some parts of this sample application were inspired by the excellent [Java FX tutorial](http://code.makery.ch/library/javafx-8-tutorial/) by *Marco Jakob*. +* This is a sample project created by [SE-EDU](https://githubcom/se-edu/) initiative. +* [Natty](http://natty.joestelmach.com/) for parsing user input dates #### Licence : [MIT](LICENSE) diff --git a/SampleData.xml b/SampleData.xml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/build.gradle b/build.gradle index 46b06c1e42ec..b2a60cde1314 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,7 @@ allprojects { junitVersion = '4.12' testFxVersion = '4.0.+' monocleVersion = '1.8.0_20' + nattyVersion = '0.11' libDir = 'lib' } @@ -52,6 +53,7 @@ allprojects { compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDataTypeVersion" compile "com.google.guava:guava:$guavaVersion" + compile "com.joestelmach:natty:$nattyVersion" testCompile "junit:junit:$junitVersion" testCompile "org.testfx:testfx-core:$testFxVersion" @@ -74,10 +76,10 @@ allprojects { } shadowJar { - archiveName = "addressbook.jar" + archiveName = "savvytasker.jar" manifest { - attributes "Main-Class": "seedu.address.MainApp" + attributes "Main-Class": "seedu.savvytasker.MainApp" } destinationDir = file("${buildDir}/jar/") diff --git a/collate.bat b/collate.bat new file mode 100644 index 000000000000..741fde7c69e2 --- /dev/null +++ b/collate.bat @@ -0,0 +1,3 @@ +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 diff --git a/collated/docs/A0097627N.md b/collated/docs/A0097627N.md new file mode 100644 index 000000000000..0ca6bea6988a --- /dev/null +++ b/collated/docs/A0097627N.md @@ -0,0 +1,399 @@ +# A0097627N +###### \DeveloperGuide.md +``` md + +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` + + +Priority | As a(n) ... | I want to ... | So that I can... +-------- | :-------- | :--------- | :----------- +`* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App +`* * *` | new user | view more information about a particular command | learn how to use various commands +`* * *` | user | add a new task | record tasks that need to be done some day +`* * *` | user | update a task description/due date/priority level | make modifications to tasks without having delete and re-add it +`* * *` | user | mark completed tasks as done | remind myself that I have completed a task +`* * *` | user | unmark marked tasks | list a resurfaced task without having to key in the same information again +`* * *` | user | delete a task | get rid of tasks that I no longer care to track +`* * *` | user | sort ongoing tasks by due date or priority | decide what needs to be done soon +`* * *` | user | view the list of ongoing tasks | decide what needs to be done +`* * *` | user | view the list of archived tasks | see what has been done +`* *` | user | find tasks by partial or full task name or date | locate a tasks / similar tasks in case I forget the exact task name I typed +`*` | user | sort tasks by priority level | see the most important tasks and prioritize accordingly +`*` | user | undo most recent command | undo the most recent operation +`*` | user | redo most recent undo command | redo the operation done by the most recent undo action +`*` | user | change storage location | choose where my tasks are saved at +`*` | advanced user | alias keywords with shorter versions | type a command faster +`*` | advanced user | remove alias of keywords with shorter versions | get rid of shorter version of certain keywords + +## Appendix B : Use Cases + +``` +###### \DeveloperGuide.md +``` md + +(For all use cases below, the **System** is the `Savvy Tasker` and the **Actor** is the `user`, unless specified otherwise) + +### Use case: Add task + +**MSS** + +1. Savvy Tasker waits for user command +2. User enters command to add a task according to some parameters
+3. Savvy Tasker adds the task to a list of tasks
+Use case ends. + +**Extensions** + +2a. At least one parameter entered by user is invalid +> 2a1. Savvy Tasker shows an error message and display the expected format.
+> Use case resumes at step 1 + + +2b. START_DATE and END_DATE are different, the RECURRING_TYPE has to be larger than the duration between START_DATE and END_DATE. (e.g. A 3d2n camp cannot be recurring daily but it can be recurring weekly) +> 2b1. Savvy Tasker shows an error message
+> Use case resumes at step 1 + + +2c. START_DATE and END_DATE are different, END_DATE is before START_DATE +> 2c1. Savvy Tasker shows an error message
+> Use case resumes at step 1 + + +2d. START_DATE and END_DATE are the same, END_TIME is before START_TIME +> 2d1. Savvy Tasker shows an error message
+> Use case resumes at step 1 + +### Use case: List tasks + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to list tasks +3. Savvy Tasker shows a list of tasks
+Use case ends. + +**Extensions** + +3a. The list is empty + +> 3a1. Savvy Tasker shows an error message
+ Use case ends + +### Use case: Find task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to find tasks by keyword +3. Savvy Tasker displays the list of tasks that contains the keyword in the name
+Use case ends. + +**Extensions** + +2a. No parameter entered after command word +> Savvy Tasker shows a 'no parameter entered' error message.
+> Use case resumes at step 1 + + +3a. The list is empty +> 2a1. Savvy Tasker shows a 'no task found' error message.
+> Use case ends + + +### Use case: Modify task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to modify a certain attribute of a specific task +3. Savvy Tasker modifies the task and saves it in memory
+Use case ends. + +**Extensions** + +1a. The list is empty + +> 1a1. Use case ends + +2a. The given index is invalid + +> 2a1. Savvy Tasker shows an error message
+ Use case resumes at step 3 + +2b. At least one parameter entered by user is invalid + +> 2b1. Savvy Tasker shows an error message and display the expected format
+ Use case resumes at step 3 + + +### Use case: Change storage location + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to change the storage location of Savvy Tasker +3. Savvy Tasker changes the storage location, saving all existing data in the new location
+Use case ends. + +**Extensions** + +2a. The given path is invalid + +> 2a1. Savvy Tasker shows an error message
+ Use case ends + +2b. The given path is is not accessible (read/write) by Savvy Tasker + +> 2b1. Savvy Tasker shows an error message
+ Use case ends + + +### Use case: Mark task as done + +**MSS** + +1. Savvy Tasker waits for user command +2. User request to mark specific tasks in the list based on task’s index +3. Savvy Tasker marks the tasks, removes it from the task list, and adds it to the Archived list
+Use case ends. + +**Extensions** + +1a. The list is empty +> 2a1. Savvy Tasker shows a 'no task found' error message.
+> Use case ends + +2a. The given index is invalid +> 3a1. Savvy Tasker shows a 'invalid index' error message
+> Use case resumes at step 1 + +2b. The task is already marked as done +> 3b1. Savvy Tasker shows a 'task already marked' error message.
+> Use case resumes at step 1 + +### Use case: Unmark marked task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to list archived tasks +3. Savvy Tasker displays a list of archived tasks, sorted by time and date the task has been marked +4. User requests to unmark the specific task in the list based on task’s index +5. Savvy Tasker removes the marked status of the specific task, removes it from the Archived list, and adds it back to the task list
+Use case ends. + +**Extensions** + +2a. The list is empty +> Use case ends + +3a. The given index is invalid +> 3a1. Savvy Tasker shows a 'invalid index' error message
+> Use case resumes at step 1 + + +### Use case: Delete task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to list tasks +3. Savvy Tasker shows a list of tasks +4. User requests to delete a specific task in the list +5. Savvy Tasker deletes the task
+Use case ends. + +**Extensions** + +3a. The list is empty + +> Use case ends + +4a. The given index is invalid + +> 4a1. Savvy Tasker shows an error message +> Use case resumes at step 3
+ +### Use case: Alias keyword and use shorten keyword + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to alias a keyword (can be a command or any other frequently used word), with a shorten keyword +3. Savvy Tasker store the shorten keyword associated with the keyword in its database +4. User request a command +4. Savvy Tasker check if the command contain any shorten keyword, if it does, replace the shorten keyword with the associated keyword from its database +5. Savvy Tasker carry out the command
+Use case ends. + +**Extensions** + +2a. The shorten keyword contains only 1 character +> 2a1. Savvy Tasker shows a error message +> Use case resumes at step 1
+ +2b. The shorten keyword has already been associated with other keywords +> 2b1. Savvy Tasker shows a error message and the shorten keyword's original associated keyword +> Use case resumes at step 1
+ +### Use case: Unalias keyword + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to unalias a shorten keyword +3. Savvy Tasker remove the shorten keyword associated with the keyword in its database
+Use case ends. + +**Extensions** + +2a. The shorten keyword could not be found in Savvy Tasker database +> 2a1. Savvy Tasker shows a 'not found' error message +> Use case resumes at step 1
+ +### Use case: Undo previous command + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to undo last executed command +3. Savvy Tasker undos the last executed command to return to the state before that command was executed
+Use case ends. + +**Extensions** + +2a. There is no previously executed command to undo +> 2a1. Savvy Tasker shows a 'cannot undo' error message
+> Use case ends + +### Use case: Redo most recently undone command + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to redo last undone command +3. Savvy Tasker re executes the executed command that was last undone
+Use case ends. + +**Extensions** + +2a. There are no executed undo commands to redo +> 2a1. Savvy Tasker shows a 'cannot redo' error message
+> Use case ends + +## Appendix C : Non Functional Requirements + +1. Should work on any [mainstream OS](#mainstream-os) as long as it has Java `1.8.0_60` or higher installed. +2. Should be able to hold up to 1000 tasks. +3. Should come with automated unit tests and open source code. +4. Should favor DOS style commands over Unix-style commands. +6. Should work stand-alone and should not be a plug-in to another software. +7. Should work without internet connection. +8. Should store data in text file. +9. Should work without requiring an installer. + + +## Appendix D : Glossary + +##### Mainstream OS + +> Windows, Linux, Unix, OS-X + +##### Private contact detail + +> A contact detail that is not meant to be shared with others + +## Appendix E : Product Survey + +``` +###### \DeveloperGuide.md +``` md + +#### Competing product: Google Calendar + +**Pros:** + +1. Able to color-code different events +2. Able to set reminders and task/events on repeat +3. Able to add description such as location, remarks and people +4. Able to sync on different devices +5. Able to share calendar +6. Able to undo previous action +7. Able to drag and drop task/events to another date/timing
+ +**Cons:** + +1. Unable to check(tick) completed event +2. Do not have a list of archived task +3. Does not cater for floating task
+ +#### Competing product: Todo + +**Pros:** + +1. Auto prioritization +2. Unlimited contexts +3. Reminders +4. Auto sync with iCal, Toodledo, Outlook, Todo Online
+ +#### Competing product: Remember The Milk + +**Pros:** + +1. Unlimited contexts +2. Reminders +3. Calendar tasks +4. Auto sync with Gmail (Firefox plugin), Google Calendar, Twitter (direct integration), Atom/RSS, IM (feed) +5. Email notifications, autoprocess +6. API
+ +``` +###### \UserGuide.md +``` md + +#### Mark a task as done : `mark` +Marks the task as completed identified by the index number used in the last task listing. Completed task will be remove from the normal list and placed under archived list
+Format: `mark INDEX [MORE_INDEX]` + +> Selects the task and marks the task as done at the specified `INDEX`. + The index refers to the index number shown in the most recent listing.
+ The index **must be a positive integer** 1, 2, 3, ... + +Examples: +* `list`
+ `mark 2`
+ Marks the 2nd task in the task book as completed. +* `find CS2103`
+ `mark 1`
+ Marks the 1st task in the results of the `find` command as completed. + +#### Unmark a task as done : `unmark` +Unmarks the task identified by the index number used in the last task listing.
+Format: `Unmark INDEX [MORE_INDEX]` + +> Selects the task and marks the task as done at the specified `INDEX`. + The index refers to the index number shown in the most recent listing.
+ The index **must be a positive integer** 1, 2, 3, ... + +Examples: +* `list`
+ `unmark 2`
+ Unmarks the 2nd task in the task book as done. +* `find CS2103`
+ `unmark 1`
+ Unmarks the 1st task in the results of the `find` command as done. + +#### Undo the most recent operation : `undo` +Undo the most recent command that was executed.
+Format: `undo` + +#### Redo the most recent undo operation : `redo` +Redo the most recent command that was executed by the undo.
+Format: `redo` +>Redo is unavailable if the most recent command is not undo + +#### Clearing all entries : `clear` +Clears all entries from the Savvy Task.
+Format: `clear` + +``` diff --git a/collated/docs/A0138431L.md b/collated/docs/A0138431L.md new file mode 100644 index 000000000000..02e7cee1bff4 --- /dev/null +++ b/collated/docs/A0138431L.md @@ -0,0 +1,63 @@ +# A0138431L +###### \DeveloperGuide.md +``` md +### UI component + +
+ +**API** : [`Ui.java`](../src/main/java/seedu/savvytasker/ui/Ui.java) + +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TaskListPanel`, `UpcomingPanel`, `DailyPanel`, `FloatingPanel`, +`StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class +and they can be loaded using the `UiPartLoader`. + +The `UI` component uses 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`](../src/main/java/seedu/savvytasker/ui/MainWindow.java) is specified in + [`MainWindow.fxml`](../src/main/resources/view/MainWindow.fxml) + +The `UI` component, +* Executes user commands using the `Logic` component. +* Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. +* Responds to events raised from various parts of the App and updates the UI accordingly. + +The cursor will be focus to the `CommandBox` by default as the `CommandBox` carries out numerous keyboard shortcuts to make the app more user-friendly. + +``` +###### \UserGuide.md +``` md + +#### Command stack history +UP: Return last user input command in command box
+DOWN: Return (if any) next user input command in command box
+> Note that DOWN is only allowed after at least an UP is being entered + + +#### Week Selection +Ctrl + LEFT: Display previous week’s daily task list
+Ctrl + RIGHT: Display next week’s daily task list + +``` +###### \UserGuide.md +``` md + +## Keyboard Shortcuts + +Key Codes | Function | Command Box Input +-------- | :-------- | :-------- +Esc | Toggle to show/hide a list of keyboard shortcuts | - +Ctrl + D | [Clear](#clearing-all-entries--clear) all entries | `clear` +Ctrl + Q | [Exit](#exiting-the-program--exit) | `exit` +Ctrl + L | [List](#listing-all-tasks-list) all unmarked task by date, earliest task first | `list` +Ctrl + A | [List](#listing-all-tasks-list) archived tasks | `list archived` +Ctrl + P | [List](#listing-all-tasks-list) all unmarked task by priority level, high priority first | `list priorityLevel` +Ctrl + I | [List](#listing-all-tasks-list) all alias keys | `list alias` +Ctrl + H | [Help](#viewing-help--help) | `help` +Ctrl + S | Popups a directory chooser dialog box to choose a new filepath | `storage NEW_FILEPATH` +Ctrl + Z | [Undo](#undo-the-most-recent-operation--undo) | `undo` +Ctrl + Y | [Redo](#redo-the-most-recent-undo-operation--redo) | `redo` +Ctrl + UP | Return [last user input](#command-stack-history) command in command box | - +Ctrl + DOWN | Return (if any) [next user input](#command-stack-history) command in command box | - +Ctrl + LEFT | Displays [previous week’s](#week-selection) daily task list | - +Ctrl + RIGHT | Display [next week’s](#week-selection) daily task list | - +``` diff --git a/collated/docs/A0139915W.md b/collated/docs/A0139915W.md new file mode 100644 index 000000000000..bf3949d34116 --- /dev/null +++ b/collated/docs/A0139915W.md @@ -0,0 +1,211 @@ +# A0139915W +###### \DeveloperGuide.md +``` md + + + +>Note how the `Model` simply raises a `SavvyTaskerChangedEvent` when the Savvy Tasker data are changed, + instead of asking the `Storage` to save the updates to the hard disk. + +The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates +being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.
+ + +> Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having + to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct + coupling between components. + +The sections below give more details of each component. + +``` +###### \DeveloperGuide.md +``` md + +### Model component + +
+ +**API** : [`Model.java`](../src/main/java/seedu/savvytasker/model/Model.java) + +The `Model`, +* stores a `UserPref` object that represents the user's preferences. +* stores the Savvy Tasker data. +* exposes a `UnmodifiableObservableList` 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. +* does not depend on any of the other three components. + +### Storage component + +
+ +**API** : [`Storage.java`](../src/main/java/seedu/savvytasker/storage/Storage.java) + +The `Storage` component, +* can save `UserPref` objects in json format and read it back. +* can save the Saavy Tasker data in xml format and read it back. + +``` +###### \DeveloperGuide.md +``` md + +#### Competing product: WunderList + +**Pros:** + +1. Allows creation of subtasks within a task. +2. Allows local storage, in case there isn't internet access. +3. Can sync across devices by signing in. +4. Allows the grouping of tasks as a list and even grouping into folders. +5. Can invite other person(s), sharing the tasks with them. + +**Cons:** + +1. Doesn't allow adding of tasks through the command line (one-shot). +2. Cannot block slots. +3. No calendar view. +``` +###### \UserGuide.md +``` md + +#### Adding a task: `add` +Adds a task to Savvy Tasker.
+Format: `add TASK_NAME [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]` + +> Parameters | Description +> -------- | :-------- +> TASK_NAME | `Mandatory` Specifies the name of the task. +> START_DATE (See [DATE](#date)) | `Optional` Specifies the starting date and time of the task. +> END_DATE (See [DATE](#date)) | `Optional` Specifies the ending date and time of the task. +> LOCATION | `Optional` Specifies the location where the task happens. +> PRIORITY_LEVEL | `Optional` Specifies the priority level of the task.
`Accepts` values `low`, `medium`, `high`
`Defaults` to `???` +> RECURRING_TYPE | `Optional` Specifies the recurring type of the task.
`Accepts` values `none`, `daily`, `weekly`, `monthly`, `yearly`
`Defaults` to `none` +> NUMBER_OF_RECURRENCE | `Optional` Specifies the number of times the task recurrs.
`Defaults` to `1`
`Ignored` if RECURRING_TYPE is `none` +> CATEGORY | `Optional` Specifies a custom category for the task. This can be used for keeping track of similar tasks. +> DESCRIPTION | `Optional` Describes the task. + +##### Date + +> If only the DATE is specified, the TIME defaults to starting at 12am or ending at 11:59pm.
If only the TIME is specified, the DATE defaults to today.

If only `START_DATE` is supplied, the task will be a 1-day event starting from the specified `START_DATE` and ending on the same day at 11:59pm.
If only `END_DATE` is supplied, the task will start today at 12am.

The date and time can be entered in a formal format like 17-03-2016, or a natural format like next wednesday, 2pm. The formal format follows the system's settings for whether mm-dd-yyyy or dd-mm-yyyy is used. + +Examples: +* `add Project Meeting s/05-10-2016 2pm e/6pm r/daily n/2 c/CS2103 d/Discuss about roles and milestones`
+ Add task named, Project Meeting, under CS2103 category. The task is schedule to take place on 5th and 6th of October 2016 from 2pm to 6pm each day. +* `add NUSSU Leadership Camp s/05-10-2016 2pm e/08-10-2016 6pm c/NUSSU` + Add task named, NUSSU Leadership Camp, under NUSSU category. The 4 day 3 night is schedule to take place from 5th October, 2pm to 8th of October 2016, 6pm. + +#### Listing all tasks: `list` +Shows a list of all tasks in Savvy Tasker
+Format: `list [LIST_TYPE]` + +> Parameters | Description +> -------- | :-------- +> LIST_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `DueDate`, `PriorityLevel`, `Archived`, `Alias`
`Defaults` to `DueDate` + +`LIST_TYPE` Explanation: +* `DueDate`
+ Tasks are sorted according to END_DATE beginning with the earliest.
+ If no END_DATE is specified for the task (floating tasks), they will be sorted to bottom of list.
+* `PriorityLevel`
+ Tasks are sorted according to priority level beginning with the highest.
+* `Archived`
+ Tasks that have been [marked](#mark-a-task-as-done--mark) are listed. They are sorted according to the time of creation of the task. +* `Alias`
+ [Aliases](#alias-a-keyword--alias) that have been registered are listed.
+ +#### Finding all task containing any keyword in its name: `find` +Finds tasks whose names contain any of the given keywords.
+Format: `find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS...]` + +> Parameters | Description +> -------- | :-------- +> FIND_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `Partial`, `Full`, `Exact`
`Defaults` to `Partial` + +> Only the TASK_NAME is searched.
+> The search is case insensitive. e.g `task` will match `Task`
+> The order of the keywords does not matter for `Partial` and `Full`. e.g. `project meeting` will match `meeting project`
+> Tasks matching at least one keyword will be returned, except for the case of `Exact`.
+ +`FIND_TYPE` Explanation: +* `Partial`
+ Partial keywords will be matched e.g. `task` will match `2103 tasks`
+* `Full`
+ Only full keywords will be matched e.g. `task` will not match `2103 tasks` but matches `CS2103 Single Task`
+* `Exact`
+ The exact set of keywords will be matched e.g. `Project Meeting` will match `Project Meeting` but not `2103 Project Meeting`
+ +Examples: +* `find t/Full Project meeting`
+ Returns any task containing names `Project` or `Meeting`, but not `meet` +* `find t/Exact Project meeting`
+ Returns any task containing names `Project Meeting` exactly +* `find meet CS2103`
+ Returns any task containing names `meet`, or `CS2103`. This matches `meeting` and any other words containing `meet` or `CS2103` + +#### Deleting a task : `delete` +Deletes the specified task from Savvy Tasker.
+Format: `delete INDEX [MORE_INDEX...]` + +> Parameters | Description +> -------- | :-------- +> INDEX | `Mandatory` Specifies the index of the listing shown to modify.
The index **must be a positive integer** 1, 2, 3, ... +> MORE_INDEX... | `Optional` Same as INDEX. +> Deletes the task at the specified `INDEX` and `[MORE_INDEX...]`. + +Examples: +* `list`
+ `delete 2 3 5`
+ Deletes the 2nd, 3rd and 5th task listed by Savvy Tasker. +* `find CS1010`
+ `delete 1`
+ Deletes the 1st task in the results of the `find` command. + +#### Modifies a task : `modify` +Modifies the task identified by the index number used in the last task listing.
+Format: `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]` + +> Parameters | Description +> -------- | :-------- +> INDEX | `Mandatory` Specifies the index of the listing shown to modify.
The index **must be a positive integer** 1, 2, 3, ... +> TASK_NAME
START_DATE
END_DATE
LOCATION
PRIORITY_LEVEL
RECURRING_TYPE
NUMBER_OF_RECURRENCE
CATEGORY
DESCRIPTION | Similar to [Adding a task](#adding-a-task-add) +>
+> Overwrites any of the specified fields ('LOCATION', 'DESCRIPTION'...) with the new values + +#### Change storage location : `storage` +Changes the storage location of Savvy Tasker.
+Format: `storage NEW_FILEPATH` + +> Parameters | Description +> -------- | :-------- +> PATH | `Mandatory` Specifies the path where Savvy Tasker's task list is saved at. +>
+> If the new storage location specified by `NEW_FILEPATH` is not accessible by Savvy Tasker, no change will be made to the existing path. + +``` +###### \UserGuide.md +``` md + +``` +###### \UserGuide.md +``` md + +## Command Summary + +Command | Format +-------- | :-------- +[Add](#adding-a-task-add) | `add TASK_NAME [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]`
Example: `add Weekly Milestone s/Thurs r/weekly n/3 l/COM1 c/Meeting` +[Alias](#alias-a-keyword--alias) | `alias k/KEYWORD s/SHORT_KEYWORD`
Example: `alias k/main success scenario s/mss` +[Clear](#clearing-all-entries--clear) | `clear` +[Delete](#deleting-a-task--delete) | `delete INDEX [MORE_INDEX]`
Example: `delete 1 2 3` +[Exit](#exiting-the-program--exit) | `exit` +[Find](#finding-all-task-containing-any-keyword-in-its-name-find) | `find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]`
Example: `find t/exact CS2103 Meeting` +[List](#listing-all-tasks-list) | `list [LIST_TYPE]`
Example: `list archived` +[Help](#viewing-help--help) | `help` +[Modify](#modifies-a-task--modify) | `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]`
Example: `modify 2 t/Wednesday Weekly Milestone s/wed d/Project Meeting and Finalization` +[Mark](#mark-a-task-as-done--mark) | `mark INDEX [MORE_INDEX]`
Example: `mark 1 2 3` +[Storage](#change-storage-location--storage) | `storage NEW_FILEPATH`
Example: `storage data/savvytasker.xml` +[Unmark](#unmark-a-task-as-done--unmark) | `unmark INDEX [MORE_INDEX]`
Example: `unmark 1 2 3` +[Undo](#undo-the-most-recent-operation--undo) | `undo` +[Redo](#redo-the-most-recent-undo-operation--redo) | `redo` +[Unalias](#unalias-a-keyword--unalias) | `unalias s/SHORT_KEYWORD`
Example: `unalias s/mss` + +``` diff --git a/collated/docs/A0139916U.md b/collated/docs/A0139916U.md new file mode 100644 index 000000000000..1f2e1869055d --- /dev/null +++ b/collated/docs/A0139916U.md @@ -0,0 +1,97 @@ +# A0139916U +###### \DeveloperGuide.md +``` md + +### Logic component + +
+ +**API** : [`Logic.java`](../src/main/java/seedu/savvytasker/logic/Logic.java) + +1. `Logic` uses the `Parser` class to parse the user command. +2. This results in a `Command` object which is executed by the `LogicManager`. +3. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. + +Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` + API call.
+
+ +### Parser component + +
+ +**API** : [`MasterParser.java`](../src/main/java/seedu/savvytasker/logic/parser/MasterParser.java) + +The `Parser` component, +* can parse text input into commands. +* supports adding and removing of keyword aliases + +``` +###### \DeveloperGuide.md +``` md + +#### Competing product: MIUI Calendar + +**Pros:** + +1. Able to sync to different devices +2. Able to set privacy to private or public +3. Able to set 2 reminders +4. Able to set reminder as a notification popup or an alarm + +**Cons:** + +1. Unable to check(tick) completed event +2. Does not cater for tasks, only events + + +``` +###### \UserGuide.md +``` md + +#### Alias a keyword : `alias` +Alias a keyword to represent another text.
+Format: `alias k/KEYWORD r/REPRESENTATION` + +> Parameters | Description +> -------- | :-------- +> KEYWORD | Specifies the keyword that will be replaced when met in a command, must be a single word. +> REPRESENTATION | Specifies the text that will replace the keyword. Cannot contain slashes. + +Examples: +* `alias k/pjm r/Project Meeting`
+System will interpret subsequent keywords "pjm" as "Project Meeting"
+ `add pjm s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103`
+Add "Project Meeting" to task list
+ +> Note that only keywords must be a single word by themselves, or they will not be replaced. +> The below example shows 'pjm' as part of another word and it is not replaced with its representation.
+ +* `add pjm2 s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103`
+Add "pjm2" to task list, and not "Project Meeting2"
+ + +#### Unalias a keyword : `unalias` +Unalias a keyword
+Format: `unalias KEYWORD` + +> Parameters | Description +> -------- | :-------- +> KEYWORD | Specifies the keyword to unaliased + +Examples: +* `unalias pjm`
+Removes the replacement of keyword "pjm" with "Project Meeting"
+ `add pjm s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103`
+Add task named "pjm" to task list + + +## FAQ + +**Q**: How do I transfer my data to another Computer?
+**A**: Install the app in the other computer and overwrite the empty data file it creates with + the file that contains the data of your previous Savvy Tasker folder. + + +``` diff --git a/collated/main/A0097627N.md b/collated/main/A0097627N.md new file mode 100644 index 000000000000..71f0fe56738c --- /dev/null +++ b/collated/main/A0097627N.md @@ -0,0 +1,1069 @@ +# A0097627N +###### \java\seedu\savvytasker\logic\commands\AddCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + Iterator itr = tasksAdded.iterator(); + while (itr.hasNext()) { + try { + model.deleteTask(itr.next()); + } catch (TaskNotFoundException e) { + // do nothing. + } + } + // clears the list of tasks added. + // if redo is performed, the list will be populated again. + tasksAdded.clear(); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\AliasCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the alias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + /** + * Undo the alias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // TODO Auto-generated method stub + + assert model != null; + + AliasSymbol toRemove = null; + for(AliasSymbol symbol : model.getSavvyTasker().getReadOnlyListOfAliasSymbols()) { + if (symbol.getKeyword().equals(this.keyword)) { + toRemove = symbol; + break; + } + } + try { + model.removeAliasSymbol(toRemove); + } catch (SymbolKeywordNotFoundException e) { + e.printStackTrace(); + } + + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +} +``` +###### \java\seedu\savvytasker\logic\commands\ClearCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the clear command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the clear command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + assert model != null; + model.resetData(original); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\Command.java +``` java + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + public abstract boolean isUndo(); + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + public abstract boolean isRedo(); +``` +###### \java\seedu\savvytasker\logic\commands\DeleteCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the delete command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + + /* + * METHOD 2 + UnmodifiableObservableList lastShownList = model.getFilteredTaskListTask(); + + for(Task toUndo : tasksToUndo){ + for (int i = 0; i < lastShownList.size(); i++) { + if (lastShownList.get(i) == toUndo){ + ReadOnlyTask taskToDelete = lastShownList.get(i); + try { + model.deleteTask(taskToDelete); + } catch (TaskNotFoundException e) { + e.printStackTrace(); + } + } + } + } + */ + return true; + } + + /** + * Undo the delete command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + + assert model != null; + model.resetData(original); + + /* + * METHOD 2 + assert model != null; + + for(Task deleted : tasksToUndo) + try { + model.addTask(deleted); + } catch (DuplicateTaskException e) { + e.printStackTrace(); + } + */ + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\ExitCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the exit command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the exit command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\FindCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the find command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the find command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\HelpCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the help command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the help command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\IncorrectCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the "incorrect" command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the "incorrect" command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\ListCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the list command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + + /** + * Undo the list command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\MarkCommand.java +``` java + public static final String COMMAND_WORD = "mark"; + public static final String COMMAND_FORMAT = "mark INDEX [MORE_INDEX]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the tasks identified by the index number used in the last task listing as done.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_MARK_TASK_SUCCESS = "Marked Task: %1$s\n"; + public static final String MESSAGE_MARK_TASK_FAIL = "Task is already marked!\n"; + + public final int[] targetIndices; + private ReadOnlySavvyTasker original; + + public MarkCommand(int[] targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + LinkedList tasksToMark = new LinkedList(); + for(int targetIndex : targetIndices) { + if (lastShownList.size() < targetIndex || targetIndex <= 0) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + tasksToMark.add((Task) lastShownList.get(targetIndex - 1)); + } + + original = new SavvyTasker(model.getSavvyTasker()); + + StringBuilder resultSb = new StringBuilder(); + try { + for(Task taskToMark : tasksToMark) { + if (!taskToMark.isArchived()){ + taskToMark.setArchived(true); + model.modifyTask(taskToMark, taskToMark); + resultSb.append(String.format(MESSAGE_MARK_TASK_SUCCESS, taskToMark)); + } else { + resultSb.append(String.format(MESSAGE_MARK_TASK_FAIL, taskToMark)); + } + } + } catch (TaskNotFoundException pnfe) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException e) { + assert false : "The target task should be valid, only the archived flag is set"; + } + return new CommandResult(resultSb.toString()); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the mark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the mark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + assert model != null; + model.resetData(original); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\ModifyCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + + assert model != null; + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + ReadOnlyTask taskToModify = lastShownList.get(index - 1); + + try { + model.modifyTask(taskToModify, originalTask); + } catch (TaskNotFoundException e) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException ex) { + //TODO: Verify branch + //assert false : "The target task cannot be having an invalid start end "; + //return new CommandResult(Messages.MESSAGE_INVALID_START_END); + } + + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\RedoCommand.java +``` java + public static final String COMMAND_WORD = "redo"; + + public static final String MESSAGE_REDO_ACKNOWLEDGEMENT = "Last command redone"; + + @Override + public CommandResult execute() { + return new CommandResult(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the redo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the redo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return true; + } +``` +###### \java\seedu\savvytasker\logic\commands\SelectCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\StorageCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\UnaliasCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the unalias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + /** + * Undo the unalias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + + try { + model.addAliasSymbol(toUndo); + } catch (DuplicateSymbolKeywordException e) { + e.printStackTrace(); + } + + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\UndoCommand.java +``` java + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_UNDO_ACKNOWLEDGEMENT = "Last command undone"; + + @Override + public CommandResult execute() { + return new CommandResult(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the undo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the undo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return true; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\UnmarkCommand.java +``` java + public static final String COMMAND_WORD = "unmark"; + public static final String COMMAND_FORMAT = "unmark INDEX [MORE_INDEX]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unmarks the marked tasks identified by the index number used in the last task listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNMARK_TASK_SUCCESS = "Unmarked Task: %1$s\n"; + public static final String MESSAGE_UNMARK_TASK_FAIL = "Task is already unmarked!\n"; + + private final int[] targetIndices; + private ReadOnlySavvyTasker original; + + public UnmarkCommand(int[] targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + LinkedList tasksToUnmark = new LinkedList(); + for(int targetIndex : targetIndices) { + if (lastShownList.size() < targetIndex || targetIndex <= 0) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + tasksToUnmark.add((Task) lastShownList.get(targetIndex - 1)); + } + + original = new SavvyTasker(model.getSavvyTasker()); + + StringBuilder resultSb = new StringBuilder(); + try { + for(Task taskToUnmark : tasksToUnmark) { + if (taskToUnmark.isArchived()){ + taskToUnmark.setArchived(false); + model.modifyTask(taskToUnmark, taskToUnmark); + model.updateFilteredListToShowArchived(); + resultSb.append(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark)); + } else { + resultSb.append(String.format(MESSAGE_UNMARK_TASK_FAIL, taskToUnmark)); + } + } + } catch (TaskNotFoundException pnfe) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException e) { + assert false : "The target task should be valid, only the archived flag is set"; + } + return new CommandResult(resultSb.toString()); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the unmark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the unmark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + assert model != null; + model.resetData(original); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\LogicManager.java +``` java + if (command.isUndo()){ + if (!undo()) { + result = new CommandResult("Cannot Undo"); + } + } + else if (command.isRedo()){ + if (!redo()) { + result = new CommandResult("Cannot Redo"); + } + } + else if (command.canUndo()){ +``` diff --git a/collated/main/A0138431L.md b/collated/main/A0138431L.md new file mode 100644 index 000000000000..c09bfabfb133 --- /dev/null +++ b/collated/main/A0138431L.md @@ -0,0 +1,2386 @@ +# A0138431L +###### \java\seedu\savvytasker\commons\events\storage\DataSavingLocationChangedEvent.java +``` java +/** + * Indicates a change in location of the storage + */ +public class DataSavingLocationChangedEvent extends BaseEvent { + + public final ReadOnlySavvyTasker data; + public final String newPath; + + public DataSavingLocationChangedEvent(ReadOnlySavvyTasker data, String newPath) { + this.data = data; + this.newPath = newPath; + } + + @Override + public String toString() { + return "number of tasks " + data.getReadOnlyListOfTasks().size() + + " new path " + this.newPath; + } + +} +``` +###### \java\seedu\savvytasker\commons\events\ui\ShowCheatsheetEvent.java +``` java + +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; + +/** Indicates cheatsheet display has been toggled */ +public class ShowCheatsheetEvent extends BaseEvent { + @Override + public String toString() { + return "Cheatsheet display has been toggled"; + } + +} +``` +###### \java\seedu\savvytasker\commons\events\ui\WeekSelectionChangedEvent.java +``` java + +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; + +/** Indicates the SavvyTasker in the model has changed*/ + +public class WeekSelectionChangedEvent extends BaseEvent { + + @Override + public String toString() { + return "Selected week has been changed"; + } + +} +``` +###### \java\seedu\savvytasker\logic\commands\StorageAndModelRequiringCommand.java +``` java +/** + * Represents a command which requires the Storage class as a dependency. + * Commands should inherit this class if they only require dependency for + * storage and model components +*/ +public abstract class StorageAndModelRequiringCommand extends Command { + protected Storage storage; + protected Model model; + + public void setStorage(Storage storage) { + this.storage = storage; + } + + public void setModel(Model model) { + this.model = model; + } +} +``` +###### \java\seedu\savvytasker\logic\commands\StorageCommand.java +``` java +/** + * Changes the storage location of Savvy Tasker + */ +public class StorageCommand extends StorageAndModelRequiringCommand { + + public final String path; + + public static final String COMMAND_WORD = "storage"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sets the storage path to the path specified.\n" + + "Parameters: PATH\n" + + "Example: " + COMMAND_WORD + " data/savvytasker.xml"; + + public static final String MESSAGE_CHANGE_LOCATION_SUCCESS = "Changed storage location to: %1$s"; + public static final String MESSAGE_CHANGE_LOCATION_FAILED = "Failed to change storage location to: %1$s"; + + public StorageCommand(String path) { + this.path = path; + } + + @Override + public CommandResult execute() { + if (storage.setSavvyTaskerFilePath(path)) { + ReadOnlySavvyTasker savvyTasker = model.getSavvyTasker(); + EventsCenter.getInstance().post(new DataSavingLocationChangedEvent(savvyTasker, path)); + return new CommandResult(String.format(MESSAGE_CHANGE_LOCATION_SUCCESS, path)); + } else { + return new CommandResult(String.format(MESSAGE_CHANGE_LOCATION_FAILED, path)); + } + } + +``` +###### \java\seedu\savvytasker\logic\LogicManager.java +``` java + + @Override + public ObservableList getFilteredFloatingTasks() { + return model.getFilteredFloatingTasks(); + } + + @Override + public ObservableList getFilteredDailyTasks(int i, Date date) { + return model.getFilteredDailyTasks(i, date); + } + + @Override + public ObservableList getFilteredUpcomingTasks(Date date) { + return model.getFilteredUpcomingTasks(date); + } +``` +###### \java\seedu\savvytasker\logic\parser\TaskFieldParser.java +``` java + public String parsefilePath(String filePathText) throws ParseException { + if (filePathText == null) { + return null; + } + return filePathText.trim(); + } + + public String parsefileName(String fileNameText) throws ParseException { + if (fileNameText == null) { + return null; + } + return fileNameText.trim(); + } +``` +###### \java\seedu\savvytasker\MainApp.java +``` java + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + try { + String configPath = getApplicationParameter("config"); + if(configPath == null) { + configPath = Config.DEFAULT_CONFIG_FILE; + } + config.setSavvyTaskerFilePath(dslce.newPath); + ConfigUtil.saveConfig(config, configPath); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + } +``` +###### \java\seedu\savvytasker\model\Model.java +``` java + + /** Returns the filtered task list of floating task as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredFloatingTasks(); + + /** Returns the filtered task list of daily task as an {@code UnmodifiableObservableList} + * as of expected date */ + UnmodifiableObservableList getFilteredDailyTasks(int dayOfWeek, Date date); + + /** Returns the filtered task list of upcoming task as an {@code UnmodifiableObservableList} + * as of expected date */ + UnmodifiableObservableList getFilteredUpcomingTasks(Date date); + + /** Updates the filter of the filtered task list to show all floating tasks */ + void updateFilteredListToShowFloating(); + + /** Updates the filter of the filtered task list to show all tasks of the selected week*/ + void updateFilteredListToShowDaily(int i); + + /** Updates the filter of the filtered task list to show all upcoming tasks after the selected week*/ + void updateFilteredListToShowUpcoming(); + +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + private final SavvyTasker savvyTasker; + private final FilteredList filteredTasks; + private final SortedList sortedAndFilteredTasks; + private final FilteredList filteredFloatingTasks; + private final SortedList sortedAndFilteredFloatingTasks; + private final FilteredList filteredDay1Tasks; + private final SortedList sortedAndFilteredDay1Tasks; + private final FilteredList filteredDay2Tasks; + private final SortedList sortedAndFilteredDay2Tasks; + private final FilteredList filteredDay3Tasks; + private final SortedList sortedAndFilteredDay3Tasks; + private final FilteredList filteredDay4Tasks; + private final SortedList sortedAndFilteredDay4Tasks; + private final FilteredList filteredDay5Tasks; + private final SortedList sortedAndFilteredDay5Tasks; + private final FilteredList filteredDay6Tasks; + private final SortedList sortedAndFilteredDay6Tasks; + private final FilteredList filteredDay7Tasks; + private final SortedList sortedAndFilteredDay7Tasks; + private final FilteredList filteredUpcomingTasks; + private final SortedList sortedAndFilteredUpcomingTasks; + + /** + * Initializes a ModelManager with the given SavvyTasker + * and its variables should not be null + */ + public ModelManager(SavvyTasker src) { + super(); + assert src != null; + + logger.fine("Initializing with savvy tasker: " + src); + + savvyTasker = new SavvyTasker(src); + filteredTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + + filteredFloatingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredFloatingTasks = new SortedList<>(filteredFloatingTasks, new TaskSortedByDefault()); + + filteredDay1Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay1Tasks = new SortedList<>(filteredDay1Tasks, new TaskSortedByDefault()); + filteredDay2Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay2Tasks = new SortedList<>(filteredDay2Tasks, new TaskSortedByDefault()); + filteredDay3Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay3Tasks = new SortedList<>(filteredDay3Tasks, new TaskSortedByDefault()); + filteredDay4Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay4Tasks = new SortedList<>(filteredDay4Tasks, new TaskSortedByDefault()); + filteredDay5Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay5Tasks = new SortedList<>(filteredDay5Tasks, new TaskSortedByDefault()); + filteredDay6Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay6Tasks = new SortedList<>(filteredDay6Tasks, new TaskSortedByDefault()); + filteredDay7Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay7Tasks = new SortedList<>(filteredDay7Tasks, new TaskSortedByDefault()); + + filteredUpcomingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredUpcomingTasks = new SortedList<>(filteredUpcomingTasks, new TaskSortedByDefault()); + + updateFilteredListToShowActive(); // shows only active tasks on start + } + + public ModelManager() { + this(new SavvyTasker()); + } + + public ModelManager(ReadOnlySavvyTasker initialData) { + savvyTasker = new SavvyTasker(initialData); + filteredTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + + filteredFloatingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredFloatingTasks = new SortedList<>(filteredFloatingTasks, new TaskSortedByDefault()); + + filteredDay1Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay1Tasks = new SortedList<>(filteredDay1Tasks, new TaskSortedByDefault()); + filteredDay2Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay2Tasks = new SortedList<>(filteredDay2Tasks, new TaskSortedByDefault()); + filteredDay3Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay3Tasks = new SortedList<>(filteredDay3Tasks, new TaskSortedByDefault()); + filteredDay4Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay4Tasks = new SortedList<>(filteredDay4Tasks, new TaskSortedByDefault()); + filteredDay5Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay5Tasks = new SortedList<>(filteredDay5Tasks, new TaskSortedByDefault()); + filteredDay6Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay6Tasks = new SortedList<>(filteredDay6Tasks, new TaskSortedByDefault()); + filteredDay7Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay7Tasks = new SortedList<>(filteredDay7Tasks, new TaskSortedByDefault()); + + filteredUpcomingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredUpcomingTasks = new SortedList<>(filteredUpcomingTasks, new TaskSortedByDefault()); + + updateFilteredListToShowActive(); // shows only active tasks on start + } +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + /** + * Qualifier for checking if {@link Task} is an overdue task + * + * A overdue task is a deadline or event task with end dateTime after current dateTime + * + * @return true if the task is overdue + * + */ + private class TaskIsOverdueQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + Date today = new Date(); + + boolean isOverdue = false; + + if (task.getEndDateTime() != null) { + + Date endDateTime = task.getEndDateTime(); + + if (endDateTime.compareTo(today)<0 && task.isArchived() == false) { + + isOverdue = true; + } + + } + + return isOverdue; + + } + + @Override + public String toString() { + return "isOverdue=true"; + } + } + + /** + * Qualifier for checking if {@link Task} is a floating task + * + * A floating task do not have start or end time + * + * @return true if the task falls on the date specified. else return false + * + */ + private class TaskIsFloatingQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + boolean isFloating = false; + + if(task.getStartDateTime() == null && task.getEndDateTime() == null && task.isArchived() == false) { + + isFloating = true; + + } + + return isFloating; + + } + + @Override + public String toString() { + return "isFloating=true"; + } + } + + /** + * Qualifier for checking if {@link Task} falls on the selected date + * + * Check whether the task is on the date specified (for deadline tasks) + * Check whether the date specified is within the range of date the task (for event tasks) + * Includes task that are completed. + * + * @return true if the task falls on the date specified. else return false + * + */ + private class TaskIsOnDateQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + Date expectedDate = onDate; + + boolean isOnDate = false; + + //Archived Task + if(task.isArchived() == true){ + + isOnDate = false; + + } + //Deadline Task + else if(task.getStartDateTime() == null && task.getEndDateTime() != null) { + + Date endDateTime = task.getEndDateTime(); + + if (DateUtils.isSameDay(endDateTime, expectedDate)) { + + isOnDate = true; + + } + + } + //Event Task + else if(task.getStartDateTime() != null && task.getEndDateTime() != null) { + + Date startDateTime = task.getStartDateTime(); + Date endDateTime = task.getEndDateTime(); + + if (DateUtils.isSameDay(startDateTime, expectedDate)) { + + isOnDate = true; + + } else if (DateUtils.isSameDay(endDateTime, expectedDate)) { + + isOnDate = true; + + } else if (startDateTime.compareTo(expectedDate)<0 && expectedDate.compareTo(endDateTime)<0) { + + isOnDate = true; + + } + } + + return isOnDate; + } + + @Override + public String toString() { + return "isOnDate=true"; + } + } + + /** + * Qualifier for checking if {@link Task} task is upcoming + * + * A upcoming task is a task that will happen after the last day, 2359 of selected week + * + * @return true if the task is a upcoming task + * + */ + private class TaskIsUpcomingQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + Date lastDateOfExpectedWeek = firstDayOfSelectedWeek; + + //convert date object to calendar object and add 7 days, last day of the selected week + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(lastDateOfExpectedWeek); + calendarExpectedDate.add(Calendar.DAY_OF_MONTH, 7); + calendarExpectedDate.set(Calendar.HOUR_OF_DAY,23); + calendarExpectedDate.set(Calendar.MINUTE,59); + calendarExpectedDate.set(Calendar.SECOND,59); + + //convert calendar object back to date object + lastDateOfExpectedWeek = calendarExpectedDate.getTime(); + + boolean isUpcoming = true; + + //Archived Task + if(task.isArchived() == true){ + + isUpcoming = false; + + } + + //Floating Task + else if(task.getStartDateTime() == null && task.getEndDateTime() == null) { + + isUpcoming = false; + + } + //Deadline Task + else if(task.getStartDateTime() == null && task.getEndDateTime() != null) { + + + if (task.getEndDateTime().compareTo(lastDateOfExpectedWeek)<0) { + + isUpcoming = false; + + } + + } + //Event Task + else { + + if (task.getStartDateTime().compareTo(lastDateOfExpectedWeek)<0) { + + isUpcoming = false; + + } + + } + + return isUpcoming; + } + + @Override + public String toString() { + return "isUpcoming=true"; + } +``` +###### \java\seedu\savvytasker\model\task\ReadOnlyTask.java +``` java + static final String EMPTY_FIELD = " "; + + static String DATE_PATTERN = "dd MMM yy"; + static String TIME_PATTERN = "hh:mm a"; + + // String format for deadline tasks dateTime format + static String DEADLINE_FORMAT = "by %1$s, %2$s"; + + // String format for event tasks dateTime format + static String EVENT_DIFF_START_END_DATE_FORMAT = "%1$s, %2$s to %3$s, %4$s"; + static String EVENT_SAME_START_END_DATE_FORMAT = "%1$s, %2$s to %3$s"; + + static Date lastDayOfSelectedWeek = new Date(); + + /** + * Generates the DateTime Format for all tasks with time. + * + * @param task the task to have its DateTime Format generated + * + * @return DateTime Format (e.g. (31 Oct 16, 10:00PM) + **/ + default String generateDateTime(Date start, Date end) { + String dateTimeFormat; + //Floating Task + if(start == null && end == null) { + dateTimeFormat = EMPTY_FIELD; + } + //Deadline Task + else if(start == null && end != null) { + dateTimeFormat = generateDeadlineDateTime(end); + //Event Task + }else { + dateTimeFormat = generateEventDateTime(start, end); + } + + return dateTimeFormat; + + } + + /** + * Generates the dateTimeFormat for deadline tasks + * + * @param task the task to have its dateTimeFormat generated + * + * @return dateTimeFormat (e.g. 30 Oct 16, 10:00PM) + */ + default String generateDeadlineDateTime(Date end) { + + String dateTimeFormat; + + SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_PATTERN); + SimpleDateFormat timeFormatter = new SimpleDateFormat(TIME_PATTERN); + + String taskEndDateFormat = dateFormatter.format(end.getTime()); + String taskEndTimeFormat = timeFormatter.format(end.getTime()); + + dateTimeFormat = String.format(DEADLINE_FORMAT, taskEndDateFormat, taskEndTimeFormat); + + return dateTimeFormat; + + } + + /** + * Generates the dateTimeFormat for ranged tasks + * + * @param task the task to have its dateTimeFormat generated + * + * @return dateTimeFormat (e.g. 30 Oct 16, 8:00AM to 9:00PM) + */ + default String generateEventDateTime(Date start, Date end) { + + String dateTimeFormat; + + SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_PATTERN); + SimpleDateFormat timeFormatter = new SimpleDateFormat(TIME_PATTERN); + + String taskStartDateFormat = dateFormatter.format(start.getTime()); + String taskStartTimeFormat = timeFormatter.format(start.getTime()); + + String taskEndDateFormat = dateFormatter.format(end.getTime()); + String taskEndTimeFormat = timeFormatter.format(end.getTime()); + + if(DateUtils.isSameDay(start, end) == false) { + + dateTimeFormat = String.format(EVENT_DIFF_START_END_DATE_FORMAT, taskStartDateFormat, taskStartTimeFormat, taskEndDateFormat, taskEndTimeFormat); + + } else { + + dateTimeFormat = String.format(EVENT_SAME_START_END_DATE_FORMAT, taskEndDateFormat, taskStartTimeFormat, taskEndTimeFormat); + + } + + return dateTimeFormat; + + } +} +``` +###### \java\seedu\savvytasker\storage\ConfigStorage.java +``` java +package seedu.savvytasker.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.exceptions.DataConversionException; + +/** + * Represents a storage for {@link seedu.savvytasker.commons.core.Config}. + */ +public interface ConfigStorage { + /** + * Returns Config data from storage. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readConfigFile() throws DataConversionException, IOException; + + /** + * Saves the given {@link seedu.savvytasker.commons.core.Config} to the storage. + * @param config cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveConfigFile(Config config) throws IOException; + +} +``` +###### \java\seedu\savvytasker\storage\JsonConfigStorage.java +``` java + +package seedu.savvytasker.storage; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.FileUtil; + +/** + * A class to access Config stored in the hard disk as a json file + */ +public class JsonConfigStorage implements ConfigStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonConfigStorage.class); + + private String filePath; + + public JsonConfigStorage(String filePath) { + + this.filePath = filePath; + } + + @Override + public Optional readConfigFile() throws DataConversionException, IOException { + + return readConfig(filePath); + } + + @Override + public void saveConfigFile(Config config) throws IOException { + + saveConfig(config, filePath); + } + + /** + * Similar to {@link #readConfigFile()} + * @param configFilePath location of the data. Cannot be null. + * @throws DataConversionException if the file format is not as expected. + */ + public Optional readConfig(String configFilePath) throws DataConversionException { + + assert configFilePath != null; + + File configFile = new File(configFilePath); + + if (!configFile.exists()) { + + logger.info("Config file " + configFile + " not found"); + + return Optional.empty(); + + } + + Config config; + + try { + + config = FileUtil.deserializeObjectFromJsonFile(configFile, Config.class); + + } catch (IOException e) { + + logger.warning("Error reading from config file " + configFile + ": " + e); + + throw new DataConversionException(e); + + } + + return Optional.of(config); + } + + /** + * Similar to {@link #saveConfigFile(Config)} + * @param configFilePath location of the data. Cannot be null. + */ + private void saveConfig(Config config, String configFilePath) throws IOException { + + assert config != null; + assert configFilePath != null; + assert !configFilePath.isEmpty(); + + FileUtil.serializeObjectToJsonFile(new File(configFilePath), config); + } + +} +``` +###### \java\seedu\savvytasker\storage\StorageManager.java +``` java + @Override + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + logger.info(LogsCenter.getEventHandlingLogMessage(dslce, "Local storage location changed.")); + try { + saveSavvyTasker(dslce.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } +``` +###### \java\seedu\savvytasker\ui\CommandBox.java +``` java +package seedu.savvytasker.ui; + +import java.io.File; +import java.util.Calendar; +import java.util.Date; +import java.util.Stack; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.ui.IncorrectCommandAttemptedEvent; +import seedu.savvytasker.commons.events.ui.ShowCheatsheetEvent; +import seedu.savvytasker.commons.events.ui.WeekSelectionChangedEvent; +import seedu.savvytasker.commons.util.FxViewUtil; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.logic.commands.CommandResult; + +public class CommandBox extends UiPart { + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + private static final String FXML = "CommandBox.fxml"; + + private AnchorPane placeHolderPane; + private AnchorPane commandPane; + private ResultDisplay resultDisplay; + String previousCommandTest; + private Date date = new Date(); + private static int DAYS_OF_WEEK = 7; + + private final String UNDO_COMMAND = "undo"; + private final String REDO_COMMAND = "redo"; + private final String HELP_COMMAND = "help"; + private final String EXIT_COMMAND = "exit"; + private final String LIST_COMMAND = "list"; + private final String LIST_ARCHIVED_COMMAND = "list archived"; + private final String LIST_PRIORITY_COMMAND = "list priorityLevel"; + private final String LIST_ALIAS_COMMAND = "list alias"; + private final String CLEAR_COMMAND = "clear"; + private final String STORAGE_COMMAND = "storage ."; + + KeyCombination saveKey = new KeyCodeCombination(KeyCode.S, KeyCombination.META_DOWN); + KeyCombination undoKey = new KeyCodeCombination(KeyCode.Z, KeyCombination.META_DOWN); + KeyCombination redoKey = new KeyCodeCombination(KeyCode.Y, KeyCombination.META_DOWN); + KeyCombination helpKey = new KeyCodeCombination(KeyCode.H, KeyCombination.META_DOWN); + KeyCombination exitKey = new KeyCodeCombination(KeyCode.Q, KeyCombination.META_DOWN); + KeyCombination listKey = new KeyCodeCombination(KeyCode.L, KeyCombination.META_DOWN); + KeyCombination listArchivedKey = new KeyCodeCombination(KeyCode.A, KeyCombination.META_DOWN); + KeyCombination listPriorityKey = new KeyCodeCombination(KeyCode.P, KeyCombination.META_DOWN); + KeyCombination listAliasKey = new KeyCodeCombination(KeyCode.I, KeyCombination.META_DOWN); + KeyCombination clearKey = new KeyCodeCombination(KeyCode.D, KeyCombination.META_DOWN); + KeyCombination leftKey = new KeyCodeCombination(KeyCode.LEFT, KeyCombination.META_DOWN); + KeyCombination rightKey = new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.META_DOWN); + + // stack to store commands history + private static Stack COMMAND_HISTORY_STACK = new Stack(); + private static Stack COMMAND_FUTURE_STACK = new Stack(); + + private Logic logic; + + @FXML + private TextField commandTextField; + private CommandResult mostRecentResult; + + public static CommandBox load(Stage primaryStage, AnchorPane commandBoxPlaceholder, + ResultDisplay resultDisplay, Logic logic) { + CommandBox commandBox = UiPartLoader.loadUiPart(primaryStage, commandBoxPlaceholder, new CommandBox()); + commandBox.configure(resultDisplay, logic); + commandBox.addToPlaceholder(); + return commandBox; + } + + public void configure(ResultDisplay resultDisplay, Logic logic) { + this.resultDisplay = resultDisplay; + this.logic = logic; + registerAsAnEventHandler(this); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(commandTextField); + FxViewUtil.applyAnchorBoundaryParameters(commandPane, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, 0.0, 0.0); + } + + @Override + public void setNode(Node node) { + commandPane = (AnchorPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + + @FXML + private void handleCommandInputChanged() { + //Take a copy of the command text + previousCommandTest = commandTextField.getText(); + + COMMAND_HISTORY_STACK.push(previousCommandTest); + /* We assume the command is correct. If it is incorrect, the command box will be changed accordingly + * in the event handling code {@link #handleIncorrectCommandAttempted} + */ + setStyleToIndicateCorrectCommand(); + mostRecentResult = logic.execute(previousCommandTest); + resultDisplay.postMessage(mostRecentResult.feedbackToUser); + logger.info("Result: " + mostRecentResult.feedbackToUser); + } + + + /** + * Sets the command box style to indicate a correct command. + */ + private void setStyleToIndicateCorrectCommand() { + commandTextField.getStyleClass().remove("error"); + commandTextField.setText(""); + } + + @Subscribe + private void handleIncorrectCommandAttempted(IncorrectCommandAttemptedEvent event){ + logger.info(LogsCenter.getEventHandlingLogMessage(event,"Invalid command: " + previousCommandTest)); + setStyleToIndicateIncorrectCommand(); + restoreCommandText(); + } + + /** + * Restores the command box text to the previously entered command + */ + private void restoreCommandText() { + commandTextField.setText(""); + } + + /** + * Sets the command box style to indicate an error + */ + private void setStyleToIndicateIncorrectCommand() { + commandTextField.getStyleClass().add("error"); + } + + public Node getCommandTextField() { + return commandTextField; + } + + //==================== Keyboard Shortcuts Code ================================================================= + /** + * Key pressed handler for text box. + * + * @param keyEvent key event for the button that is being pressed. + */ + public void commandTextFieldOnKeyPressedHandler(KeyEvent keyEvent) { + + String userInput = commandTextField.getText().trim(); + + try { + + KeyCode keyCode = keyEvent.getCode(); + + if(saveKey.match(keyEvent)) { + + processSave(); + + }else if (keyCode == KeyCode.ESCAPE) { + + showCheatsheet(); + + } else if (keyCode == KeyCode.UP) { + + processUp(userInput); + + } else if (keyCode == KeyCode.DOWN) { + + processDown(userInput); + + } else if (leftKey.match(keyEvent)) { + + processDate(-1); + + } else if (rightKey.match(keyEvent)) { + + processDate(1); + + } else if (undoKey.match(keyEvent)) { + + executeCommand(UNDO_COMMAND); + + } else if (redoKey.match(keyEvent)) { + + executeCommand(REDO_COMMAND); + + } else if (helpKey.match(keyEvent)) { + + executeCommand(HELP_COMMAND); + + } else if (exitKey.match(keyEvent)) { + + executeCommand(EXIT_COMMAND); + + } else if (listKey.match(keyEvent)) { + + executeCommand(LIST_COMMAND); + + } else if (listArchivedKey.match(keyEvent)) { + + executeCommand(LIST_ARCHIVED_COMMAND); + + } else if (listPriorityKey.match(keyEvent)) { + + executeCommand(LIST_PRIORITY_COMMAND); + + } else if (listAliasKey.match(keyEvent)) { + + executeCommand(LIST_ALIAS_COMMAND); + + } else if (clearKey.match(keyEvent)) { + + executeCommand(CLEAR_COMMAND); + + } + + } catch (IllegalArgumentException e) { + + commandTextField.setText(""); + + COMMAND_HISTORY_STACK.add(userInput); + + this.logger.info("Illegal Argument has been entered."); + + } catch (Exception e) { + + e.printStackTrace(); + commandTextField.setText(""); + + this.logger.info("Illegal Argument has been entered."); + + } + + } + + /** + * Process the event that occurs after the user presses the ctrl-S button. + * + * @param userInput the command keyed in by the user. + */ + public void processSave() { + + //Execute the save command + DirectoryChooser directoryChooser = new DirectoryChooser(); + File selectedFile = directoryChooser.showDialog(primaryStage); + String filepath = selectedFile.getAbsolutePath(); + executeCommand(STORAGE_COMMAND + filepath + "/savvytasker.xml"); + + } + + /** + * Process the event that occurs after the user presses the [UP] button. + * + * @param userInput the command keyed in by the user. + */ + public void processUp(String userInput) { + + if (!COMMAND_HISTORY_STACK.isEmpty()) { + + String previousCommand = COMMAND_HISTORY_STACK.pop(); + + if (!userInput.equals("")) { + + COMMAND_FUTURE_STACK.push(userInput); + + } + + commandTextField.setText(previousCommand); + + } + + commandTextField.positionCaret(commandTextField.getText().length()); + + } + + /** + * Process the event that occurs after the user presses the down button. + * + * @param userInput the command keyed in by the user. + */ + public void processDown(String userInput) { + + if (!COMMAND_FUTURE_STACK.isEmpty()) { + + String nextCommand = COMMAND_FUTURE_STACK.pop(); + + COMMAND_HISTORY_STACK.push(userInput); + commandTextField.setText(nextCommand); + + } else if (!userInput.equals("")) { + + COMMAND_HISTORY_STACK.push(userInput); + commandTextField.setText(""); + + } + + commandTextField.positionCaret(commandTextField.getText().length()); + + } + + /** + * Process the event that occurs after the user presses the left or right button. + * + * @param numbers of week to be added to the current selected week to be displayed in the daily task list view + */ + public void processDate(int numberOfWeek) { + + date = addWeek(numberOfWeek, date); + indicateWeekSelectionChanged(); + } + + /** Raises an event to indicate the week to be displayed has changed */ + private void indicateWeekSelectionChanged() { + raise(new WeekSelectionChangedEvent()); + } + + /** Raises an event to indicate the week to be displayed has changed */ + private void showCheatsheet() { + raise(new ShowCheatsheetEvent()); + } + + /** + * Execute commands + * + * @param command to be executed + */ + public void executeCommand(String commandInput) { + CommandResult commandResult = logic.execute(commandInput); + resultDisplay.postMessage(commandResult.feedbackToUser); + logger.info("Result: " + commandResult.feedbackToUser); + } + + private Date addWeek(int numberOfWeek, Date date) { + + //convert date object to calendar object and add 1 days + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(date); + + calendarExpectedDate.add(Calendar.DATE, (numberOfWeek*DAYS_OF_WEEK)); + + //convert calendar object back to date object + date = calendarExpectedDate.getTime(); + + return date; + } + + public Date getDate() { + return date; + } +} + +``` +###### \java\seedu\savvytasker\ui\DailyPanel.java +``` java + +/** + * Panel containing the list overdue task. + * @author A0138431L + * + */ +public class DailyPanel extends UiPart { + + private static String TODAY_TITLE = "Today"; + private static String TOMORROW_TITLE = "Tomorrow"; + private static String DAY_PATTERN = "EEEE"; + private static String DATE_PATTERN = "dd MMM yy"; + private static String DAY_DATE_FORMAT = "%1$s, %2$s"; + + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "DailyList.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + + @FXML + private TextField header; + + @FXML + private ListView taskListView; + + public DailyPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static DailyPanel load(Stage primaryStage, AnchorPane DailyListPlaceholder, + ObservableList taskList, int dayOfTheWeek, Date date) { + DailyPanel dailyPanel = + UiPartLoader.loadUiPart(primaryStage, DailyListPlaceholder, new DailyPanel()); + dailyPanel.configure(taskList, dayOfTheWeek, date); + return dailyPanel; + } + + private void configure(ObservableList taskList, int dayOfTheWeek, Date date) { + + String dateHeader = generateHeader(dayOfTheWeek, date); + Date today = new Date(); + if(date == today) { + placeHolderPane.setStyle("-fx-background-color:#FF0000"); + header.setStyle("-fx-text-fill:#FF0000"); + } + setConnections(taskList, dateHeader); + addToPlaceholder(); + + } + + private void setConnections(ObservableList taskList, String dateHeader) { + header.clear(); + header.setText(dateHeader); + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in daily task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + private String generateHeader(int dayOfTheWeek, Date date) { + + SimpleDateFormat dayFormatter = new SimpleDateFormat(DAY_PATTERN); + SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_PATTERN); + + Date today = new Date(); + Date tomorrow = new Date(); + tomorrow = addDay(1, tomorrow); + + String day; + + if(DateUtils.isSameDay(date, today)) { + + day = TODAY_TITLE; + + } else if (DateUtils.isSameDay(date, tomorrow)) { + + day = TOMORROW_TITLE; + + } else { + + day = dayFormatter.format(date); + + } + String header = String.format(DAY_DATE_FORMAT, day, dateFormatter.format(date)); + + return header; + } + + private Date addDay(int i, Date date) { + + //convert date object to calendar object and add 1 days + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(date); + + calendarExpectedDate.add(Calendar.DATE, i); + + //convert calendar object back to date object + date = calendarExpectedDate.getTime(); + + return date; + } + + class TaskListViewCell extends ListCell { + + public TaskListViewCell() { + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, 0, false).getLayout()); + } + } + } + +} +``` +###### \java\seedu\savvytasker\ui\FloatingPanel.java +``` java + +/** + * Panel containing the list floating task. + * @author A0138431L + * + */ +public class FloatingPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "FloatingList.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView taskListView; + + public FloatingPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static FloatingPanel load(Stage primaryStage, AnchorPane floatingListPlaceholder, + ObservableList taskList) { + FloatingPanel floatingPanel = + UiPartLoader.loadUiPart(primaryStage, floatingListPlaceholder, new FloatingPanel()); + floatingPanel.configure(taskList); + return floatingPanel; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in floating task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + class TaskListViewCell extends ListCell { + + public TaskListViewCell() { + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, 0, false).getLayout()); + } + } + } + +} +``` +###### \java\seedu\savvytasker\ui\MainWindow.java +``` java + +package seedu.savvytasker.ui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.MenuItem; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.GuiSettings; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.ui.ExitAppRequestEvent; +import seedu.savvytasker.commons.events.ui.ShowCheatsheetEvent; +import seedu.savvytasker.commons.events.ui.WeekSelectionChangedEvent; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.model.UserPrefs; +import seedu.savvytasker.model.task.ReadOnlyTask; + +/** + * The Main Window. Provides the basic application layout containing + * a sorting and filtered list that display the result of the user command + * on the left and a week's view of the task + * + * The week's view contains 4 lists, namely the floating list, + * days of the week list and upcoming list + * + * Floating list contains task without start and end dateTime + * Days of the week list contains task that falls on the respective day of the selected week + * Upcoming list contains task with start date after the last day of selected week + * + * @author A0138431L + * + */ +public class MainWindow extends UiPart { + + private static final String ICON = "/images/savvytasker-icon.png"; + private static final Image image = new Image(MainWindow.class.getResourceAsStream(ICON)); + private static final String CHEATSHEET = "/images/cheatsheet.png"; + private static final Image imageOverlay = new Image(MainWindow.class.getResourceAsStream(CHEATSHEET)); + private static final String FXML = "MainWindow.fxml"; + public static final int MIN_HEIGHT = 700; + public static final int MIN_WIDTH = 1150; + + private Logic logic; + Date firstDayOfSelectedWeek = new Date(); + private static int DAYS_OF_WEEK = 7; + private boolean isShown = false; + + // Independent Ui parts residing in this Ui container + //private BrowserPanel browserPanel; + private TaskListPanel taskListPanel; + private AliasSymbolListPanel aliasSymbolListPanel; + private ResultDisplay resultDisplay; + private StatusBarFooter statusBarFooter; + private CommandBox commandBox; + private Config config; + private UserPrefs userPrefs; + @FXML + private FloatingPanel floatingPanel; + @FXML + private DailyPanel dailyPanel; + @FXML + private UpcomingPanel upcomingPanel; + + // Handles to elements of this Ui container + private VBox rootLayout; + private Scene scene; + + private String addressBookName; + + @FXML + private AnchorPane browserPlaceholder; + + @FXML + private AnchorPane commandBoxPlaceholder; + + @FXML + private ImageView imageIcon; + + @FXML + private ImageView cheatsheet; + + @FXML + private AnchorPane taskListPanelPlaceholder; + + @FXML + private AnchorPane aliasSymbolListPanelPlaceholder; + + @FXML + private AnchorPane resultDisplayPlaceholder; + + @FXML + private AnchorPane statusbarPlaceholder; + + @FXML + private VBox listPanel; + + @FXML + private AnchorPane floatingPanelPlaceholder; + + @FXML + private AnchorPane day1PanelPlaceholder; + @FXML + private AnchorPane day2PanelPlaceholder; + @FXML + private AnchorPane day3PanelPlaceholder; + @FXML + private AnchorPane day4PanelPlaceholder; + @FXML + private AnchorPane day5PanelPlaceholder; + @FXML + private AnchorPane day6PanelPlaceholder; + @FXML + private AnchorPane day7PanelPlaceholder; + + @FXML + private AnchorPane upcomingPanelPlaceholder; + + public MainWindow() { + super(); + } + + @Override + public void setNode(Node node) { + rootLayout = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { + + MainWindow mainWindow = UiPartLoader.loadUiPart(primaryStage, new MainWindow()); + mainWindow.configure(config.getAppTitle(), config.getSavvyTaskerListName(), config, prefs, logic); + return mainWindow; + } + + private void configure(String appTitle, String addressBookName, Config config, UserPrefs prefs, + Logic logic) { + + //Set dependencies + this.logic = logic; + this.addressBookName = addressBookName; + this.config = config; + this.userPrefs = prefs; + registerAsAnEventHandler(this); + + //Configure the UI + setTitle(appTitle); + setIcon(ICON); + setWindowMinSize(); + setWindowDefaultSize(prefs); + scene = new Scene(rootLayout); + primaryStage.setScene(scene); + + } + + void fillInnerParts() { + imageIcon.setImage(image); + taskListPanel = TaskListPanel.load(primaryStage, getTaskListPlaceholder(), logic.getFilteredTaskList()); + aliasSymbolListPanel = AliasSymbolListPanel.load(primaryStage, getAliasSymbolPlaceholder(), logic.getAliasSymbolList()); + setDefaultView(); + resultDisplay = ResultDisplay.load(primaryStage, getResultDisplayPlaceholder()); + statusBarFooter = StatusBarFooter.load(primaryStage, getStatusbarPlaceholder(), config.getSavvyTaskerFilePath()); + commandBox = CommandBox.load(primaryStage, getCommandBoxPlaceholder(), resultDisplay, logic); + commandBox.getCommandTextField().requestFocus(); + floatingPanel = FloatingPanel.load(primaryStage, getFloatingPanelPlaceholder(), logic.getFilteredFloatingTasks()); + loadDailyPanel(); + upcomingPanel = UpcomingPanel.load(primaryStage, getUpcomingPanelPlaceholder(), logic.getFilteredUpcomingTasks(firstDayOfSelectedWeek)); + cheatsheet.setImage(imageOverlay); + } + + private void loadDailyPanel() { + firstDayOfSelectedWeek = commandBox.getDate(); + for (int i = 0; i < DAYS_OF_WEEK; i++) { + Date onDate = new Date(); + onDate.setTime(firstDayOfSelectedWeek.getTime()); + onDate = addDay(i, onDate); + dailyPanel = DailyPanel.load(primaryStage, getDailyPanelPlaceholder(i), + logic.getFilteredDailyTasks(i, onDate), i, onDate); + } + } + + /** + * Removes all the children in the taskPanel VBox + * Shows the default list, which is the list of tasks + */ + private void setDefaultView() { + getListPanel().getChildren().remove(getAliasSymbolPlaceholder()); + getListPanel().getChildren().remove(getTaskListPlaceholder()); + getListPanel().getChildren().add(getTaskListPlaceholder()); + } + + /** + * Set to true to show the list of tasks. Set to false to show the list of alias + * @param isShown + */ + public void showTaskList(boolean isShown) { + getListPanel().getChildren().remove(getAliasSymbolPlaceholder()); + getListPanel().getChildren().remove(getTaskListPlaceholder()); + if (isShown) { + getListPanel().getChildren().add(getTaskListPlaceholder()); + } else { + getListPanel().getChildren().add(getAliasSymbolPlaceholder()); + } + } + + private VBox getListPanel() { + return listPanel; + } + + private VBox getRootLayout() { + return rootLayout; + } + + private AnchorPane getCommandBoxPlaceholder() { + return commandBoxPlaceholder; + } + + private AnchorPane getStatusbarPlaceholder() { + return statusbarPlaceholder; + } + + private AnchorPane getResultDisplayPlaceholder() { + return resultDisplayPlaceholder; + } + + public AnchorPane getTaskListPlaceholder() { + return taskListPanelPlaceholder; + } + + public AnchorPane getAliasSymbolPlaceholder() { + return aliasSymbolListPanelPlaceholder; + } + + private AnchorPane getFloatingPanelPlaceholder() { + return floatingPanelPlaceholder; + } + + private AnchorPane getDailyPanelPlaceholder(int index) { + + switch(index) { + + case 0: + + return day1PanelPlaceholder; + + case 1: + + return day2PanelPlaceholder; + + case 2: + + return day3PanelPlaceholder; + + case 3: + + return day4PanelPlaceholder; + + case 4: + + return day5PanelPlaceholder; + + case 5: + + return day6PanelPlaceholder; + + case 6: + default: + + return day7PanelPlaceholder; + + } + + } + + private AnchorPane getUpcomingPanelPlaceholder() { + return upcomingPanelPlaceholder; + } + + private Date addDay(int i, Date date) { + + //convert date object to calendar object and add 1 days + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(date); + + calendarExpectedDate.add(Calendar.DATE, i); + + //convert calendar object back to date object + date = calendarExpectedDate.getTime(); + + return date; + } + + public void hide() { + primaryStage.hide(); + } + + private void setTitle(String appTitle) { + primaryStage.setTitle(appTitle); + } + + /** + * Sets the default size based on user preferences. + */ + protected void setWindowDefaultSize(UserPrefs prefs) { + primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); + primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); + if (prefs.getGuiSettings().getWindowCoordinates() != null) { + primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); + primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); + } + } + + private void setWindowMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + + /** + * Returns the current size and the position of the main Window. + */ + public GuiSettings getCurrentGuiSetting() { + return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + } + + @FXML + public void handleHelp() { + HelpWindow helpWindow = HelpWindow.load(primaryStage); + helpWindow.show(); + } + + public void hideHelp() { + HelpWindow helpWindow = HelpWindow.load(primaryStage); + helpWindow.hide(); + } + + public void show() { + primaryStage.show(); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + raise(new ExitAppRequestEvent()); + } + + public AliasSymbolListPanel getAliasSymbolListPanel() { + return this.aliasSymbolListPanel; + } + + public TaskListPanel getTaskListPanel() { + return this.taskListPanel; + } + + public void loadPersonPage(ReadOnlyTask task) { + //feature removed + //browserPanel.loadPersonPage(task); + } + + public void releaseResources() { + //feature removed + //browserPanel.freeResources(); + } + + @Subscribe + public void handleSavvyTaskerChangedEvent(SavvyTaskerChangedEvent stce) { + loadDailyPanel(); + } + + @Subscribe + public void handleWeekSelectionChangedEvent(WeekSelectionChangedEvent stce) { + loadDailyPanel(); + } + + @Subscribe + public void handleCheatsheetDisplayToggledEvent(ShowCheatsheetEvent stce) { + + if(isShown == false) { + cheatsheet.setVisible(true); + isShown = true; + } else { + cheatsheet.setVisible(false); + isShown = false; + } + } + +} +``` +###### \java\seedu\savvytasker\ui\StatusBarFooter.java +``` java + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + setSaveLocation(dslce.newPath); + } +``` +###### \java\seedu\savvytasker\ui\TaskCard.java +``` java + +package seedu.savvytasker.ui; + +import java.util.Date; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import seedu.savvytasker.model.task.ReadOnlyTask; + +public class TaskCard extends UiPart{ + + private static final String FXML = "TaskListCard.fxml"; + + private static final String ICON = "/images/overdue.png"; + private static final Image OVERDUE_IMAGE = new Image(MainWindow.class.getResourceAsStream(ICON)); + + public static final String LOW_PRIORITY_BACKGROUND = "-fx-background-color:#CEFFDC"; + public static final String MEDIUM_PRIORITY_BACKGROUND = "-fx-background-color:#FFFED8"; + public static final String HIGH_PRIORITY_BACKGROUND = "-fx-background-color:#FF8180"; + + @FXML + private HBox cardPane; + @FXML + private Label taskName; + @FXML + private Label id; + @FXML + private Label details; + @FXML + private ImageView overdueIcon; + + private boolean isShowingIndex; + private ReadOnlyTask task; + private int displayedIndex; + + public TaskCard(boolean isShowingIndex){ + this.isShowingIndex = isShowingIndex; + } + + public static TaskCard load(ReadOnlyTask task, int displayedIndex, boolean isShowingIndex){ + TaskCard card = new TaskCard(isShowingIndex); + card.task = task; + card.displayedIndex = displayedIndex; + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + + taskName.setText(task.getTaskName()); + if (isShowingIndex) { + id.setText(displayedIndex + ". "); + } + details.setText(task.getTextForUi()); + setCardBackground(); + setOverdue(); + + } + + public HBox getLayout() { + return cardPane; + } + + private void setOverdue() { + + Date today = new Date(); + + if (task.getEndDateTime() != null) { + + Date endDateTime = task.getEndDateTime(); + + if (endDateTime.compareTo(today)<0 && task.isArchived() == false) { + + overdueIcon.setImage(OVERDUE_IMAGE); + } + } + + } + private void setCardBackground() { + + if (task.getPriority().toString().equals("High")) { + + cardPane.setStyle(HIGH_PRIORITY_BACKGROUND); + + } else if (task.getPriority().toString().equals("Medium")) { + + cardPane.setStyle(MEDIUM_PRIORITY_BACKGROUND); + + } else if (task.getPriority().toString().equals("Low")) { + + cardPane.setStyle(LOW_PRIORITY_BACKGROUND); + + } + + } + + + @Override + public void setNode(Node node) { + cardPane = (HBox)node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \java\seedu\savvytasker\ui\UpcomingPanel.java +``` java + +/** +* Panel containing the list overdue task. +* @author A0138431L +* +*/ +public class UpcomingPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "UpcomingList.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView taskListView; + + public UpcomingPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static UpcomingPanel load(Stage primaryStage, AnchorPane UpcomingListPlaceholder, + ObservableList taskList) { + UpcomingPanel upcomingPanel = + UiPartLoader.loadUiPart(primaryStage, UpcomingListPlaceholder, new UpcomingPanel()); + upcomingPanel.configure(taskList); + return upcomingPanel; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in upcoming task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + class TaskListViewCell extends ListCell { + + public TaskListViewCell() { + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, 0, false).getLayout()); + } + } + } + +} +``` +###### \resources\view\DarkTheme.css +``` css +.background { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-border-color: transparent #1d1d1d transparent #1d1d1d; + -fx-background-color: transparent, derive(#1d1d1d, 10%); +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell .label { + -fx-text-fill: #010504; + -fx-font-family:'Helvetica Condensed'; +} + +.cell_big_label { + -fx-font-size: 12px; + -fx-text-fill: #010504; + -fx-font-family:'Helvetica Condensed'; +} + +.cell_small_label { + -fx-font-size: 10px; + -fx-text-fill: #696969; +} + +.anchor-pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.anchor-pane-with-border { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; +} + +.result-display .content { + -fx-background-color: #383838; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-text-fill: white; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.context-menu { + -fx-background-color: derive(#1d1d1d, 50%); +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -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-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#1d1d1d, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar .thumb { + -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +.cardPane { + -fx-border-color: #ffffff; + -fx-border-radius: 5px; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#taskListView { + -fx-background-color: transparent; +} + +#header { + fx-font-size: 11px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Condensed'; +} + +/*------------------------------------------ FloatingPanel Styling ------------------------------------------*/ + +.floating-scrollpane, .floating-panel { + -fx-background-color:#ACEDFF; + -fx-border-radius: 15px; + -fx-font-size: 14px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Bold'; +} + +.floating-scrollpane { + -fx-border-radius: 15px; +} + +.floating-scrollpane > .scroll-bar:horizontal .thumb, +.floating-scrollpane > .scroll-bar:vertical .thumb { + -fx-background-color:#406C7F; +} + +.floating-panel .title, .floating-panel .taskname { + -fx-text-fill:#000000; +} + +.floating-panel .subtitle, .floating-panel .timestamp { + -fx-text-fill:#5997BS2; +} + +/*------------------------------------------ DailyPanel Styling ------------------------------------------*/ + +.daily-scrollpane, .daily-panel { + -fx-background-color:#99D3FF; + -fx-border-radius: 15px; + -fx-font-size: 14px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Bold'; +} + +.daily-scrollpane > .scroll-bar:horizontal .thumb, +.daily-scrollpane > .scroll-bar:vertical .thumb { + -fx-background-color:#7E7E45; +} + +.daily-panel .title, .daily-panel .taskname { + -fx-text-fill:#5D5D33; +} + +.daily-panel .subtitle, .daily-panel .timestamp { + -fx-text-fill:#B2B262; +} + +/*------------------------------------------ UpcomingPanel Styling ------------------------------------------*/ +.upcoming-scrollpane, .upcoming-panel { + -fx-background-color:#D1E0FF; + -fx-border-radius: 15px; + -fx-font-size: 14px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Bold'; +} + +.upcoming-scrollpane > .scroll-bar:horizontal .thumb, +.upcoming-scrollpane > .scroll-bar:vertical .thumb { + -fx-background-color:#657E47; +} + +.upcoming-panel .check-box > .box { + -fx-border-color:#657E47; +} + +.upcoming-panel .check-box { + -fx-font-family:'Helvetica'; + -fx-text-fill:#485A33; + -fx-font-size:10; + -fx-font-weight:bold; +} + +.upcoming-panel .title, .upcoming-panel .taskname { + -fx-text-fill:#485A33; +} + +.upcoming-panel .subtitle, .upcoming-panel .timestamp { + -fx-text-fill:#8EB264; +} + +/*------------------------------------------ ArchivedPanel Styling ------------------------------------------*/ + +.archived-panel .title { + -fx-font-family:'Helvetica Condensed'; + -fx-font-size:24; + -fx-font-weight:normal; +} + +.archived-panel .taskname, .archived-panel .timestamp { + -fx-text-fill:#FFFFFF; +} +``` diff --git a/collated/main/A0139915W.md b/collated/main/A0139915W.md new file mode 100644 index 000000000000..e81b39e96c3d --- /dev/null +++ b/collated/main/A0139915W.md @@ -0,0 +1,1791 @@ +# A0139915W +###### \java\seedu\savvytasker\commons\events\ui\TaskPanelSelectionChangedEvent.java +``` java + +/** + * Represents a selection change in the Task List Panel + */ +public class TaskPanelSelectionChangedEvent extends BaseEvent { + + + private final ReadOnlyTask newSelection; + + public TaskPanelSelectionChangedEvent(ReadOnlyTask newSelection){ + this.newSelection = newSelection; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public ReadOnlyTask getNewSelection() { + return newSelection; + } +} +``` +###### \java\seedu\savvytasker\commons\util\FileUtil.java +``` java + public static boolean createIfMissing(File file) throws IOException { + return createFile(file); + } +``` +###### \java\seedu\savvytasker\commons\util\SmartDefaultDates.java +``` java +/** + * Helper functions for handling dates. + */ + +public class SmartDefaultDates { + + private Date startDateTime; + private Date endDateTime; + private final Calendar calendar; + private final Calendar today; + + /** + * Determines the smart defaults for the dates provided. Can set both + * start and end dates as null to get a basic smart default. + * @param startDateTime Starting date time + * @param endDateTime Ending date time + */ + public SmartDefaultDates(InferredDate startDateTime, InferredDate endDateTime) { + calendar = Calendar.getInstance(); + today = Calendar.getInstance(); + today.setTime(new Date()); + if (startDateTime == null && endDateTime != null) { + // apply smart default for endDateTime only + parseEnd(endDateTime); + } else if (startDateTime != null && endDateTime == null) { + // apply smart default for startDateTime only + parseStart(startDateTime); + } else if (startDateTime != null && endDateTime != null) { + parseStartAndEnd(startDateTime, endDateTime); + } + } + + /** + * Gets the smart defaults for end date. + * + * If the date is not supplied, the date will default to today. + * If the time is not supplied, the time will default to 2359:59 on the specified date. + * If both date and time are not supplied, the date returned will be null. + * @param today the time now + * @param endDateTime the end time to parse + */ + public Date getEnd(InferredDate endDateTime) { + if (endDateTime == null) return null; + calendar.setTime(endDateTime.getInferredDateTime()); + if (endDateTime.isDateInferred() && endDateTime.isTimeInferred()) { + // remove date field + return null; + } else if (endDateTime.isDateInferred()) { + calendar.set(Calendar.DATE, today.get(Calendar.DATE)); + calendar.set(Calendar.MONTH, today.get(Calendar.MONTH)); + calendar.set(Calendar.YEAR, today.get(Calendar.YEAR)); + } else if (endDateTime.isTimeInferred()) { + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); + } + return calendar.getTime(); + } + + /** + * Sets the starting and ending date/time based on defaults for providing only + * the end time + * @param endDateTime end time supplied + */ + private void parseEnd(InferredDate endDateTime) { + assert endDateTime.getInferredDateTime() != null; + Date start = new Date(); + this.endDateTime = getEnd(endDateTime); + + // Since only end date is supplied, the task is considered to start at 12am today + calendar.setTime(start); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + this.startDateTime = calendar.getTime(); + + if (this.startDateTime.compareTo(this.endDateTime) > 0) { + // end date is before today, leave start date as null + this.startDateTime = null; + } + } + + + /** + * Gets the smart default for start date. + * + * If the date is not supplied, the date will default to today. + * If the time is not supplied, the time will default to 0000:00 on the specified date. + * If both date and time are not supplied, the date returned will be null. + * @param today the time now + * @param startDateTime the start time to parse + * @return + */ + public Date getStart(InferredDate startDateTime) { + if (startDateTime == null) return null; + calendar.setTime(startDateTime.getInferredDateTime()); + if (startDateTime.isDateInferred() && startDateTime.isTimeInferred()) { + // user didn't specify anything + // remove date field + return null; + } else if (startDateTime.isDateInferred()) { + calendar.set(Calendar.DATE, today.get(Calendar.DATE)); + calendar.set(Calendar.MONTH, today.get(Calendar.MONTH)); + calendar.set(Calendar.YEAR, today.get(Calendar.YEAR)); + } else if (startDateTime.isTimeInferred()) { + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + } + return calendar.getTime(); + } + + + /** + * Sets the starting and ending date/time based on defaults for providing only + * the start time + * @param startDateTime start time supplied + */ + private void parseStart(InferredDate startDateTime) { + assert startDateTime.getInferredDateTime() != null; + Date end = (Date)startDateTime.getInferredDateTime().clone(); + this.startDateTime = getStart(startDateTime); + + // Since only the start time is supplied, the task is considered to end today 2359 on the same day as start. + calendar.setTime(end); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); + this.endDateTime = calendar.getTime(); + } + + /** + * Sets the starting and ending date/time based on defaults for providing both + * start and end times. + * + * Note that this method has no restrictions on the starting and ending date/time. + * i.e. the starting time is later than the ending time. + * @param startDateTime start time supplied + * @param endDateTime end time supplied + */ + private void parseStartAndEnd(InferredDate startDateTime, InferredDate endDateTime) { + assert endDateTime.getInferredDateTime() != null; + assert startDateTime.getInferredDateTime() != null; + Date start = getStart(startDateTime); + Date end = getEnd(endDateTime); + this.startDateTime = start; + this.endDateTime = end; + } + + public Date getStartDate() { + return startDateTime; + } + + public Date getEndDate() { + return endDateTime; + } +} +``` +###### \java\seedu\savvytasker\commons\util\StringUtil.java +``` java + // reused original implementation of 'containsIgnoreCase' to find exact matches + public static boolean containsExactIgnoreCase(String source, String query) { + List strings = Arrays.asList(source); + return strings.stream().filter(s -> s.equalsIgnoreCase(query)).count() > 0; + } + + // reused original implementation of 'containsIgnoreCase' to find partial matches + public static boolean containsPartialIgnoreCase(String source, String query) { + if (source == null) return false; + String[] split = source.toLowerCase().split("\\s+"); + List strings = Arrays.asList(split); + return strings.stream().filter(s -> s.contains(query.toLowerCase())).count() > 0; + } +``` +###### \java\seedu\savvytasker\logic\commands\AddCommand.java +``` java + /** + * Creates an add command. + */ + public AddCommand(String taskName, InferredDate startDateTime, InferredDate endDateTime, + String location, PriorityLevel priority, RecurrenceType recurringType, + Integer numberOfRecurrence, String category, String description) { + this.taskName = taskName; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.location = location; + this.priority = priority; + this.recurringType = recurringType; + this.numberOfRecurrence = numberOfRecurrence; + this.category = category; + this.description = description; + tasksAdded = new LinkedList(); + } + + private void createTask() { + final boolean isArchived = false; // all tasks are first added as active tasks + final int taskId = 0; // taskId to be assigned by ModelManager, leave as 0 + final int groupId = 0; // groupId to be assigned by ModelManager, leave as 0 + + this.toAdd = new Task(taskId, groupId, taskName, startDateTime, endDateTime, + location, priority, recurringType, numberOfRecurrence, + category, description, isArchived); + } + + private void addToListOfTasksAdded(Task... tasks) { + for (Task t : tasks) { + tasksAdded.add(t); + } + } + + @Override + public CommandResult execute() { + assert model != null; + createTask(); + + try { + Task taskAdded = null; + if (toAdd.getRecurringType() == RecurrenceType.None) { + // not a recurring task, add a single task + taskAdded = model.addTask(toAdd); + addToListOfTasksAdded(taskAdded); + } else { + // a recurring task, add a group of recurring tasks + LinkedList tasksAdded = model.addRecurringTask(toAdd); + taskAdded = tasksAdded.peekFirst(); + addToListOfTasksAdded(tasksAdded.toArray(new Task[tasksAdded.size()])); + } + + // always >= 0 unless this is being run without UI. + int targetIndex = getIndexOfTask(taskAdded); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (InvalidDateException ex) { + return new CommandResult(MESSAGE_INVALID_START_END); + } + + } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + model.updateFilteredListToShowActive(); //because newly added tasks are all active. + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } +``` +###### \java\seedu\savvytasker\logic\commands\DeleteCommand.java +``` java + public DeleteCommand(int[] targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + LinkedList tasksToDelete = new LinkedList(); + for(int targetIndex : this.targetIndices) { + if (lastShownList.size() < targetIndex || targetIndex <= 0) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + tasksToDelete.add(lastShownList.get(targetIndex - 1)); + } + + original = new SavvyTasker(model.getSavvyTasker()); + + StringBuilder resultSb = new StringBuilder(); + try { + for(ReadOnlyTask taskToDelete : tasksToDelete) { + model.deleteTask(taskToDelete); + //tasksToUndo.add((Task)taskToDelete); + resultSb.append(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); + } + } catch (TaskNotFoundException tnfe) { + assert false : "The target task cannot be missing"; + } + + return new CommandResult(resultSb.toString()); + } +``` +###### \java\seedu\savvytasker\logic\commands\FindCommand.java +``` java + public FindCommand(FindType findType, String[] keywords) { + this.findType = findType; + this.keywords = keywords; + } + + @Override + public CommandResult execute() { + FindType _findType = findType; + if (_findType == null) { + // use default find type, partial. + _findType = FindType.Partial; + } + model.updateFilteredTaskList(_findType, keywords); + return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } +``` +###### \java\seedu\savvytasker\logic\commands\ListCommand.java +``` java + /** + * Creates the List command to list the specified tasks + * @param commandModel Arguments for the List command, must not be null + */ + public ListCommand(ListType listType) { + this.listType = listType; + } + + @Override + public CommandResult execute() { + ListType _listType = listType; + if (listType == null) { + // use default, sort by due date + _listType = ListType.DueDate; + } + + // shows the task list by default, unless user + // specifies to show the alias + switch (_listType) + { + case PriorityLevel: + model.updateFilteredListToShowActiveSortedByPriorityLevel(); + break; + case Archived: + model.updateFilteredListToShowArchived(); + break; + case Alias: + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Alias)); + break; + case DueDate: + // fall through. + default: // shows lists sorted by due date by default + model.updateFilteredListToShowActiveSortedByDueDate(); + break; + } + + if (_listType != ListType.Alias) { + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Task)); + return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } else { + return new CommandResult(getMessageForAliasListShownSummary(model.getAliasSymbolCount())); + } + } +``` +###### \java\seedu\savvytasker\logic\commands\ModifyCommand.java +``` java + /** + * Creates an add command. + */ + public ModifyCommand(int index, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, + String description) { + this.index = index; + this.taskName = taskName; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.location = location; + this.priority = priority; + this.recurringType = recurringType; + this.numberOfRecurrence = numberOfRecurrence; + this.category = category; + this.description = description; + this.originalTask = null; + } + + @Override + public CommandResult execute() { + assert model != null; + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() < index) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + ReadOnlyTask taskToModify = lastShownList.get(index - 1); + Task replacement = new Task(taskToModify, taskName, startDateTime, + endDateTime, location, priority, + recurringType, numberOfRecurrence, + category, description); + + try { + originalTask = (Task)taskToModify; + Task taskModified = model.modifyTask(taskToModify, replacement); + + // GUI will always get index >= 0; + int targetIndex = getIndexOfTask(taskModified); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } + } catch (TaskNotFoundException e) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException ex) { + return new CommandResult(Messages.MESSAGE_INVALID_START_END); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, replacement)); + } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } +``` +###### \java\seedu\savvytasker\logic\LogicManager.java +``` java + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + @Override + public ObservableList getAliasSymbolList() { + return parser.getAliasSymbolList(); + } +``` +###### \java\seedu\savvytasker\logic\parser\FindCommandParser.java +``` java + @Override + public FindCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + FindType findType = parseFindType(matcher.group(REGEX_REF_FIND_TYPE)); + String[] keywords = parseKeywords(matcher.group(REGEX_REF_KEYWORDS_BEFORE_TYPE), + matcher.group(REGEX_REF_KEYWORDS_AFTER_TYPE)); + + return new FindCommand(findType, keywords); + } + + throw new ParseException(commandText, String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } +``` +###### \java\seedu\savvytasker\model\Model.java +``` java + /** + * Deletes the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. + * */ + Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException; + + /** + * Modifies the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the modify operation is successful, an exception is thrown otherwise. + * */ + Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException; + + /** Adds the given Task. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the add operation is successful, an exception is thrown otherwise. + * */ + Task addTask(Task task) throws InvalidDateException; + + /** Adds the given Task as a recurring task. The task's recurrence type must not be null. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of Tasks added if the add operation is successful, an exception is thrown otherwise. + * */ + LinkedList addRecurringTask(Task task) throws InvalidDateException; + + /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredTaskList(); + + /** Updates the filter of the filtered task list to show all active tasks sorted by due date */ + void updateFilteredListToShowActiveSortedByDueDate(); + + /** Updates the filter of the filtered task list to show all active tasks sorted by priority level */ + void updateFilteredListToShowActiveSortedByPriorityLevel(); + + /** Updates the filter of the filtered task list to show all active tasks */ + void updateFilteredListToShowActive(); + + /** Updates the filter of the filtered task list to show all archived tasks */ + void updateFilteredListToShowArchived(); + + /** Updates the filter of the filtered task list to filter by the given keywords*/ + void updateFilteredTaskList(FindType findType, String[] keywords); +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + @Override + public synchronized Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException { + Task taskDeleted = savvyTasker.removeTask(target); + indicateSavvyTaskerChanged(); + return taskDeleted; + } + + @Override + public synchronized Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException { + Task taskModified = savvyTasker.replaceTask(target, replacement); + indicateSavvyTaskerChanged(); + return taskModified; + } + + @Override + public synchronized Task addTask(Task t) throws InvalidDateException { + Task taskAdded = savvyTasker.addTask(t); + updateFilteredListToShowActive(); + indicateSavvyTaskerChanged(); + return taskAdded; + } + + @Override + public synchronized LinkedList addRecurringTask(Task recurringTask) throws InvalidDateException { + LinkedList recurringTasks = savvyTasker.addRecurringTasks(recurringTask); + updateFilteredListToShowActive(); + indicateSavvyTaskerChanged(); + return recurringTasks; + } +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + @Override + public UnmodifiableObservableList getFilteredTaskList() { + return new UnmodifiableObservableList(sortedAndFilteredTasks); + } + + @Override + public void updateFilteredListToShowActiveSortedByDueDate() { + updateFilteredListToShowActive(new TaskSortedByDueDate()); + } + + @Override + public void updateFilteredListToShowActiveSortedByPriorityLevel() { + updateFilteredListToShowActive(new TaskSortedByPriorityLevel()); + } + + @Override + public void updateFilteredListToShowActive() { + updateFilteredTaskList(new PredicateExpression(new TaskIsActiveQualifier())); + } + + private void updateFilteredListToShowActive(Comparator comparator) { + updateFilteredTaskList( + new PredicateExpression(new TaskIsActiveQualifier()), + comparator); + } + + @Override + public void updateFilteredListToShowArchived() { + updateFilteredTaskList(new PredicateExpression(new TaskIsArchivedQualifier())); + } + + @Override + public void updateFilteredTaskList(FindType findType, String[] keywords) { + assert findType != null; + Qualifier qualifier = null; + switch (findType) + { + case Partial: + qualifier = new TaskNamePartialMatchQualifier(keywords); + break; + case Full: + qualifier = new TaskNameFullMatchQualifier(keywords); + break; + case Exact: + qualifier = new TaskNameExactMatchQualifier(keywords); + break; + case Category: + qualifier = new CategoryPartialMatchQualifier(keywords); + break; + default: + assert false; // should never get here. + break; + } + updateFilteredTaskList(new PredicateExpression(qualifier)); + } + + private void updateFilteredTaskList(Expression expression) { + updateFilteredTaskList(expression, new TaskSortedByDefault()); + } + + private void updateFilteredTaskList(Expression expression, Comparator comparator) { + filteredTasks.setPredicate(expression::satisfies); + sortedAndFilteredTasks.setComparator(comparator); + } +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + /** + * Qualifier matching a partial word from the set of keywords + */ + private class CategoryPartialMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + CategoryPartialMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = createSet(keyWordsToMatch); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsPartialIgnoreCase(task.getCategory(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "category(PartialMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier matching a partial word from the set of keywords + */ + private class TaskNamePartialMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + TaskNamePartialMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = createSet(keyWordsToMatch); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsPartialIgnoreCase(task.getTaskName(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "taskName(PartialMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier matching a full word from the set of keywords + */ + private class TaskNameFullMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + TaskNameFullMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = createSet(keyWordsToMatch); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsIgnoreCase(task.getTaskName(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "taskName(FullMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier matching a exactly from the set of keywords + */ + private class TaskNameExactMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + TaskNameExactMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = new HashSet(); + this.keyWordsToMatch.add(buildSingleString(keyWordsToMatch)); + } + + /** + * Builds a single string to be matched exactly against the task name. + * @param keyWordsToMatch list of keywords to match. + * @return A single string built from the list of keywords. + */ + private String buildSingleString(String[] keyWordsToMatch) { + StringBuilder sb = new StringBuilder(); + List keywords = Arrays.asList(keyWordsToMatch); + Iterator itr = keywords.iterator(); + while (itr.hasNext()) { + sb.append(itr.next()); + if (itr.hasNext()) { + sb.append(" "); + } + } + return sb.toString(); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsExactIgnoreCase(task.getTaskName(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "taskName(ExactMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier for checking if {@link Task} is active. Tasks that are not archived are active. + * + */ + private class TaskIsActiveQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + return !task.isArchived(); + } + + @Override + public String toString() { + return "isArchived=false"; + } + } + + /** + * Qualifier for checking if {@link Task} is archived + * + */ + private class TaskIsArchivedQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + return task.isArchived(); + } + + @Override + public String toString() { + return "isArchived=true"; + } +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + + //========== Inner classes/interfaces used for sorting ================================================== + + /** + * Compares {@link Task} by their default field, id + */ + private class TaskSortedByDefault implements Comparator { + + @Override + public int compare(Task task1, Task task2) { + if (task1 == null && task2 == null) { + return 0; + } else if (task1 == null) { + return 1; + } else if (task2 == null) { + return -1; + } else { + return task1.getId() - task2.getId(); + } + } + + } + + /** + * Compares {@link Task} by their DueDate + */ + private class TaskSortedByDueDate implements Comparator { + + @Override + public int compare(Task task1, Task task2) { + if (task1 == null && task2 == null) { + return 0; + } else if (task1 == null) { + return 1; + } else if (task2 == null) { + return -1; + } else { + // End dates can be nulls (floating tasks) + // Check for existence of endDateTime before comparing + if (task1.getEndDateTime() == null && + task2.getEndDateTime() == null) { + return 0; + } else if (task1.getEndDateTime() == null) { + return 1; + } else if (task2.getEndDateTime() == null) { + return -1; + } else { + return task1.getEndDateTime().compareTo(task2.getEndDateTime()); + } + } + } + + } + + /** + * Compares {@link Task} by their PriorityLevel + */ + private class TaskSortedByPriorityLevel implements Comparator { + + @Override + public int compare(Task task1, Task task2) { + if (task1 == null && task2 == null) return 0; + else if (task1 == null) return 1; + else if (task2 == null) return -1; + else { + return task2.getPriority().compareTo(task1.getPriority()); + } + } + + } +``` +###### \java\seedu\savvytasker\model\ReadOnlySavvyTasker.java +``` java + /** + * Returns a defensively copied task list. + */ + TaskList getTaskList(); +``` +###### \java\seedu\savvytasker\model\ReadOnlySavvyTasker.java +``` java + /** + * Returns an unmodifiable view of task list + */ + List getReadOnlyListOfTasks(); +``` +###### \java\seedu\savvytasker\model\SavvyTasker.java +``` java + + /** + * Adds a task to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the task added if the operation succeeds, an exception is thrown otherwise. + */ + public Task addTask(Task t) throws InvalidDateException { + // guarantees unique ID + t.setId(tasks.getNextId()); + try { + return tasks.add(t); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } + } + + /** + * Adds a group of recurring tasks to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of recurring tasks if the operation succeeds, an exception is thrown otherwise + */ + public LinkedList addRecurringTasks(Task recurringTask) throws InvalidDateException { + LinkedList tasksToAdd = + createRecurringTasks(recurringTask, recurringTask.getRecurringType(), + recurringTask.getNumberOfRecurrence()); + Iterator itr = tasksToAdd.iterator(); + + while(itr.hasNext()) { + // this will be an atomic operation + // guaranteed no duplicates + // if the start/end dates are invalid, + // the first task to be added will fail immediately, + // subsequent tasks will not be added + try { + tasks.add(itr.next()); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } + } + return tasksToAdd; + } + + /** + * Creates a list of recurring tasks to be added into savvy tasker. + * @param recurringTask the task that recurs + * @param recurringType the type of recurrence + * @param numberOfRecurrences the number of recurrences + * @return A list of tasks that represents a recurring task. + */ + private LinkedList createRecurringTasks(Task recurringTask, RecurrenceType recurringType, int numberOfRecurrences) { + assert recurringTask.getRecurringType() != null; + assert recurringTask.getNumberOfRecurrence() > 0; + + LinkedList listOfTasks = new LinkedList(); + recurringTask.setGroupId(tasks.getNextGroupId()); + + for (int i = 0; i < numberOfRecurrences; ++i) { + Task t = recurringTask.clone(); + // guarantees uniqueness + t.setId(tasks.getNextId()); + listOfTasks.add(setDatesForRecurringType(t, recurringType, i)); + } + + return listOfTasks; + } + + /** + * Helper function for createRecurringTasks(). Sets the respective start/end datetime for the + * i-th recurring task to be added + * @param t The first recurring task + * @param recurringType the type of recurrence + * @param index the index of the loop + * @return A task with its respective datetime set. + */ + private Task setDatesForRecurringType(Task t, RecurrenceType recurringType, int index) { + Date startDate = t.getStartDateTime(); + Date endDate = t.getEndDateTime(); + Calendar calendar = Calendar.getInstance(); + switch (recurringType) { + case Daily: // add one day to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 1 * index); + endDate = calendar.getTime(); + } + break; + case Weekly: // add 7 days to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 7 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 7 * index); + endDate = calendar.getTime(); + } + break; + case Monthly: // add 1 month to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.MONTH, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.MONTH, 1 * index); + endDate = calendar.getTime(); + } + break; + case Yearly: // add 1 year to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.YEAR, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.YEAR, 1 * index); + endDate = calendar.getTime(); + } + break; + case None: + //fall through + default: + assert false; // should not come here + break; + } + t.setStartDateTime(startDate); + t.setEndDateTime(endDate); + return t; + } + + /** + * Removes a task from savvy tasker. + * @param key the task to be removed + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the remove operation is successful, an exception is thrown otherwise. + */ + public Task removeTask(ReadOnlyTask key) throws TaskNotFoundException { + return tasks.remove(key); + } + + /** + * Replaces a task from savvy tasker. + * @param key the task to be replaced + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return true if the task is removed successfully + */ + public Task replaceTask(ReadOnlyTask key, Task replacement) throws TaskNotFoundException, InvalidDateException { + return tasks.replace(key, replacement); + } +``` +###### \java\seedu\savvytasker\model\SavvyTasker.java +``` java + @Override + public List getReadOnlyListOfTasks() { + return Collections.unmodifiableList(tasks.getInternalList()); + } + + @Override + public TaskList getTaskList() { + TaskList defensiveCopy = new TaskList(); + defensiveCopy.getInternalList().addAll(tasks.getInternalList()); + return defensiveCopy; + } +``` +###### \java\seedu\savvytasker\model\SavvyTasker.java +``` java + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SavvyTasker // instanceof handles nulls + && this.tasks.equals(((SavvyTasker) other).tasks) + && this.symbols.equals(((SavvyTasker) other).symbols)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(tasks, symbols); + } +``` +###### \java\seedu\savvytasker\model\task\ReadOnlyTask.java +``` java +/** + * A read-only immutable interface for a Task in the TaskList. + * Implementations should guarantee: details are present and not null, field values are validated. + */ +public interface ReadOnlyTask { + + int getId(); + int getGroupId(); + boolean isMarked(); + boolean isArchived(); + String getTaskName(); + Date getStartDateTime(); + Date getEndDateTime(); + String getLocation(); + PriorityLevel getPriority(); + RecurrenceType getRecurringType(); + int getNumberOfRecurrence(); + String getCategory(); + String getDescription(); + + /** + * Returns true if both tasks have the same state. (interfaces cannot override .equals) + */ + default boolean isSameStateAs(ReadOnlyTask other) { + return getId() == other.getId(); + } + + /** + * Formats the task as text, showing all task details. + */ + default String getAsText() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Id: ") + .append(getId()) + .append(" Task Name: ") + .append(getTaskName()) + .append(" Archived: ") + .append(isArchived()); + if (getStartDateTime() != null) { + builder.append(" Start: ") + .append(getStartDateTime()); + } + if (getEndDateTime() != null) { + builder.append(" End: ") + .append(getEndDateTime()); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()); + } + builder.append(" Priority: ") + .append(getPriority()); + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + .append(getDescription()); + } + builder.append(" Archived: ") + .append(isArchived()); + return builder.toString(); + } + + + /** + * Formats the task as text, showing all task details, formatted for the UI. + */ + default String getTextForUi() { + final StringBuilder builder = new StringBuilder(); + if (getStartDateTime() != null || getEndDateTime() != null) { + builder.append(generateDateTime(getStartDateTime(), getEndDateTime())) + .append("\n"); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()) + .append("\n"); + } + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()) + .append("\n"); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + .append(getDescription()) + .append("\n"); + } + return builder.toString(); + } +``` +###### \java\seedu\savvytasker\model\task\Task.java +``` java +/** + * Represents a Task in the task list. + */ +public class Task implements ReadOnlyTask { + + private int id; + private int groupId; + private String taskName; + private Date startDateTime; + private Date endDateTime; + private String location; + private PriorityLevel priority; + private RecurrenceType recurringType; + private int numberOfRecurrence; + private String category; + private String description; + private boolean isArchived; + + /** + * Constructor with smart defaults + */ + public Task(int id, int groupId, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, + String category, String description, boolean isArchived) { + + SmartDefaultDates sdd = new SmartDefaultDates(startDateTime, endDateTime); + this.id = id; + this.groupId = groupId; + this.taskName = taskName; + this.startDateTime = sdd.getStartDate(); + this.endDateTime = sdd.getEndDate(); + this.location = location; + if (priority == null) { + this.priority = PriorityLevel.Medium; + } else { + this.priority = priority; + } + if (recurringType == null) { + this.recurringType = RecurrenceType.None; + } else { + this.recurringType = recurringType; + } + if (numberOfRecurrence == null) { + this.numberOfRecurrence = 1; + } else { + this.numberOfRecurrence = numberOfRecurrence.intValue(); + } + this.category = category; + this.description = description; + this.isArchived = isArchived; + } + + /** + * Constructor without smart defaults + */ + public Task(int id, int groupId, String taskName, Date startDateTime, Date endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, + String category, String description, boolean isArchived) { + + this.id = id; + this.groupId = groupId; + this.taskName = taskName; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.location = location; + if (priority == null) { + this.priority = PriorityLevel.Medium; + } else { + this.priority = priority; + } + if (recurringType == null) { + this.recurringType = RecurrenceType.None; + } else { + this.recurringType = recurringType; + } + if (numberOfRecurrence == null) { + this.numberOfRecurrence = 1; + } else { + this.numberOfRecurrence = numberOfRecurrence.intValue(); + } + this.category = category; + this.description = description; + this.isArchived = isArchived; + } + + public Task(String taskName) { + this.taskName = taskName; + // sets initial default values + this.priority = PriorityLevel.Medium; + } + + /** + * Copy constructor. + */ + public Task(ReadOnlyTask source) { + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), + source.getEndDateTime(), source.getLocation(), source.getPriority(), + source.getRecurringType(), source.getNumberOfRecurrence(), + source.getCategory(), source.getDescription(), source.isArchived()); + } + + /** + * Copy and modify constructor + */ + public Task(ReadOnlyTask source, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, + String description) { + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), + source.getEndDateTime(), source.getLocation(), source.getPriority(), + source.getRecurringType(), source.getNumberOfRecurrence(), + source.getCategory(), source.getDescription(), source.isArchived()); + + //this.id should follow that of the source. + //this.groupId should follow that of the source. + //this.isArchived should follow that of the source. + this.taskName = taskName == null ? this.taskName : taskName; + setStartDate(startDateTime); + setEndDate(endDateTime); + this.location = location == null ? this.location : location; + this.priority = priority == null ? this.priority : priority; + this.recurringType = recurringType == null ? this.recurringType : recurringType; + this.numberOfRecurrence = numberOfRecurrence == null ? this.numberOfRecurrence : numberOfRecurrence.intValue(); + this.category = category == null ? this.category : category; + this.description = description == null ? this.description : description; + } + + private void setStartDate(InferredDate inferredDate) { + if (inferredDate != null) { + if (inferredDate.isDateInferred() && inferredDate.isTimeInferred()) { + // user specified s/ but with nothing tagged to it + // remove existing start date + this.startDateTime = null; + } else { + // user specified s/ with something tagged to it + // update existing start date + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + this.startDateTime = sdd.getStart(inferredDate); + } + } + } + + private void setEndDate(InferredDate inferredDate) { + if (inferredDate != null) { + if (inferredDate.isDateInferred() && inferredDate.isTimeInferred()) { + // user specified e/ but with nothing tagged to it + // remove existing end date + this.endDateTime = null; + } else { + // user specified e/ with something tagged to it + // update existing end date + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + this.endDateTime = sdd.getStart(inferredDate); + } + } + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getGroupId() { + return this.groupId; + } + + @Override + public boolean isMarked() { + return isArchived(); // all marked tasks are archived + } + + @Override + public boolean isArchived() { + return this.isArchived; + } + + @Override + public String getTaskName() { + return taskName; + } + + @Override + public Date getStartDateTime() { + return startDateTime; + } + + @Override + public Date getEndDateTime() { + return endDateTime; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public PriorityLevel getPriority() { + return priority; + } + + @Override + public RecurrenceType getRecurringType() { + return recurringType; + } + + @Override + public int getNumberOfRecurrence() { + return numberOfRecurrence; + } + + @Override + public String getCategory() { + return category; + } + + @Override + public String getDescription() { + return description; + } + + public void setId(int id) { + this.id = id; + } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public void setStartDateTime(Date startDateTime) { + this.startDateTime = startDateTime; + } + + public void setEndDateTime(Date endDateTime) { + this.endDateTime = endDateTime; + } + + public void setLocation(String location) { + this.location = location; + } + + public void setPriority(PriorityLevel priority) { + this.priority = priority; + } + + public void setRecurringType(RecurrenceType recurringType) { + this.recurringType = recurringType; + } + + public void setNumberOfRecurrence(int numberOfRecurrence) { + this.numberOfRecurrence = numberOfRecurrence; + } + + public void setCategory(String category) { + this.category = category; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setArchived(boolean isArchived) { + if (isArchived) { + mark(); + } else { + unmark(); + } + } + + /** + * Marks the task as complete, also moves this task from the active list + * to the archived list. + */ + public void mark() { + if (!isMarked()) { + this.isArchived = true; + } + } + + /** + * Unmarks the task as complete, also moves this task from the archived list + * to the active list. + */ + public void unmark() { + if (isMarked()) { + this.isArchived = false; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReadOnlyTask // instanceof handles nulls + && this.isSameStateAs((ReadOnlyTask) other)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(taskName); + } + + @Override + public String toString() { + return getAsText(); + } + + /** + * Creates a deep copy of this object. + */ + public Task clone() { + Task t = new Task(id, groupId, taskName, + (Date)startDateTime.clone(), (Date)endDateTime.clone(), + location, priority, recurringType, numberOfRecurrence, + category, description, isArchived); + return t; + } + +} +``` +###### \java\seedu\savvytasker\model\task\TaskList.java +``` java +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Task#equals(Object) + */ +public class TaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private int nextId = 0; + private boolean isNextIdInitialized = false; + private int nextGroupId = 0; + private boolean isNextGroupIdInitialized = false; + + /** + * Signals that an operation would have violated the 'end time earlier than start time' property of the list. + */ + public static class InvalidDateException extends IllegalValueException { + + /** + * Generated serial ID + */ + private static final long serialVersionUID = 8933810750762846403L; + + protected InvalidDateException() { + super("Operation would result in invalid start/end dates"); + } + } + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateTaskException extends DuplicateDataException { + + /** + * Generated serial ID + */ + private static final long serialVersionUID = 8933810750762846403L; + + protected DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } + } + + /** + * Signals that an operation targeting a specified task in the list would fail because + * there is no such matching task in the list. + */ + public static class TaskNotFoundException extends Exception { + + /** + * Generated serial ID + */ + private static final long serialVersionUID = -7591982407764643511L; + } + + /** + * Gets the next available id for uniquely identifying a task in + * Savvy Tasker. + * @return The next available id; + */ + public int getNextId() { + if (!isNextIdInitialized) { + int nextLowest = 0; // first id to be used is 1. Start finding with 0 + for (Task t : internalList) { + if (t.getId() > nextLowest) { + nextLowest = t.getId(); + } + } + // assumption that the number of tasks < 2^31 + // implementation will be buggy if nextId exceeds 2^31 + nextId = nextLowest; + assert nextId < Integer.MAX_VALUE; + isNextIdInitialized = true; + } + nextId++; + return nextId; + } + + /** + * Gets the next available group id for uniquely identifying a group of recurring tasks in + * Savvy Tasker. + * @return The next available group id; + */ + public int getNextGroupId() { + if (!isNextGroupIdInitialized) { + int nextLowest = 0; // first id to be used is 1. Start finding with 0 + for (Task t : internalList) { + if (t.getId() > nextLowest) { + nextLowest = t.getGroupId(); + } + } + // assumption that the number of tasks < 2^31 + // implementation will be buggy if nextId exceeds 2^31 + nextGroupId = nextLowest; + assert nextGroupId < Integer.MAX_VALUE; + isNextGroupIdInitialized = true; + } + nextGroupId++; + return nextGroupId; + } + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(ReadOnlyTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Returns true if the end time is not earlier than the start time. + */ + public boolean isValidStartEnd(ReadOnlyTask toCheck) { + assert toCheck != null; + return toCheck.getStartDateTime() == null || toCheck.getEndDateTime() == null || + toCheck.getStartDateTime().compareTo(toCheck.getEndDateTime()) < 0; + } + + /** + * Adds a task to the list. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task added if the add is successful, an exception is thrown otherwise. + */ + public Task add(Task toAdd) throws DuplicateTaskException, InvalidDateException { + assert toAdd != null; + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + if (!isValidStartEnd(toAdd)) { + throw new InvalidDateException(); + } + internalList.add(toAdd); + return toAdd; + } + + /** + * Removes the equivalent task from the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. + */ + public Task remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + assert toRemove != null; + int index = internalList.indexOf(toRemove); + if (index >= 0) { + final Task taskToDelete = internalList.get(index); + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskToDelete; + } else { + throw new TaskNotFoundException(); + } + } + + /** + * Replaces the equivalent task from the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task replaced if the replace is successful, an exception is thrown otherwise. + */ + public Task replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotFoundException, InvalidDateException { + assert toReplace != null; + assert replacement != null; + if (internalList.contains(toReplace)) { + if (!isValidStartEnd(replacement)) { + throw new InvalidDateException(); + } + internalList.set(internalList.indexOf(toReplace), replacement); + return replacement; + } + else { + throw new TaskNotFoundException(); + } + } + + public ObservableList getInternalList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskList // instanceof handles nulls + && this.internalList.equals( ((TaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\savvytasker\storage\SavvyTaskerStorage.java +``` java + /** + * Sets the file path of the data file. Returns true if successful, false otherwise + */ + boolean setSavvyTaskerFilePath(String path); +``` +###### \java\seedu\savvytasker\storage\StorageManager.java +``` java + @Override + public boolean setSavvyTaskerFilePath(String path) { + return savvyTaskerStorage.setSavvyTaskerFilePath(path); + } +``` +###### \java\seedu\savvytasker\storage\XmlAdaptedTask.java +``` java +/** + * JAXB-friendly version of the Task. + */ +public class XmlAdaptedTask { + + @XmlElement(required = true) + private int id; + @XmlElement(required = false) + private int groupId; + @XmlElement(required = true) + private String taskName; + @XmlElement(required = false) + private Date startDateTime; + @XmlElement(required = false) + private Date endDateTime; + @XmlElement(required = false) + private String location; + @XmlElement(required = false) + private PriorityLevel priority; + @XmlElement(required = false) + private String category; + @XmlElement(required = false) + private String description; + @XmlElement(required = true) + private boolean isArchived; + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedTask() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedTask(ReadOnlyTask source) { + id = source.getId(); + groupId = source.getGroupId(); + taskName = source.getTaskName(); + startDateTime = source.getStartDateTime(); + endDateTime = source.getEndDateTime(); + location = source.getLocation(); + priority = source.getPriority(); + category = source.getCategory(); + description = source.getDescription(); + 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 int id = this.id; + final String taskName = this.taskName; + final Date startDateTime = this.startDateTime; + final Date endDateTime = this.endDateTime; + final String location = this.location; + final PriorityLevel priority = this.priority; + final String category = this.category; + final String description = this.description; + final boolean isArchived = this.isArchived; + return new Task(id, groupId, taskName, startDateTime, endDateTime, location, priority, + null, null, category, description, isArchived); + } +} +``` +###### \java\seedu\savvytasker\storage\XmlSavvyTaskerStorage.java +``` java + @Override + /** + * Changes the path of the storage location if the file at newPath can be successfully created. + */ + public boolean setSavvyTaskerFilePath(String newPath) { + try { + File file = new File(newPath); + if (FileUtil.createIfMissing(file)) { + this.filePath = newPath; + return true; + } else { + return false; + } + } catch (IOException e) { + // do nothing, can't set the path to new path + } + return false; + } +``` diff --git a/collated/main/A0139916U.md b/collated/main/A0139916U.md new file mode 100644 index 000000000000..9d12c61e67f8 --- /dev/null +++ b/collated/main/A0139916U.md @@ -0,0 +1,2282 @@ +# A0139916U +###### \java\seedu\savvytasker\commons\events\model\AliasSymbolChangedEvent.java +``` java +package seedu.savvytasker.commons.events.model; + +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.model.alias.AliasSymbol; + +/** + * Represents an event where the user has added an alias symbol. + */ +public class AliasSymbolChangedEvent extends BaseEvent { + /** + * The action that was performed, i.e. the symbol was added or removed. + */ + public enum Action { + Added, + Removed; + } + + private final AliasSymbol symbol; + private final Action action; + + public AliasSymbolChangedEvent(AliasSymbol symbol, Action action) { + assert symbol != null; + assert action != null; + + this.symbol = symbol; + this.action = action; + } + + @Override + public String toString() { + return "Alias symbol " + action.toString() + ": " + symbol.toString(); + } + + public AliasSymbol getSymbol() { + return this.symbol; + } + + public Action getAction() { + return this.action; + } + +} +``` +###### \java\seedu\savvytasker\logic\commands\AliasCommand.java +``` java +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; + +/** + * Command to create aliases + */ +public class AliasCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "alias"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates an alias for a command or parameter. " + + "Parameters: k/KEYWORD s/SHORT_KEYWORD\n" + + "Example: " + COMMAND_WORD + + " k/Project Meeting s/pjm"; + + public static final String MESSAGE_SUCCESS = "New alias added: %1$s"; + public static final String MESSAGE_DUPLICATE_ALIAS = "This alias is already in use"; + public static final String MESSAGE_INVALID_KEYWORD = "Unable to use a command name as a keyword!"; + + private Logic logic; + private final String keyword; + private final String representingText; + /** + * Creates an alias command + */ + public AliasCommand(String keyword, String representingText) { + assert keyword != null; + assert representingText != null; + this.keyword = keyword; + this.representingText = representingText; + } + + @Override + public CommandResult execute() { + assert model != null; + + AliasSymbol toAdd = new AliasSymbol(keyword, representingText); + + if (logic.canParseHeader(toAdd.getKeyword())) { + return new CommandResult(MESSAGE_INVALID_KEYWORD); + } + + try { + model.addAliasSymbol(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateSymbolKeywordException e) { + return new CommandResult(MESSAGE_DUPLICATE_ALIAS); + } + } + + /** + * Provides logic related dependencies to the alias command. + */ + @Override + public void setLogic(Logic logic) { + assert logic != null; + this.logic = logic; + } + +``` +###### \java\seedu\savvytasker\logic\commands\Command.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + public abstract boolean canUndo(); + + /** + * Redo the command. + * @return true if the operation completed successfully, false otherwise + */ + public abstract boolean redo(); + + /** + * Undo the command + * @return true if the operation completed successfully, false otherwise + */ + public abstract boolean undo(); + +``` +###### \java\seedu\savvytasker\logic\commands\UnaliasCommand.java +``` java +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; + +/** + * Command to remove aliases + */ +public class UnaliasCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "unalias"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes an alias for a command or parameter. " + + "Parameters: s/SHORT_KEYWORD\n" + + "Example: " + COMMAND_WORD + + " s/pjm"; + + public static final String MESSAGE_SUCCESS = "Alias removed: %1$s"; + public static final String MESSAGE_UNREGOGNIZED_ALIAS = "This alias is not in use"; + + private final String keyword; + private AliasSymbol toUndo; + + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + */ + public UnaliasCommand(String keyword) { + this.keyword = keyword; + this.toUndo = null; + } + + @Override + public CommandResult execute() { + assert model != null; + + AliasSymbol toRemove = null; + for(AliasSymbol symbol : model.getSavvyTasker().getReadOnlyListOfAliasSymbols()) { + if (symbol.getKeyword().equals(this.keyword)) { + toRemove = symbol; + break; + } + } + + try { + if (toRemove == null) { + return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); + } + + toUndo = toRemove; + model.removeAliasSymbol(toRemove); + return new CommandResult(String.format(MESSAGE_SUCCESS, toRemove)); + } catch (SymbolKeywordNotFoundException e) { + return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); + } + } + +``` +###### \java\seedu\savvytasker\logic\LogicManager.java +``` java + undoDeque.addLast(command); + if (undoDeque.size() > MAX_UNDO_REDO_QUEUE_SIZE) { + undoDeque.removeFirst(); + } + redoDeque.clear(); + } +``` +###### \java\seedu\savvytasker\logic\LogicManager.java +``` java + private void registerAllDefaultCommandParsers() { + parser.registerCommandParser(new AddCommandParser()); + parser.registerCommandParser(new DeleteCommandParser()); + parser.registerCommandParser(new FindCommandParser()); + parser.registerCommandParser(new ListCommandParser()); + parser.registerCommandParser(new ModifyCommandParser()); + parser.registerCommandParser(new HelpCommandParser()); + parser.registerCommandParser(new ClearCommandParser()); + parser.registerCommandParser(new ExitCommandParser()); + parser.registerCommandParser(new MarkCommandParser()); + parser.registerCommandParser(new UnmarkCommandParser()); + parser.registerCommandParser(new UndoCommandParser()); + parser.registerCommandParser(new RedoCommandParser()); + parser.registerCommandParser(new AliasCommandParser()); + parser.registerCommandParser(new UnaliasCommandParser()); + parser.registerCommandParser(new StorageCommandParser()); + } + + private void loadAllAliasSymbols() { + List allSynbols = model.getSavvyTasker().getReadOnlyListOfAliasSymbols(); + for (AliasSymbol symbol : allSynbols) { + parser.addAliasSymbol(symbol); + } + } + + /** + * Undo last command and add it to the redo deque. + * @return true if undone successfully, false otherwise + */ + private boolean undo() { + boolean undone = false; + + if (!undoDeque.isEmpty()) { + Command command = undoDeque.removeLast(); + command.undo(); + redoDeque.addLast(command); + undone = true; + } + + return undone; + } + + /** + * Redo last command and add it to undone deque. + * @return true if redone successfully, false otherwise + */ + private boolean redo() { + boolean redone = false; + + if (!redoDeque.isEmpty()) { + Command command = redoDeque.removeLast(); + command.redo(); + undoDeque.addLast(command); + redone = true; + } + + return redone; + } + + /** + * Log the result of adding/removing a symbol in the parser. + * + * @param success if the operation succeeded + * @param changedSymbol the symbol that was involved in the operation + * @param successMsgFormat the message to print if the operation succeeded. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + * @param failureMsgFormat the message to print if the operation failed. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + */ + private void logParserSymbolChange(boolean success, AliasSymbol changedSymbol, + String successMsgFormat, String failureMsgFormat) { + if (success) { + logger.info(String.format(successMsgFormat, changedSymbol)); + } else { + logger.warning(String.format(failureMsgFormat, changedSymbol)); + } + } + + @Subscribe + public void handleAliasSymbolChangedEvent(AliasSymbolChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage( + event, "Alias symbol " + event.getAction().toString().toLowerCase())); + + if (event.getAction().equals(AliasSymbolChangedEvent.Action.Added)) { + logParserSymbolChange( + parser.addAliasSymbol(event.getSymbol()), + event.getSymbol(), + "Added alias symbol '%s' to parser", + "Failed to add alias symbol '%s' to parser"); + } else if (event.getAction().equals(AliasSymbolChangedEvent.Action.Removed)) { + logParserSymbolChange( + parser.removeAliasSymbol(event.getSymbol().getKeyword()), + event.getSymbol(), + "Removed alias symbol '%s' from parser", + "Failed to remove alias symbol '%s' from parser"); + } + } + + @Override + public boolean canParseHeader(String header) { + return parser.isCommandParserRegistered(header); + } +} +``` +###### \java\seedu\savvytasker\logic\parser\AddCommandParser.java +``` java +// Please see CommandParser interface for documentation for many of the overridden methods +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.AddCommand; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +public class AddCommandParser extends TaskFieldParser { + private static final String HEADER = "add"; + private static final String READABLE_FORMAT = HEADER+" TASK_NAME [s/START_DATE] " + + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_TASK_NAME+">([^/]+?(\\s+|$))+)((?<=\\s)(" + + "(s/(?<"+REGEX_REF_START_DATE+">[^/]+)(?!.*\\ss/))|" + + "(e/(?<"+REGEX_REF_END_DATE+">[^/]+)(?!.*\\se/))|" + + "(l/(?<"+REGEX_REF_LOCATION+">[^/]+)(?!.*\\sl/))|" + + "(p/(?<"+REGEX_REF_PRIORITY_LEVEL+">[^/]+)(?!.*\\sp/))|" + + "(r/(?<"+REGEX_REF_RECURRING_TYPE+">[^/]+)(?!.*\\sr/))|" + + "(n/(?<"+REGEX_REF_NUMBER_OF_RECURRENCE+">[^/]+)(?!.*\\sn/))|" + + "(c/(?<"+REGEX_REF_CATEGORY+">[^/]+)(?!.*\\sc/))|" + + "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]+)(?!.*\\sd/))" + + ")(\\s|$)){0,10}", Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public AddCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + InferredDate startDate = parseStartDate(matcher.group(REGEX_REF_START_DATE)); + InferredDate endDate = parseEndDate(matcher.group(REGEX_REF_END_DATE)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + + return new AddCommand(taskName, startDate, + endDate, location, priority, + recurrence, nrOfRecurrence, + category, description); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\AliasCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.AliasCommand; + +public class AliasCommandParser implements CommandParser { + private static final String HEADER = "alias"; + private static final String READABLE_FORMAT = HEADER+" k/KEYWORD r/REPRESENTATION"; + + private static final String REGEX_REF_REPRESENTATION = "Text"; + private static final String REGEX_REF_KEYWORD = "Keyword"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+((?<=\\s)(" + + "(r/(?<"+REGEX_REF_REPRESENTATION+">[^/]+)(?!.*\\sr/))|" + + "(k/(?<"+REGEX_REF_KEYWORD+">[^/]+)(?!.*\\sk/))" + + ")(\\s|$)){2}", + Pattern.CASE_INSENSITIVE + ); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public boolean shouldPreprocess() { + return false; + } + + @Override + public AliasCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String representation = parseRepresentation(matcher.group(REGEX_REF_REPRESENTATION)); + String keyword = parseKeyword(matcher.group(REGEX_REF_KEYWORD)); + return new AliasCommand(keyword, representation); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private String parseRepresentation(String originalText) throws ParseException { + String trimmedText = originalText.trim(); + + if (trimmedText.isEmpty()) { + throw new ParseException(trimmedText, "REPRESENTATION: Needs to be at least one character!"); + } + + return trimmedText; + } + + private String parseKeyword(String keywordText) throws ParseException { + String trimmedKeywordText = keywordText.trim(); + if (trimmedKeywordText.length() < 2) { + throw new ParseException(trimmedKeywordText, "KEYWORD: Needs to consist at least 2 character."); + } + if (trimmedKeywordText.matches(".*\\s+.*")) { + throw new ParseException(trimmedKeywordText, "KEYWORD: Needs to be a single word without spaces."); + } + return trimmedKeywordText; + } +} +``` +###### \java\seedu\savvytasker\logic\parser\ClearCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ClearCommand; + +public class ClearCommandParser implements CommandParser { + private static final String HEADER = "clear"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ClearCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new ClearCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\CommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.logic.commands.Command; + +/** + * This interface should be implemented by specialized parsers that want to + * parse a specific command. The text passed to such a parser will be + * guaranteed to have its header (the leading word) matching the String + * returned by the abstract getHeader() method supplied by its implementation. + * + * @param A Command that this Parser is going to produce upon successful parsing + */ +public interface CommandParser { + /** + * Indicates whether this parser would like to have the text supplied to it + * preprocessed. The default behaviour is to allow preprocessing. + * Subclasses should override this method to return false if they do not want + * the text to be preprocessed. + * + * @return true if preprocessing is desired, false otherwise + */ + public default boolean shouldPreprocess() { + return true; + } + + /** + * Gets the header of this command parser. + * The header is the first token of a command text, used to identify which + * CommandParser the command text will be dispatched to. Subclasses should + * implement this method to indicate their header so that any command text that + * starts with such a header will be given to them. This method should always + * return the same value, must return a string as a single token with no whitespaces + * in between, and must not return null or empty string. + * + * @return header string + */ + public String getHeader(); + + /** + * Gets the human-readable format of the string required by this command parser to + * parse correctly. Subclasses should implement this method to provide the above described. + * This method should not return null, and should return the same value every time. + * + * @return a human-readable format required by this parser + */ + public String getRequiredFormat(); + + /** + * Parses the command text and returns the resulting command built from the parse. + * Subclasses should implement this method to do their parsing. If the command text + * is not as what they expect, subclasses should throw a ParseException. + * + * @param commandText the full command text, including the header + * @return the resulting command object which can then be executed + * @throws ParseException if the commandText has an incorrect syntax + */ + public T parse(String commandText) throws ParseException; + +} +``` +###### \java\seedu\savvytasker\logic\parser\DateParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import com.joestelmach.natty.DateGroup; + +public class DateParser { + /** + * Represents a date-time that may be partially inferred on its + * date component or time component. If both components are not + * inferred, then the date-time is exactly as the user has specified + * it to be. + */ + public class InferredDate { + private final Date inferredDateTime; + private final boolean dateInferred; + private final boolean timeInferred; + + public InferredDate(Date inferredDateTime, boolean dateInferred, boolean timeInferred) { + this.inferredDateTime = inferredDateTime; + this.dateInferred = dateInferred; + this.timeInferred = timeInferred; + } + + /** + * Gets the inferred date-time. + * @return the inferred date-time + */ + public Date getInferredDateTime() { + return this.inferredDateTime; + } + + /** + * Checks if the date component is inferred + * @return true if inferred, false otherwise + */ + public boolean isDateInferred() { + return this.dateInferred; + } + + /** + * Checks if the time component is inferred + * @return true if inferred, false otherwise + */ + public boolean isTimeInferred() { + return this.timeInferred; + } + } + + private com.joestelmach.natty.Parser nattyParser; + + public DateParser() { + this.nattyParser = new com.joestelmach.natty.Parser(); + } + + /** + * Parses an input for a single date. It is considered a parsing error + * if multiple dates are specified in the given input. + * + * @param input the input to be parsed + * @return an InferredDate object resulting from the parsing + * @throws ParseException if the input contains multiple dates or no dates. + */ + public InferredDate parseSingle(String input) throws ParseException { + assert input != null; + + // Temporary workaround for natty's failure at adapting to locales dd-mm-yyyy, until + // their issue is closed. + if (!Locale.getDefault().equals(Locale.US)) { + input = input.replaceAll("(\\d{1,2})-(\\d{1,2})-((?:\\d\\d){1,2})", "$2-$1-$3"); + } + + List dateGroups = this.nattyParser.parse(input); + int totalDates = countDates(dateGroups); + + if (totalDates == 0) { + throw new ParseException(input, "Failed to understand given date."); + } + + if (totalDates > 1) { + throw new ParseException(input, "Too many dates entered."); + } + + DateGroup group = dateGroups.get(0); + + return new InferredDate( + group.getDates().get(0), + group.isDateInferred(), + group.isTimeInferred()); + + } + + private int countDates(List dateGroups) { + int totalNumOfDates = 0; + for(DateGroup group : dateGroups) { + totalNumOfDates += group.getDates().size(); + } + return totalNumOfDates; + } +} +``` +###### \java\seedu\savvytasker\logic\parser\DeleteCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.DeleteCommand; + +public class DeleteCommandParser implements CommandParser { + private static final String HEADER = "delete"; + private static final String READABLE_FORMAT = HEADER+" INDEX [MORE_INDEX]"; + + private static final String REGEX_REF_INDICES = "Indices"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDICES+">[^/]+)", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public DeleteCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + int[] indices = parseIndices(matcher.group(REGEX_REF_INDICES)); + return new DeleteCommand(indices); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int[] parseIndices(String indicesText) throws ParseException { + try { + return INDEX_PARSER.parseMultiple(indicesText); + } catch (ParseException ex) { + throw new ParseException(indicesText, String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat() + ": " + ex.getFailureDetails())); + } + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\ExitCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ExitCommand; + +public class ExitCommandParser implements CommandParser { + private static final String HEADER = "exit"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ExitCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new ExitCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\FindCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.FindCommand; +import seedu.savvytasker.model.task.FindType; + +public class FindCommandParser implements CommandParser { + private static final String HEADER = "find"; + private static final String READABLE_FORMAT = "find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]"; + + private static final String REGEX_REF_KEYWORDS_BEFORE_TYPE = "Before"; + private static final String REGEX_REF_FIND_TYPE = "FindType"; + private static final String REGEX_REF_KEYWORDS_AFTER_TYPE = "After"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_KEYWORDS_BEFORE_TYPE+">([^/]+(\\s+|$))*)" + + "(t/(?<"+REGEX_REF_FIND_TYPE+">[^/]+?)(?!.*\\st/)(\\s+|$))?" + + "(?<"+REGEX_REF_KEYWORDS_AFTER_TYPE+">([^/]+(\\s+|$))*)" + , Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + +``` +###### \java\seedu\savvytasker\logic\parser\HelpCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.HelpCommand; + +public class HelpCommandParser implements CommandParser { + private static final String HEADER = "help"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public HelpCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new HelpCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\IndexParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.Arrays; + +/** + * A class for parsing a single index or multiple indices. An index is + * a positive integer that may be used by commands to indicate which task + * they act on. + */ +public class IndexParser { + public static String INDEX_MUST_BE_POSITIVE = "Must be positive whole numbers."; + /** + * Parses a single index. + * + * @param indexText the text to parse + * @return the resulting index + * @throws ParseException if there are more than one integer in the text, or if + * the integer given is not positive + */ + public int parseSingle(String indexText) throws ParseException { + boolean parseError = false; + + String trimmedIndexText = indexText.trim(); + int index = 0; + try { + index = Integer.parseInt(trimmedIndexText); + + if (index <= 0) { + parseError = true; + } + } catch (NumberFormatException ex) { + parseError = true; + } + + if (parseError) { + throw new ParseException(trimmedIndexText, "Must be a positive whole number."); + } + + return index; + } + + /** + * Parses multiple indices, delimited by space. + * + * @param indicesText the text to parse + * @return an array of the resulting indices + * @throws ParseException if any of the indices is not a positive integer + */ + public int[] parseMultiple(String indicesText) throws ParseException { + boolean parseError = false; + + String trimmedIndicesText = indicesText.trim(); + int[] indices = null; + try { + indices = Arrays + .stream(trimmedIndicesText.split("\\s+")) + .mapToInt(Integer::parseInt) + .toArray(); + + for(int index : indices) { + if (index <= 0) { + parseError = true; + break; + } + } + } catch (NumberFormatException ex) { + parseError = true; + } + + if (parseError) { + throw new ParseException(trimmedIndicesText, INDEX_MUST_BE_POSITIVE); + } + + return indices; + } +} +``` +###### \java\seedu\savvytasker\logic\parser\ListCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ListCommand; +import seedu.savvytasker.model.ListType; + +public class ListCommandParser implements CommandParser { + private static final String HEADER = "list"; + private static final String READABLE_FORMAT = HEADER+" [LIST_TYPE]"; + + private static final String REGEX_REF_LIST_TYPE = "ListType"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s*((?<=\\s)(?<"+REGEX_REF_LIST_TYPE+">[^/]+))?", + Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ListCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + ListType listType = parseListType(matcher.group(REGEX_REF_LIST_TYPE)); + + return new ListCommand(listType); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private ListType parseListType(String listTypeText) throws ParseException { + if (listTypeText == null) { + return null; + } + + try { + listTypeText = listTypeText.trim(); + return ListType.valueOfIgnoreCase(listTypeText.replaceAll("\\s", "")); + } catch (IllegalArgumentException ex) { + throw new ParseException(listTypeText, "LIST_TYPE: Unknown type '" + listTypeText + "'"); + } + } +} +``` +###### \java\seedu\savvytasker\logic\parser\MarkCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.MarkCommand; + +public class MarkCommandParser implements CommandParser { + private static final String HEADER = "mark"; + private static final String READABLE_FORMAT = HEADER+" INDEX [MORE_INDEX]"; + + private static final String REGEX_REF_INDICES = "Indices"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDICES+">[^/]+)", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public MarkCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + int[] indices = parseIndices(matcher.group(REGEX_REF_INDICES)); + return new MarkCommand(indices); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int[] parseIndices(String indicesText) throws ParseException { + try { + return INDEX_PARSER.parseMultiple(indicesText); + } catch (ParseException ex) { + throw new ParseException(indicesText, "INDEX [MORE_INDEX]: " + ex.getFailureDetails()); + } + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\MasterParser.java +``` java +package seedu.savvytasker.logic.parser; + +import static seedu.savvytasker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.logic.commands.IncorrectCommand; +import seedu.savvytasker.model.alias.AliasSymbol; + +/** + * Represents the master parser that is used by Logic. This is a parser containing + * all subparsers that will do the actual parsing to produce Command objects. This + * parser replaces keywords of the input with aliased representations before selecting + * a subparser to do the parsing. The selection of the subparser is based on the first + * word of the input, called the header, and matching it to the subparser that declares + * that it parses it. + */ +public class MasterParser { + private static final Pattern KEYWORD_PATTERN = + Pattern.compile("([^\\s/]+)([\\s/]+|$)"); + + private final Map> commandParsers; + private final Map aliasingSymbols; + private final ObservableList aliasList = FXCollections.observableArrayList(); + + + public MasterParser() { + this.commandParsers = new HashMap>(); + this.aliasingSymbols = new HashMap(); + } + + /** + * Parses the input text, selecting an appropriate registered parser to parse it. + * The parser selected is based on the first header word of the input text. The text + * is preprocessed, replacing any of its tokens that are keywords to an alias, before + * being passed to the parser. + * + * @param userInput the text to be parse + * @return the command that was parsed if successful, or IncorrectCommand if there is no + * parser that can parse the text or if there is a format error with the text. + */ + public Command parse(String userInput) { + String[] pieces = preprocessInitial(userInput.trim()); + if (pieces == null) { + return new IncorrectCommand(userInput, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + String header = pieces[0]; + String body = pieces[1]; + CommandParser parser = selectParser(extractTrueHeader(header)); + if (parser == null) { + return new IncorrectCommand(header + body, + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + + if (parser.shouldPreprocess()) { + return makeParserParse(parser, header + preprocessBody(body)); + } else { + return makeParserParse(parser, header + body); + } + } + + /** + * Makes the parser parse the preprocessed text. + * + * @param parser the parser to use to parse + * @param preprocessedText the text to parse + * @return the output Command from the parsing, or IncorrectCommand object if the parse failed + */ + private Command makeParserParse(CommandParser parser, String preprocessedText) { + try { + return parser.parse(preprocessedText); + } catch (ParseException pe) { + return new IncorrectCommand(preprocessedText, String.format(pe.getFailureDetails())); + } + } + + private CommandParser selectParser(String header) { + return commandParsers.get(header); + } + + /** + * Does an initial preprocessing of a command text in case the header is aliased. + * Returns a string array with 2 elements: the first is the header which is possibly aliased, + * which will be replaced with the representation of the aliasing, whereas the + * second is the body. If no header is found, this method returns null. + * + * @param commandText the command text + * @return a string array containing 2 elements, first is the header, second is the body; or null if + * there is no header in the command text + */ + private String[] preprocessInitial(String commandText) { + Matcher matcher = KEYWORD_PATTERN.matcher(commandText); + + if (matcher.find()) { + String header = matcher.group(1); + String spaces = matcher.group(2); + + AliasSymbol symbol = aliasingSymbols.get(header); + if (symbol != null) { + header = symbol.getRepresentation(); + } + + String body = commandText.substring(matcher.end()); + return new String[] {header + spaces, body}; + } + + return null; + } + + /** + * Gets the true header from the preprocessed header as a preprocessed header may contain + * several tokens. + * + * @param preprocessedHeader the preprocessed header + * @return the true header + */ + private String extractTrueHeader(String preprocessedHeader) { + Matcher matcher = KEYWORD_PATTERN.matcher(preprocessedHeader); + + if (matcher.find()) { + return matcher.group(1); + } + + return null; + } + + /** + * Preprocess the body to replace keywords with their replacement if they are aliased. + * @param bodyText the body text + * @return the preprocessed body text + */ + private String preprocessBody(String bodyText) { + StringBuilder builder = new StringBuilder(); + Matcher matcher = KEYWORD_PATTERN.matcher(bodyText); + + while (matcher.find()) { + String keyword = matcher.group(1); + String spaces = matcher.group(2); // Preserves the amount of spaces as that may be what user wants + + AliasSymbol symbol = aliasingSymbols.get(keyword); + if (symbol != null) { + keyword = symbol.getRepresentation(); + } + + builder.append(keyword); + builder.append(spaces); + } + + return builder.toString(); + } + + /** + * Registers a command parser that will be used by the master parser, and return true if it + * is successfully registered. The header of this command parser should not be used by any + * other registered command parsers or used by any AliasSymbol whose keyword is the registered + * with the same name, or false will be return and the parser will not be added. Use + * {@link #isCommandParserRegistered(String) isCommandParserRegistered } method to check if + * a command parser is already registered, and {@link #doesAliasSymbolExist(String) doesAliasSymbolExist} + * + * Parameter commandParser should not be null. + * + * @param commandParser the command parser + * @return true if successfully registered, false if there is an parser with the same header + * already registered or if an alias with the same keyword is previously added. + */ + public boolean registerCommandParser(CommandParser commandParser) { + assert commandParser != null; + + if (commandParsers.containsKey(commandParser.getHeader())) { + return false; + } + if (aliasingSymbols.containsKey(commandParser.getHeader())) { + return false; + } + + commandParsers.put(commandParser.getHeader(), commandParser); + return true; + } + + /** + * Checks if a command parser with the specified header is already + * registered into the master parser. + * + * @param header the header to check against + * @return true if such a command parser is registered, false otherwise + */ + public boolean isCommandParserRegistered(String header) { + return commandParsers.containsKey(header); + } + + /** + * Unregisters and returns the command parser that uses the specified header. + * If such a parser is not registered, null is returned. + * + * @param header the header to check against + * @return the CommandParser object that uses the specified header. + */ + public CommandParser unregisterCommandParser(String header) { + return commandParsers.remove(header); + } + + /** + * Adds an aliasing symbol to be used by the parser to replace all such the symbol's keyword with + * its representation before parsing. If a symbol with an identical keyword exists, calling this + * method has no effect and just returns false. + * + * @param symbol the symbol, cannot be null. + * @return true if this symbol is added successfully, false if another symbol with the same keyword + * already exists or the keyword is used by a command and this symbol cannot be added. + */ + public boolean addAliasSymbol(AliasSymbol symbol) { + assert symbol != null; + + if (aliasingSymbols.containsKey(symbol.getKeyword())) { + return false; + } + if (isCommandParserRegistered(symbol.getKeyword())) { + return false; + } + + aliasList.add(symbol); + aliasingSymbols.put(symbol.getKeyword(), symbol); + return true; + } + + /** + * Removes an aliasing symbol, identified by its keyword. The parser will no longer replace the + * keyword of this symbol with its representation before parsing. + * + * @param symbol the symbol to remove, cannot be null + * @return true if the symbol exists and is removed, false otherwise + */ + public boolean removeAliasSymbol(String symbolKeyword) { + assert symbolKeyword != null; + + AliasSymbol symbol = aliasingSymbols.remove(symbolKeyword); + if (symbol != null) { + return aliasList.remove(symbol); + } else { + return false; + } + } + + /** + * Returns true if a symbol with the specified keyword exists, false otherwise. + * @param symbolKeyword the keyword to check for, cannot be null + * @return true if the symbol exists, false otherwise + */ + public boolean doesAliasSymbolExist(String symbolKeyword) { + assert symbolKeyword != null; + + return aliasingSymbols.containsKey(symbolKeyword); + } + + /** + * Clears all existing symbols. + * @see #removeAliasSymbol + */ + public void clearAllAliasSymbols() { + aliasList.clear(); + aliasingSymbols.clear(); + } + + public ObservableList getAliasSymbolList() { + return aliasList; + } +} +``` +###### \java\seedu\savvytasker\logic\parser\ModifyCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ModifyCommand; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +public class ModifyCommandParser extends TaskFieldParser { + private static final String HEADER = "modify"; + private static final String READABLE_FORMAT = HEADER+" INDEX [t/TASK_NAME] [s/START_DATE] " + + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; + + private static final String REGEX_REF_INDEX = "Index"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDEX+">([^/]+?(\\s+|$))+)((?<=\\s)(" + + "(t/(?<"+REGEX_REF_TASK_NAME+">[^/]*)(?!.*\\st/))|" + + "(s/(?<"+REGEX_REF_START_DATE+">[^/]*)(?!.*\\ss/))|" + + "(e/(?<"+REGEX_REF_END_DATE+">[^/]*)(?!.*\\se/))|" + + "(l/(?<"+REGEX_REF_LOCATION+">[^/]*)(?!.*\\sl/))|" + + "(p/(?<"+REGEX_REF_PRIORITY_LEVEL+">[^/]+)(?!.*\\sp/))|" + + "(r/(?<"+REGEX_REF_RECURRING_TYPE+">[^/]+)(?!.*\\sr/))|" + + "(n/(?<"+REGEX_REF_NUMBER_OF_RECURRENCE+">[^/]+)(?!.*\\sn/))|" + + "(c/(?<"+REGEX_REF_CATEGORY+">[^/]*)(?!.*\\sc/))|" + + "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]*)(?!.*\\sd/))" + + ")(\\s|$)){0,11}", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ModifyCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + + int index = parseIndex(matcher.group(REGEX_REF_INDEX)); + InferredDate startDate = parseDate(matcher.group(REGEX_REF_START_DATE)); + InferredDate endDate = parseDate(matcher.group(REGEX_REF_END_DATE)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + + return new ModifyCommand(index, taskName, startDate, + endDate, location, priority, + recurrence, nrOfRecurrence, + category, description); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int parseIndex(String indexText) throws ParseException { + try { + return INDEX_PARSER.parseSingle(indexText); + } catch (ParseException ex) { + throw new ParseException(indexText, "INDEX: " + ex.getFailureDetails()); + } + } + + private InferredDate parseDate(String dateText) throws ParseException { + if (dateText != null && dateText.trim().isEmpty()) { + return dateParser.new InferredDate(new Date(), true, true); + } + + return parseStartDate(dateText); + } +} +``` +###### \java\seedu\savvytasker\logic\parser\ParseException.java +``` java +package seedu.savvytasker.logic.parser; + +/** + * This exception should be thrown by parsers when they are unable to + * parse a string of tokens. + */ +public class ParseException extends Exception { + private static final long serialVersionUID = -1157747299012674373L; + private final String parsedString; + private final String failureDetails; + + /** + * Constructs a ParseException object with the original string that + * the parser failed to parse, with no further details to why the + * parser failed to parse the string + * + * @param parsedString the erroneous string + */ + public ParseException(String parsedString) { + this(parsedString, ""); + } + + /** + * Constructs a ParseException object with the original string that + * the parser failed to parse, and further details to why the parser + * failed to parse the string. + * + * @param parsedString the erroneous string + * @param failureDetails the failure details + */ + public ParseException(String parsedString, String failureDetails) { + super("Failed to parse " + parsedString + ". " + failureDetails); + this.parsedString = parsedString; + this.failureDetails = failureDetails; + } + + public String getParsedString() { + return this.parsedString; + } + + public String getFailureDetails() { + return this.failureDetails; + } +} +``` +###### \java\seedu\savvytasker\logic\parser\RedoCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.RedoCommand; + +public class RedoCommandParser implements CommandParser { + private static final String HEADER = "redo"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public RedoCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new RedoCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\StorageCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.StorageCommand; + +public class StorageCommandParser implements CommandParser { + private static final String HEADER = "storage"; + private static final String READABLE_FORMAT = HEADER+" NEW_PATH"; + + private static final String REGEX_REF_PATH = "Path"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_PATH+">.*)", + Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public StorageCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String path = matcher.group(REGEX_REF_PATH).trim(); + + if (!path.isEmpty()) { + return new StorageCommand(path); + } + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\TaskFieldParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +/** + * This class contains common parsing methods for parsing Task fields. + * Each of the parse method takes in a string which can be null, and return + * the respective parsed object. If null is provided to each of the parse methods, + * null will be returned. + */ +public abstract class TaskFieldParser implements CommandParser { + /* + * Provides standard regex ref names for various task fields that might be used by subclasses. + */ + protected static final String REGEX_REF_TASK_NAME = "TaskName"; + protected static final String REGEX_REF_START_DATE = "StartDate"; + protected static final String REGEX_REF_END_DATE = "EndDate"; + protected static final String REGEX_REF_LOCATION = "Location"; + protected static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; + protected static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; + protected static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; + protected static final String REGEX_REF_CATEGORY = "Category"; + protected static final String REGEX_REF_DESCRIPTION = "Description"; + + protected final DateParser dateParser; + + protected TaskFieldParser() { + this.dateParser = new DateParser(); + } + + protected String parseTaskName(String taskNameText) throws ParseException { + if (taskNameText == null) { + return null; + } + return taskNameText.trim(); + } + + protected InferredDate parseStartDate(String dateText) throws ParseException { + return parseDate(dateText, "START_DATE: "); + } + + protected InferredDate parseEndDate(String dateText) throws ParseException { + return parseDate(dateText, "END_DATE: "); + } + + private InferredDate parseDate(String dateText, String errorField) throws ParseException { + if (dateText == null) { + return null; + } + String trimmedDateText = dateText.trim(); + try { + return dateParser.parseSingle(trimmedDateText); + } catch (ParseException ex) { + throw new ParseException(trimmedDateText, errorField + ex.getFailureDetails()); + } + } + + protected String parseLocation(String locationText) throws ParseException { + if (locationText == null) { + return null; + } + return locationText.trim(); + } + + protected PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseException { + if (priorityLevelText == null) { + return null; + } + + String trimmedPriorityLevelText = priorityLevelText.trim(); + try { + return PriorityLevel.valueOfIgnoreCase(trimmedPriorityLevelText); + } catch (IllegalArgumentException ex) { + throw new ParseException(trimmedPriorityLevelText, "PRIORITY_LEVEL: Unknown type '" + priorityLevelText + "'"); + } + } + + protected RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws ParseException { + if (recurrenceTypeText == null) { + return null; + } + + String trimmedRecurrenceTypeText = recurrenceTypeText.trim(); + try { + return RecurrenceType.valueOfIgnoreCase(trimmedRecurrenceTypeText); + } catch (IllegalArgumentException ex) { + throw new ParseException(trimmedRecurrenceTypeText, "RECURRING_TYPE: Unknown type '" + recurrenceTypeText + "'"); + } + } + + protected Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseException { + if (numRecurrenceText == null) { + return null; + } + + String trimmedNumRecurrenceText = numRecurrenceText.trim(); + int numRecurrence = 0; + boolean parseError = false; + + try { + numRecurrence = Integer.parseInt(trimmedNumRecurrenceText); + if (numRecurrence < 0) { + parseError = true; + } + } catch (NumberFormatException ex) { + parseError = true; + } + + if (parseError) { + throw new ParseException(trimmedNumRecurrenceText, "NUMBER_OF_RECURRENCE: Must be a nonnegative whole number!"); + } + + return numRecurrence; + } + + protected String parseCategory(String categoryText) throws ParseException { + if (categoryText == null) { + return null; + } + return categoryText.trim(); + } + + protected String parseDescription(String descriptionText) throws ParseException { + if (descriptionText == null) { + return null; + } + return descriptionText.trim(); + } +``` +###### \java\seedu\savvytasker\logic\parser\UnaliasCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.UnaliasCommand; + +public class UnaliasCommandParser implements CommandParser { + private static final String HEADER = "unalias"; + private static final String READABLE_FORMAT = HEADER+" KEYWORD"; + + private static final String REGEX_REF_KEYWORD = "Keyword"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_KEYWORD+">[^/]+)", + Pattern.CASE_INSENSITIVE + ); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public boolean shouldPreprocess() { + return false; + } + + @Override + public UnaliasCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String keyword = parseKeyword(matcher.group(REGEX_REF_KEYWORD)); + return new UnaliasCommand(keyword); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private String parseKeyword(String keywordText) throws ParseException { + String trimmedKeywordText = keywordText.trim(); + if (trimmedKeywordText.isEmpty()) { + throw new ParseException(trimmedKeywordText, "KEYWORD: Cannot be empty."); + } + return trimmedKeywordText; + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\UndoCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.UndoCommand; + +public class UndoCommandParser implements CommandParser { + private static final String HEADER = "undo"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public UndoCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new UndoCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} +``` +###### \java\seedu\savvytasker\logic\parser\UnmarkCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.UnmarkCommand; + +public class UnmarkCommandParser implements CommandParser { + private static final String HEADER = "unmark"; + private static final String READABLE_FORMAT = HEADER+" INDEX [MORE_INDEX]"; + + private static final String REGEX_REF_INDICES = "Indices"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDICES+">[^/]+)", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public UnmarkCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + int[] indices = parseIndices(matcher.group(REGEX_REF_INDICES)); + + // TODO: Integrate UnmarkCommand properly! + return new UnmarkCommand(indices); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int[] parseIndices(String indicesText) throws ParseException { + try { + return INDEX_PARSER.parseMultiple(indicesText); + } catch (ParseException ex) { + throw new ParseException(indicesText, "INDEX [MORE_INDEX]: " + ex.getFailureDetails()); + } + } + +} +``` +###### \java\seedu\savvytasker\model\alias\AliasSymbol.java +``` java +package seedu.savvytasker.model.alias; + +import java.util.Objects; + +/** + * A class that represents a keyword-representation pair. This pair can be used + * by the parser for substituting keywords found in a commandText with its + * representation. Instances of this class are immutable. + */ +public class AliasSymbol { + private final String keyword; + private final String representation; + + /** + * Creates a Symbol object with the specified keyword and its representation. + * The keyword must be a single word without spaces while the representation + * can be any non-empty string. Both parameters cannot be null. + * + * @param keyword + * @param representation + */ + public AliasSymbol(String keyword, String representation) { + assert keyword != null && !keyword.matches(".*\\s+.*"); + assert representation != null && !representation.isEmpty(); + + this.keyword = keyword.toLowerCase(); + this.representation = representation; + } + + public String getKeyword() { + return this.keyword; + } + + public String getRepresentation() { + return this.representation; + } + + @Override + public String toString() { + return "[Keyword: " + this.keyword + ", Representation: " + this.representation + "]"; + } + + @Override + public boolean equals(Object object) { + return this == object || (object instanceof AliasSymbol && + this.keyword.equals(((AliasSymbol)object).keyword) && + this.representation.equals(((AliasSymbol)object).representation)); + } + + @Override + public int hashCode() { + return Objects.hash(keyword, representation); + } +} +``` +###### \java\seedu\savvytasker\model\alias\AliasSymbolList.java +``` java +package seedu.savvytasker.model.alias; + +import java.util.Iterator; +import java.util.List; + +import edu.emory.mathcs.backport.java.util.Collections; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +public class AliasSymbolList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** Default constructor */ + public AliasSymbolList() {} + + /** + * Copy constructor + * @param src the list to copy from, cannot be null + */ + public AliasSymbolList(AliasSymbolList src) { + assert src != null; + internalList.addAll(src.internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Returns true if a symbol with the specified symbolKeyword exists in the list + * @param symbolKeyword the symbolKeyword to check for + * @return true if exists, false otherwise + */ + public boolean contains(String symbolKeyword) { + for (AliasSymbol symbol : internalList) { + if (symbol.getKeyword().equals(symbolKeyword)) + return true; + } + + return false; + } + + /** + * Returns true if the symbol exists in the list. Both keyword and representation must match. + * @param symbol the symol to check for + * @return true if exists, false otherwise + */ + public boolean contains(AliasSymbol symbol) { + return internalList.contains(symbol); + } + + /** + * Adds a symbol to the list. The symbol being added must not have a keyword that clashes with another + * symbol in the list. + * @param symbol the symbol to add, cannot be null + * @throws DuplicateSymbolKeywordException if a symbol with the same keyword already exists + */ + public void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException { + assert symbol != null; + if (contains(symbol.getKeyword())) + throw new DuplicateSymbolKeywordException(); + internalList.add(symbol); + } + + /** + * Removes a symbol from the list. + * @param symbol the symbol to remove, cannot be null + * @throws SymbolKeywordNotFoundException if no such symbol can be found. + */ + public void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException { + assert symbol != null; + if (!contains(symbol)) + throw new SymbolKeywordNotFoundException(); + internalList.remove(symbol); + } + + /** + * Replace an old symbol with a new symbol. The new symbol's keyword must equal to the old + * symbol's keyword. + * + * @param oldSymbol the old symbol to be replaced, cannot be null + * @param newSymbol the new symbol to used to replace, cannot be null + * @throws SymbolKeywordNotFoundException if an old symbol with the keyword is not found. + */ + public void replaceAliasSymbol(AliasSymbol oldSymbol, AliasSymbol newSymbol) throws SymbolKeywordNotFoundException { + assert oldSymbol != null && newSymbol != null; + assert oldSymbol.getKeyword().equals(newSymbol.getKeyword()); + + removeAliasSymbol(oldSymbol); + internalList.add(newSymbol); + } + + /** + * Clears this list and copy all elements from the other list to this. + * @param other the other list + */ + public void reset(AliasSymbolList other) { + assert other != null; + internalList.addAll(other.internalList); // AliasSymbol is immutable, no need for deep copy. + } + + /** + * Gets the size of this list. + * @return the size of this list + */ + public int size() { + return internalList.size(); + } + + public List asReadonly() { + return Collections.unmodifiableList(internalList); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AliasSymbolList // instanceof handles nulls + && this.internalList.equals( ((AliasSymbolList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\savvytasker\model\alias\DuplicateSymbolKeywordException.java +``` java +package seedu.savvytasker.model.alias; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; + +public class DuplicateSymbolKeywordException extends IllegalValueException { + private static final long serialVersionUID = -5516547118656055929L; + + public DuplicateSymbolKeywordException() { + super("Operation will result in two symbols with the same keyword defined."); + } +} +``` +###### \java\seedu\savvytasker\model\alias\SymbolKeywordNotFoundException.java +``` java +package seedu.savvytasker.model.alias; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; + +public class SymbolKeywordNotFoundException extends IllegalValueException { + private static final long serialVersionUID = -5516547118656055929L; + + public SymbolKeywordNotFoundException() { + super("Unable to find a symbol with the specified keyword."); + } +} +``` +###### \java\seedu\savvytasker\model\ListType.java +``` java +package seedu.savvytasker.model; + +/** + * This enum represents what and how to list tasks/symbols. + */ +public enum ListType { + /** + * List tasks by due date. + */ + DueDate, + + /** + * List tasks by priority level. + */ + PriorityLevel, + + /** + * List archived tasks. + */ + Archived, + + /** + * List aliases. + */ + Alias; + + /** + * Gets a ListType enum object from its enum name, ignoring cases. + * @param name the name of the ListType enum object + * @return the corresponding ListType enum object + */ + public static ListType valueOfIgnoreCase(String name) { + for (ListType type : ListType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown list type: " + name); + } +} +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + + private void indicateAliasSymbolAdded(AliasSymbol symbol) { + raise(new AliasSymbolChangedEvent(symbol, AliasSymbolChangedEvent.Action.Added)); + } + + private void indicateAliasSymbolRemoved(AliasSymbol symbol) { + raise(new AliasSymbolChangedEvent(symbol, AliasSymbolChangedEvent.Action.Removed)); + } +``` +###### \java\seedu\savvytasker\model\ModelManager.java +``` java + @Override + public synchronized void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException { + savvyTasker.addAliasSymbol(symbol); + indicateSavvyTaskerChanged(); + indicateAliasSymbolAdded(symbol); + } + + @Override + public synchronized void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException { + savvyTasker.removeAliasSymbol(symbol); + indicateSavvyTaskerChanged(); + indicateAliasSymbolRemoved(symbol); + } + + @Override + public int getAliasSymbolCount() { + return savvyTasker.getAliasSymbolCount(); + } +``` +###### \java\seedu\savvytasker\model\ReadOnlySavvyTasker.java +``` java + + /** + * Returns a defensively copied alias symbol list. + */ + + AliasSymbolList getAliasSymbolList(); +``` +###### \java\seedu\savvytasker\model\ReadOnlySavvyTasker.java +``` java + + /** + * Returns unmodifiable view of symbols list + */ + List getReadOnlyListOfAliasSymbols(); +``` +###### \java\seedu\savvytasker\model\SavvyTasker.java +``` java + + /** + * Adds an alias symbol to savvy tasker. + * @param symbol the symbol to add + * @throws DuplicateSymbolKeywordException if another symbol with the same keyword already exists + */ + public void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException { + symbols.addAliasSymbol(symbol); + } + + /** + * Removes an alias symbol from savvy tasker. + * @param symbol the symbol to remove + * @throws SymbolKeywordNotFoundException if there is no such symbol + */ + public void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException { + symbols.removeAliasSymbol(symbol); + } +``` +###### \java\seedu\savvytasker\model\SavvyTasker.java +``` java + @Override + public List getReadOnlyListOfAliasSymbols() { + return symbols.asReadonly(); + } + + @Override + public AliasSymbolList getAliasSymbolList() { + return new AliasSymbolList(symbols); + } +``` +###### \java\seedu\savvytasker\model\task\FindType.java +``` java +package seedu.savvytasker.model.task; + +/** + * This enum represents the type of matching used when trying to find tasks + * from keywords. + */ +public enum FindType { + /** + * Specifies partial matching of a single keyword. + * Searches by Task's name. + * E.g. 'ap' matches 'happy' + */ + Partial, + /** + * Specifies full matching of a single keyword. + * Searches by Task's name. + * E.g. 'ap' does not match 'happy', 'happy' matches 'very happy' + */ + Full, + /** + * Specifies full matching of all keywords in a set of keywords. + * Searches by Task's name. + * E.g. 'happy' does not match 'very happy', 'very happy' matches 'very happy' + */ + Exact, + /** + * Specifies partial matching of all keywords in a set of keywords. + * Searches by Task's category. + * E.g. 'happy' does not match 'very happy', 'very happy' matches 'very happy' + */ + Category; + + /** + * Gets a FindType enum object from its enum name, ignoring cases. + * @param name the name of the FindType enum object + * @return the corresponding FindType enum object + */ + public static FindType valueOfIgnoreCase(String name) { + for (FindType type : FindType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown find type: " + name); + } +} +``` +###### \java\seedu\savvytasker\model\task\PriorityLevel.java +``` java +package seedu.savvytasker.model.task; + +/** + * This enum represents the priority level of tasks. + */ +public enum PriorityLevel { + Low, + Medium, + High; + + /** + * Gets a PriorityLevel enum object from its enum name, ignoring cases. + * @param name the name of the PriorityLevel enum object + * @return the corresponding PriorityLevel enum object + */ + public static PriorityLevel valueOfIgnoreCase(String name) { + for (PriorityLevel type : PriorityLevel.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown priority level: " + name); + } +} +``` +###### \java\seedu\savvytasker\model\task\RecurrenceType.java +``` java +package seedu.savvytasker.model.task; + +/** + * This enum represents the type of recurrence of a recurring task. + */ +public enum RecurrenceType { + /** + * Specifies no recurrence i.e. the task is a one-time task. + */ + None, + /** + * Specifies daily recurrence. + */ + Daily, + /** + * Specifies weekly recurrence. + */ + Weekly, + /** + * Specifies monthly recurrence. + */ + Monthly, + /** + * Specifies yearly recurrence. + */ + Yearly; + + /** + * Gets a RecurrenceType enum object from its enum name, ignoring cases. + * @param name the name of the RecurrenceType enum object + * @return the corresponding RecurrenceType enum object + */ + public static RecurrenceType valueOfIgnoreCase(String name) { + for (RecurrenceType type : RecurrenceType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown recurrence type: " + name); + } +} +``` +###### \java\seedu\savvytasker\storage\XmlAdaptedAliasSymbol.java +``` java +package seedu.savvytasker.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.alias.AliasSymbol; + +public class XmlAdaptedAliasSymbol { + + @XmlElement(required = true) + private String keyword; + @XmlElement(required = true) + private String representation; + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedAliasSymbol() {} + + + /** + * Converts a given AliasSymbol into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedAliasSymbol + */ + public XmlAdaptedAliasSymbol(AliasSymbol source) { + keyword = source.getKeyword(); + representation = source.getRepresentation(); + } + + /** + * Converts this jaxb-friendly adapted alias symbol object into the model's AliasSymbol object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted AliasSymbol + */ + public AliasSymbol toModelType() throws IllegalValueException { + return new AliasSymbol(keyword, representation); + } +} +``` diff --git a/collated/test/A0097627N.md b/collated/test/A0097627N.md new file mode 100644 index 000000000000..72053ece4fe2 --- /dev/null +++ b/collated/test/A0097627N.md @@ -0,0 +1,300 @@ +# A0097627N +###### \java\guitests\RedoCommandTest.java +``` java +package guitests; + +import guitests.guihandles.TaskCardHandle; + +import org.junit.Test; + +import seedu.savvytasker.logic.commands.UndoCommand; +import seedu.savvytasker.logic.commands.RedoCommand; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; + +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.savvytasker.logic.commands.RedoCommand.MESSAGE_REDO_ACKNOWLEDGEMENT; + +public class RedoCommandTest extends SavvyTaskerGuiTest { + + TestTask[] expectedList = td.getTypicalTasks(); + TestTask[] currentList = td.getTypicalTasks(); + TestTask firstTaskToAdd = td.happy; + TestTask secondTaskToAdd = td.haloween; + TestTask pjmTaskToAdd = td.pjm; + TestTask projectMeetingTaskToAdd = td.projectMeeting; + + @Test + // redo one add command + public void redoAddTest() { + expectedList = TestUtil.addTasksToList(currentList, firstTaskToAdd); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + @Test + // redo a delete command + public void redoDeleteTest() { + expectedList = TestUtil.removeTaskFromList(currentList, 1); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + @Test + // redo clear command + public void redoClearTest() { + commandBox.runCommand("clear"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertListSize(0); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + @Test + // redo alias command + public void redoAliasTest() { + expectedList = td.getTypicalTasks(); + expectedList = TestUtil.addTasksToList(expectedList, projectMeetingTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + @Test + // redo unalias command + public void redoUnaliasTest() { + expectedList = TestUtil.addTasksToList(currentList, pjmTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("unalias pjm"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + // redo two add commands + @Test + public void redoTwoAddTest() { + expectedList = TestUtil.addTasksToList(currentList, firstTaskToAdd); + expectedList = TestUtil.addTasksToList(expectedList, secondTaskToAdd); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand(secondTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // redo two delete commands + @Test + public void redoTwoDeleteTest() { + expectedList = TestUtil.removeTaskFromList(currentList, 1); + expectedList = TestUtil.removeTaskFromList(expectedList, 1); + commandBox.runCommand("delete 1"); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // redo a delete command followed by an add command + @Test + public void redoDeleteAddTest() { + expectedList = TestUtil.addTasksToList(currentList, firstTaskToAdd); + expectedList = TestUtil.removeTaskFromList(expectedList, 1); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // redo an add command followed by a delete command + @Test + public void redoAddDeleteTest() { + expectedList = TestUtil.removeTaskFromList(currentList, 1); + expectedList = TestUtil.addTasksToList(expectedList, firstTaskToAdd); + commandBox.runCommand("delete 1"); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // invalid command + @Test + public void invalidTest() { + commandBox.runCommand("redos"); + assertResultMessage("Input: redos\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\guitests\UndoCommandTest.java +``` java +package guitests; + +import guitests.guihandles.TaskCardHandle; + +import org.junit.Test; + +import seedu.savvytasker.logic.commands.UndoCommand; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; + +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.savvytasker.logic.commands.UndoCommand.MESSAGE_UNDO_ACKNOWLEDGEMENT; + +public class UndoCommandTest extends SavvyTaskerGuiTest { + + TestTask[] expectedList = td.getTypicalTasks(); + TestTask[] currentList = td.getTypicalTasks(); + TestTask firstTaskToAdd = td.happy; + TestTask secondTaskToAdd = td.haloween; + TestTask pjmTaskToAdd = td.pjm; + TestTask projectMeetingTaskToAdd = td.projectMeeting; + + @Test + // undo one add command + public void undoAddTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + @Test + // undo a delete command + public void undoDeleteTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + @Test + // undo clear command + public void undoClearTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("clear"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + @Test + // undo alias command + public void undoAliasTest() { + expectedList = td.getTypicalTasks(); + expectedList = TestUtil.addTasksToList(expectedList, pjmTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("undo"); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + @Test + // undo unalias command + public void undoUnaliasTest() { + expectedList = TestUtil.addTasksToList(currentList, projectMeetingTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("unalias pjm"); + commandBox.runCommand("undo"); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + // undo mark command + @Test + public void undoMarkTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("mark 1"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo two add commands + @Test + public void undoTwoAddTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand(secondTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo two delete commands + @Test + public void undoTwoDeleteTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("delete 1"); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo a delete command followed by an add command + @Test + public void undoDeleteAddTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo an add command followed by a delete command + @Test + public void undoAddDeleteTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("delete 1"); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // invalid command + @Test + public void invalidTest() { + commandBox.runCommand("undos"); + assertResultMessage("Input: undos\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } +} +``` diff --git a/collated/test/A0138431L.md b/collated/test/A0138431L.md new file mode 100644 index 000000000000..2df548b81caf --- /dev/null +++ b/collated/test/A0138431L.md @@ -0,0 +1,36 @@ +# A0138431L +###### \java\guitests\StorageCommandTest.java +``` java +public class StorageCommandTest extends SavvyTaskerGuiTest { + + private static final String CONFIG_JSON = "config.json"; + private static final String CONFIG_LOCATION = "./src/test/data/SaveLocationCommandTest"; + + @Test + public void saveToValidFilePath_success() throws DataConversionException, IOException, DuplicateTaskException { + String testFilePath = "./src/test/data/StorageCommandTest/newStorageLocation/"; + commandBox.runCommand("storage " + testFilePath); + assertWriteToJsonSuccess(); + assertWriteToXmlSuccess(); + } + private void assertWriteToJsonSuccess() throws DataConversionException { + JsonConfigStorage jsonConfigStorage = new JsonConfigStorage(CONFIG_LOCATION); + Optional config = jsonConfigStorage.readConfig(CONFIG_JSON); + assert(config.isPresent()); + } + + private void assertWriteToXmlSuccess() { + TestTask[] currentList = td.getTypicalTasks(); + assertTrue(taskListPanel.isListMatching(currentList)); + } + + @Test + public void storage() { + //invalid command + commandBox.runCommand("store"); + assertResultMessage("Input: store\n" + + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + +} +``` diff --git a/collated/test/A0139915W.md b/collated/test/A0139915W.md new file mode 100644 index 000000000000..23287a4c2f9e --- /dev/null +++ b/collated/test/A0139915W.md @@ -0,0 +1,1230 @@ +# A0139915W +###### \java\guitests\AddCommandTest.java +``` java +public class AddCommandTest extends SavvyTaskerGuiTest { + + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + + @Test + public void add() { + //add one task + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToAdd = td.happy; + assertAddSuccess(taskToAdd, currentList); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + //add another task + taskToAdd = td.haloween; + assertAddSuccess(taskToAdd, currentList); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + //add to empty list + commandBox.runCommand("clear"); + assertAddSuccess(td.highPriority); + + //invalid command + commandBox.runCommand("adds Bad Command Task"); + assertResultMessage("Input: adds Bad Command Task\n" + + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + + //invalid start end date + Date start = getDate("31/12/2015"); + Date end = getDate("30/12/2015"); + commandBox.runCommand("add bad start-end pair s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(AddCommand.MESSAGE_INVALID_START_END)); + } + + private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { + commandBox.runCommand(taskToAdd.getAddCommand()); + + //confirm the new card contains the right data + TaskCardHandle addedCard = taskListPanel.navigateToTask(taskToAdd.getTaskName()); + assertMatching(taskToAdd, addedCard); + + //confirm the list now contains all previous tasks plus the new task + TestTask[] expectedList = TestUtil.addTasksToList(currentList, taskToAdd); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +``` +###### \java\guitests\DeleteCommandTest.java +``` java +public class DeleteCommandTest extends SavvyTaskerGuiTest { + + @Test + public void delete() { + + //delete the first in the list + TestTask[] currentList = td.getTypicalTasks(); + int targetIndex = 1; + assertDeleteSuccess(targetIndex, currentList); + + //delete the last in the list + currentList = TestUtil.removeTaskFromList(currentList, targetIndex); + targetIndex = currentList.length; + assertDeleteSuccess(targetIndex, currentList); + + //delete from the middle of the list + currentList = TestUtil.removeTaskFromList(currentList, targetIndex); + targetIndex = currentList.length/2; + assertDeleteSuccess(targetIndex, currentList); + + //invalid index + commandBox.runCommand("delete " + currentList.length + 1); + assertResultMessage("The task index provided is invalid"); + + } + + /** + * Runs the delete command to delete the person at specified index and confirms the result is correct. + * @param targetIndexOneIndexed e.g. to delete the first person in the list, 1 should be given as the target index. + * @param currentList A copy of the current list of persons (before deletion). + */ + private void assertDeleteSuccess(int targetIndexOneIndexed, final TestTask[] currentList) { + TestTask taskToDelete = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndexOneIndexed); + + commandBox.runCommand("delete " + targetIndexOneIndexed); + + //confirm the list now contains all previous persons except the deleted person + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + + //confirm the result message is correct + assertResultMessage(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); + } + +} +``` +###### \java\guitests\FindCommandTest.java +``` java +public class FindCommandTest extends SavvyTaskerGuiTest { + + @Test + public void find_nonEmptyList() { + assertFindResult("find Zoo"); //no results + assertFindResult("find Priority", td.highPriority, td.medPriority, td.lowPriority); //multiple results + + //find after deleting one result + commandBox.runCommand("delete 1"); + assertFindResult("find Priority", td.medPriority, td.lowPriority); + } + + @Test + public void find_nonEmptyList_byPartialMatch() { + // covered by find_nonEmptyList() + } + + @Test + public void find_nonEmptyList_byFullMatch() { + assertFindResult("find t/full Due", td.furthestDue, td.nearerDue, + td.notSoNearerDue, td.earliestDue, td.longDue); //multiple results + } + + @Test + public void find_nonEmptyList_byExactMatch() { + assertFindResult("find t/exact Nearer Due Task", td.nearerDue); // one matching result only + } + + @Test + public void find_nonEmptyList_byCategory() { + assertFindResult("find t/category priority", td.highPriority, td.medPriority, td.lowPriority); // matching 3 results + } + + @Test + public void find_emptyList(){ + commandBox.runCommand("clear"); + assertFindResult("find Shopping"); //no results + } + + @Test + public void find_invalidCommand_fail() { + commandBox.runCommand("findmyring"); + assertResultMessage("Input: findmyring\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + + private void assertFindResult(String command, TestTask... expectedHits) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + assertResultMessage(expectedHits.length + " tasks listed!"); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } +} +``` +###### \java\guitests\guihandles\TaskCardHandle.java +``` java +/** + * Provides a handle to a person card in the person list panel. + */ +public class TaskCardHandle extends GuiHandle { + private static final String TASKNAME_FIELD_ID = "#taskName"; + private static final String DETAILS_FIELD_ID = "#details"; + + private Node node; + + public TaskCardHandle(GuiRobot guiRobot, Stage primaryStage, Node node){ + super(guiRobot, primaryStage, null); + this.node = node; + } + + protected String getTextFromLabel(String fieldId) { + return getTextFromLabel(fieldId, node); + } + + public String getTaskName() { + return getTextFromLabel(TASKNAME_FIELD_ID); + } + + public String getDetails() { + return getTextFromLabel(DETAILS_FIELD_ID); + } + + public boolean isSameTask(ReadOnlyTask task) { + return getTaskName().equals(task.getTaskName()) && getDetails().equals(task.getTextForUi()); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof TaskCardHandle) { + TaskCardHandle handle = (TaskCardHandle) obj; + return getTaskName().equals(handle.getTaskName()) && + getDetails().equals(handle.getDetails()); + } + return super.equals(obj); + } + + @Override + public String toString() { + return getTaskName(); + } +} +``` +###### \java\guitests\guihandles\TaskListPanelHandle.java +``` java +/** + * Provides a handle for the panel containing the person list. + */ +public class TaskListPanelHandle extends GuiHandle { + + public static final int NOT_FOUND = -1; + public static final String CARD_PANE_ID = "#cardPane"; + + private static final String TASK_LIST_VIEW_ID = "#taskListView"; + + public TaskListPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public List getSelectedTasks() { + ListView taskList = getListView(); + return taskList.getSelectionModel().getSelectedItems(); + } + + @SuppressWarnings("unchecked") + public ListView getListView() { + // Should be a listview of readonlytask only. + return (ListView) getNode(TASK_LIST_VIEW_ID); + } + + /** + * Returns true if the list is showing the person details correctly and in correct order. + * @param persons A list of person in the correct order. + */ + public boolean isListMatching(ReadOnlyTask... tasks) { + return this.isListMatching(0, tasks); + } + + /** + * Clicks on the ListView. + */ + public void clickOnListView() { + Point2D point= TestUtil.getScreenMidPoint(getListView()); + guiRobot.clickOn(point.getX(), point.getY()); + } + + /** + * Returns true if the {@code persons} appear as the sub list (in that order) at position {@code startPosition}. + */ + public boolean containsInOrder(int startPosition, ReadOnlyTask... tasks) { + List tasksInList = getListView().getItems(); + + // Return false if the list in panel is too short to contain the given list + if (startPosition + tasks.length > tasksInList.size()){ + return false; + } + + // Return false if any of the persons doesn't match + for (int i = 0; i < tasks.length; i++) { + if (!tasksInList.get(startPosition + i).getTaskName().equals(tasks[i].getTaskName())){ + return false; + } + } + + return true; + } + + /** + * Returns true if the list is showing the person details correctly and in correct order. + * @param startPosition The starting position of the sub list. + * @param persons A list of person in the correct order. + */ + public boolean isListMatching(int startPosition, ReadOnlyTask... tasks) throws IllegalArgumentException { + if (tasks.length + startPosition != getListView().getItems().size()) { + throw new IllegalArgumentException("List size mismatched\n" + + "Expected " + (getListView().getItems().size()) + " tasks"); + } + assertTrue(this.containsInOrder(startPosition, tasks)); + for (int i = 0; i < tasks.length; i++) { + final int scrollTo = i + startPosition; + guiRobot.interact(() -> getListView().scrollTo(scrollTo)); + guiRobot.sleep(200); + if (!TestUtil.compareCardAndTask(getTaskCardHandle(startPosition + i), tasks[i])) { + return false; + } + } + return true; + } + + + public TaskCardHandle navigateToTask(String name) { + guiRobot.sleep(500); //Allow a bit of time for the list to be updated + final Optional task = getListView().getItems().stream().filter(p -> p.getTaskName().equals(name)).findAny(); + if (!task.isPresent()) { + throw new IllegalStateException("Name not found: " + name); + } + + return navigateToTask(task.get()); + } + + /** + * Navigates the listview to display and select the person. + */ + public TaskCardHandle navigateToTask(ReadOnlyTask task) { + int index = getTaskIndex(task); + + guiRobot.interact(() -> { + getListView().scrollTo(index); + guiRobot.sleep(150); + getListView().getSelectionModel().select(index); + }); + guiRobot.sleep(100); + return getTaskCardHandle(task); + } + + + /** + * Returns the position of the person given, {@code NOT_FOUND} if not found in the list. + */ + public int getTaskIndex(ReadOnlyTask targetTask) { + List tasksInList = getListView().getItems(); + for (int i = 0; i < tasksInList.size(); i++) { + if(tasksInList.get(i).getTaskName().equals(targetTask.getTaskName())){ + return i; + } + } + return NOT_FOUND; + } + + /** + * Gets a person from the list by index + */ + public ReadOnlyTask getTask(int index) { + return getListView().getItems().get(index); + } + + public TaskCardHandle getTaskCardHandle(int index) { + return getTaskCardHandle(new Task(getListView().getItems().get(index))); + } + + public TaskCardHandle getTaskCardHandle(ReadOnlyTask task) { + Set nodes = getAllCardNodes(); + Optional taskCardNode = nodes.stream() + .filter(n -> new TaskCardHandle(guiRobot, primaryStage, n).isSameTask(task)) + .findFirst(); + if (taskCardNode.isPresent()) { + return new TaskCardHandle(guiRobot, primaryStage, taskCardNode.get()); + } else { + return null; + } + } + + protected Set getAllCardNodes() { + return guiRobot.lookup(CARD_PANE_ID).queryAll(); + } + + public int getNumberOfTasks() { + return getListView().getItems().size(); + } +} +``` +###### \java\guitests\ListCommandTest.java +``` java +public class ListCommandTest extends SavvyTaskerGuiTest { + + @Test + public void list_nonEmptyList() { + assertListResult("list", td.earliestDue, td.nearerDue, td.notSoNearerDue, td.furthestDue, + td.highPriority, td.medPriority, td.lowPriority); //multiple results + + //list after deleting one result + commandBox.runCommand("delete 1"); + assertListResult("list", td.nearerDue, td.notSoNearerDue, td.furthestDue, + td.highPriority, td.medPriority, td.lowPriority); + } + + @Test + public void list_nonEmptyList_byInvalidSwitch() { + commandBox.runCommand("list badswitch"); + assertResultMessage("Input: list badswitch\nLIST_TYPE: Unknown type \'badswitch\'"); + } + + @Test + public void list_nonEmptyList_byDueDate() { + // covered by list_nonEmptyList() + } + + @Test + public void list_nonEmptyList_byPriority() { + assertListResult("list PriorityLevel", td.highPriority, td.medPriority, + td.furthestDue, td.nearerDue, td.notSoNearerDue, td.earliestDue, td.lowPriority); //multiple results + } + + @Test + public void list_nonEmptyList_byArchived() { + assertListResult("list Archived", td.longDue); // one matching result only + } + + @Test + public void list_emptyList(){ + commandBox.runCommand("clear"); + assertListResult("list"); //no results + } + + @Test + public void find_invalidCommand_fail() { + commandBox.runCommand("listmytasks"); + assertResultMessage("Input: listmytasks\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + + private void assertListResult(String command, TestTask... expectedHits ) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + assertResultMessage(expectedHits.length + " tasks listed!"); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } +} +``` +###### \java\guitests\ModifyCommandTest.java +``` java +public class ModifyCommandTest extends SavvyTaskerGuiTest { + + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + + @Test + public void add() { + //modify task + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToModify = currentList[0]; + Date start = getDate("30/12/2016"); + Date end = getDate("31/12/2016"); + taskToModify.setStartDateTime(start); + taskToModify.setEndDateTime(end); + assertModifySuccess("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end), taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + taskToModify.setStartDateTime(null); + taskToModify.setEndDateTime(null); + assertModifySuccess("modify 1 s/ e/", taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + //modify invalid index + commandBox.runCommand("modify " + currentList.length + "1" + " s/sat"); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX)); + + //modify with invalid end date + start = getDate("31/12/2016"); + end = getDate("30/12/2016"); + commandBox.runCommand("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_START_END)); + } + + private void assertModifySuccess(String command, TestTask taskToModify, TestTask... currentList) { + commandBox.runCommand(command); + + //confirm the new card contains the right data + TaskCardHandle modifiedCard = taskListPanel.navigateToTask(taskToModify.getTaskName()); + assertMatching(taskToModify, modifiedCard); + + //confirm the list now contains all previous persons plus the new person + TestTask[] expectedList = TestUtil.replaceTaskFromList(currentList, taskToModify); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + +} +``` +###### \java\seedu\savvytasker\commons\util\SmartDefaultDatesTest.java +``` java +public class SmartDefaultDatesTest { + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HHmmss"); + + @Test + public void smartDefaultDates_parseStart() { + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredEnd = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start date, assumed to on the given date at 12am + // and to end on the given date at 2359:59 + assertStartEndEquals( + getDate("31/12/2016 000000"), getDate("31/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start time, assumed to start today at the given time + // and to end today 2359:59 + assertStartEndEquals( + getDate(sdf.format(today) + " 150000"), getDate(sdf.format(today) + " 235959"), + sdd.getStartDate(), sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseEnd() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredStart = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date, assumed to start today at 12am + // and to end on the given date at 2359:59 + assertStartEndEquals( + getDate(sdf.format(today) + " 000000"), getDate("31/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end time, assumed to start today at 12am + // and to end at the given time today + assertStartEndEquals( + getDate(sdf.format(today) + " 000000"), getDate(sdf.format(today) + " 150000"), + sdd.getStartDate(), sdd.getEndDate()); + + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2000"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date in the past, start date will be null + // and to end on the given date at 2359:59 + assertStartEndEquals( + null, getDate("31/12/2000 235959"), + sdd.getStartDate(), sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseStartEnd() { + // START_TIME + // date not supplied -> today + // time not supplied -> 0000 + // END_TIME + // date not supplied -> today + // time not supplied -> 2359 + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end + // start defaults to 0000 + // end defaults to 2359:59 + assertStartEndEquals( + getDate("31/12/2016 000000"), getDate("31/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/30/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end, end date earlier than start + // start defaults to 0000 + // end defaults to 2359:59 + // no restrictions imposed on end time earlier than start time + assertStartEndEquals( + getDate("31/12/2016 000000"), getDate("30/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10am"); + inferredEnd = dateParser.parseSingle("10pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end + // start and end defaults to the given time today + assertStartEndEquals( + getDate(sdf.format(today) + " 100000"), getDate(sdf.format(today) + " 220000"), + sdd.getStartDate(), sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10pm"); + inferredEnd = dateParser.parseSingle("10am"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end, end time ends before start time + // start and end defaults to the given time today + // no restrictions imposed on end time being earlier + assertStartEndEquals( + getDate(sdf.format(today) + " 220000"), getDate(sdf.format(today) + " 100000"), + sdd.getStartDate(), sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_defaultParse() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + Date actualStart = sdd.getStart(dateParser.new InferredDate(new Date(), true, true)); + Date actualEnd = sdd.getEnd(dateParser.new InferredDate(new Date(), true, true)); + assertStartEndEquals(null, null, + actualStart, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("10pm")); + actualEnd = sdd.getEnd(dateParser.parseSingle("10am")); + } catch (ParseException e) { + assert false; //won't get here + } + assertStartEndEquals( + getDate(sdf.format(today) + " 220000"), getDate(sdf.format(today) + " 100000"), + actualStart, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("12/31/2016")); + actualEnd = sdd.getEnd(dateParser.parseSingle("12/31/2016")); + } catch (ParseException e) { + assert false; //won't get here + } + assertStartEndEquals(getDate("31/12/2016 000000"), getDate("31/12/2016 235959"), + actualStart, actualEnd); + } + + private void assertStartEndEquals(Date expectedStart, Date expectedEnd, + Date actualStart, Date actualEnd) { + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date today(int hours_24, int minute_60) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.set(Calendar.HOUR_OF_DAY, hours_24); + calendar.set(Calendar.MINUTE, minute_60); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } +} +``` +###### \java\seedu\savvytasker\model\task\TaskListTest.java +``` java +public class TaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void taskList_addDuplicate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(DuplicateTaskException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + tasks.add(t); // passes + assertEquals(1, tasks.getInternalList().size()); + tasks.add(t); // fails + } + + @Test + public void taskList_addInvalidDate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.add(t); // fails, end date earlier than start date + } + + @Test + public void taskList_removeNonExistent() throws TaskNotFoundException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.remove(t); // fails + } + + @Test + public void taskList_replaceNonExistent() throws TaskNotFoundException, InvalidDateException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.replace(t, t); // fails + } + + @Test + public void taskList_replaceInvalidDate() throws TaskNotFoundException, InvalidDateException, DuplicateTaskException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("30/12/2016")); + t.setEndDateTime(getDate("31/12/2016")); + tasks.add(t); + assertEquals(1, tasks.getInternalList().size()); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.replace(t, t); // fails, end date earlier than start date + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +``` +###### \java\seedu\savvytasker\testutil\SavvyTaskerBuilder.java +``` java +/** + * A utility class to help with building SavvyTasker objects. + * Example usage:
+ * {@code SavvyTasker st = new SavvyTaskerBuilder().withTask("Hello Task").build();} + */ +public class SavvyTaskerBuilder { + + private SavvyTasker savvyTasker; + + public SavvyTaskerBuilder(SavvyTasker savvyTasker){ + this.savvyTasker = savvyTasker; + } + + public SavvyTaskerBuilder withTask(Task task) throws DuplicateTaskException, InvalidDateException { + savvyTasker.addTask(task); + return this; + } + + public SavvyTasker build(){ + return savvyTasker; + } +} +``` +###### \java\seedu\savvytasker\testutil\TaskBuilder.java +``` java +/** + * Helper to build Task objects + */ +public class TaskBuilder { + + private TestTask task; + + public TaskBuilder() { + this.task = new TestTask(); + } + + public TaskBuilder withId(int id) { + this.task.setId(id); + return this; + } + + public TaskBuilder withTaskName(String taskName) throws IllegalValueException { + this.task.setTaskName(taskName); + return this; + } + + public TaskBuilder withStartDateTime(Date startDateTime) { + this.task.setStartDateTime(startDateTime); + return this; + } + + public TaskBuilder withEndDateTime(Date endDateTime) { + this.task.setEndDateTime(endDateTime); + return this; + } + + public TaskBuilder withLocation(String location) { + this.task.setLocation(location); + return this; + } + + public TaskBuilder withPriority(PriorityLevel priority) { + this.task.setPriority(priority); + return this; + } + + public TaskBuilder withRecurringType(RecurrenceType recurringType) { + this.task.setRecurringType(recurringType); + return this; + } + + public TaskBuilder withNumberOfRecurrence(int numberOfRecurrence) { + this.task.setNumberOfRecurrence(numberOfRecurrence); + return this; + } + + public TaskBuilder withCategory(String category) { + this.task.setCategory(category); + return this; + } + + public TaskBuilder withDescription(String description) { + this.task.setDescription(description); + return this; + } + + public TaskBuilder withArchived(boolean isArchived) { + this.task.setArchived(isArchived); + return this; + } + + public TestTask build() { + return this.task; + } + +} +``` +###### \java\seedu\savvytasker\testutil\TestTask.java +``` java +/** + * A mutable task object. For testing only. + */ +public class TestTask implements ReadOnlyTask { + + private int id; + private int groupId; + private String taskName; + private Date startDateTime; + private Date endDateTime; + private String location; + private PriorityLevel priority; + private RecurrenceType recurringType; + private int numberOfRecurrence; + private String category; + private String description; + private boolean isArchived; + + public TestTask() { + // sets initial default values + this.priority = PriorityLevel.Medium; + this.recurringType = RecurrenceType.None; + this.numberOfRecurrence = 0; + } + + @Override + public int getId() { + return id; + } + + @Override + public int getGroupId() { + return groupId; + } + + @Override + public String getTaskName() { + return taskName; + } + + @Override + public Date getStartDateTime() { + return startDateTime; + } + + @Override + public Date getEndDateTime() { + return endDateTime; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public PriorityLevel getPriority() { + return priority; + } + + @Override + public RecurrenceType getRecurringType() { + return recurringType; + } + + @Override + public int getNumberOfRecurrence() { + return numberOfRecurrence; + } + + @Override + public String getCategory() { + return category; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public boolean isMarked() { + return isArchived(); // all marked tasks are archived + } + + @Override + public boolean isArchived() { + return isArchived; + } + + public void setId(int id) { + this.id = id; + } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public void setStartDateTime(Date startDateTime) { + this.startDateTime = startDateTime; + } + + public void setEndDateTime(Date endDateTime) { + this.endDateTime = endDateTime; + } + + public void setLocation(String location) { + this.location = location; + } + + public void setPriority(PriorityLevel priority) { + this.priority = priority; + } + + public void setRecurringType(RecurrenceType recurringType) { + this.recurringType = recurringType; + } + + public void setNumberOfRecurrence(int numberOfRecurrence) { + this.numberOfRecurrence = numberOfRecurrence; + } + + public void setCategory(String category) { + this.category = category; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setArchived(boolean isArchived) { + this.isArchived = isArchived; + } + + @Override + public String toString() { + return getAsText(); + } + + public String getAddCommand() { + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HHmm"); + StringBuilder sb = new StringBuilder(); + sb.append("add " + this.getTaskName()); + if (startDateTime != null) { + sb.append(" s/ ").append(sdf.format(startDateTime)); + } + if (endDateTime != null) { + sb.append(" e/ ").append(sdf.format(endDateTime)); + } + if (location != null && !location.isEmpty()) { + sb.append(" l/ ").append(location); + } + if (priority != null && priority != PriorityLevel.Medium) { + // p/ defaults to medium, if set to medium, take as non-existent + sb.append(" p/ ").append(priority.toString()); + } + if (recurringType != null && recurringType != RecurrenceType.None) { + // r/ defaults to none, if set to none, take as non-existent + sb.append(" r/ ").append(recurringType.toString()); + } + if (category != null && !category.isEmpty()) { + sb.append(" c/ ").append(category); + } + if (description != null && !description.isEmpty()) { + sb.append(" d/ ").append(description); + } + return sb.toString(); + } +} +``` +###### \java\seedu\savvytasker\testutil\TestUtil.java +``` java + + private static Task[] getSampleTaskData() { + return new Task[]{ + new Task("Sample Task Leh"), + new Task("Sample Task La"), + new Task("Sample Task Lo"), + new Task("Sample Task Ah") + }; + } +``` +###### \java\seedu\savvytasker\testutil\TestUtil.java +``` java + /** + * Removes a subset from the list of tasks. + * @param tasks The list of tasks + * @param tasksToRemove The subset of tasjs. + * @return The modified tasks after removal of the subset from tasks. + */ + public static TestTask[] removeTasksFromList(final TestTask[] tasks, TestTask... tasksToRemove) { + List listOfTasks = asList(tasks); + listOfTasks.removeAll(asList(tasksToRemove)); + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); + } + + + /** + * Returns a copy of the list with the task at specified index removed. + * @param list original list to copy from + * @param targetIndexInOneIndexedFormat e.g. if the first element to be removed, 1 should be given as index. + */ + public static TestTask[] removeTaskFromList(final TestTask[] list, int targetIndexInOneIndexedFormat) { + return removeTasksFromList(list, list[targetIndexInOneIndexedFormat-1]); + } + + /** + * Replaces tasks[i] with a task. + * @param tasks The array of tasks. + * @param task The replacement task + * @param index The index of the task to be replaced. + * @return + */ + public static TestTask[] replaceTaskFromList(TestTask[] tasks, TestTask task) { + for (int i = 0; i < tasks.length; ++i) { + if (tasks[i].getId() == task.getId()) { + tasks[i] = task; + break; + } + } + return tasks; + } + + /** + * Appends tasks to the array of tasks. + * @param tasks A array of tasks. + * @param tasksToAdd The tasks that are to be appended behind the original array. + * @return The modified array of tasks. + */ + public static TestTask[] addTasksToList(final TestTask[] tasks, TestTask... tasksToAdd) { + List listOfTasks = asList(tasks); + listOfTasks.addAll(asList(tasksToAdd)); + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); + } +``` +###### \java\seedu\savvytasker\testutil\TestUtil.java +``` java + public static boolean compareCardAndTask(TaskCardHandle card, ReadOnlyTask task) { + return card.isSameTask(task); + } +``` +###### \java\seedu\savvytasker\testutil\TypicalTestTasks.java +``` java +/** + * Test tasks used to test cases + */ +public class TypicalTestTasks { + + public TestTask highPriority, medPriority, lowPriority, furthestDue, + nearerDue, notSoNearerDue, earliestDue, longDue, happy, haloween, pjm, projectMeeting; + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + + public TypicalTestTasks() { + try { + highPriority = new TaskBuilder().withId(1).withTaskName("High Priority Task") + .withPriority(PriorityLevel.High).withCategory("priority").build(); + medPriority = new TaskBuilder().withId(2).withTaskName("Medium Priority Task") + .withPriority(PriorityLevel.Medium).withCategory("priority").build(); + lowPriority = new TaskBuilder().withId(3).withTaskName("Low Priority Task") + .withPriority(PriorityLevel.Low).withCategory("priority").build(); + furthestDue = new TaskBuilder().withId(4).withTaskName("Furthest Due Task") + .withEndDateTime(getDate("01/12/2016")).build(); + nearerDue = new TaskBuilder().withId(5).withTaskName("Nearer Due Task") + .withEndDateTime(getDate("01/11/2016")).build(); + notSoNearerDue = new TaskBuilder().withId(6).withTaskName("Not So Nearer Due Task") + .withEndDateTime(getDate("02/11/2016")).build(); + earliestDue = new TaskBuilder().withId(7).withTaskName("Earliest Due Task") + .withEndDateTime(getDate("01/10/2016")).build(); + longDue = new TaskBuilder().withId(8).withTaskName("Long Due Task") + .withEndDateTime(getDate("01/1/2016")).withArchived(true).build(); + + //Manually added + happy = new TaskBuilder().withId(9).withTaskName("Happy Task").build(); + haloween = new TaskBuilder().withId(10).withTaskName("Haloween Task").build(); + pjm = new TaskBuilder().withId(11).withTaskName("pjm").build(); + projectMeeting = new TaskBuilder().withId(12).withTaskName("Project Meeting").build(); + } catch (IllegalValueException e) { + e.printStackTrace(); + assert false : "not possible"; + } + } + + public static void loadSavvyTaskerWithSampleData(SavvyTasker st) { + TypicalTestTasks td = new TypicalTestTasks(); + try { + st.addTask(new Task(td.highPriority)); + st.addTask(new Task(td.medPriority)); + st.addTask(new Task(td.lowPriority)); + st.addTask(new Task(td.furthestDue)); + st.addTask(new Task(td.nearerDue)); + st.addTask(new Task(td.notSoNearerDue)); + st.addTask(new Task(td.earliestDue)); + st.addTask(new Task(td.longDue)); + } catch (InvalidDateException e) { + assert false : "not possible"; + } + } + + public TestTask[] getTypicalTasks() { + return new TestTask[]{ highPriority, medPriority, lowPriority, + furthestDue, nearerDue, notSoNearerDue, earliestDue }; + } + + public SavvyTasker getTypicalSavvyTasker(){ + SavvyTasker st = new SavvyTasker(); + loadSavvyTaskerWithSampleData(st); + return st; + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +``` diff --git a/collated/test/A0139916U.md b/collated/test/A0139916U.md new file mode 100644 index 000000000000..8870b3a9d0e2 --- /dev/null +++ b/collated/test/A0139916U.md @@ -0,0 +1,521 @@ +# A0139916U +###### \java\seedu\savvytasker\logic\parser\ParserTest.java +``` java +package seedu.savvytasker.logic.parser; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.savvytasker.logic.commands.AddCommand; +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.commands.IncorrectCommand; +import seedu.savvytasker.model.alias.AliasSymbol; + +public class ParserTest { + private AddCommandParser addParser; + private DeleteCommandParser deleteParser; + private ModifyCommandParser modifyParser; + private ClearCommandParser clearParser; + private ListCommandParser listParser; + private FindCommandParser findParser; + private HelpCommandParser helpParser; + private ExitCommandParser exitParser; + private MarkCommandParser markParser; + private UnmarkCommandParser unmarkParser; + private UndoCommandParser undoParser; + private RedoCommandParser redoParser; + private AliasCommandParser aliasParser; + private UnaliasCommandParser unaliasParser; + private StorageCommandParser storageParser; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setup() { + addParser = new AddCommandParser(); + deleteParser = new DeleteCommandParser(); + modifyParser = new ModifyCommandParser(); + clearParser = new ClearCommandParser(); + listParser = new ListCommandParser(); + findParser = new FindCommandParser(); + helpParser = new HelpCommandParser(); + exitParser = new ExitCommandParser(); + markParser = new MarkCommandParser(); + unmarkParser = new UnmarkCommandParser(); + undoParser = new UndoCommandParser(); + redoParser = new RedoCommandParser(); + aliasParser = new AliasCommandParser(); + unaliasParser = new UnaliasCommandParser(); + storageParser = new StorageCommandParser(); + } + + @Test + public void parseAdd_reorder() throws ParseException { + assertNotNull(addParser.parse("add task l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); + } + + @Test + public void parseAdd_multipleSpaces() throws ParseException { + assertNotNull(addParser.parse("add Multiple Spaces s/ 2pm")); + } + + @Test + public void parseAdd_sameOptionMultipleTimes_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task s/ tomorrow 3pm s/ tomorrow 10pm"); + } + + @Test + public void parseAdd_missingTaskName_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add s/ tomorrow 3pm"); + } + + @Test + public void parseAdd_arbitrarySlash_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task s/ tomorrow 2pm/3pm e/ sunday"); + } + + @Test + public void parseAdd_fullValid() throws ParseException { + assertNotNull(addParser.parse("add task s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); + } + + @Test + public void parseAdd_invalidRecurrenceType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task r/ Error "); + } + + @Test + public void parseAdd_invalidPriorityLevel_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task p/ Error "); + } + + //================================================================================== + + @Test + public void parseDelete_noIndexSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + deleteParser.parse("delete"); + } + + @Test + public void parseDelete_oneIndex() throws ParseException { + assertNotNull(deleteParser.parse("delete 1")); + } + + @Test + public void parseDelete_multipleIndices() throws ParseException { + assertNotNull(deleteParser.parse("delete 1 2 3")); + } + + @Test + public void parseDelete_multipleSpacesIndices() throws ParseException { + assertNotNull(deleteParser.parse("delete 1 2 3")); + } + + @Test + public void parseDelete_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + deleteParser.parse("delete -1"); + } + + @Test + public void parseDelete_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + deleteParser.parse("delete 0"); + } + + //================================================================================== + + @Test + public void parseModify_noIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify t/ newtask"); + } + + @Test + public void parseModify_multipleIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify 1 2 3 t/ newtask"); + } + + @Test + public void parseModify_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify -1 t/ newtask"); + } + + @Test + public void parseModify_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify 0 t/ newtask"); + } + + @Test + public void parseModify_onlySpecifyIndex() throws ParseException { + assertNotNull(modifyParser.parse("modify 1")); + } + + @Test + public void parseModify_fullValid() throws ParseException { + assertNotNull(modifyParser.parse("modify 3 t/ newtask s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); + } + + @Test + public void parseModify_reorder() throws ParseException { + assertNotNull(modifyParser.parse("modify 1 l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); + } + + @Test + public void parseModify_multipleSpaces() throws ParseException { + assertNotNull(modifyParser.parse("modify 1 t/ Multiple Spaces s/ 2pm")); + } + + //================================================================================== + + @Test + public void parseClear_spaces() throws ParseException { + assertNotNull(clearParser.parse("clear ")); + } + + @Test + public void parseClear_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + clearParser.parse("clear 1"); + } + + @Test + public void parseClear_valid() throws ParseException { + assertNotNull(clearParser.parse("clear")); + } + + //================================================================================== + + @Test + public void parseList_noParameters() throws ParseException { + assertNotNull(listParser.parse("list")); + } + + @Test + public void parseList_noParametersSpaces() throws ParseException { + assertNotNull(listParser.parse("list ")); + } + + @Test + public void parse_list_valid() throws ParseException { + assertNotNull(listParser.parse("list Priority Level ")); + } + + @Test + public void parseList_invalidType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + listParser.parse("list Error "); + } + + //================================================================================== + + @Test + public void parseFind_noKeywords_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find"); + } + + @Test + public void parseFind_noKeywordsSpaces_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find "); + } + + @Test + public void parseFind_noKeywordsButWithType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find t/ Exact "); + } + + @Test + public void parseFind_invalidType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find t/ Error some words"); + } + + @Test + public void parseFind_validAfter() throws ParseException { + assertNotNull(findParser.parse("find t/ Exact this word ")); + } + + @Test + public void parseFind_validBefore() throws ParseException { + assertNotNull(findParser.parse("find some words t/ Partial ")); + } + + @Test + public void parseFind_validBeforeAndAfter() throws ParseException { + assertNotNull(findParser.parse("find some words t/ Full some words after ")); + } + + //================================================================================== + + @Test + public void parseHelp_spaces() throws ParseException { + assertNotNull(helpParser.parse("help ")); + } + + @Test + public void parseHelp_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("help 1"); + } + + @Test + public void parseHelp_valid() throws ParseException { + assertNotNull(helpParser.parse("help")); + } + + //================================================================================== + + @Test + public void parseExit_spaces() throws ParseException { + assertNotNull(exitParser.parse("exit ")); + } + + @Test + public void parseExit_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("exit 1"); + } + + @Test + public void parseExit_valid() throws ParseException { + assertNotNull(exitParser.parse("exit")); + } + + //================================================================================== + + @Test + public void parseMark_noIndexSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + markParser.parse("mark"); + } + + @Test + public void parseMark_oneIndex() throws ParseException { + assertNotNull(markParser.parse("mark 1")); + } + + @Test + public void parseMark_multipleIndices() throws ParseException { + assertNotNull(markParser.parse("mark 1 2 3")); + } + + @Test + public void parseMark_multipleSpacesIndices() throws ParseException { + assertNotNull(markParser.parse("mark 1 2 3")); + } + + @Test + public void parseMark_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + markParser.parse("mark -1"); + } + + @Test + public void parseMark_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + markParser.parse("mark 0"); + } + + //================================================================================== + + @Test + public void parseUnmark_noIndexSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unmarkParser.parse("unmark"); + } + + @Test + public void parseUnmark_oneIndex() throws ParseException { + assertNotNull(unmarkParser.parse("unmark 1")); + } + + @Test + public void parseUnmark_multipleIndices() throws ParseException { + assertNotNull(unmarkParser.parse("unmark 1 2 3")); + } + + @Test + public void parseUnmark_multipleSpacesIndices() throws ParseException { + assertNotNull(unmarkParser.parse("unmark 1 2 3")); + } + + @Test + public void parseUnmark_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unmarkParser.parse("unmark -1"); + } + + @Test + public void parseUnmark_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unmarkParser.parse("unmark 0"); + } + + //================================================================================== + + @Test + public void parseUndo_spaces() throws ParseException { + assertNotNull(undoParser.parse("undo ")); + } + + @Test + public void parseUndo_invalid() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("undo 1"); + } + + @Test + public void parseUndo_valid() throws ParseException { + assertNotNull(undoParser.parse("undo")); + } + + //================================================================================== + + @Test + public void parseRedo_spaces() throws ParseException { + assertNotNull(redoParser.parse("redo ")); + } + + @Test + public void parseRedo_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("redo 1"); + } + + @Test + public void parseRedo_valid() throws ParseException { + assertNotNull(redoParser.parse("redo")); + } + + //================================================================================== + + @Test + public void parseAlias_keywordUnspecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias r/ a string of things"); + } + + @Test + public void parseAlias_textUnspecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ xyz"); + } + + @Test + public void parseAlias_noSwitchesSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias power overwhelming"); + } + + @Test + public void parseAlias_keywordNotSingleWord_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ not a single word r/ project management"); + } + + @Test + public void parseAlias_keywordEmpty_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ r/ project management"); + } + + @Test + public void parseAlias_textEmpty_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ pjm r/ "); + } + + @Test + public void parseAlias_fullValid() throws ParseException { + assertNotNull(aliasParser.parse("alias k/ pjm r/ project management ")); + } + + //================================================================================== + + @Test + public void parseUnalias_emptyKeyword_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unaliasParser.parse("unalias "); + } + + @Test + public void parseUnalias_valid() throws ParseException { + assertNotNull(unaliasParser.parse("unalias something ")); + } + + //================================================================================== + + @Test + public void parseStorage_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + storageParser.parse("storage "); + } + + @Test + public void parseStorage_valid() throws ParseException { + assertNotNull(storageParser.parse("storage C:/Users/Brown/Desktop/file.xml ")); + } + + //================================================================================== + @Test + public void masterParser_subparserParsing_returnParsedCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + assertTrue(parser.parse(" add A New Task s/ tomorrow e/ the day after tomorrow, l/ SR10 ") instanceof AddCommand); + } + + @Test + public void masterParser_subparserRemoved_returnIncorrectCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + parser.unregisterCommandParser("add"); + assertTrue(parser.parse(" add A New Task s/ tomorrow e/ the day after tomorrow, l/ SR10 ") instanceof IncorrectCommand); + } + + @Test + public void masterParser_alias_returnParsedCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); + parser.addAliasSymbol(new AliasSymbol("pqr", "s/ tomorrow e/ 30 september 3pm")); + assertTrue(parser.parse("xyz pqr") instanceof AddCommand); + } + + @Test + public void masterParser_invalidAlias_returnFalse() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + assertFalse(parser.addAliasSymbol(new AliasSymbol("add", "add A New Task"))); + } + + @Test + public void masterParser_removedAlias_returnIncorrectCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); + parser.removeAliasSymbol("xyz"); + assertTrue(parser.parse("xyz pqr") instanceof IncorrectCommand); + } +} +``` diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 33df65bea583..b4a3d6ee4b84 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,52 +1,81 @@ # About Us -We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). - ## Project Team -#### [Damith C. Rajapakse](http://www.comp.nus.edu.sg/~damithch)
-
-**Role**: Project Advisor +#### [Akshay Narayan](https://github.com/okkhoy) +
+**Role**: Project Mentor
----- -#### [Joshua Lee](http://github.com/lejolly) -
-Role: Developer
-Responsibilities: UI +#### [Nishant Budhdev](https://github.com/nishantbudhdev) +
+**Role**: Project Mentor
----- -#### [Leow Yijin](http://github.com/yijinl) -
-Role: Developer
-Responsibilities: Data - +#### [Chen Rui Wen](http://github.com/ruiwen905) +
+**Role**: Developer
+Responsibilities: Team Lead
+Component SME: [UI](DeveloperGuide.md#UI-component) +* Aspects/tools in charge of: Documentation, Code Quality +* Features implemented: + * [Storage](UserGuide.md#change-storage-location--storage) + * [UP](UserGuide.md#command-stack-history) + * [DOWN](UserGuide.md#command-stack-history) + * [LEFT](UserGuide.md#week-selection) + * [RIGHT](UserGuide.md#week-selection) +* Code written: [[functional code](../collated/main/A0138431L.md)][[test code](../collated/test/A0138431L.md)][[docs](../collated/docs/A0138431L.md)] +* Other major contributions: + * Design of UI which includes MainWindow.fxml, DailyList.fxml, FloatingList.fxml and UpcomingList.fxml + ----- -#### [Martin Choo](http://github.com/m133225) -
-Role: Developer
-Responsibilities: Dev Ops +#### [Low Zheng Heng Henry](http://github.com/e0003801) +
+**Role**: Developer
+* Components in charge of: [Logic](DeveloperGuide.md#logic-component), [Parser](DeveloperGuide.md#parser-component) +* Aspects/tools in charge of: Documentation, Code Quality +* Features implemented: + * [Alias](UserGuide.md##alias-a-keyword--alias) + * [Unalias](UserGuide.md#unalias-a-keyword--unalias) +* Code written: [[functional code](../collated/main/A0139916U.md)][[test code](../collated/test/A0139916U.md)][[docs](../collated/docs/A0139916U.md)] +* Other major contributions: + * Initial gradle natty dependency configuration [[8360c0d](https://github.com/CS2103AUG2016-T14-C2/main/commit/8360c0d66fc55f36bed0d0e9d4c6b63186aeb590)] + * Set up initial interface for undo/redo in Command classes and implement Logic-level undo/redo [[4dfafa0](https://github.com/CS2103AUG2016-T14-C2/main/commit/4dfafa07337f731959bcd7e267e0de9a093ae285)] + * Almost all parsing related code for all commands [[Parser](../src/main/java/seedu/savvytasker/logic/parser)] ----- -#### [Thien Nguyen](https://github.com/ndt93) - Role: Developer
- Responsibilities: Threading - - ----- +#### [Ng Qing Hua](http://github.com/qhng)
+
+**Role**: Developer
+* Components in charge of: [Storage](DeveloperGuide.md#storage-component), [Model](DeveloperGuide.md#model-component) +* Aspects/tools in charge of: Testing, Deliverables and Deadlines +* Features implemented: + * [Add task](UserGuide.md#adding-a-task-add) + * [List task](UserGuide.md#listing-all-tasks-list) + * [Find task](UserGuide.md#finding-all-task-containing-any-keyword-in-its-name-find) + * [Delete task](UserGuide.md#deleting-a-task--delete) + * [Modify task](UserGuide.md#modifies-a-task--modify) +* Code written: [[functional code](../collated/main/A0139915W.md)][[test code](../collated/test/A0139915W.md)][[docs](../collated/docs/A0139915W.md)] +* Other major contributions: + * Did the initial refactoring from AddressBook to SavvyTasker [[#34](https://github.com/CS2103AUG2016-T14-C2/main/pull/34) ] + * Set up Travis and Coveralls -#### [You Liang](http://github.com/yl-coder) -
- Role: Developer
- Responsibilities: UI +----- + +#### [Teh Ee Thai](http://github.com/tet54)
+
+**Role**: Developer
+* Components in charge of: - +* Aspects/tools in charge of: Integration +* Features implemented: + * [Mark task](UserGuide.md#mark-a-task-as-done--mark) + * [Unmark task](UserGuide.md#unmark-a-task-as-done--unmark) + * [Undo task](UserGuide.md#undo-the-most-recent-operation--undo) + * [Redo task](UserGuide.md#redo-the-most-recent-undo-operation--redo) +* Code written: [[functional code](../collated/main/A0097627N.md)][[test code](../collated/test/A0097627N.md)][[docs](../collated/docs/A0097627N.md)] ----- - -# Contributors - -We welcome contributions. See [Contact Us](ContactUs.md) page for more info. - -* [Akshay Narayan](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Aokkhoy) -* [Sam Yong](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Amauris) \ No newline at end of file diff --git a/docs/ContactUs.md b/docs/ContactUs.md index 866d0de3fddc..7b3c43bd5ea6 100644 --- a/docs/ContactUs.md +++ b/docs/ContactUs.md @@ -1,8 +1,8 @@ # Contact Us -* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/se-edu/addressbook-level4/issues) +* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/CS2103AUG2016-T14-C2/main/issues) if you noticed bugs or have suggestions on how to improve. * **Contributing** : We welcome pull requests. Follow the process described [here](https://github.com/oss-generic/process) -* **Email us** : You can also reach us at `damith [at] comp.nus.edu.sg` \ No newline at end of file +* **Email us** : You can also reach us at `qhng [at] u.nus.edu` diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 690b6d386627..455eabffc885 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,15 +1,15 @@ -# Developer Guide +# Developer Guide * [Setting Up](#setting-up) * [Design](#design) * [Implementation](#implementation) * [Testing](#testing) * [Dev Ops](#dev-ops) -* [Appendix A: User Stories](#appendix-a--user-stories) -* [Appendix B: Use Cases](#appendix-b--use-cases) -* [Appendix C: Non Functional Requirements](#appendix-c--non-functional-requirements) -* [Appendix D: Glossary](#appendix-d--glossary) -* [Appendix E : Product Survey](#appendix-e-product-survey) +* [Appendix A : User Stories](#appendix-a--user-stories) +* [Appendix B : Use Cases](#appendix-b--use-cases) +* [Appendix C : Non Functional Requirements](#appendix-c--non-functional-requirements) +* [Appendix D : Glossary](#appendix-d--glossary) +* [Appendix E : Product Survey](#appendix-e--product-survey) ## Setting up @@ -51,7 +51,7 @@ **Problem: Eclipse reports some required libraries missing** * Reason: Required libraries may not have been downloaded during the project import. -* Solution: [Run tests using Gardle](UsingGradle.md) once (to refresh the libraries). +* Solution: [Run tests using Gradle](UsingGradle.md) once (to refresh the libraries). ## Design @@ -62,7 +62,7 @@ The **_Architecture Diagram_** given above explains the high-level design of the App. Given below is a quick overview of each component. -`Main` has only one class called [`MainApp`](../src/main/java/seedu/address/MainApp.java). It is responsible for, +`Main` has only one class called [`MainApp`](../src/main/java/seedu/savvytasker/MainApp.java). It is responsible for, * At app launch: Initializes the components in the correct sequence, and connect them up with each other. * At shut down: Shuts down the components and invoke cleanup method where necessary. @@ -75,6 +75,7 @@ Two of those classes play important roles at the architecture level. The rest of the App consists four components. * [**`UI`**](#ui-component) : The UI of tha App. * [**`Logic`**](#logic-component) : The command executor. +* [**`Parser`**](#parser-component) : The command executor. * [**`Model`**](#model-component) : Holds the data of the App in-memory. * [**`Storage`**](#storage-component) : Reads data from, and writes data to, the hard disk. @@ -89,9 +90,11 @@ interface and exposes its functionality using the `LogicManager.java` class.
The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 3`. +[//]: # (@@author A0139915W) + ->Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, +>Note how the `Model` simply raises a `SavvyTaskerChangedEvent` when the Savvy Tasker data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates @@ -104,19 +107,22 @@ being saved to the hard disk and the status bar of the UI being updated to refle The sections below give more details of each component. +[//]: # (@@author) + +[//]: # (@@author A0138431L) ### UI component
-**API** : [`Ui.java`](../src/main/java/seedu/address/ui/Ui.java) +**API** : [`Ui.java`](../src/main/java/seedu/savvytasker/ui/Ui.java) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, -`StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TaskListPanel`, `UpcomingPanel`, `DailyPanel`, `FloatingPanel`, +`StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class and they can be loaded using the `UiPartLoader`. The `UI` component uses 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`](../src/main/java/seedu/address/ui/MainWindow.java) is specified in + For example, the layout of the [`MainWindow`](../src/main/java/seedu/savvytasker/ui/MainWindow.java) is specified in [`MainWindow.fxml`](../src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -124,11 +130,17 @@ The `UI` component, * Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. * Responds to events raised from various parts of the App and updates the UI accordingly. +The cursor will be focus to the `CommandBox` by default as the `CommandBox` carries out numerous keyboard shortcuts to make the app more user-friendly. + +[//]: # (@@author) + +[//]: # (@@author A0139916U) + ### Logic component
-**API** : [`Logic.java`](../src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](../src/main/java/seedu/savvytasker/logic/Logic.java) 1. `Logic` uses the `Parser` class to parse the user command. 2. This results in a `Command` object which is executed by the `LogicManager`. @@ -139,16 +151,28 @@ Given below is the Sequence Diagram for interactions within the `Logic` componen API call.

+### Parser component + +
+ +**API** : [`MasterParser.java`](../src/main/java/seedu/savvytasker/logic/parser/MasterParser.java) + +The `Parser` component, +* can parse text input into commands. +* supports adding and removing of keyword aliases + +[//]: # (@@author A0139915W) + ### Model component
-**API** : [`Model.java`](../src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](../src/main/java/seedu/savvytasker/model/Model.java) The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes a `UnmodifiableObservableList` that can be 'observed' e.g. the UI can be bound to this list +* stores the Savvy Tasker data. +* exposes a `UnmodifiableObservableList` 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. * does not depend on any of the other three components. @@ -156,15 +180,17 @@ The `Model`,
-**API** : [`Storage.java`](../src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](../src/main/java/seedu/savvytasker/storage/Storage.java) The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the Saavy Tasker data in xml format and read it back. + +[//]: # (@@author) ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.saavytasker.commons` package. ## Implementation @@ -258,7 +284,7 @@ Here are the steps to create a new release. ### Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the +A project often depends on third-party libraries. For example, Savvy Tasker depends on the [Jackson library](http://wiki.fasterxml.com/JacksonHome) for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
@@ -267,55 +293,296 @@ b. Require developers to download those libraries manually (this creates extra w ## Appendix A : User Stories +[//]: # (@@author A0097627N) + Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -Priority | As a ... | I want to ... | So that I can... +Priority | As a(n) ... | 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](#private-contact-detail) by default | minimize chance of someone else seeing them by accident -`*` | user with many persons in the address book | sort persons by name | locate a person easily - -{More to be added} +`* * *` | new user | view more information about a particular command | learn how to use various commands +`* * *` | user | add a new task | record tasks that need to be done some day +`* * *` | user | update a task description/due date/priority level | make modifications to tasks without having delete and re-add it +`* * *` | user | mark completed tasks as done | remind myself that I have completed a task +`* * *` | user | unmark marked tasks | list a resurfaced task without having to key in the same information again +`* * *` | user | delete a task | get rid of tasks that I no longer care to track +`* * *` | user | sort ongoing tasks by due date or priority | decide what needs to be done soon +`* * *` | user | view the list of ongoing tasks | decide what needs to be done +`* * *` | user | view the list of archived tasks | see what has been done +`* *` | user | find tasks by partial or full task name or date | locate a tasks / similar tasks in case I forget the exact task name I typed +`*` | user | sort tasks by priority level | see the most important tasks and prioritize accordingly +`*` | user | undo most recent command | undo the most recent operation +`*` | user | redo most recent undo command | redo the operation done by the most recent undo action +`*` | user | change storage location | choose where my tasks are saved at +`*` | advanced user | alias keywords with shorter versions | type a command faster +`*` | advanced user | remove alias of keywords with shorter versions | get rid of shorter version of certain keywords ## Appendix B : Use Cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +[//]: # (@@author A0097627N) -#### Use case: Delete person +(For all use cases below, the **System** is the `Savvy Tasker` and the **Actor** is the `user`, unless specified otherwise) + +### Use case: Add task **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person
+1. Savvy Tasker waits for user command +2. User enters command to add a task according to some parameters
+3. Savvy Tasker adds the task to a list of tasks
Use case ends. **Extensions** -2a. The list is empty +2a. At least one parameter entered by user is invalid +> 2a1. Savvy Tasker shows an error message and display the expected format.
+> Use case resumes at step 1 + + +2b. START_DATE and END_DATE are different, the RECURRING_TYPE has to be larger than the duration between START_DATE and END_DATE. (e.g. A 3d2n camp cannot be recurring daily but it can be recurring weekly) +> 2b1. Savvy Tasker shows an error message
+> Use case resumes at step 1 + + +2c. START_DATE and END_DATE are different, END_DATE is before START_DATE +> 2c1. Savvy Tasker shows an error message
+> Use case resumes at step 1 + + +2d. START_DATE and END_DATE are the same, END_TIME is before START_TIME +> 2d1. Savvy Tasker shows an error message
+> Use case resumes at step 1 + +### Use case: List tasks + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to list tasks +3. Savvy Tasker shows a list of tasks
+Use case ends. + +**Extensions** + +3a. The list is empty + +> 3a1. Savvy Tasker shows an error message
+ Use case ends + +### Use case: Find task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to find tasks by keyword +3. Savvy Tasker displays the list of tasks that contains the keyword in the name
+Use case ends. + +**Extensions** + +2a. No parameter entered after command word +> Savvy Tasker shows a 'no parameter entered' error message.
+> Use case resumes at step 1 + + +3a. The list is empty +> 2a1. Savvy Tasker shows a 'no task found' error message.
+> Use case ends + + +### Use case: Modify task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to modify a certain attribute of a specific task +3. Savvy Tasker modifies the task and saves it in memory
+Use case ends. + +**Extensions** + +1a. The list is empty + +> 1a1. Use case ends + +2a. The given index is invalid + +> 2a1. Savvy Tasker shows an error message
+ Use case resumes at step 3 + +2b. At least one parameter entered by user is invalid + +> 2b1. Savvy Tasker shows an error message and display the expected format
+ Use case resumes at step 3 + + +### Use case: Change storage location + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to change the storage location of Savvy Tasker +3. Savvy Tasker changes the storage location, saving all existing data in the new location
+Use case ends. + +**Extensions** + +2a. The given path is invalid + +> 2a1. Savvy Tasker shows an error message
+ Use case ends + +2b. The given path is is not accessible (read/write) by Savvy Tasker + +> 2b1. Savvy Tasker shows an error message
+ Use case ends + + +### Use case: Mark task as done + +**MSS** + +1. Savvy Tasker waits for user command +2. User request to mark specific tasks in the list based on task’s index +3. Savvy Tasker marks the tasks, removes it from the task list, and adds it to the Archived list
+Use case ends. + +**Extensions** + +1a. The list is empty +> 2a1. Savvy Tasker shows a 'no task found' error message.
+> Use case ends + +2a. The given index is invalid +> 3a1. Savvy Tasker shows a 'invalid index' error message
+> Use case resumes at step 1 +2b. The task is already marked as done +> 3b1. Savvy Tasker shows a 'task already marked' error message.
+> Use case resumes at step 1 + +### Use case: Unmark marked task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to list archived tasks +3. Savvy Tasker displays a list of archived tasks, sorted by time and date the task has been marked +4. User requests to unmark the specific task in the list based on task’s index +5. Savvy Tasker removes the marked status of the specific task, removes it from the Archived list, and adds it back to the task list
+Use case ends. + +**Extensions** + +2a. The list is empty > Use case ends 3a. The given index is invalid +> 3a1. Savvy Tasker shows a 'invalid index' error message
+> Use case resumes at step 1 + + +### Use case: Delete task + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to list tasks +3. Savvy Tasker shows a list of tasks +4. User requests to delete a specific task in the list +5. Savvy Tasker deletes the task
+Use case ends. + +**Extensions** + +3a. The list is empty + +> Use case ends + +4a. The given index is invalid + +> 4a1. Savvy Tasker shows an error message +> Use case resumes at step 3
+ +### Use case: Alias keyword and use shorten keyword + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to alias a keyword (can be a command or any other frequently used word), with a shorten keyword +3. Savvy Tasker store the shorten keyword associated with the keyword in its database +4. User request a command +4. Savvy Tasker check if the command contain any shorten keyword, if it does, replace the shorten keyword with the associated keyword from its database +5. Savvy Tasker carry out the command
+Use case ends. + +**Extensions** + +2a. The shorten keyword contains only 1 character +> 2a1. Savvy Tasker shows a error message +> Use case resumes at step 1
+ +2b. The shorten keyword has already been associated with other keywords +> 2b1. Savvy Tasker shows a error message and the shorten keyword's original associated keyword +> Use case resumes at step 1
+ +### Use case: Unalias keyword + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to unalias a shorten keyword +3. Savvy Tasker remove the shorten keyword associated with the keyword in its database
+Use case ends. + +**Extensions** + +2a. The shorten keyword could not be found in Savvy Tasker database +> 2a1. Savvy Tasker shows a 'not found' error message +> Use case resumes at step 1
-> 3a1. AddressBook shows an error message
- Use case resumes at step 2 +### Use case: Undo previous command -{More to be added} +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to undo last executed command +3. Savvy Tasker undos the last executed command to return to the state before that command was executed
+Use case ends. + +**Extensions** + +2a. There is no previously executed command to undo +> 2a1. Savvy Tasker shows a 'cannot undo' error message
+> Use case ends + +### Use case: Redo most recently undone command + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to redo last undone command +3. Savvy Tasker re executes the executed command that was last undone
+Use case ends. + +**Extensions** + +2a. There are no executed undo commands to redo +> 2a1. Savvy Tasker shows a 'cannot redo' error message
+> Use case ends ## Appendix C : Non Functional Requirements 1. Should work on any [mainstream OS](#mainstream-os) as long as it has Java `1.8.0_60` or higher installed. -2. Should be able to hold up to 1000 persons. +2. Should be able to hold up to 1000 tasks. 3. Should come with automated unit tests and open source code. 4. Should favor DOS style commands over Unix-style commands. +6. Should work stand-alone and should not be a plug-in to another software. +7. Should work without internet connection. +8. Should store data in text file. +9. Should work without requiring an installer. -{More to be added} ## Appendix D : Glossary @@ -329,5 +596,77 @@ Use case ends. ## Appendix E : Product Survey -{TODO: Add a summary of competing products} +[//]: # (@@author A0097627N) + +#### Competing product: Google Calendar + +**Pros:** + +1. Able to color-code different events +2. Able to set reminders and task/events on repeat +3. Able to add description such as location, remarks and people +4. Able to sync on different devices +5. Able to share calendar +6. Able to undo previous action +7. Able to drag and drop task/events to another date/timing
+ +**Cons:** + +1. Unable to check(tick) completed event +2. Do not have a list of archived task +3. Does not cater for floating task
+ +#### Competing product: Todo + +**Pros:** + +1. Auto prioritization +2. Unlimited contexts +3. Reminders +4. Auto sync with iCal, Toodledo, Outlook, Todo Online
+ +#### Competing product: Remember The Milk + +**Pros:** + +1. Unlimited contexts +2. Reminders +3. Calendar tasks +4. Auto sync with Gmail (Firefox plugin), Google Calendar, Twitter (direct integration), Atom/RSS, IM (feed) +5. Email notifications, autoprocess +6. API
+ +[//]: # (@@author A0139916U) + +#### Competing product: MIUI Calendar + +**Pros:** + +1. Able to sync to different devices +2. Able to set privacy to private or public +3. Able to set 2 reminders +4. Able to set reminder as a notification popup or an alarm + +**Cons:** + +1. Unable to check(tick) completed event +2. Does not cater for tasks, only events + + +[//]: # (@@author A0139915W) + +#### Competing product: WunderList + +**Pros:** + +1. Allows creation of subtasks within a task. +2. Allows local storage, in case there isn't internet access. +3. Can sync across devices by signing in. +4. Allows the grouping of tasks as a list and even grouping into folders. +5. Can invite other person(s), sharing the tasks with them. + +**Cons:** +1. Doesn't allow adding of tasks through the command line (one-shot). +2. Cannot block slots. +3. No calendar view. diff --git a/docs/LearningOutcomes.md b/docs/LearningOutcomes.md index 5ee57072a8d8..1b1d0e9bc618 100644 --- a/docs/LearningOutcomes.md +++ b/docs/LearningOutcomes.md @@ -29,7 +29,7 @@ facilitate communication between event creators and event consumers. ## Use API Design `[LO-ApiDesign]` -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component +Note how components of Saavy Tasker have well-defined APIs. For example, the API of the `Logic` component is given in the [`Logic.java`](../src/main/java/seedu/address/logic/Logic.java)
@@ -41,7 +41,7 @@ is given in the [`Logic.java`](../src/main/java/seedu/address/logic/Logic.java) ## Use Assertions `[LO-Assertions]` -Note how the AddressBook app uses Java `assert`s to verify assumptions. +Note how the Saavy Tasker app uses Java `assert`s to verify assumptions. **Resources** * [Programming With Assertions](http://docs.oracle.com/javase/6/docs/technotes/guides/language/assert.html) - a @@ -52,13 +52,13 @@ Note how the AddressBook app uses Java `assert`s to verify assumptions. * Make sure assertions are enabled in Eclipse by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). - * Add more assertions to AddressBook as you see fit. + * Add more assertions to Saavy Tasker as you see fit. ------------------------------------------------------------------------------------------------------ ## Use Logging `[LO-Logging]` -Note [how the AddressBook app uses Java's `java.util.log` package to do logging](DeveloperGuide.md#logging). +Note [how the Saavy Tasker app uses Java's `java.util.log` package to do logging](DeveloperGuide.md#logging). **Resources** * Tutorials @@ -71,17 +71,17 @@ Note [how the AddressBook app uses Java's `java.util.log` package to do logging] * [Base 22 Java Logging Standards and Guidelines](https://wiki.base22.com/display/btg/Java+Logging+Standards+and+Guidelines) #### Exercise: Add more logging - Add more logging to AddressBook as you see fit. + Add more logging to Saavy Tasker as you see fit. ------------------------------------------------------------------------------------------------------ ## Use Defensive Coding `[LO-DefensiveCoding]` - Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not + Note how Saavy Tasker uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. #### Exercise: identify more places for defensive coding - Analyze the AddressBook code/design to identify, + Analyze the Savvy Tasker code/design to identify, * where defensive coding is used * where the code can be more defensive @@ -89,7 +89,7 @@ Note [how the AddressBook app uses Java's `java.util.log` package to do logging] ## Use Build Automation `[LO-BuildAutomation]` -Note [how the AddressBook app uses Gradle to automate build tasks](UsingGradle.md). +Note [how the Saavy Tasker app uses Gradle to automate build tasks](UsingGradle.md). **Resources** * Tutorials @@ -108,7 +108,7 @@ Note [how the AddressBook app uses Gradle to automate build tasks](UsingGradle.m ## Use Continuous Integration `[LO-ContinuousIntegration]` -Note [how the AddressBook app uses Travis to perform Continuous Integration](UsingTravis.md). +Note [how the Savvy Tasker app uses Travis to perform Continuous Integration](UsingTravis.md). **Resources** * Tutorials diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 0cf4b84f7470..a2c2b000470e 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -11,18 +11,18 @@ > Having any Java 8 version is not enough.
This app will not work with earlier versions of Java 8. -1. Download the latest `addressbook.jar` from the [releases](../../../releases) tab. -2. Copy the file to the folder you want to use as the home folder for your Address Book. +1. Download the latest `savvytasker.jar` from the [releases](../../../releases) tab. +2. Copy the file to the folder you want to use as the home folder for your Savvy Tasker. 3. Double-click the file to start the app. The GUI should appear in a few seconds. > 4. 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. 5. Some example commands you can try: - * **`list`** : lists all contacts - * **`add`**` John Doe p/98765432 e/johnd@gmail.com a/John street, block 123, #01-01` : - adds a contact named `John Doe` to the Address Book. - * **`delete`**` 3` : deletes the 3rd contact shown in the current list + * **`list`** : lists all tasks + * **`add`**` Project Meeting s/05-10-2016 r/daily n/2 c/CS2103 d/Discuss about roles and milestones` : + adds a task named `Project Meeting` to Savvy Tasker for 2 days. + * **`delete`**` 3` : deletes the 3rd task shown in the current list * **`exit`** : exits the app 6. Refer to the [Features](#features) section below for details of each command.
@@ -38,97 +38,278 @@ #### Viewing help : `help` Format: `help` -> Help is also shown if you enter an incorrect command e.g. `abcd` - -#### Adding a person: `add` -Adds a person to the address book
-Format: `add NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +> Help is also shown if you enter an invalid command e.g. `abcd` -> Persons can have any number of tags (including 0) +#### Exiting the program : `exit` +Exits Savvy Tasker.
+Format: `exit` + +#### Saving the data +Savvy Tasker data are saved in the hard disk automatically after any command that changes the data.
+There is no need to save manually. + +[//]: # (@@author A0139915W) + +#### Adding a task: `add` +Adds a task to Savvy Tasker.
+Format: `add TASK_NAME [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]` + +> Parameters | Description +> -------- | :-------- +> TASK_NAME | `Mandatory` Specifies the name of the task. +> START_DATE (See [DATE](#date)) | `Optional` Specifies the starting date and time of the task. +> END_DATE (See [DATE](#date)) | `Optional` Specifies the ending date and time of the task. +> LOCATION | `Optional` Specifies the location where the task happens. +> PRIORITY_LEVEL | `Optional` Specifies the priority level of the task.
`Accepts` values `low`, `medium`, `high`
`Defaults` to `medium` +> RECURRING_TYPE | `Optional` Specifies the recurring type of the task.
`Accepts` values `none`, `daily`, `weekly`, `monthly`, `yearly`
`Defaults` to `none` +> NUMBER_OF_RECURRENCE | `Optional` Specifies the number of times the task recurrs.
`Defaults` to `1`
`Ignored` if RECURRING_TYPE is `none` +> CATEGORY | `Optional` Specifies a custom category for the task. This can be used for keeping track of similar tasks. +> DESCRIPTION | `Optional` Describes the task. + +##### Date + +> If only the DATE is specified, the TIME defaults to starting at 12am or ending at 11:59pm.
If only the TIME is specified, the DATE defaults to today.

If only `START_DATE` is supplied, the task will be a 1-day event starting from the specified `START_DATE` and ending on the same day at 11:59pm.
If only `END_DATE` is supplied, the task will start today at 12am.

The date and time can be entered in a formal format like 17-03-2016, or a natural format like next wednesday, 2pm. The formal format follows the system's settings for whether mm-dd-yyyy or dd-mm-yyyy is used. Examples: -* `add John Doe p/98765432 e/johnd@gmail.com a/John street, block 123, #01-01` -* `add Betsy Crowe p/1234567 e/betsycrowe@gmail.com a/Newgate Prison t/criminal t/friend` +* `add Project Meeting s/05-10-2016 2pm e/6pm r/daily n/2 c/CS2103 d/Discuss about roles and milestones`
+ Add task named, Project Meeting, under CS2103 category. The task is schedule to take place on 5th and 6th of October 2016 from 2pm to 6pm each day. +* `add NUSSU Leadership Camp s/05-10-2016 2pm e/08-10-2016 6pm c/NUSSU` + Add task named, NUSSU Leadership Camp, under NUSSU category. The 4 day 3 night is schedule to take place from 5th October, 2pm to 8th of October 2016, 6pm. + +#### Listing all tasks: `list` +Shows a list of all tasks in Savvy Tasker
+Format: `list [LIST_TYPE]` + +> Parameters | Description +> -------- | :-------- +> LIST_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `DueDate`, `PriorityLevel`, `Archived`, `Alias`
`Defaults` to `DueDate` + +`LIST_TYPE` Explanation: +* `DueDate`
+ Tasks are sorted according to END_DATE beginning with the earliest.
+ If no END_DATE is specified for the task (floating tasks), they will be sorted to bottom of list.
+* `PriorityLevel`
+ Tasks are sorted according to priority level beginning with the highest.
+* `Archived`
+ Tasks that have been [marked](#mark-a-task-as-done--mark) are listed. They are sorted according to the time of creation of the task. +* `Alias`
+ [Aliases](#alias-a-keyword--alias) that have been registered are listed.
-#### Listing all persons : `list` -Shows a list of all persons in the address book.
-Format: `list` +#### Finding all task containing any keyword in its name: `find` +Finds tasks whose names contain any of the given keywords.
+Format: `find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS...]` -#### Finding all persons containing any keyword in their name: `find` -Finds persons whose names contain any of the given keywords.
-Format: `find KEYWORD [MORE_KEYWORDS]` +> Parameters | Description +> -------- | :-------- +> FIND_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `Partial`, `Full`, `Exact`
`Defaults` to `Partial` -> * The search is case sensitive. e.g `hans` will not 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` will match `Hans Bo` +> Only the TASK_NAME is searched.
+> The search is case insensitive. e.g `task` will match `Task`
+> The order of the keywords does not matter for `Partial` and `Full`. e.g. `project meeting` will match `meeting project`
+> Tasks matching at least one keyword will be returned, except for the case of `Exact`.
+`FIND_TYPE` Explanation: +* `Partial`
+ Partial keywords will be matched e.g. `task` will match `2103 tasks`
+* `Full`
+ Only full keywords will be matched e.g. `task` will not match `2103 tasks` but matches `CS2103 Single Task`
+* `Exact`
+ The exact set of keywords will be matched e.g. `Project Meeting` will match `Project Meeting` but not `2103 Project Meeting`
+ +Examples: +* `find t/Full Project meeting`
+ Returns any task containing names `Project` or `Meeting`, but not `meet` +* `find t/Exact Project meeting`
+ Returns any task containing names `Project Meeting` exactly +* `find meet CS2103`
+ Returns any task containing names `meet`, or `CS2103`. This matches `meeting` and any other words containing `meet` or `CS2103` + +#### Deleting a task : `delete` +Deletes the specified task from Savvy Tasker.
+Format: `delete INDEX [MORE_INDEX...]` + +> Parameters | Description +> -------- | :-------- +> INDEX | `Mandatory` Specifies the index of the listing shown to modify.
The index **must be a positive integer** 1, 2, 3, ... +> MORE_INDEX... | `Optional` Same as INDEX. +> Deletes the task at the specified `INDEX` and `[MORE_INDEX...]`. + Examples: -* `find John`
- Returns `John Doe` but not `john` -* `find Betsy Tim John`
- Returns Any person having names `Betsy`, `Tim`, or `John` +* `list`
+ `delete 2 3 5`
+ Deletes the 2nd, 3rd and 5th task listed by Savvy Tasker. +* `find CS1010`
+ `delete 1`
+ Deletes the 1st task in the results of the `find` command. -#### Deleting a person : `delete` -Deletes the specified person from the address book. Irreversible.
-Format: `delete INDEX` +#### Modifies a task : `modify` +Modifies the task identified by the index number used in the last task listing.
+Format: `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]` -> Deletes the person at the specified `INDEX`. +> Parameters | Description +> -------- | :-------- +> INDEX | `Mandatory` Specifies the index of the listing shown to modify.
The index **must be a positive integer** 1, 2, 3, ... +> TASK_NAME
START_DATE
END_DATE
LOCATION
PRIORITY_LEVEL
RECURRING_TYPE
NUMBER_OF_RECURRENCE
CATEGORY
DESCRIPTION | Similar to [Adding a task](#adding-a-task-add) +>
+> Overwrites any of the specified fields ('LOCATION', 'DESCRIPTION'...) with the new values + +#### Change storage location : `storage` +Changes the storage location of Savvy Tasker.
+Format: `storage NEW_FILEPATH` + +> Parameters | Description +> -------- | :-------- +> PATH | `Mandatory` Specifies the path where Savvy Tasker's task list is saved at. +>
+> If the new storage location specified by `NEW_FILEPATH` is not accessible by Savvy Tasker, no change will be made to the existing path. + +[//]: # (@@author A0097627N) + +#### Mark a task as done : `mark` +Marks the task as completed identified by the index number used in the last task listing. Completed task will be remove from the normal list and placed under archived list
+Format: `mark INDEX [MORE_INDEX]` + +> Selects the task and marks the task as done at the specified `INDEX`. The index refers to the index number shown in the most recent listing.
The index **must be a positive integer** 1, 2, 3, ... Examples: * `list`
- `delete 2`
- Deletes the 2nd person in the address book. -* `find Betsy`
- `delete 1`
- Deletes the 1st person in the results of the `find` command. + `mark 2`
+ Marks the 2nd task in the task book as completed. +* `find CS2103`
+ `mark 1`
+ Marks the 1st task in the results of the `find` command as completed. -#### Select a person : `select` -Selects the person identified by the index number used in the last person listing.
-Format: `select INDEX` +#### Unmark a task as done : `unmark` +Unmarks the task identified by the index number used in the last task listing.
+Format: `Unmark INDEX [MORE_INDEX]` -> Selects the person and loads the Google search page the person at the specified `INDEX`. +> Selects the task and marks the task as done at the specified `INDEX`. The index refers to the index number shown in the most recent listing.
The index **must be a positive integer** 1, 2, 3, ... Examples: * `list`
- `select 2`
- Selects the 2nd person in the address book. -* `find Betsy`
- `select 1`
- Selects the 1st person in the results of the `find` command. + `unmark 2`
+ Unmarks the 2nd task in the task book as done. +* `find CS2103`
+ `unmark 1`
+ Unmarks the 1st task in the results of the `find` command as done. + +#### Undo the most recent operation : `undo` +Undo the most recent command that was executed.
+Format: `undo` + +#### Redo the most recent undo operation : `redo` +Redo the most recent command that was executed by the undo.
+Format: `redo` +>Redo is unavailable if the most recent command is not undo #### Clearing all entries : `clear` -Clears all entries from the address book.
+Clears all entries from the Savvy Task.
Format: `clear` -#### Exiting the program : `exit` -Exits the program.
-Format: `exit` +[//]: # (@@author A0139916U) -#### Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data.
-There is no need to save manually. +#### Alias a keyword : `alias` +Alias a keyword to represent another text.
+Format: `alias k/KEYWORD r/REPRESENTATION` + +> Parameters | Description +> -------- | :-------- +> KEYWORD | Specifies the keyword that will be replaced when met in a command, must be a single word. +> REPRESENTATION | Specifies the text that will replace the keyword. Cannot contain slashes. + +Examples: +* `alias k/pjm r/Project Meeting`
+System will interpret subsequent keywords "pjm" as "Project Meeting"
+ `add pjm s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103`
+Add "Project Meeting" to task list
+ +> Note that only keywords must be a single word by themselves, or they will not be replaced. +> The below example shows 'pjm' as part of another word and it is not replaced with its representation.
+ +* `add pjm2 s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103`
+Add "pjm2" to task list, and not "Project Meeting2"
+ + +#### Unalias a keyword : `unalias` +Unalias a keyword
+Format: `unalias KEYWORD` + +> Parameters | Description +> -------- | :-------- +> KEYWORD | Specifies the keyword to unaliased + +Examples: +* `unalias pjm`
+Removes the replacement of keyword "pjm" with "Project Meeting"
+ `add pjm s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103`
+Add task named "pjm" to task list + ## FAQ **Q**: How do I transfer my data to another Computer?
**A**: Install the app in the other computer and overwrite the empty data file it creates with - the file that contains the data of your previous Address Book folder. + the file that contains the data of your previous Savvy Tasker folder. + +[//]: # (@@author A0139915W) + +[//]: # (@@author A0138431L) + +#### Command stack history +UP: Return last user input command in command box
+DOWN: Return (if any) next user input command in command box
+> Note that DOWN is only allowed after at least an UP is being entered + + +#### Week Selection +Ctrl + LEFT: Display previous week’s daily task list
+Ctrl + RIGHT: Display next week’s daily task list + +[//]: # (@@author A0139915W) + ## Command Summary Command | Format -------- | :-------- -Add | `add NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` -Clear | `clear` -Delete | `delete INDEX` -Find | `find KEYWORD [MORE_KEYWORDS]` -List | `list` -Help | `help` -Select | `select INDEX` +[Add](#adding-a-task-add) | `add TASK_NAME [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]`
Example: `add Weekly Milestone s/Thurs r/weekly n/3 l/COM1 c/Meeting` +[Alias](#alias-a-keyword--alias) | `alias k/KEYWORD s/SHORT_KEYWORD`
Example: `alias k/main success scenario s/mss` +[Clear](#clearing-all-entries--clear) | `clear` +[Delete](#deleting-a-task--delete) | `delete INDEX [MORE_INDEX]`
Example: `delete 1 2 3` +[Exit](#exiting-the-program--exit) | `exit` +[Find](#finding-all-task-containing-any-keyword-in-its-name-find) | `find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]`
Example: `find t/exact CS2103 Meeting` +[List](#listing-all-tasks-list) | `list [LIST_TYPE]`
Example: `list archived` +[Help](#viewing-help--help) | `help` +[Modify](#modifies-a-task--modify) | `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]`
Example: `modify 2 t/Wednesday Weekly Milestone s/wed d/Project Meeting and Finalization` +[Mark](#mark-a-task-as-done--mark) | `mark INDEX [MORE_INDEX]`
Example: `mark 1 2 3` +[Storage](#change-storage-location--storage) | `storage NEW_FILEPATH`
Example: `storage data/savvytasker.xml` +[Unmark](#unmark-a-task-as-done--unmark) | `unmark INDEX [MORE_INDEX]`
Example: `unmark 1 2 3` +[Undo](#undo-the-most-recent-operation--undo) | `undo` +[Redo](#redo-the-most-recent-undo-operation--redo) | `redo` +[Unalias](#unalias-a-keyword--unalias) | `unalias s/SHORT_KEYWORD`
Example: `unalias s/mss` + +[//]: # (@@author A0138431L) + +## Keyboard Shortcuts + +Key Codes | Function | Command Box Input +-------- | :-------- | :-------- +Esc | Toggle to show/hide a list of keyboard shortcuts | - +Ctrl + D | [Clear](#clearing-all-entries--clear) all entries | `clear` +Ctrl + Q | [Exit](#exiting-the-program--exit) | `exit` +Ctrl + L | [List](#listing-all-tasks-list) all unmarked task by date, earliest task first | `list` +Ctrl + A | [List](#listing-all-tasks-list) archived tasks | `list archived` +Ctrl + P | [List](#listing-all-tasks-list) all unmarked task by priority level, high priority first | `list priorityLevel` +Ctrl + I | [List](#listing-all-tasks-list) all alias keys | `list alias` +Ctrl + H | [Help](#viewing-help--help) | `help` +Ctrl + S | Popups a directory chooser dialog box to choose a new filepath | `storage NEW_FILEPATH` +Ctrl + Z | [Undo](#undo-the-most-recent-operation--undo) | `undo` +Ctrl + Y | [Redo](#redo-the-most-recent-undo-operation--redo) | `redo` +Ctrl + UP | Return [last user input](#command-stack-history) command in command box | - +Ctrl + DOWN | Return (if any) [next user input](#command-stack-history) command in command box | - +Ctrl + LEFT | Displays [previous week’s](#week-selection) daily task list | - +Ctrl + RIGHT | Display [next week’s](#week-selection) daily task list | - diff --git a/docs/UsingGradle.md b/docs/UsingGradle.md index 578c5f8634c2..627af8dae1f2 100644 --- a/docs/UsingGradle.md +++ b/docs/UsingGradle.md @@ -35,7 +35,7 @@ Gradle commands look like this: ## Creating the JAR file * **`shadowJar`**
- Creates the `addressbook.jar` file in the `build/jar` folder, _if the current file is outdated_.
+ Creates the `saavytasker.jar` file in the `build/jar` folder, _if the current file is outdated_.
e.g. `./gradlew shadowJar` > To force Gradle to create the JAR file even if the current one is up-to-date, you can '`clean`' first.
diff --git a/docs/diagrams/Diagrams.pptx b/docs/diagrams/Diagrams.pptx index 3c28abe9c1d3..05ad0e1aeb69 100644 Binary files a/docs/diagrams/Diagrams.pptx and b/docs/diagrams/Diagrams.pptx differ diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png index bdc789000f77..8c2b9061268f 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/DamithRajapakse.jpg b/docs/images/DamithRajapakse.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/DamithRajapakse.jpg and /dev/null differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png index 6c272fb17af6..658388997391 100644 Binary files a/docs/images/DeletePersonSdForLogic.png and b/docs/images/DeletePersonSdForLogic.png differ diff --git a/docs/images/JoshuaLee.jpg b/docs/images/JoshuaLee.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/JoshuaLee.jpg and /dev/null differ diff --git a/docs/images/LeowYijin.jpg b/docs/images/LeowYijin.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/LeowYijin.jpg and /dev/null differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index a973d02047a2..475e8486ff80 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/MartinChoo.jpg b/docs/images/MartinChoo.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/MartinChoo.jpg and /dev/null differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 8cdf11ec93a1..3abf6bc7892e 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClassDiagram.png b/docs/images/ParserClassDiagram.png new file mode 100644 index 000000000000..2b3d6895bd9f Binary files /dev/null and b/docs/images/ParserClassDiagram.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png index 1e836f10dcd8..4e557dbe1345 100644 Binary files a/docs/images/SDforDeletePerson.png and b/docs/images/SDforDeletePerson.png differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png index ecec0805d32c..5feee5c1342a 100644 Binary files a/docs/images/SDforDeletePersonEventHandling.png and b/docs/images/SDforDeletePersonEventHandling.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..38916d7647ea 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 7121a50a442a..5ac53387fb40 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 459245e267af..dd8be5c36d4e 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/YouLiang.jpg b/docs/images/YouLiang.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/YouLiang.jpg and /dev/null differ diff --git a/docs/images/akshay.jpg b/docs/images/akshay.jpg new file mode 100644 index 000000000000..65856ab608b1 Binary files /dev/null and b/docs/images/akshay.jpg differ diff --git a/docs/images/eethai.png b/docs/images/eethai.png new file mode 100644 index 000000000000..3b3e272f6cb1 Binary files /dev/null and b/docs/images/eethai.png differ diff --git a/docs/images/henry.png b/docs/images/henry.png new file mode 100644 index 000000000000..4afd373947dd Binary files /dev/null and b/docs/images/henry.png differ diff --git a/docs/images/mentor.png b/docs/images/mentor.png new file mode 100644 index 000000000000..aefaee0992d6 Binary files /dev/null and b/docs/images/mentor.png differ diff --git a/docs/images/nishant.jpg b/docs/images/nishant.jpg new file mode 100644 index 000000000000..89a1d96aae5d Binary files /dev/null and b/docs/images/nishant.jpg differ diff --git a/docs/images/qinghua.png b/docs/images/qinghua.png new file mode 100644 index 000000000000..4ecf3e8fe1f3 Binary files /dev/null and b/docs/images/qinghua.png differ diff --git a/docs/images/ruiwen.png b/docs/images/ruiwen.png new file mode 100644 index 000000000000..27bf89a49643 Binary files /dev/null and b/docs/images/ruiwen.png differ diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e4695..000000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index 347a8359e0d5..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data){ - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index 95377b326fa6..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final ReadOnlyPerson newSelection; - - public PersonPanelSelectionChangedEvent(ReadOnlyPerson newSelection){ - this.newSelection = newSelection; - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - - public ReadOnlyPerson getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java deleted file mode 100644 index fde8394f31e5..000000000000 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -package seedu.address.commons.util; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/** - * Utility methods related to Collections - */ -public class CollectionUtil { - - /** - * Returns true if any of the given items are null. - */ - public static boolean isAnyNull(Object... items) { - for (Object item : items) { - if (item == null) { - return true; - } - } - return false; - } - - - - /** - * Throws an assertion error if the collection or any item in it is null. - */ - public static void assertNoNullElements(Collection items) { - assert items != null; - assert !isAnyNull(items); - } - - /** - * Returns true if every element in a collection are unique by {@link Object#equals(Object)}. - */ - public static boolean elementsAreUnique(Collection items) { - final Set testSet = new HashSet<>(); - for (Object item : items) { - final boolean itemAlreadyExists = !testSet.add(item); // see Set documentation - if (itemAlreadyExists) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 4df1bc65cabb..000000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.address.logic; - -import javafx.collections.ObservableList; -import seedu.address.logic.commands.CommandResult; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - */ - CommandResult execute(String commandText); - - /** Returns the filtered list of persons */ - ObservableList getFilteredPersonList(); - -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index ce4dc1903cff..000000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,41 +0,0 @@ -package seedu.address.logic; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.parser.Parser; -import seedu.address.model.Model; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.storage.Storage; - -import java.util.logging.Logger; - -/** - * The main LogicManager of the app. - */ -public class LogicManager extends ComponentManager implements Logic { - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Parser parser; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.parser = new Parser(); - } - - @Override - public CommandResult execute(String commandText) { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - Command command = parser.parseCommand(commandText); - command.setData(model); - return command.execute(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } -} 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 2860a9ab2a85..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.*; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import java.util.HashSet; -import java.util.Set; - -/** - * 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: NAME p/PHONE e/EMAIL a/ADDRESS [t/TAG]...\n" - + "Example: " + COMMAND_WORD - + " John Doe p/98765432 e/johnd@gmail.com a/311, Clementi Ave 2, #02-25 t/friends t/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; - - /** - * Convenience constructor using raw values. - * - * @throws IllegalValueException if any of the raw values are invalid - */ - public AddCommand(String name, String phone, String email, String address, Set tags) - throws IllegalValueException { - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(new Tag(tagName)); - } - this.toAdd = new Person( - new Name(name), - new Phone(phone), - new Email(email), - new Address(address), - new UniqueTagList(tagSet) - ); - } - - @Override - public CommandResult execute() { - assert model != null; - try { - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } catch (UniquePersonList.DuplicatePersonException e) { - return new CommandResult(MESSAGE_DUPLICATE_PERSON); - } - - } - -} 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 522d57189f51..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.AddressBook; - -/** - * 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!"; - - public ClearCommand() {} - - - @Override - public CommandResult execute() { - assert model != null; - model.resetData(AddressBook.getEmptyAddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java deleted file mode 100644 index 7c0ba2fd0161..000000000000 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.events.ui.IncorrectCommandAttemptedEvent; -import seedu.address.model.Model; - -/** - * Represents a command with hidden internal logic and the ability to be executed. - */ -public abstract class Command { - protected Model model; - - /** - * Constructs a feedback message to summarise an operation that displayed a listing of persons. - * - * @param displaySize used to generate summary - * @return summary message for persons displayed - */ - public static String getMessageForPersonListShownSummary(int displaySize) { - return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); - } - - /** - * Executes the command and returns the result message. - * - * @return feedback message of the operation result for display - */ - public abstract CommandResult execute(); - - /** - * Provides any needed dependencies to the command. - * Commands making use of any of these should override this method to gain - * access to the dependencies. - */ - public void setData(Model model) { - this.model = model; - } - - /** - * Raises an event to indicate an attempt to execute an incorrect command - */ - protected void indicateAttemptToExecuteIncorrectCommand() { - EventsCenter.getInstance().post(new IncorrectCommandAttemptedEvent(this)); - } -} 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 1bfebe8912a8..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,50 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList.PersonNotFoundException; - -/** - * Deletes a person identified using it's last 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 last person listing.\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"; - - public final int targetIndex; - - public DeleteCommand(int targetIndex) { - this.targetIndex = targetIndex; - } - - - @Override - public CommandResult execute() { - - UnmodifiableObservableList lastShownList = model.getFilteredPersonList(); - - if (lastShownList.size() < targetIndex) { - indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - ReadOnlyPerson personToDelete = lastShownList.get(targetIndex - 1); - - try { - model.deletePerson(personToDelete); - } catch (PersonNotFoundException pnfe) { - assert false : "The target person cannot be missing"; - } - - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java deleted file mode 100644 index d98233ce2a0b..000000000000 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; - -/** - * Terminates the program. - */ -public class ExitCommand extends Command { - - public static final String COMMAND_WORD = "exit"; - - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; - - public ExitCommand() {} - - @Override - public CommandResult execute() { - EventsCenter.getInstance().post(new ExitAppRequestEvent()); - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); - } - -} 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 1d61bf6cc857..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package seedu.address.logic.commands; - -import java.util.Set; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case sensitive. - */ -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-sensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final Set keywords; - - public FindCommand(Set keywords) { - this.keywords = keywords; - } - - @Override - public CommandResult execute() { - model.updateFilteredPersonList(keywords); - return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java deleted file mode 100644 index 65af96940242..000000000000 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.logic.commands; - - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; - -/** - * Format full help instructions for every command for display. - */ -public class HelpCommand extends Command { - - public static final String COMMAND_WORD = "help"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" - + "Example: " + COMMAND_WORD; - - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; - - public HelpCommand() {} - - @Override - public CommandResult execute() { - EventsCenter.getInstance().post(new ShowHelpRequestEvent()); - return new CommandResult(SHOWING_HELP_MESSAGE); - } -} diff --git a/src/main/java/seedu/address/logic/commands/IncorrectCommand.java b/src/main/java/seedu/address/logic/commands/IncorrectCommand.java deleted file mode 100644 index 491d9cb9da35..000000000000 --- a/src/main/java/seedu/address/logic/commands/IncorrectCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package seedu.address.logic.commands; - - -/** - * Represents an incorrect command. Upon execution, produces some feedback to the user. - */ -public class IncorrectCommand extends Command { - - public final String feedbackToUser; - - public IncorrectCommand(String feedbackToUser){ - this.feedbackToUser = feedbackToUser; - } - - @Override - public CommandResult execute() { - indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(feedbackToUser); - } - -} - 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 9bdd457a1b01..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package seedu.address.logic.commands; - - -/** - * 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"; - - public ListCommand() {} - - @Override - public CommandResult execute() { - model.updateFilteredListToShowAll(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index 9ca0551f1951..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * Selects a person identified using it's last displayed index from the address book. - */ -public class SelectCommand extends Command { - - public final int targetIndex; - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - public SelectCommand(int targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute() { - - UnmodifiableObservableList lastShownList = model.getFilteredPersonList(); - - if (lastShownList.size() < targetIndex) { - indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex - 1)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex)); - - } - -} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java deleted file mode 100644 index 959b2cd0383c..000000000000 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ /dev/null @@ -1,192 +0,0 @@ -package seedu.address.logic.parser; - -import seedu.address.logic.commands.*; -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.exceptions.IllegalValueException; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -/** - * Parses user input. - */ -public class Parser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - private static final Pattern PERSON_INDEX_ARGS_FORMAT = Pattern.compile("(?.+)"); - - private static final Pattern KEYWORDS_ARGS_FORMAT = - Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); // one or more keywords separated by whitespace - - private static final Pattern PERSON_DATA_ARGS_FORMAT = // '/' forward slashes are reserved for delimiter prefixes - Pattern.compile("(?[^/]+)" - + " (?p?)p/(?[^/]+)" - + " (?p?)e/(?[^/]+)" - + " (?p?)a/(?
[^/]+)" - + "(?(?: t/[^/]+)*)"); // variable number of tags - - public Parser() {} - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - */ - public Command parseCommand(String userInput) { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return prepareAdd(arguments); - - case SelectCommand.COMMAND_WORD: - return prepareSelect(arguments); - - case DeleteCommand.COMMAND_WORD: - return prepareDelete(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return prepareFind(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - return new IncorrectCommand(MESSAGE_UNKNOWN_COMMAND); - } - } - - /** - * Parses arguments in the context of the add person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareAdd(String args){ - final Matcher matcher = PERSON_DATA_ARGS_FORMAT.matcher(args.trim()); - // Validate arg string format - if (!matcher.matches()) { - return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - try { - return new AddCommand( - matcher.group("name"), - matcher.group("phone"), - matcher.group("email"), - matcher.group("address"), - getTagsFromArgs(matcher.group("tagArguments")) - ); - } catch (IllegalValueException ive) { - return new IncorrectCommand(ive.getMessage()); - } - } - - /** - * Extracts the new person's tags from the add command's tag arguments string. - * Merges duplicate tag strings. - */ - private static Set getTagsFromArgs(String tagArguments) throws IllegalValueException { - // no tags - if (tagArguments.isEmpty()) { - return Collections.emptySet(); - } - // replace first delimiter prefix, then split - final Collection tagStrings = Arrays.asList(tagArguments.replaceFirst(" t/", "").split(" t/")); - return new HashSet<>(tagStrings); - } - - /** - * Parses arguments in the context of the delete person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareDelete(String args) { - - Optional index = parseIndex(args); - if(!index.isPresent()){ - return new IncorrectCommand( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); - } - - return new DeleteCommand(index.get()); - } - - /** - * Parses arguments in the context of the select person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareSelect(String args) { - Optional index = parseIndex(args); - if(!index.isPresent()){ - return new IncorrectCommand( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - } - - return new SelectCommand(index.get()); - } - - /** - * Returns the specified index in the {@code command} IF a positive unsigned integer is given as the index. - * Returns an {@code Optional.empty()} otherwise. - */ - private Optional parseIndex(String command) { - final Matcher matcher = PERSON_INDEX_ARGS_FORMAT.matcher(command.trim()); - if (!matcher.matches()) { - return Optional.empty(); - } - - String index = matcher.group("targetIndex"); - if(!StringUtil.isUnsignedInteger(index)){ - return Optional.empty(); - } - return Optional.of(Integer.parseInt(index)); - - } - - /** - * Parses arguments in the context of the find person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareFind(String args) { - final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); - if (!matcher.matches()) { - return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, - FindCommand.MESSAGE_USAGE)); - } - - // keywords delimited by whitespace - final String[] keywords = matcher.group("keywords").split("\\s+"); - final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); - return new FindCommand(keywordSet); - } - -} \ No newline at end of file diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 298cc1b82ce8..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,163 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .equals comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - private final UniqueTagList tags; - - { - persons = new UniquePersonList(); - tags = new UniqueTagList(); - } - - public AddressBook() {} - - /** - * Persons and Tags are copied into this addressbook - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(toBeCopied.getUniquePersonList(), toBeCopied.getUniqueTagList()); - } - - /** - * Persons and Tags are copied into this addressbook - */ - public AddressBook(UniquePersonList persons, UniqueTagList tags) { - resetData(persons.getInternalList(), tags.getInternalList()); - } - - public static ReadOnlyAddressBook getEmptyAddressBook() { - return new AddressBook(); - } - -//// list overwrite operations - - public ObservableList getPersons() { - return persons.getInternalList(); - } - - public void setPersons(List persons) { - this.persons.getInternalList().setAll(persons); - } - - public void setTags(Collection tags) { - this.tags.getInternalList().setAll(tags); - } - - public void resetData(Collection newPersons, Collection newTags) { - setPersons(newPersons.stream().map(Person::new).collect(Collectors.toList())); - setTags(newTags); - } - - public void resetData(ReadOnlyAddressBook newData) { - resetData(newData.getPersonList(), newData.getTagList()); - } - -//// person-level operations - - /** - * Adds a person to the address book. - * Also checks the new person's tags and updates {@link #tags} with any new tags found, - * and updates the Tag objects in the person to point to those in {@link #tags}. - * - * @throws UniquePersonList.DuplicatePersonException if an equivalent person already exists. - */ - public void addPerson(Person p) throws UniquePersonList.DuplicatePersonException { - syncTagsWithMasterList(p); - persons.add(p); - } - - /** - * Ensures that every tag in this person: - * - exists in the master list {@link #tags} - * - points to a Tag object in the master list - */ - private void syncTagsWithMasterList(Person person) { - final UniqueTagList personTags = person.getTags(); - tags.mergeFrom(personTags); - - // Create map with values = tag object references in the master list - final Map masterTagObjects = new HashMap<>(); - for (Tag tag : tags) { - masterTagObjects.put(tag, tag); - } - - // Rebuild the list of person tags using references from the master list - final Set commonTagReferences = new HashSet<>(); - for (Tag tag : personTags) { - commonTagReferences.add(masterTagObjects.get(tag)); - } - person.setTags(new UniqueTagList(commonTagReferences)); - } - - public boolean removePerson(ReadOnlyPerson key) throws UniquePersonList.PersonNotFoundException { - if (persons.remove(key)) { - return true; - } else { - throw new UniquePersonList.PersonNotFoundException(); - } - } - -//// tag-level operations - - public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { - tags.add(t); - } - -//// util methods - - @Override - public String toString() { - return persons.getInternalList().size() + " persons, " + tags.getInternalList().size() + " tags"; - // TODO: refine later - } - - @Override - public List getPersonList() { - return Collections.unmodifiableList(persons.getInternalList()); - } - - @Override - public List getTagList() { - return Collections.unmodifiableList(tags.getInternalList()); - } - - @Override - public UniquePersonList getUniquePersonList() { - return this.persons; - } - - @Override - public UniqueTagList getUniqueTagList() { - return this.tags; - } - - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && this.persons.equals(((AddressBook) other).persons) - && this.tags.equals(((AddressBook) other).tags)); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d14a27a93b5e..000000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,35 +0,0 @@ -package seedu.address.model; - -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; - -import java.util.Set; - -/** - * The API of the Model component. - */ -public interface Model { - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** Deletes the given person. */ - void deletePerson(ReadOnlyPerson target) throws UniquePersonList.PersonNotFoundException; - - /** Adds the given person */ - void addPerson(Person person) throws UniquePersonList.DuplicatePersonException; - - /** Returns the filtered person list as an {@code UnmodifiableObservableList} */ - UnmodifiableObservableList getFilteredPersonList(); - - /** Updates the filter of the filtered person list to show all persons */ - void updateFilteredListToShowAll(); - - /** Updates the filter of the filtered person list to filter by the given keywords*/ - void updateFilteredPersonList(Set keywords); - -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 869226d02bf1..000000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,153 +0,0 @@ -package seedu.address.model; - -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.core.ComponentManager; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.person.UniquePersonList.PersonNotFoundException; - -import java.util.Set; -import java.util.logging.Logger; - -/** - * Represents the in-memory model of the address book data. - * All changes to any model should be synchronized. - */ -public class ModelManager extends ComponentManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given AddressBook - * AddressBook and its variables should not be null - */ - public ModelManager(AddressBook src, UserPrefs userPrefs) { - super(); - assert src != null; - assert userPrefs != null; - - logger.fine("Initializing with address book: " + src + " and user prefs " + userPrefs); - - addressBook = new AddressBook(src); - filteredPersons = new FilteredList<>(addressBook.getPersons()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - public ModelManager(ReadOnlyAddressBook initialData, UserPrefs userPrefs) { - addressBook = new AddressBook(initialData); - filteredPersons = new FilteredList<>(addressBook.getPersons()); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - addressBook.resetData(newData); - indicateAddressBookChanged(); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(addressBook)); - } - - @Override - public synchronized void deletePerson(ReadOnlyPerson target) throws PersonNotFoundException { - addressBook.removePerson(target); - indicateAddressBookChanged(); - } - - @Override - public synchronized void addPerson(Person person) throws UniquePersonList.DuplicatePersonException { - addressBook.addPerson(person); - updateFilteredListToShowAll(); - indicateAddressBookChanged(); - } - - //=========== Filtered Person List Accessors =============================================================== - - @Override - public UnmodifiableObservableList getFilteredPersonList() { - return new UnmodifiableObservableList<>(filteredPersons); - } - - @Override - public void updateFilteredListToShowAll() { - filteredPersons.setPredicate(null); - } - - @Override - public void updateFilteredPersonList(Set keywords){ - updateFilteredPersonList(new PredicateExpression(new NameQualifier(keywords))); - } - - private void updateFilteredPersonList(Expression expression) { - filteredPersons.setPredicate(expression::satisfies); - } - - //========== Inner classes/interfaces used for filtering ================================================== - - interface Expression { - boolean satisfies(ReadOnlyPerson person); - String toString(); - } - - private class PredicateExpression implements Expression { - - private final Qualifier qualifier; - - PredicateExpression(Qualifier qualifier) { - this.qualifier = qualifier; - } - - @Override - public boolean satisfies(ReadOnlyPerson person) { - return qualifier.run(person); - } - - @Override - public String toString() { - return qualifier.toString(); - } - } - - interface Qualifier { - boolean run(ReadOnlyPerson person); - String toString(); - } - - private class NameQualifier implements Qualifier { - private Set nameKeyWords; - - NameQualifier(Set nameKeyWords) { - this.nameKeyWords = nameKeyWords; - } - - @Override - public boolean run(ReadOnlyPerson person) { - return nameKeyWords.stream() - .filter(keyword -> StringUtil.containsIgnoreCase(person.getName().fullName, keyword)) - .findAny() - .isPresent(); - } - - @Override - public String toString() { - return "name=" + String.join(", ", nameKeyWords); - } - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index bfca099b1e81..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,30 +0,0 @@ -package seedu.address.model; - - -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import java.util.List; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - UniqueTagList getUniqueTagList(); - - UniquePersonList getUniquePersonList(); - - /** - * Returns an unmodifiable view of persons list - */ - List getPersonList(); - - /** - * Returns an unmodifiable view of tags list - */ - List getTagList(); - -} 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 a2bd109c005e..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.person; - - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * 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_ADDRESS_CONSTRAINTS = "Person addresses can be in any format"; - public static final String ADDRESS_VALIDATION_REGEX = ".+"; - - public final String value; - - /** - * Validates given address. - * - * @throws IllegalValueException if given address string is invalid. - */ - public Address(String address) throws IllegalValueException { - assert address != null; - if (!isValidAddress(address)) { - throw new IllegalValueException(MESSAGE_ADDRESS_CONSTRAINTS); - } - this.value = address; - } - - /** - * Returns true if a given string is a valid person email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_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 - && this.value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} \ No newline at end of file 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 5da4d1078236..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,56 +0,0 @@ -package seedu.address.model.person; - - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - public static final String MESSAGE_EMAIL_CONSTRAINTS = - "Person emails should be 2 alphanumeric/period strings separated by '@'"; - public static final String EMAIL_VALIDATION_REGEX = "[\\w\\.]+@[\\w\\.]+"; - - public final String value; - - /** - * Validates given email. - * - * @throws IllegalValueException if given email address string is invalid. - */ - public Email(String email) throws IllegalValueException { - assert email != null; - email = email.trim(); - if (!isValidEmail(email)) { - throw new IllegalValueException(MESSAGE_EMAIL_CONSTRAINTS); - } - this.value = email; - } - - /** - * Returns if a given string is a valid person email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_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 - && this.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 4f30033e70fe..000000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * 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_NAME_CONSTRAINTS = "Person names should be spaces or alphanumeric characters"; - public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum} ]+"; - - public final String fullName; - - /** - * Validates given name. - * - * @throws IllegalValueException if given name string is invalid. - */ - public Name(String name) throws IllegalValueException { - assert name != null; - name = name.trim(); - if (!isValidName(name)) { - throw new IllegalValueException(MESSAGE_NAME_CONSTRAINTS); - } - this.fullName = name; - } - - /** - * Returns true if a given string is a valid person name. - */ - public static boolean isValidName(String test) { - return test.matches(NAME_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 - && this.fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 03ffce7d2e79..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,90 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.commons.util.CollectionUtil; -import seedu.address.model.tag.UniqueTagList; - -import java.util.Objects; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated. - */ -public class Person implements ReadOnlyPerson { - - private Name name; - private Phone phone; - private Email email; - private Address address; - - private UniqueTagList tags; - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, UniqueTagList tags) { - assert !CollectionUtil.isAnyNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags = new UniqueTagList(tags); // protect internal tags from changes in the arg list - } - - /** - * Copy constructor. - */ - public Person(ReadOnlyPerson source) { - this(source.getName(), source.getPhone(), source.getEmail(), source.getAddress(), source.getTags()); - } - - @Override - public Name getName() { - return name; - } - - @Override - public Phone getPhone() { - return phone; - } - - @Override - public Email getEmail() { - return email; - } - - @Override - public Address getAddress() { - return address; - } - - @Override - public UniqueTagList getTags() { - return new UniqueTagList(tags); - } - - /** - * Replaces this person's tags with the tags in the argument tag list. - */ - public void setTags(UniqueTagList replacement) { - tags.setTags(replacement); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof ReadOnlyPerson // instanceof handles nulls - && this.isSameStateAs((ReadOnlyPerson) other)); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - return getAsText(); - } - -} 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 d27b2244b727..000000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * 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_PHONE_CONSTRAINTS = "Person phone numbers should only contain numbers"; - public static final String PHONE_VALIDATION_REGEX = "\\d+"; - - public final String value; - - /** - * Validates given phone number. - * - * @throws IllegalValueException if given phone string is invalid. - */ - public Phone(String phone) throws IllegalValueException { - assert phone != null; - phone = phone.trim(); - if (!isValidPhone(phone)) { - throw new IllegalValueException(MESSAGE_PHONE_CONSTRAINTS); - } - this.value = phone; - } - - /** - * Returns true if a given string is a valid person phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(PHONE_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 - && this.value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/ReadOnlyPerson.java b/src/main/java/seedu/address/model/person/ReadOnlyPerson.java deleted file mode 100644 index d45be4b5fe36..000000000000 --- a/src/main/java/seedu/address/model/person/ReadOnlyPerson.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.model.tag.UniqueTagList; - -/** - * A read-only immutable interface for a Person in the addressbook. - * Implementations should guarantee: details are present and not null, field values are validated. - */ -public interface ReadOnlyPerson { - - Name getName(); - Phone getPhone(); - Email getEmail(); - Address getAddress(); - - /** - * The returned TagList is a deep copy of the internal TagList, - * changes on the returned list will not affect the person's internal tags. - */ - UniqueTagList getTags(); - - /** - * Returns true if both have the same state. (interfaces cannot override .equals) - */ - default boolean isSameStateAs(ReadOnlyPerson other) { - return other == this // short circuit if same object - || (other != null // this is first to avoid NPE below - && other.getName().equals(this.getName()) // state checks here onwards - && other.getPhone().equals(this.getPhone()) - && other.getEmail().equals(this.getEmail()) - && other.getAddress().equals(this.getAddress())); - } - - /** - * Formats the person as text, showing all contact details. - */ - default String getAsText() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - - /** - * Returns a string representation of this Person's tags - */ - default String tagsString() { - final StringBuffer buffer = new StringBuffer(); - final String separator = ", "; - getTags().forEach(tag -> buffer.append(tag).append(separator)); - if (buffer.length() == 0) { - return ""; - } else { - return buffer.substring(0, buffer.length() - separator.length()); - } - } - -} 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 263f1fcc7dd5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,98 +0,0 @@ -package seedu.address.model.person; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.DuplicateDataException; - -import java.util.*; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * - * Supports a minimal set of list operations. - * - * @see Person#equals(Object) - * @see CollectionUtil#elementsAreUnique(Collection) - */ -public class UniquePersonList implements Iterable { - - /** - * Signals that an operation would have violated the 'no duplicates' property of the list. - */ - public static class DuplicatePersonException extends DuplicateDataException { - protected DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } - } - - /** - * Signals that an operation targeting a specified person in the list would fail because - * there is no such matching person in the list. - */ - public static class PersonNotFoundException extends Exception {} - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Constructs empty PersonList. - */ - public UniquePersonList() {} - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(ReadOnlyPerson toCheck) { - assert toCheck != null; - return internalList.contains(toCheck); - } - - /** - * Adds a person to the list. - * - * @throws DuplicatePersonException if the person to add is a duplicate of an existing person in the list. - */ - public void add(Person toAdd) throws DuplicatePersonException { - assert toAdd != null; - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Removes the equivalent person from the list. - * - * @throws PersonNotFoundException if no such person could be found in the list. - */ - public boolean remove(ReadOnlyPerson toRemove) throws PersonNotFoundException { - assert toRemove != null; - final boolean personFoundAndDeleted = internalList.remove(toRemove); - if (!personFoundAndDeleted) { - throw new PersonNotFoundException(); - } - return personFoundAndDeleted; - } - - public ObservableList getInternalList() { - return internalList; - } - - @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 - && this.internalList.equals( - ((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.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 5bcffdb5ddf1..000000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.tag; - - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * 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_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; - - public String tagName; - - public Tag() { - } - - /** - * Validates given tag name. - * - * @throws IllegalValueException if the given tag name string is invalid. - */ - public Tag(String name) throws IllegalValueException { - assert name != null; - name = name.trim(); - if (!isValidTagName(name)) { - throw new IllegalValueException(MESSAGE_TAG_CONSTRAINTS); - } - this.tagName = name; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(TAG_VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && this.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/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java deleted file mode 100644 index 76fb7ff3dc5d..000000000000 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ /dev/null @@ -1,143 +0,0 @@ -package seedu.address.model.tag; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.DuplicateDataException; - -import java.util.*; - -/** - * A list of tags that enforces no nulls and uniqueness between its elements. - * - * Supports minimal set of list operations for the app's features. - * - * @see Tag#equals(Object) - * @see CollectionUtil#elementsAreUnique(Collection) - */ -public class UniqueTagList implements Iterable { - - /** - * Signals that an operation would have violated the 'no duplicates' property of the list. - */ - public static class DuplicateTagException extends DuplicateDataException { - protected DuplicateTagException() { - super("Operation would result in duplicate tags"); - } - } - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Constructs empty TagList. - */ - public UniqueTagList() {} - - /** - * Varargs/array constructor, enforces no nulls or duplicates. - */ - public UniqueTagList(Tag... tags) throws DuplicateTagException { - assert !CollectionUtil.isAnyNull((Object[]) tags); - final List initialTags = Arrays.asList(tags); - if (!CollectionUtil.elementsAreUnique(initialTags)) { - throw new DuplicateTagException(); - } - internalList.addAll(initialTags); - } - - /** - * java collections constructor, enforces no null or duplicate elements. - */ - public UniqueTagList(Collection tags) throws DuplicateTagException { - CollectionUtil.assertNoNullElements(tags); - if (!CollectionUtil.elementsAreUnique(tags)) { - throw new DuplicateTagException(); - } - internalList.addAll(tags); - } - - /** - * java set constructor, enforces no nulls. - */ - public UniqueTagList(Set tags) { - CollectionUtil.assertNoNullElements(tags); - internalList.addAll(tags); - } - - /** - * Copy constructor, insulates from changes in source. - */ - public UniqueTagList(UniqueTagList source) { - internalList.addAll(source.internalList); // insulate internal list from changes in argument - } - - /** - * All tags in this list as a Set. This set is mutable and change-insulated against the internal list. - */ - public Set toSet() { - return new HashSet<>(internalList); - } - - /** - * Replaces the Tags in this list with those in the argument tag list. - */ - public void setTags(UniqueTagList replacement) { - this.internalList.clear(); - this.internalList.addAll(replacement.internalList); - } - - /** - * Adds every tag from the argument list that does not yet exist in this list. - */ - public void mergeFrom(UniqueTagList tags) { - final Set alreadyInside = this.toSet(); - for (Tag tag : tags) { - if (!alreadyInside.contains(tag)) { - internalList.add(tag); - } - } - } - - /** - * Returns true if the list contains an equivalent Tag as the given argument. - */ - public boolean contains(Tag toCheck) { - assert toCheck != null; - return internalList.contains(toCheck); - } - - /** - * Adds a Tag to the list. - * - * @throws DuplicateTagException if the Tag to add is a duplicate of an existing Tag in the list. - */ - public void add(Tag toAdd) throws DuplicateTagException { - assert toAdd != null; - if (contains(toAdd)) { - throw new DuplicateTagException(); - } - internalList.add(toAdd); - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - public ObservableList getInternalList() { - return internalList; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniqueTagList // instanceof handles nulls - && this.internalList.equals( - ((UniqueTagList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 80033086985b..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,44 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -import java.io.IOException; -import java.util.Optional; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - String getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(String filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index 91002a8a821a..000000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(UserPrefs userPrefs) throws IOException; - - @Override - String getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. - * Raises {@link DataSavingExceptionEvent} if there was an error during saving. - */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index ba1f72f15c27..000000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package seedu.address.storage; - -import com.google.common.eventbus.Subscribe; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager extends ComponentManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - public StorageManager(String addressBookFilePath, String userPrefsFilePath) { - this(new XmlAddressBookStorage(addressBookFilePath), new JsonUserPrefsStorage(userPrefsFilePath)); - } - - // ================ UserPrefs methods ============================== - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(UserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public String getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(String filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - - - @Override - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); - try { - saveAddressBook(event.data); - } catch (IOException e) { - raise(new DataSavingExceptionEvent(e)); - } - } - -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index f2167ec201b4..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,68 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.*; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import javax.xml.bind.annotation.XmlElement; -import java.util.ArrayList; -import java.util.List; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List tagged = new ArrayList<>(); - - /** - * No-arg constructor for JAXB use. - */ - public XmlAdaptedPerson() {} - - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(ReadOnlyPerson source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = new ArrayList<>(); - for (Tag tag : source.getTags()) { - tagged.add(new XmlAdaptedTag(tag)); - } - } - - /** - * Converts this jaxb-friendly adapted person object into the model's 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 (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - final Name name = new Name(this.name); - final Phone phone = new Phone(this.phone); - final Email email = new Email(this.email); - final Address address = new Address(this.address); - final UniqueTagList tags = new UniqueTagList(personTags); - return new Person(name, phone, email, address, tags); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java deleted file mode 100644 index b9723fafbc67..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ /dev/null @@ -1,40 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -import javax.xml.bind.annotation.XmlValue; - -/** - * JAXB-friendly adapted version of the Tag. - */ -public class XmlAdaptedTag { - - @XmlValue - public String tagName; - - /** - * No-arg constructor for JAXB use. - */ - public XmlAdaptedTag() {} - - /** - * Converts a given Tag into this class for JAXB use. - * - * @param source future changes to this will not affect the created - */ - public XmlAdaptedTag(Tag source) { - tagName = source.tagName; - } - - /** - * Converts this jaxb-friendly adapted tag object into the model's Tag object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Tag toModelType() throws IllegalValueException { - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index 30cb00270cc4..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,73 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private String filePath; - - public XmlAddressBookStorage(String filePath){ - this.filePath = filePath; - } - - public String getAddressBookFilePath(){ - return filePath; - } - - /** - * Similar to {@link #readAddressBook()} - * @param filePath location of the data. Cannot be null - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(String filePath) throws DataConversionException, FileNotFoundException { - assert filePath != null; - - File addressBookFile = new File(filePath); - - if (!addressBookFile.exists()) { - logger.info("AddressBook file " + addressBookFile + " not found"); - return Optional.empty(); - } - - ReadOnlyAddressBook addressBookOptional = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); - - return Optional.of(addressBookOptional); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} - * @param filePath location of the data. Cannot be null - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - assert addressBook != null; - assert filePath != null; - - File file = new File(filePath); - FileUtil.createIfMissing(file); - XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index b7ec533a3a1e..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,88 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook implements ReadOnlyAddressBook { - - @XmlElement - private List persons; - @XmlElement - private List tags; - - { - persons = new ArrayList<>(); - tags = new ArrayList<>(); - } - - /** - * Empty constructor required for marshalling - */ - public XmlSerializableAddressBook() {} - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - tags = src.getTagList(); - } - - @Override - public UniqueTagList getUniqueTagList() { - try { - return new UniqueTagList(tags); - } catch (UniqueTagList.DuplicateTagException e) { - //TODO: better error handling - e.printStackTrace(); - return null; - } - } - - @Override - public UniquePersonList getUniquePersonList() { - UniquePersonList lists = new UniquePersonList(); - for (XmlAdaptedPerson p : persons) { - try { - lists.add(p.toModelType()); - } catch (IllegalValueException e) { - //TODO: better error handling - } - } - return lists; - } - - @Override - public List getPersonList() { - return persons.stream().map(p -> { - try { - return p.toModelType(); - } catch (IllegalValueException e) { - e.printStackTrace(); - //TODO: better error handling - return null; - } - }).collect(Collectors.toCollection(ArrayList::new)); - } - - @Override - public List getTagList() { - return Collections.unmodifiableList(tags); - } - -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java deleted file mode 100644 index 2e1409a3016c..000000000000 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ /dev/null @@ -1,114 +0,0 @@ -package seedu.address.ui; - -import com.google.common.eventbus.Subscribe; -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.SplitPane; -import javafx.scene.control.TextField; -import javafx.scene.layout.AnchorPane; -import javafx.stage.Stage; -import seedu.address.commons.events.ui.IncorrectCommandAttemptedEvent; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.*; -import seedu.address.commons.util.FxViewUtil; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; - -public class CommandBox extends UiPart { - private final Logger logger = LogsCenter.getLogger(CommandBox.class); - private static final String FXML = "CommandBox.fxml"; - - private AnchorPane placeHolderPane; - private AnchorPane commandPane; - private ResultDisplay resultDisplay; - String previousCommandTest; - - private Logic logic; - - @FXML - private TextField commandTextField; - private CommandResult mostRecentResult; - - public static CommandBox load(Stage primaryStage, AnchorPane commandBoxPlaceholder, - ResultDisplay resultDisplay, Logic logic) { - CommandBox commandBox = UiPartLoader.loadUiPart(primaryStage, commandBoxPlaceholder, new CommandBox()); - commandBox.configure(resultDisplay, logic); - commandBox.addToPlaceholder(); - return commandBox; - } - - public void configure(ResultDisplay resultDisplay, Logic logic) { - this.resultDisplay = resultDisplay; - this.logic = logic; - registerAsAnEventHandler(this); - } - - private void addToPlaceholder() { - SplitPane.setResizableWithParent(placeHolderPane, false); - placeHolderPane.getChildren().add(commandTextField); - FxViewUtil.applyAnchorBoundaryParameters(commandPane, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, 0.0, 0.0); - } - - @Override - public void setNode(Node node) { - commandPane = (AnchorPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - @Override - public void setPlaceholder(AnchorPane pane) { - this.placeHolderPane = pane; - } - - - @FXML - private void handleCommandInputChanged() { - //Take a copy of the command text - previousCommandTest = commandTextField.getText(); - - /* We assume the command is correct. If it is incorrect, the command box will be changed accordingly - * in the event handling code {@link #handleIncorrectCommandAttempted} - */ - setStyleToIndicateCorrectCommand(); - mostRecentResult = logic.execute(previousCommandTest); - resultDisplay.postMessage(mostRecentResult.feedbackToUser); - logger.info("Result: " + mostRecentResult.feedbackToUser); - } - - - /** - * Sets the command box style to indicate a correct command. - */ - private void setStyleToIndicateCorrectCommand() { - commandTextField.getStyleClass().remove("error"); - commandTextField.setText(""); - } - - @Subscribe - private void handleIncorrectCommandAttempted(IncorrectCommandAttemptedEvent event){ - logger.info(LogsCenter.getEventHandlingLogMessage(event,"Invalid command: " + previousCommandTest)); - setStyleToIndicateIncorrectCommand(); - restoreCommandText(); - } - - /** - * Restores the command box text to the previously entered command - */ - private void restoreCommandText() { - commandTextField.setText(previousCommandTest); - } - - /** - * Sets the command box style to indicate an error - */ - private void setStyleToIndicateIncorrectCommand() { - commandTextField.getStyleClass().add("error"); - } - -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 2c76aced3b04..000000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,196 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.control.MenuItem; -import javafx.scene.input.KeyCombination; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.VBox; -import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String ICON = "/images/address_book_32.png"; - private static final String FXML = "MainWindow.fxml"; - public static final int MIN_HEIGHT = 600; - public static final int MIN_WIDTH = 450; - - private Logic logic; - - // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private StatusBarFooter statusBarFooter; - private CommandBox commandBox; - private Config config; - private UserPrefs userPrefs; - - // Handles to elements of this Ui container - private VBox rootLayout; - private Scene scene; - - private String addressBookName; - - @FXML - private AnchorPane browserPlaceholder; - - @FXML - private AnchorPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private AnchorPane personListPanelPlaceholder; - - @FXML - private AnchorPane resultDisplayPlaceholder; - - @FXML - private AnchorPane statusbarPlaceholder; - - - public MainWindow() { - super(); - } - - @Override - public void setNode(Node node) { - rootLayout = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { - - MainWindow mainWindow = UiPartLoader.loadUiPart(primaryStage, new MainWindow()); - mainWindow.configure(config.getAppTitle(), config.getAddressBookName(), config, prefs, logic); - return mainWindow; - } - - private void configure(String appTitle, String addressBookName, Config config, UserPrefs prefs, - Logic logic) { - - //Set dependencies - this.logic = logic; - this.addressBookName = addressBookName; - this.config = config; - this.userPrefs = prefs; - - //Configure the UI - setTitle(appTitle); - setIcon(ICON); - setWindowMinSize(); - setWindowDefaultSize(prefs); - scene = new Scene(rootLayout); - primaryStage.setScene(scene); - - setAccelerators(); - } - - private void setAccelerators() { - helpMenuItem.setAccelerator(KeyCombination.valueOf("F1")); - } - - void fillInnerParts() { - browserPanel = BrowserPanel.load(browserPlaceholder); - personListPanel = PersonListPanel.load(primaryStage, getPersonListPlaceholder(), logic.getFilteredPersonList()); - resultDisplay = ResultDisplay.load(primaryStage, getResultDisplayPlaceholder()); - statusBarFooter = StatusBarFooter.load(primaryStage, getStatusbarPlaceholder(), config.getAddressBookFilePath()); - commandBox = CommandBox.load(primaryStage, getCommandBoxPlaceholder(), resultDisplay, logic); - } - - private AnchorPane getCommandBoxPlaceholder() { - return commandBoxPlaceholder; - } - - private AnchorPane getStatusbarPlaceholder() { - return statusbarPlaceholder; - } - - private AnchorPane getResultDisplayPlaceholder() { - return resultDisplayPlaceholder; - } - - public AnchorPane getPersonListPlaceholder() { - return personListPanelPlaceholder; - } - - public void hide() { - primaryStage.hide(); - } - - private void setTitle(String appTitle) { - primaryStage.setTitle(appTitle); - } - - /** - * Sets the default size based on user preferences. - */ - protected void setWindowDefaultSize(UserPrefs prefs) { - primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); - primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); - if (prefs.getGuiSettings().getWindowCoordinates() != null) { - primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); - primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); - } - } - - private void setWindowMinSize() { - primaryStage.setMinHeight(MIN_HEIGHT); - primaryStage.setMinWidth(MIN_WIDTH); - } - - /** - * Returns the current size and the position of the main Window. - */ - public GuiSettings getCurrentGuiSetting() { - return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - } - - @FXML - public void handleHelp() { - HelpWindow helpWindow = HelpWindow.load(primaryStage); - helpWindow.show(); - } - - public void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - raise(new ExitAppRequestEvent()); - } - - public PersonListPanel getPersonListPanel() { - return this.personListPanel; - } - - public void loadPersonPage(ReadOnlyPerson person) { - browserPanel.loadPersonPage(person); - } - - public void releaseResources() { - browserPanel.freeResources(); - } -} 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 259e9ad0d333..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.HBox; -import seedu.address.model.person.ReadOnlyPerson; - -public class PersonCard extends UiPart{ - - private static final String FXML = "PersonListCard.fxml"; - - @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 Label tags; - - private ReadOnlyPerson person; - private int displayedIndex; - - public PersonCard(){ - - } - - public static PersonCard load(ReadOnlyPerson person, int displayedIndex){ - PersonCard card = new PersonCard(); - card.person = person; - card.displayedIndex = displayedIndex; - return UiPartLoader.loadUiPart(card); - } - - @FXML - public void initialize() { - name.setText(person.getName().fullName); - id.setText(displayedIndex + ". "); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - tags.setText(person.tagsString()); - } - - public HBox getLayout() { - return cardPane; - } - - @Override - public void setNode(Node node) { - cardPane = (HBox)node; - } - - @Override - public String getFxmlPath() { - return FXML; - } -} 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 27d9381c47b5..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,108 +0,0 @@ -package seedu.address.ui; - -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.control.SplitPane; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.VBox; -import javafx.stage.Stage; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - private static final String FXML = "PersonListPanel.fxml"; - private VBox panel; - private AnchorPane placeHolderPane; - - @FXML - private ListView personListView; - - public PersonListPanel() { - super(); - } - - @Override - public void setNode(Node node) { - panel = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - @Override - public void setPlaceholder(AnchorPane pane) { - this.placeHolderPane = pane; - } - - public static PersonListPanel load(Stage primaryStage, AnchorPane personListPlaceholder, - ObservableList personList) { - PersonListPanel personListPanel = - UiPartLoader.loadUiPart(primaryStage, personListPlaceholder, new PersonListPanel()); - personListPanel.configure(personList); - return personListPanel; - } - - private void configure(ObservableList personList) { - setConnections(personList); - addToPlaceholder(); - } - - private void setConnections(ObservableList personList) { - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void addToPlaceholder() { - SplitPane.setResizableWithParent(placeHolderPane, false); - placeHolderPane.getChildren().add(panel); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - public void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - class PersonListViewCell extends ListCell { - - public PersonListViewCell() { - } - - @Override - protected void updateItem(ReadOnlyPerson person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(PersonCard.load(person, getIndex() + 1).getLayout()); - } - } - } - -} diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/savvytasker/MainApp.java similarity index 65% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/savvytasker/MainApp.java index 36dc72a74b7a..878815f4a358 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/savvytasker/MainApp.java @@ -1,38 +1,43 @@ -package seedu.address; +package seedu.savvytasker; -import com.google.common.eventbus.Subscribe; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.*; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; - -import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; +import com.google.common.eventbus.Subscribe; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.core.Version; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; +import seedu.savvytasker.commons.events.ui.ExitAppRequestEvent; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.ConfigUtil; +import seedu.savvytasker.commons.util.StringUtil; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.logic.LogicManager; +import seedu.savvytasker.model.Model; +import seedu.savvytasker.model.ModelManager; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.UserPrefs; +import seedu.savvytasker.storage.Storage; +import seedu.savvytasker.storage.StorageManager; +import seedu.savvytasker.ui.Ui; +import seedu.savvytasker.ui.UiManager; + /** * The main entry point to the application. */ public class MainApp extends Application { private static final Logger logger = LogsCenter.getLogger(MainApp.class); - public static final Version VERSION = new Version(1, 0, 0, true); + public static final Version VERSION = new Version(0, 4, 0, true); protected Ui ui; protected Logic logic; @@ -40,16 +45,18 @@ public class MainApp extends Application { protected Model model; protected Config config; protected UserPrefs userPrefs; - + protected static MainApp instance; + public MainApp() {} @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing Savvy Tasker ]==========================="); super.init(); + instance = this; config = initConfig(getApplicationParameter("config")); - storage = new StorageManager(config.getAddressBookFilePath(), config.getUserPrefsFilePath()); + storage = new StorageManager(config.getSavvyTaskerFilePath(), config.getUserPrefsFilePath()); userPrefs = initPrefs(config); @@ -62,31 +69,32 @@ public void init() throws Exception { ui = new UiManager(logic, config, userPrefs); initEventsCenter(); + } - + private String getApplicationParameter(String parameterName){ Map applicationParameters = getParameters().getNamed(); return applicationParameters.get(parameterName); } private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional savvyTaskerOptional; + ReadOnlySavvyTasker initialData; try { - addressBookOptional = storage.readAddressBook(); - if(!addressBookOptional.isPresent()){ - logger.info("Data file not found. Will be starting with an empty AddressBook"); + savvyTaskerOptional = storage.readSavvyTasker(); + if(!savvyTaskerOptional.isPresent()){ + logger.info("Data file not found. Will be starting with an empty Savvy Tasker"); } - initialData = addressBookOptional.orElse(new AddressBook()); + initialData = savvyTaskerOptional.orElse(new SavvyTasker()); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty Savvy Tasker"); + initialData = new SavvyTasker(); } catch (IOException e) { - logger.warning("Problem while reading from the file. . Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. . Will be starting with an empty Savvy Tasker"); + initialData = new SavvyTasker(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialData); } private void initLogging(Config config) { @@ -139,7 +147,7 @@ protected UserPrefs initPrefs(Config config) { "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. . Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. . Will be starting with an empty Savvy Tasker"); initializedPrefs = new UserPrefs(); } @@ -159,13 +167,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting Savvy Tasker " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Savvy Tasker ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); @@ -175,6 +183,22 @@ public void stop() { Platform.exit(); System.exit(0); } + + //@@author A0138431L + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + try { + String configPath = getApplicationParameter("config"); + if(configPath == null) { + configPath = Config.DEFAULT_CONFIG_FILE; + } + config.setSavvyTaskerFilePath(dslce.newPath); + ConfigUtil.saveConfig(config, configPath); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + } + //@@author @Subscribe public void handleExitAppRequestEvent(ExitAppRequestEvent event) { diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/savvytasker/commons/core/ComponentManager.java similarity index 85% rename from src/main/java/seedu/address/commons/core/ComponentManager.java rename to src/main/java/seedu/savvytasker/commons/core/ComponentManager.java index 4bc8564e5824..1466655fd288 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/savvytasker/commons/core/ComponentManager.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package seedu.savvytasker.commons.core; -import seedu.address.commons.events.BaseEvent; +import seedu.savvytasker.commons.events.BaseEvent; /** * Base class for *Manager classes diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/savvytasker/commons/core/Config.java similarity index 65% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/savvytasker/commons/core/Config.java index 6441c9ef20f4..56b65d4bf159 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/savvytasker/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.savvytasker.commons.core; import java.util.Objects; import java.util.logging.Level; @@ -11,12 +11,11 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "Savvy Tasker"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; - private String addressBookFilePath = "data/addressbook.xml"; - private String addressBookName = "MyAddressBook"; - + private String savvyTaskerFilePath = "data/savvytasker.xml"; + private String savvyTaskerListName = "MyTaskList"; public Config() { } @@ -45,23 +44,22 @@ public void setUserPrefsFilePath(String userPrefsFilePath) { this.userPrefsFilePath = userPrefsFilePath; } - public String getAddressBookFilePath() { - return addressBookFilePath; + public String getSavvyTaskerFilePath() { + return savvyTaskerFilePath; } - public void setAddressBookFilePath(String addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setSavvyTaskerFilePath(String savvyTaskerFilePath) { + this.savvyTaskerFilePath = savvyTaskerFilePath; } - public String getAddressBookName() { - return addressBookName; + public String getSavvyTaskerListName() { + return savvyTaskerListName; } - public void setAddressBookName(String addressBookName) { - this.addressBookName = addressBookName; + public void setSavvyTaskerName(String savvyTaskerName) { + this.savvyTaskerListName = savvyTaskerName; } - @Override public boolean equals(Object other) { if (other == this){ @@ -76,13 +74,13 @@ public boolean equals(Object other) { return Objects.equals(appTitle, o.appTitle) && Objects.equals(logLevel, o.logLevel) && Objects.equals(userPrefsFilePath, o.userPrefsFilePath) - && Objects.equals(addressBookFilePath, o.addressBookFilePath) - && Objects.equals(addressBookName, o.addressBookName); + && Objects.equals(savvyTaskerFilePath, o.savvyTaskerFilePath) + && Objects.equals(savvyTaskerListName, o.savvyTaskerListName); } @Override public int hashCode() { - return Objects.hash(appTitle, logLevel, userPrefsFilePath, addressBookFilePath, addressBookName); + return Objects.hash(appTitle, logLevel, userPrefsFilePath, savvyTaskerFilePath, savvyTaskerListName); } @Override @@ -91,8 +89,8 @@ public String toString(){ sb.append("App title : " + appTitle); sb.append("\nCurrent log level : " + logLevel); sb.append("\nPreference file Location : " + userPrefsFilePath); - sb.append("\nLocal data file location : " + addressBookFilePath); - sb.append("\nAddressBook name : " + addressBookName); + sb.append("\nLocal data file location : " + savvyTaskerFilePath); + sb.append("\nSavvy Tasker List name : " + savvyTaskerListName); return sb.toString(); } diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/seedu/savvytasker/commons/core/EventsCenter.java similarity index 91% rename from src/main/java/seedu/address/commons/core/EventsCenter.java rename to src/main/java/seedu/savvytasker/commons/core/EventsCenter.java index 9652cd5c227b..2fc38e500309 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/seedu/savvytasker/commons/core/EventsCenter.java @@ -1,9 +1,10 @@ -package seedu.address.commons.core; +package seedu.savvytasker.commons.core; + +import java.util.logging.Logger; import com.google.common.eventbus.EventBus; -import seedu.address.commons.events.BaseEvent; -import java.util.logging.Logger; +import seedu.savvytasker.commons.events.BaseEvent; /** * Manages the event dispatching of the app. diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/savvytasker/commons/core/GuiSettings.java similarity index 92% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/savvytasker/commons/core/GuiSettings.java index e157ac8b8679..70a000c20a18 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/savvytasker/commons/core/GuiSettings.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package seedu.savvytasker.commons.core; -import java.awt.*; +import java.awt.Point; import java.io.Serializable; import java.util.Objects; @@ -9,6 +9,10 @@ */ public class GuiSettings implements Serializable { + /** + * Generated serial + */ + private static final long serialVersionUID = -7442507632162266083L; private static final double DEFAULT_HEIGHT = 600; private static final double DEFAULT_WIDTH = 740; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/savvytasker/commons/core/LogsCenter.java similarity index 92% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/savvytasker/commons/core/LogsCenter.java index 17939bab4975..bf86bc58c263 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/savvytasker/commons/core/LogsCenter.java @@ -1,9 +1,14 @@ -package seedu.address.commons.core; - -import seedu.address.commons.events.BaseEvent; +package seedu.savvytasker.commons.core; import java.io.IOException; -import java.util.logging.*; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import seedu.savvytasker.commons.events.BaseEvent; /** * Configures and manages loggers and handlers, including their logging level diff --git a/src/main/java/seedu/savvytasker/commons/core/Messages.java b/src/main/java/seedu/savvytasker/commons/core/Messages.java new file mode 100644 index 000000000000..bb130b8ebb7e --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/core/Messages.java @@ -0,0 +1,15 @@ +package seedu.savvytasker.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command! \n%1$s"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_ALIASES_LISTED_OVERVIEW = "%1$d aliases listed!"; + public static final String MESSAGE_INVALID_START_END = "The end time cannot be earlier than the start time"; + +} diff --git a/src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java b/src/main/java/seedu/savvytasker/commons/core/UnmodifiableObservableList.java similarity index 97% rename from src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java rename to src/main/java/seedu/savvytasker/commons/core/UnmodifiableObservableList.java index 5c25d8647a8d..17ca7991be01 100644 --- a/src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java +++ b/src/main/java/seedu/savvytasker/commons/core/UnmodifiableObservableList.java @@ -1,18 +1,23 @@ -package seedu.address.commons.core; - -import javafx.beans.InvalidationListener; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import javafx.collections.transformation.SortedList; +package seedu.savvytasker.commons.core; import java.text.Collator; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Stream; +import javafx.beans.InvalidationListener; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; + /** * Unmodifiable view of an observable list */ @@ -95,7 +100,7 @@ public final SortedList sorted(Comparator comparator) { public final SortedList sorted() { return sorted(Comparator.nullsFirst((o1, o2) -> { if (o1 instanceof Comparable) { - return ((Comparable) o1).compareTo(o2); + return ((Comparable) o1).compareTo(o2); } return Collator.getInstance().compare(o1.toString(), o2.toString()); })); diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/savvytasker/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/savvytasker/commons/core/Version.java index 7ecb85b18f82..69bd28883b19 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/savvytasker/commons/core/Version.java @@ -1,11 +1,11 @@ -package seedu.address.commons.core; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; +package seedu.savvytasker.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Represents a version with major, minor and patch number */ diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/seedu/savvytasker/commons/events/BaseEvent.java similarity index 89% rename from src/main/java/seedu/address/commons/events/BaseEvent.java rename to src/main/java/seedu/savvytasker/commons/events/BaseEvent.java index 723a9c69fbd5..dc63fb70ebcd 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/BaseEvent.java @@ -1,4 +1,4 @@ -package seedu.address.commons.events; +package seedu.savvytasker.commons.events; public abstract class BaseEvent { diff --git a/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java new file mode 100644 index 000000000000..c3c2ff8a129b --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java @@ -0,0 +1,43 @@ +//@@author A0139916U +package seedu.savvytasker.commons.events.model; + +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.model.alias.AliasSymbol; + +/** + * Represents an event where the user has added an alias symbol. + */ +public class AliasSymbolChangedEvent extends BaseEvent { + /** + * The action that was performed, i.e. the symbol was added or removed. + */ + public enum Action { + Added, + Removed; + } + + private final AliasSymbol symbol; + private final Action action; + + public AliasSymbolChangedEvent(AliasSymbol symbol, Action action) { + assert symbol != null; + assert action != null; + + this.symbol = symbol; + this.action = action; + } + + @Override + public String toString() { + return "Alias symbol " + action.toString() + ": " + symbol.toString(); + } + + public AliasSymbol getSymbol() { + return this.symbol; + } + + public Action getAction() { + return this.action; + } + +} diff --git a/src/main/java/seedu/savvytasker/commons/events/model/SavvyTaskerChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/model/SavvyTaskerChangedEvent.java new file mode 100644 index 000000000000..d6038c3ef833 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/model/SavvyTaskerChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.savvytasker.commons.events.model; + +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + +/** Indicates the SavvyTasker in the model has changed*/ +public class SavvyTaskerChangedEvent extends BaseEvent { + + public final ReadOnlySavvyTasker data; + + public SavvyTaskerChangedEvent(ReadOnlySavvyTasker data){ + this.data = data; + } + + @Override + public String toString() { + return "number of tasks " + data.getReadOnlyListOfTasks().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java b/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingExceptionEvent.java similarity index 77% rename from src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java rename to src/main/java/seedu/savvytasker/commons/events/storage/DataSavingExceptionEvent.java index f0a0640ee523..30e13bd50cb0 100644 --- a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingExceptionEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.storage; +package seedu.savvytasker.commons.events.storage; -import seedu.address.commons.events.BaseEvent; +import seedu.savvytasker.commons.events.BaseEvent; /** * Indicates an exception during a file saving diff --git a/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingLocationChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingLocationChangedEvent.java new file mode 100644 index 000000000000..1f5f384e1056 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingLocationChangedEvent.java @@ -0,0 +1,27 @@ +package seedu.savvytasker.commons.events.storage; + +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + +//@@author A0138431L +/** + * Indicates a change in location of the storage + */ +public class DataSavingLocationChangedEvent extends BaseEvent { + + public final ReadOnlySavvyTasker data; + public final String newPath; + + public DataSavingLocationChangedEvent(ReadOnlySavvyTasker data, String newPath) { + this.data = data; + this.newPath = newPath; + } + + @Override + public String toString() { + return "number of tasks " + data.getReadOnlyListOfTasks().size() + + " new path " + this.newPath; + } + +} +//@@author diff --git a/src/main/java/seedu/savvytasker/commons/events/ui/ChangeListRequestEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/ChangeListRequestEvent.java new file mode 100644 index 000000000000..8f1f4f7fef28 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/ui/ChangeListRequestEvent.java @@ -0,0 +1,26 @@ +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; + +/** + * Indicates a request to jump to the list of tasks + */ +public class ChangeListRequestEvent extends BaseEvent { + + public enum DisplayedList { + Task, + Alias + } + + public final DisplayedList displayedList; + + public ChangeListRequestEvent(DisplayedList displayedList) { + this.displayedList = displayedList; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/ExitAppRequestEvent.java similarity index 68% rename from src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java rename to src/main/java/seedu/savvytasker/commons/events/ui/ExitAppRequestEvent.java index 9af6194543a3..d702e3515d41 100644 --- a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/ui/ExitAppRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.savvytasker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.savvytasker.commons.events.BaseEvent; /** * Indicates a request for App termination diff --git a/src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/IncorrectCommandAttemptedEvent.java similarity index 67% rename from src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java rename to src/main/java/seedu/savvytasker/commons/events/ui/IncorrectCommandAttemptedEvent.java index 991f7ae9fa25..8ef5837c464c 100644 --- a/src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/ui/IncorrectCommandAttemptedEvent.java @@ -1,7 +1,7 @@ -package seedu.address.commons.events.ui; +package seedu.savvytasker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; -import seedu.address.logic.commands.Command; +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.logic.commands.Command; /** * Indicates an attempt to execute an incorrect command diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/JumpToListRequestEvent.java similarity index 67% rename from src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java rename to src/main/java/seedu/savvytasker/commons/events/ui/JumpToListRequestEvent.java index 0580d27aecf5..104ee70b0659 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/ui/JumpToListRequestEvent.java @@ -1,9 +1,9 @@ -package seedu.address.commons.events.ui; +package seedu.savvytasker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.savvytasker.commons.events.BaseEvent; /** - * Indicates a request to jump to the list of persons + * Indicates a request to jump to the list of tasks */ public class JumpToListRequestEvent extends BaseEvent { diff --git a/src/main/java/seedu/savvytasker/commons/events/ui/ShowCheatsheetEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/ShowCheatsheetEvent.java new file mode 100644 index 000000000000..30ffd9d6605e --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/ui/ShowCheatsheetEvent.java @@ -0,0 +1,14 @@ +//@@author A0138431L + +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; + +/** Indicates cheatsheet display has been toggled */ +public class ShowCheatsheetEvent extends BaseEvent { + @Override + public String toString() { + return "Cheatsheet display has been toggled"; + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/ShowHelpRequestEvent.java similarity index 68% rename from src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java rename to src/main/java/seedu/savvytasker/commons/events/ui/ShowHelpRequestEvent.java index a7e40940b2c7..8ffda6634aba 100644 --- a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/ui/ShowHelpRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.savvytasker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.savvytasker.commons.events.BaseEvent; /** * An event requesting to view the help page. diff --git a/src/main/java/seedu/savvytasker/commons/events/ui/TaskPanelSelectionChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/TaskPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..317fa9e493ba --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/ui/TaskPanelSelectionChangedEvent.java @@ -0,0 +1,29 @@ +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.model.task.ReadOnlyTask; + +//@@author A0139915W + +/** + * Represents a selection change in the Task List Panel + */ +public class TaskPanelSelectionChangedEvent extends BaseEvent { + + + private final ReadOnlyTask newSelection; + + public TaskPanelSelectionChangedEvent(ReadOnlyTask newSelection){ + this.newSelection = newSelection; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public ReadOnlyTask getNewSelection() { + return newSelection; + } +} +//@@author \ No newline at end of file diff --git a/src/main/java/seedu/savvytasker/commons/events/ui/WeekSelectionChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/WeekSelectionChangedEvent.java new file mode 100644 index 000000000000..dc74c7311f14 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/ui/WeekSelectionChangedEvent.java @@ -0,0 +1,16 @@ +//@@author A0138431L + +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; + +/** Indicates the SavvyTasker in the model has changed*/ + +public class WeekSelectionChangedEvent extends BaseEvent { + + @Override + public String toString() { + return "Selected week has been changed"; + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/savvytasker/commons/exceptions/DataConversionException.java similarity index 58% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/savvytasker/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..087f61d9b771 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/savvytasker/commons/exceptions/DataConversionException.java @@ -1,9 +1,14 @@ -package seedu.address.commons.exceptions; +package seedu.savvytasker.commons.exceptions; /** * Represents an error during conversion of data from one format to another */ public class DataConversionException extends Exception { + /** + * Generated serial + */ + private static final long serialVersionUID = -5908631495910190437L; + public DataConversionException(Exception cause) { super(cause); } diff --git a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java b/src/main/java/seedu/savvytasker/commons/exceptions/DuplicateDataException.java similarity index 60% rename from src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java rename to src/main/java/seedu/savvytasker/commons/exceptions/DuplicateDataException.java index 17aa63d5020c..e096adf6b272 100644 --- a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java +++ b/src/main/java/seedu/savvytasker/commons/exceptions/DuplicateDataException.java @@ -1,9 +1,14 @@ -package seedu.address.commons.exceptions; +package seedu.savvytasker.commons.exceptions; /** * Signals an error caused by duplicate data where there should be none. */ public abstract class DuplicateDataException extends IllegalValueException { + /** + * Generated serial + */ + private static final long serialVersionUID = 5045611929659070003L; + public DuplicateDataException(String message) { super(message); } diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/savvytasker/commons/exceptions/IllegalValueException.java similarity index 66% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/savvytasker/commons/exceptions/IllegalValueException.java index a473b43bd86f..fd5295f907a3 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/savvytasker/commons/exceptions/IllegalValueException.java @@ -1,9 +1,14 @@ -package seedu.address.commons.exceptions; +package seedu.savvytasker.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. */ public class IllegalValueException extends Exception { + /** + * Generated serial + */ + private static final long serialVersionUID = 193885632897265498L; + /** * @param message should contain relevant information on the failed constraint(s) */ diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/savvytasker/commons/util/AppUtil.java similarity index 79% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/savvytasker/commons/util/AppUtil.java index 649cc19aaeda..fd702bc03eb7 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/AppUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.savvytasker.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/savvytasker/commons/util/ConfigUtil.java similarity index 90% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/savvytasker/commons/util/ConfigUtil.java index af42e03df06c..8a3fbade09b6 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/ConfigUtil.java @@ -1,14 +1,14 @@ -package seedu.address.commons.util; - -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +package seedu.savvytasker.commons.util; import java.io.File; import java.io.IOException; import java.util.Optional; import java.util.logging.Logger; +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.exceptions.DataConversionException; + /** * A class for accessing the Config File. */ diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/savvytasker/commons/util/FileUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/savvytasker/commons/util/FileUtil.java index ca8221250de4..f5eb599f2cfe 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import java.io.File; import java.io.IOException; @@ -14,11 +14,11 @@ public static boolean isFileExists(File file) { return file.exists() && file.isFile(); } - public static void createIfMissing(File file) throws IOException { - if (!isFileExists(file)) { - createFile(file); - } + //@@author A0139915W + public static boolean createIfMissing(File file) throws IOException { + return createFile(file); } + //@@author /** * Creates a file if it does not exist along with its missing parent directories diff --git a/src/main/java/seedu/address/commons/util/FxViewUtil.java b/src/main/java/seedu/savvytasker/commons/util/FxViewUtil.java similarity index 92% rename from src/main/java/seedu/address/commons/util/FxViewUtil.java rename to src/main/java/seedu/savvytasker/commons/util/FxViewUtil.java index 900efa6bf5c3..a582e8dbe100 100644 --- a/src/main/java/seedu/address/commons/util/FxViewUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/FxViewUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/savvytasker/commons/util/JsonUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/savvytasker/commons/util/JsonUtil.java index 80b67de5b7e8..2b81ec5f6a93 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/JsonUtil.java @@ -1,4 +1,7 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; + +import java.io.IOException; +import java.util.logging.Level; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; @@ -11,15 +14,17 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import java.io.IOException; -import java.util.logging.Level; - /** * Converts a Java object instance to JSON and vice versa */ public class JsonUtil { private static class LevelDeserializer extends FromStringDeserializer { + /** + * Generated serial + */ + private static final long serialVersionUID = 7901046138205615293L; + protected LevelDeserializer(Class vc) { super(vc); } diff --git a/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java b/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java new file mode 100644 index 000000000000..5eeac6948a67 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java @@ -0,0 +1,170 @@ +package seedu.savvytasker.commons.util; + +import java.util.Calendar; +import java.util.Date; + +import seedu.savvytasker.logic.parser.DateParser.InferredDate; + +//@@author A0139915W +/** + * Helper functions for handling dates. + */ + +public class SmartDefaultDates { + + private Date startDateTime; + private Date endDateTime; + private final Calendar calendar; + private final Calendar today; + + /** + * Determines the smart defaults for the dates provided. Can set both + * start and end dates as null to get a basic smart default. + * @param startDateTime Starting date time + * @param endDateTime Ending date time + */ + public SmartDefaultDates(InferredDate startDateTime, InferredDate endDateTime) { + calendar = Calendar.getInstance(); + today = Calendar.getInstance(); + today.setTime(new Date()); + if (startDateTime == null && endDateTime != null) { + // apply smart default for endDateTime only + parseEnd(endDateTime); + } else if (startDateTime != null && endDateTime == null) { + // apply smart default for startDateTime only + parseStart(startDateTime); + } else if (startDateTime != null && endDateTime != null) { + parseStartAndEnd(startDateTime, endDateTime); + } + } + + /** + * Gets the smart defaults for end date. + * + * If the date is not supplied, the date will default to today. + * If the time is not supplied, the time will default to 2359:59 on the specified date. + * If both date and time are not supplied, the date returned will be null. + * @param today the time now + * @param endDateTime the end time to parse + */ + public Date getEnd(InferredDate endDateTime) { + if (endDateTime == null) return null; + calendar.setTime(endDateTime.getInferredDateTime()); + if (endDateTime.isDateInferred() && endDateTime.isTimeInferred()) { + // remove date field + return null; + } else if (endDateTime.isDateInferred()) { + calendar.set(Calendar.DATE, today.get(Calendar.DATE)); + calendar.set(Calendar.MONTH, today.get(Calendar.MONTH)); + calendar.set(Calendar.YEAR, today.get(Calendar.YEAR)); + } else if (endDateTime.isTimeInferred()) { + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); + } + return calendar.getTime(); + } + + /** + * Sets the starting and ending date/time based on defaults for providing only + * the end time + * @param endDateTime end time supplied + */ + private void parseEnd(InferredDate endDateTime) { + assert endDateTime.getInferredDateTime() != null; + Date start = new Date(); + this.endDateTime = getEnd(endDateTime); + + // Since only end date is supplied, the task is considered to start at 12am today + calendar.setTime(start); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + this.startDateTime = calendar.getTime(); + + if (this.startDateTime.compareTo(this.endDateTime) > 0) { + // end date is before today, leave start date as null + this.startDateTime = null; + } + } + + + /** + * Gets the smart default for start date. + * + * If the date is not supplied, the date will default to today. + * If the time is not supplied, the time will default to 0000:00 on the specified date. + * If both date and time are not supplied, the date returned will be null. + * @param today the time now + * @param startDateTime the start time to parse + * @return + */ + public Date getStart(InferredDate startDateTime) { + if (startDateTime == null) return null; + calendar.setTime(startDateTime.getInferredDateTime()); + if (startDateTime.isDateInferred() && startDateTime.isTimeInferred()) { + // user didn't specify anything + // remove date field + return null; + } else if (startDateTime.isDateInferred()) { + calendar.set(Calendar.DATE, today.get(Calendar.DATE)); + calendar.set(Calendar.MONTH, today.get(Calendar.MONTH)); + calendar.set(Calendar.YEAR, today.get(Calendar.YEAR)); + } else if (startDateTime.isTimeInferred()) { + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + } + return calendar.getTime(); + } + + + /** + * Sets the starting and ending date/time based on defaults for providing only + * the start time + * @param startDateTime start time supplied + */ + private void parseStart(InferredDate startDateTime) { + assert startDateTime.getInferredDateTime() != null; + Date end = (Date)startDateTime.getInferredDateTime().clone(); + this.startDateTime = getStart(startDateTime); + + // Since only the start time is supplied, the task is considered to end today 2359 on the same day as start. + calendar.setTime(end); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); + this.endDateTime = calendar.getTime(); + } + + /** + * Sets the starting and ending date/time based on defaults for providing both + * start and end times. + * + * Note that this method has no restrictions on the starting and ending date/time. + * i.e. the starting time is later than the ending time. + * @param startDateTime start time supplied + * @param endDateTime end time supplied + */ + private void parseStartAndEnd(InferredDate startDateTime, InferredDate endDateTime) { + assert endDateTime.getInferredDateTime() != null; + assert startDateTime.getInferredDateTime() != null; + Date start = getStart(startDateTime); + Date end = getEnd(endDateTime); + this.startDateTime = start; + this.endDateTime = end; + } + + public Date getStartDate() { + return startDateTime; + } + + public Date getEndDate() { + return endDateTime; + } +} +//@@author \ No newline at end of file diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/savvytasker/commons/util/StringUtil.java similarity index 58% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/savvytasker/commons/util/StringUtil.java index 2e94740456a6..115b96e7d353 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/StringUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import java.io.PrintWriter; import java.io.StringWriter; @@ -9,6 +9,22 @@ * Helper functions for handling strings. */ public class StringUtil { + //@@author A0139915W + // reused original implementation of 'containsIgnoreCase' to find exact matches + public static boolean containsExactIgnoreCase(String source, String query) { + List strings = Arrays.asList(source); + return strings.stream().filter(s -> s.equalsIgnoreCase(query)).count() > 0; + } + + // reused original implementation of 'containsIgnoreCase' to find partial matches + public static boolean containsPartialIgnoreCase(String source, String query) { + if (source == null) return false; + String[] split = source.toLowerCase().split("\\s+"); + List strings = Arrays.asList(split); + return strings.stream().filter(s -> s.contains(query.toLowerCase())).count() > 0; + } + //@@author + public static boolean containsIgnoreCase(String source, String query) { String[] split = source.toLowerCase().split("\\s+"); List strings = Arrays.asList(split); diff --git a/src/main/java/seedu/address/commons/util/UrlUtil.java b/src/main/java/seedu/savvytasker/commons/util/UrlUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/UrlUtil.java rename to src/main/java/seedu/savvytasker/commons/util/UrlUtil.java index 6bbab52b9840..3a6741f30465 100644 --- a/src/main/java/seedu/address/commons/util/UrlUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/UrlUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import java.net.URL; diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/seedu/savvytasker/commons/util/XmlUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/XmlUtil.java rename to src/main/java/seedu/savvytasker/commons/util/XmlUtil.java index 2087e7628a1d..d135fd4d51a8 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/XmlUtil.java @@ -1,11 +1,12 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; + +import java.io.File; +import java.io.FileNotFoundException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; -import java.io.File; -import java.io.FileNotFoundException; /** * Helps with reading from and writing to XML files. diff --git a/src/main/java/seedu/savvytasker/logic/Logic.java b/src/main/java/seedu/savvytasker/logic/Logic.java new file mode 100755 index 000000000000..0250b878c806 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/Logic.java @@ -0,0 +1,39 @@ +package seedu.savvytasker.logic; + +import java.util.Date; + +import javafx.collections.ObservableList; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.logic.commands.CommandResult; +import seedu.savvytasker.model.task.ReadOnlyTask; + +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param commandText The command as entered by the user. + * @return the result of the command execution. + */ + CommandResult execute(String commandText); + + /** Returns the filtered list of tasks */ + ObservableList getFilteredTaskList(); + + /** Returns the filtered list of alias symbol */ + ObservableList getAliasSymbolList(); + + /** */ + boolean canParseHeader(String keyword); + + /** Returns the list of floating tasks */ + ObservableList getFilteredFloatingTasks(); + + /** Returns the list of tasks on a specific day */ + ObservableList getFilteredDailyTasks(int i, Date date); + + /** Returns the list of tasks that occur after the selected week */ + ObservableList getFilteredUpcomingTasks(Date date); + +} diff --git a/src/main/java/seedu/savvytasker/logic/LogicManager.java b/src/main/java/seedu/savvytasker/logic/LogicManager.java new file mode 100755 index 000000000000..69a3d1ffdb54 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/LogicManager.java @@ -0,0 +1,237 @@ +package seedu.savvytasker.logic; + +import java.util.Date; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.collections.ObservableList; +import seedu.savvytasker.commons.core.ComponentManager; +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.AliasSymbolChangedEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent.DisplayedList; +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.commands.CommandResult; +import seedu.savvytasker.logic.commands.ListCommand; +import seedu.savvytasker.logic.parser.AddCommandParser; +import seedu.savvytasker.logic.parser.AliasCommandParser; +import seedu.savvytasker.logic.parser.ClearCommandParser; +import seedu.savvytasker.logic.parser.DeleteCommandParser; +import seedu.savvytasker.logic.parser.ExitCommandParser; +import seedu.savvytasker.logic.parser.FindCommandParser; +import seedu.savvytasker.logic.parser.HelpCommandParser; +import seedu.savvytasker.logic.parser.ListCommandParser; +import seedu.savvytasker.logic.parser.MarkCommandParser; +import seedu.savvytasker.logic.parser.MasterParser; +import seedu.savvytasker.logic.parser.ModifyCommandParser; +import seedu.savvytasker.logic.parser.RedoCommandParser; +import seedu.savvytasker.logic.parser.StorageCommandParser; +import seedu.savvytasker.logic.parser.UnaliasCommandParser; +import seedu.savvytasker.logic.parser.UndoCommandParser; +import seedu.savvytasker.logic.parser.UnmarkCommandParser; +import seedu.savvytasker.model.Model; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.storage.Storage; + +/** + * The main LogicManager of the app. + */ +public class LogicManager extends ComponentManager implements Logic { + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + private final int MAX_UNDO_REDO_QUEUE_SIZE = 50; + + private final Model model; + private final Storage storage; + private final MasterParser parser; + private final Deque undoDeque; + private final Deque redoDeque; + + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + this.parser = new MasterParser(); + this.undoDeque = new LinkedList(); + this.redoDeque = new LinkedList(); + + registerAllDefaultCommandParsers(); + loadAllAliasSymbols(); + } + + @Override + public CommandResult execute(String commandText) { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + Command command = parser.parse(commandText); + command.setModel(model); + command.setLogic(this); + command.setStorage(storage); + + CommandResult result = command.execute(); + + if (!(command instanceof ListCommand)) { + // forcefully show the task list instead + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Task)); + } + + //@@author A0097627N + if (command.isUndo()){ + if (!undo()) { + result = new CommandResult("Cannot Undo"); + } + } + else if (command.isRedo()){ + if (!redo()) { + result = new CommandResult("Cannot Redo"); + } + } + else if (command.canUndo()){ + //@@author A0139916U + undoDeque.addLast(command); + if (undoDeque.size() > MAX_UNDO_REDO_QUEUE_SIZE) { + undoDeque.removeFirst(); + } + redoDeque.clear(); + } + //@@author + + return result; + } + + //@@author A0139915W + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + @Override + public ObservableList getAliasSymbolList() { + return parser.getAliasSymbolList(); + } + //@@author + + //@@author A0138431L + + @Override + public ObservableList getFilteredFloatingTasks() { + return model.getFilteredFloatingTasks(); + } + + @Override + public ObservableList getFilteredDailyTasks(int i, Date date) { + return model.getFilteredDailyTasks(i, date); + } + + @Override + public ObservableList getFilteredUpcomingTasks(Date date) { + return model.getFilteredUpcomingTasks(date); + } + //@@author + + //@@author A0139916U + private void registerAllDefaultCommandParsers() { + parser.registerCommandParser(new AddCommandParser()); + parser.registerCommandParser(new DeleteCommandParser()); + parser.registerCommandParser(new FindCommandParser()); + parser.registerCommandParser(new ListCommandParser()); + parser.registerCommandParser(new ModifyCommandParser()); + parser.registerCommandParser(new HelpCommandParser()); + parser.registerCommandParser(new ClearCommandParser()); + parser.registerCommandParser(new ExitCommandParser()); + parser.registerCommandParser(new MarkCommandParser()); + parser.registerCommandParser(new UnmarkCommandParser()); + parser.registerCommandParser(new UndoCommandParser()); + parser.registerCommandParser(new RedoCommandParser()); + parser.registerCommandParser(new AliasCommandParser()); + parser.registerCommandParser(new UnaliasCommandParser()); + parser.registerCommandParser(new StorageCommandParser()); + } + + private void loadAllAliasSymbols() { + List allSynbols = model.getSavvyTasker().getReadOnlyListOfAliasSymbols(); + for (AliasSymbol symbol : allSynbols) { + parser.addAliasSymbol(symbol); + } + } + + /** + * Undo last command and add it to the redo deque. + * @return true if undone successfully, false otherwise + */ + private boolean undo() { + boolean undone = false; + + if (!undoDeque.isEmpty()) { + Command command = undoDeque.removeLast(); + command.undo(); + redoDeque.addLast(command); + undone = true; + } + + return undone; + } + + /** + * Redo last command and add it to undone deque. + * @return true if redone successfully, false otherwise + */ + private boolean redo() { + boolean redone = false; + + if (!redoDeque.isEmpty()) { + Command command = redoDeque.removeLast(); + command.redo(); + undoDeque.addLast(command); + redone = true; + } + + return redone; + } + + /** + * Log the result of adding/removing a symbol in the parser. + * + * @param success if the operation succeeded + * @param changedSymbol the symbol that was involved in the operation + * @param successMsgFormat the message to print if the operation succeeded. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + * @param failureMsgFormat the message to print if the operation failed. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + */ + private void logParserSymbolChange(boolean success, AliasSymbol changedSymbol, + String successMsgFormat, String failureMsgFormat) { + if (success) { + logger.info(String.format(successMsgFormat, changedSymbol)); + } else { + logger.warning(String.format(failureMsgFormat, changedSymbol)); + } + } + + @Subscribe + public void handleAliasSymbolChangedEvent(AliasSymbolChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage( + event, "Alias symbol " + event.getAction().toString().toLowerCase())); + + if (event.getAction().equals(AliasSymbolChangedEvent.Action.Added)) { + logParserSymbolChange( + parser.addAliasSymbol(event.getSymbol()), + event.getSymbol(), + "Added alias symbol '%s' to parser", + "Failed to add alias symbol '%s' to parser"); + } else if (event.getAction().equals(AliasSymbolChangedEvent.Action.Removed)) { + logParserSymbolChange( + parser.removeAliasSymbol(event.getSymbol().getKeyword()), + event.getSymbol(), + "Removed alias symbol '%s' from parser", + "Failed to remove alias symbol '%s' from parser"); + } + } + + @Override + public boolean canParseHeader(String header) { + return parser.isCommandParserRegistered(header); + } +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java b/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java new file mode 100644 index 000000000000..faed3f637a21 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java @@ -0,0 +1,182 @@ +package seedu.savvytasker.logic.commands; + +import java.util.Iterator; +import java.util.LinkedList; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.RecurrenceType; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +/** + * Adds a person to the address book. + */ +public class AddCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to Savvy Tasker. " + + "Parameters: TASK_NAME [s/START_DATE] [st/START_TIME] [e/END_DATE] [et/END_TIME] [l/LOCATION] [p/PRIORITY_LEVEL]" + + "[r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]\n" + + "Example: " + COMMAND_WORD + + " Project Meeting s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103 d/Discuss about roles and milestones"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task list"; + public static final String MESSAGE_INVALID_START_END = "The end time cannot be earlier than the start time"; + + private final String taskName; + private final InferredDate startDateTime; + private final InferredDate endDateTime; + private final String location; + private final PriorityLevel priority; + private final RecurrenceType recurringType; + private final Integer numberOfRecurrence; + private final String category; + private final String description; + + private Task toAdd; + private LinkedList tasksAdded; + + //@@author A0139915W + /** + * Creates an add command. + */ + public AddCommand(String taskName, InferredDate startDateTime, InferredDate endDateTime, + String location, PriorityLevel priority, RecurrenceType recurringType, + Integer numberOfRecurrence, String category, String description) { + this.taskName = taskName; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.location = location; + this.priority = priority; + this.recurringType = recurringType; + this.numberOfRecurrence = numberOfRecurrence; + this.category = category; + this.description = description; + tasksAdded = new LinkedList(); + } + + private void createTask() { + final boolean isArchived = false; // all tasks are first added as active tasks + final int taskId = 0; // taskId to be assigned by ModelManager, leave as 0 + final int groupId = 0; // groupId to be assigned by ModelManager, leave as 0 + + this.toAdd = new Task(taskId, groupId, taskName, startDateTime, endDateTime, + location, priority, recurringType, numberOfRecurrence, + category, description, isArchived); + } + + private void addToListOfTasksAdded(Task... tasks) { + for (Task t : tasks) { + tasksAdded.add(t); + } + } + + @Override + public CommandResult execute() { + assert model != null; + createTask(); + + try { + Task taskAdded = null; + if (toAdd.getRecurringType() == RecurrenceType.None) { + // not a recurring task, add a single task + taskAdded = model.addTask(toAdd); + addToListOfTasksAdded(taskAdded); + } else { + // a recurring task, add a group of recurring tasks + LinkedList tasksAdded = model.addRecurringTask(toAdd); + taskAdded = tasksAdded.peekFirst(); + addToListOfTasksAdded(tasksAdded.toArray(new Task[tasksAdded.size()])); + } + + // always >= 0 unless this is being run without UI. + int targetIndex = getIndexOfTask(taskAdded); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (InvalidDateException ex) { + return new CommandResult(MESSAGE_INVALID_START_END); + } + + } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + model.updateFilteredListToShowActive(); //because newly added tasks are all active. + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } + //@@author + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + Iterator itr = tasksAdded.iterator(); + while (itr.hasNext()) { + try { + model.deleteTask(itr.next()); + } catch (TaskNotFoundException e) { + // do nothing. + } + } + // clears the list of tasks added. + // if redo is performed, the list will be populated again. + tasksAdded.clear(); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java b/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java new file mode 100644 index 000000000000..fe751a23a821 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java @@ -0,0 +1,127 @@ +//@@author A0139916U +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; + +/** + * Command to create aliases + */ +public class AliasCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "alias"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates an alias for a command or parameter. " + + "Parameters: k/KEYWORD s/SHORT_KEYWORD\n" + + "Example: " + COMMAND_WORD + + " k/Project Meeting s/pjm"; + + public static final String MESSAGE_SUCCESS = "New alias added: %1$s"; + public static final String MESSAGE_DUPLICATE_ALIAS = "This alias is already in use"; + public static final String MESSAGE_INVALID_KEYWORD = "Unable to use a command name as a keyword!"; + + private Logic logic; + private final String keyword; + private final String representingText; + /** + * Creates an alias command + */ + public AliasCommand(String keyword, String representingText) { + assert keyword != null; + assert representingText != null; + this.keyword = keyword; + this.representingText = representingText; + } + + @Override + public CommandResult execute() { + assert model != null; + + AliasSymbol toAdd = new AliasSymbol(keyword, representingText); + + if (logic.canParseHeader(toAdd.getKeyword())) { + return new CommandResult(MESSAGE_INVALID_KEYWORD); + } + + try { + model.addAliasSymbol(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateSymbolKeywordException e) { + return new CommandResult(MESSAGE_DUPLICATE_ALIAS); + } + } + + /** + * Provides logic related dependencies to the alias command. + */ + @Override + public void setLogic(Logic logic) { + assert logic != null; + this.logic = logic; + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the alias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + /** + * Undo the alias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // TODO Auto-generated method stub + + assert model != null; + + AliasSymbol toRemove = null; + for(AliasSymbol symbol : model.getSavvyTasker().getReadOnlyListOfAliasSymbols()) { + if (symbol.getKeyword().equals(this.keyword)) { + toRemove = symbol; + break; + } + } + try { + model.removeAliasSymbol(toRemove); + } catch (SymbolKeywordNotFoundException e) { + e.printStackTrace(); + } + + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/ClearCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ClearCommand.java new file mode 100644 index 000000000000..a59f2f1b0377 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/ClearCommand.java @@ -0,0 +1,73 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; + +/** + * Clears the address book. + */ +public class ClearCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Savvy Tasker has been cleared!"; + + private ReadOnlySavvyTasker original; + + @Override + public CommandResult execute() { + assert model != null; + original = new SavvyTasker(model.getSavvyTasker()); + model.resetData(SavvyTasker.getEmptySavvyTasker()); + return new CommandResult(MESSAGE_SUCCESS); + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the clear command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the clear command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + assert model != null; + model.resetData(original); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/Command.java b/src/main/java/seedu/savvytasker/logic/commands/Command.java new file mode 100644 index 000000000000..db06165bb9b5 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/Command.java @@ -0,0 +1,102 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.commons.events.ui.IncorrectCommandAttemptedEvent; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.model.Model; +import seedu.savvytasker.storage.Storage; + +/** + * Represents a command with hidden internal logic and the ability to be executed. + */ +public abstract class Command { + /** + * Constructs a feedback message to summarise an operation that displayed a listing of tasks. + * + * @param displaySize used to generate summary + * @return summary message for tasks displayed + */ + public static String getMessageForTaskListShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, displaySize); + } + /** + * Constructs a feedback message to summarise an operation that displayed a listing of aliases. + * + * @param displaySize used to generate summary + * @return summary message for tasks displayed + */ + public static String getMessageForAliasListShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_ALIASES_LISTED_OVERVIEW, displaySize); + } + + /** + * Executes the command and returns the result message. + * + * @return feedback message of the operation result for display + */ + public abstract CommandResult execute(); + + /** + * Provides any model related dependencies to the command. + * Commands making use of any of these should override this method to gain + * access to the dependencies. + */ + public void setModel(Model model) { /* Intentionally does nothing */ } + + /** + * Provides any logic related dependencies to the command. + * Commands making use of any of these should override this method to gain + * access to the dependencies. + */ + public void setLogic(Logic logic) { /* Intentionally does nothing */ } + + /** + * Provides any storage related dependencies to the command. + * Commands making use of any of these should override this method to gain + * access to the dependencies. + */ + public void setStorage(Storage storage) { /* Intentionally does nothing */ } + + /** + * Raises an event to indicate an attempt to execute an incorrect command + */ + protected void indicateAttemptToExecuteIncorrectCommand() { + EventsCenter.getInstance().post(new IncorrectCommandAttemptedEvent(this)); + } + + //@@author A0139916U + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + public abstract boolean canUndo(); + + /** + * Redo the command. + * @return true if the operation completed successfully, false otherwise + */ + public abstract boolean redo(); + + /** + * Undo the command + * @return true if the operation completed successfully, false otherwise + */ + public abstract boolean undo(); + + //@@author + + //@@author A0097627N + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + public abstract boolean isUndo(); + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + public abstract boolean isRedo(); + //@@author +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/savvytasker/logic/commands/CommandResult.java similarity index 86% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/savvytasker/logic/commands/CommandResult.java index f46f2f31353e..4c118841c769 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/savvytasker/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.savvytasker.logic.commands; /** * Represents the result of a command execution. diff --git a/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java b/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java new file mode 100644 index 000000000000..40a64c594447 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java @@ -0,0 +1,149 @@ +package seedu.savvytasker.logic.commands; + +import java.util.LinkedList; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.ReadOnlyTask; +//import seedu.savvytasker.model.task.Task; +//import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +/** + * Deletes a person identified using it's last displayed index from the address book. + */ +public class DeleteCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_FORMAT = "delete INDEX [MORE_INDEX]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the tasks identified by the index number used in the last task listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s\n"; + + //private LinkedList tasksToUndo = new LinkedList(); + private ReadOnlySavvyTasker original; + private final int[] targetIndices; + + //@@author A0139915W + public DeleteCommand(int[] targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + LinkedList tasksToDelete = new LinkedList(); + for(int targetIndex : this.targetIndices) { + if (lastShownList.size() < targetIndex || targetIndex <= 0) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + tasksToDelete.add(lastShownList.get(targetIndex - 1)); + } + + original = new SavvyTasker(model.getSavvyTasker()); + + StringBuilder resultSb = new StringBuilder(); + try { + for(ReadOnlyTask taskToDelete : tasksToDelete) { + model.deleteTask(taskToDelete); + //tasksToUndo.add((Task)taskToDelete); + resultSb.append(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); + } + } catch (TaskNotFoundException tnfe) { + assert false : "The target task cannot be missing"; + } + + return new CommandResult(resultSb.toString()); + } + //@@author + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the delete command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + + /* + * METHOD 2 + UnmodifiableObservableList lastShownList = model.getFilteredTaskListTask(); + + for(Task toUndo : tasksToUndo){ + for (int i = 0; i < lastShownList.size(); i++) { + if (lastShownList.get(i) == toUndo){ + ReadOnlyTask taskToDelete = lastShownList.get(i); + try { + model.deleteTask(taskToDelete); + } catch (TaskNotFoundException e) { + e.printStackTrace(); + } + } + } + } + */ + return true; + } + + /** + * Undo the delete command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + + assert model != null; + model.resetData(original); + + /* + * METHOD 2 + assert model != null; + + for(Task deleted : tasksToUndo) + try { + model.addTask(deleted); + } catch (DuplicateTaskException e) { + e.printStackTrace(); + } + */ + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/ExitCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ExitCommand.java new file mode 100644 index 000000000000..c92f09110fa3 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/ExitCommand.java @@ -0,0 +1,69 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.ui.ExitAppRequestEvent; + +/** + * Terminates the program. + */ +public class ExitCommand extends Command { + + public static final String COMMAND_WORD = "exit"; + + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Savvy Tasker as requested ..."; + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ExitAppRequestEvent()); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the exit command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the exit command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/FindCommand.java b/src/main/java/seedu/savvytasker/logic/commands/FindCommand.java new file mode 100644 index 000000000000..43b3b0d1ae63 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/FindCommand.java @@ -0,0 +1,88 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.model.task.FindType; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "find"; + public static final String COMMAND_FORMAT = "find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all tasks whose names contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final FindType findType; + private final String[] keywords; + + //@@author A0139915W + public FindCommand(FindType findType, String[] keywords) { + this.findType = findType; + this.keywords = keywords; + } + + @Override + public CommandResult execute() { + FindType _findType = findType; + if (_findType == null) { + // use default find type, partial. + _findType = FindType.Partial; + } + model.updateFilteredTaskList(_findType, keywords); + return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } + //@@author + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the find command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the find command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/HelpCommand.java b/src/main/java/seedu/savvytasker/logic/commands/HelpCommand.java new file mode 100644 index 000000000000..b9d73365693c --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/HelpCommand.java @@ -0,0 +1,73 @@ +package seedu.savvytasker.logic.commands; + + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.ui.ShowHelpRequestEvent; + +/** + * Format full help instructions for every command for display. + */ +public class HelpCommand extends Command { + + public static final String COMMAND_WORD = "help"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + + "Example: " + COMMAND_WORD; + + public static final String SHOWING_HELP_MESSAGE = "Opened help window."; + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ShowHelpRequestEvent()); + return new CommandResult(SHOWING_HELP_MESSAGE); + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the help command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the help command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java b/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java new file mode 100644 index 000000000000..b369336ba941 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java @@ -0,0 +1,71 @@ +package seedu.savvytasker.logic.commands; + + +/** + * Represents an incorrect command. Upon execution, produces some feedback to the user. + */ +public class IncorrectCommand extends Command { + public final String resolvedText; + public final String errorFeedback; + + public IncorrectCommand(String resolvedText, String errorFeedback){ + this.resolvedText = resolvedText; + this.errorFeedback = errorFeedback; + } + + @Override + public CommandResult execute() { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult("Input: " + resolvedText + "\n" + errorFeedback); + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the "incorrect" command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the "incorrect" command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} + diff --git a/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java new file mode 100644 index 000000000000..5c8f960bbb68 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java @@ -0,0 +1,120 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent.DisplayedList; +import seedu.savvytasker.model.ListType; + +/** + * Lists all tasks in the savvy tasker to the user. + */ +public class ListCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists the tasks in the task list.\n" + + "Parameters: [t/LIST_TYPE]\n" + + "Example: " + COMMAND_WORD + " t/Archived"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + + private final ListType listType; + + //@@author A0139915W + /** + * Creates the List command to list the specified tasks + * @param commandModel Arguments for the List command, must not be null + */ + public ListCommand(ListType listType) { + this.listType = listType; + } + + @Override + public CommandResult execute() { + ListType _listType = listType; + if (listType == null) { + // use default, sort by due date + _listType = ListType.DueDate; + } + + // shows the task list by default, unless user + // specifies to show the alias + switch (_listType) + { + case PriorityLevel: + model.updateFilteredListToShowActiveSortedByPriorityLevel(); + break; + case Archived: + model.updateFilteredListToShowArchived(); + break; + case Alias: + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Alias)); + break; + case DueDate: + // fall through. + default: // shows lists sorted by due date by default + model.updateFilteredListToShowActiveSortedByDueDate(); + break; + } + + if (_listType != ListType.Alias) { + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Task)); + return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } else { + return new CommandResult(getMessageForAliasListShownSummary(model.getAliasSymbolCount())); + } + } + //@@author + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the list command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + + /** + * Undo the list command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} + diff --git a/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java b/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java new file mode 100644 index 000000000000..88f2bac1d142 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java @@ -0,0 +1,118 @@ +package seedu.savvytasker.logic.commands; + +import java.util.LinkedList; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +public class MarkCommand extends ModelRequiringCommand { + + //@@author A0097627N + public static final String COMMAND_WORD = "mark"; + public static final String COMMAND_FORMAT = "mark INDEX [MORE_INDEX]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the tasks identified by the index number used in the last task listing as done.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_MARK_TASK_SUCCESS = "Marked Task: %1$s\n"; + public static final String MESSAGE_MARK_TASK_FAIL = "Task is already marked!\n"; + + public final int[] targetIndices; + private ReadOnlySavvyTasker original; + + public MarkCommand(int[] targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + LinkedList tasksToMark = new LinkedList(); + for(int targetIndex : targetIndices) { + if (lastShownList.size() < targetIndex || targetIndex <= 0) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + tasksToMark.add((Task) lastShownList.get(targetIndex - 1)); + } + + original = new SavvyTasker(model.getSavvyTasker()); + + StringBuilder resultSb = new StringBuilder(); + try { + for(Task taskToMark : tasksToMark) { + if (!taskToMark.isArchived()){ + taskToMark.setArchived(true); + model.modifyTask(taskToMark, taskToMark); + resultSb.append(String.format(MESSAGE_MARK_TASK_SUCCESS, taskToMark)); + } else { + resultSb.append(String.format(MESSAGE_MARK_TASK_FAIL, taskToMark)); + } + } + } catch (TaskNotFoundException pnfe) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException e) { + assert false : "The target task should be valid, only the archived flag is set"; + } + return new CommandResult(resultSb.toString()); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the mark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the mark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + assert model != null; + model.resetData(original); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/ModelRequiringCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ModelRequiringCommand.java new file mode 100644 index 000000000000..d5536971392b --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/ModelRequiringCommand.java @@ -0,0 +1,16 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.model.Model; + +/** + * Represents a command which requires the Model class as a dependency. + * Commands should inherit this class if they only require dependency the model + * and not the logic. +*/ +public abstract class ModelRequiringCommand extends Command { + protected Model model; + + public void setModel(Model model) { + this.model = model; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java new file mode 100644 index 000000000000..1b5358706a60 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java @@ -0,0 +1,172 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.RecurrenceType; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +/** + * Modifies a task in savvy tasker. + */ +public class ModifyCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "modify"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Madifies a task in Savvy Tasker. " + + "Parameters: INDEX [t/TASK_NAME] [s/START_DATE] [st/START_TIME] [e/END_DATE] [et/END_TIME] [l/LOCATION] [p/PRIORITY_LEVEL]" + + "[r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]\n" + + "Example: " + COMMAND_WORD + + " 1 t/Project Meeting s/05-10-2016 st/14:00 et/18:00 r/daily n/2 c/CS2103 d/Discuss about roles and milestones"; + + public static final String MESSAGE_SUCCESS = "Task modified: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task list"; + + private Task originalTask; + private final int index; + private final String taskName; + private final InferredDate startDateTime; + private final InferredDate endDateTime; + private final String location; + private final PriorityLevel priority; + private final RecurrenceType recurringType; + private final Integer numberOfRecurrence; + private final String category; + private final String description; + + //@@author A0139915W + /** + * Creates an add command. + */ + public ModifyCommand(int index, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, + String description) { + this.index = index; + this.taskName = taskName; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.location = location; + this.priority = priority; + this.recurringType = recurringType; + this.numberOfRecurrence = numberOfRecurrence; + this.category = category; + this.description = description; + this.originalTask = null; + } + + @Override + public CommandResult execute() { + assert model != null; + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() < index) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + ReadOnlyTask taskToModify = lastShownList.get(index - 1); + Task replacement = new Task(taskToModify, taskName, startDateTime, + endDateTime, location, priority, + recurringType, numberOfRecurrence, + category, description); + + try { + originalTask = (Task)taskToModify; + Task taskModified = model.modifyTask(taskToModify, replacement); + + // GUI will always get index >= 0; + int targetIndex = getIndexOfTask(taskModified); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } + } catch (TaskNotFoundException e) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException ex) { + return new CommandResult(Messages.MESSAGE_INVALID_START_END); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, replacement)); + } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } + //@@author + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the add command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + + assert model != null; + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + ReadOnlyTask taskToModify = lastShownList.get(index - 1); + + try { + model.modifyTask(taskToModify, originalTask); + } catch (TaskNotFoundException e) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException ex) { + //TODO: Verify branch + //assert false : "The target task cannot be having an invalid start end "; + //return new CommandResult(Messages.MESSAGE_INVALID_START_END); + } + + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/RedoCommand.java b/src/main/java/seedu/savvytasker/logic/commands/RedoCommand.java new file mode 100644 index 000000000000..39721d6e3321 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/RedoCommand.java @@ -0,0 +1,65 @@ +package seedu.savvytasker.logic.commands; + +/** + * Terminates the program. + */ +public class RedoCommand extends Command { + + //@@author A0097627N + public static final String COMMAND_WORD = "redo"; + + public static final String MESSAGE_REDO_ACKNOWLEDGEMENT = "Last command redone"; + + @Override + public CommandResult execute() { + return new CommandResult(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the redo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the redo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return true; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/SelectCommand.java b/src/main/java/seedu/savvytasker/logic/commands/SelectCommand.java new file mode 100644 index 000000000000..1a9f52cec097 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/SelectCommand.java @@ -0,0 +1,92 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; +import seedu.savvytasker.model.task.ReadOnlyTask; + +/** + * Selects a person identified using it's last displayed index from the address book. + */ +public class SelectCommand extends ModelRequiringCommand { + + public final int targetIndex; + + public static final String COMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the task identified by the index number used in the last task listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Task: %1$s"; + + public SelectCommand(int targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex - 1)); + return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex)); + + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/StorageAndModelRequiringCommand.java b/src/main/java/seedu/savvytasker/logic/commands/StorageAndModelRequiringCommand.java new file mode 100644 index 000000000000..88f26a7f3072 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/StorageAndModelRequiringCommand.java @@ -0,0 +1,23 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.model.Model; +import seedu.savvytasker.storage.Storage; + +//@@author A0138431L +/** + * Represents a command which requires the Storage class as a dependency. + * Commands should inherit this class if they only require dependency for + * storage and model components +*/ +public abstract class StorageAndModelRequiringCommand extends Command { + protected Storage storage; + protected Model model; + + public void setStorage(Storage storage) { + this.storage = storage; + } + + public void setModel(Model model) { + this.model = model; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/StorageCommand.java b/src/main/java/seedu/savvytasker/logic/commands/StorageCommand.java new file mode 100644 index 000000000000..cc3172379958 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/StorageCommand.java @@ -0,0 +1,89 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + + +//@@author A0138431L +/** + * Changes the storage location of Savvy Tasker + */ +public class StorageCommand extends StorageAndModelRequiringCommand { + + public final String path; + + public static final String COMMAND_WORD = "storage"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sets the storage path to the path specified.\n" + + "Parameters: PATH\n" + + "Example: " + COMMAND_WORD + " data/savvytasker.xml"; + + public static final String MESSAGE_CHANGE_LOCATION_SUCCESS = "Changed storage location to: %1$s"; + public static final String MESSAGE_CHANGE_LOCATION_FAILED = "Failed to change storage location to: %1$s"; + + public StorageCommand(String path) { + this.path = path; + } + + @Override + public CommandResult execute() { + if (storage.setSavvyTaskerFilePath(path)) { + ReadOnlySavvyTasker savvyTasker = model.getSavvyTasker(); + EventsCenter.getInstance().post(new DataSavingLocationChangedEvent(savvyTasker, path)); + return new CommandResult(String.format(MESSAGE_CHANGE_LOCATION_SUCCESS, path)); + } else { + return new CommandResult(String.format(MESSAGE_CHANGE_LOCATION_FAILED, path)); + } + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java b/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java new file mode 100644 index 000000000000..380fc236cfdf --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java @@ -0,0 +1,115 @@ +//@@author A0139916U +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; + +/** + * Command to remove aliases + */ +public class UnaliasCommand extends ModelRequiringCommand { + + public static final String COMMAND_WORD = "unalias"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes an alias for a command or parameter. " + + "Parameters: s/SHORT_KEYWORD\n" + + "Example: " + COMMAND_WORD + + " s/pjm"; + + public static final String MESSAGE_SUCCESS = "Alias removed: %1$s"; + public static final String MESSAGE_UNREGOGNIZED_ALIAS = "This alias is not in use"; + + private final String keyword; + private AliasSymbol toUndo; + + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + */ + public UnaliasCommand(String keyword) { + this.keyword = keyword; + this.toUndo = null; + } + + @Override + public CommandResult execute() { + assert model != null; + + AliasSymbol toRemove = null; + for(AliasSymbol symbol : model.getSavvyTasker().getReadOnlyListOfAliasSymbols()) { + if (symbol.getKeyword().equals(this.keyword)) { + toRemove = symbol; + break; + } + } + + try { + if (toRemove == null) { + return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); + } + + toUndo = toRemove; + model.removeAliasSymbol(toRemove); + return new CommandResult(String.format(MESSAGE_SUCCESS, toRemove)); + } catch (SymbolKeywordNotFoundException e) { + return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); + } + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the unalias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + /** + * Undo the unalias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + + try { + model.addAliasSymbol(toUndo); + } catch (DuplicateSymbolKeywordException e) { + e.printStackTrace(); + } + + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/UndoCommand.java b/src/main/java/seedu/savvytasker/logic/commands/UndoCommand.java new file mode 100644 index 000000000000..125e9305bebf --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/UndoCommand.java @@ -0,0 +1,65 @@ +package seedu.savvytasker.logic.commands; + +/** + * Command to undo the last action performed + */ +public class UndoCommand extends Command { + + //@@author A0097627N + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_UNDO_ACKNOWLEDGEMENT = "Last command undone"; + + @Override + public CommandResult execute() { + return new CommandResult(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the undo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the undo command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return true; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java b/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java new file mode 100644 index 000000000000..cee98003b600 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java @@ -0,0 +1,119 @@ +package seedu.savvytasker.logic.commands; + +import java.util.LinkedList; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +public class UnmarkCommand extends ModelRequiringCommand { + + //@@author A0097627N + public static final String COMMAND_WORD = "unmark"; + public static final String COMMAND_FORMAT = "unmark INDEX [MORE_INDEX]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unmarks the marked tasks identified by the index number used in the last task listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNMARK_TASK_SUCCESS = "Unmarked Task: %1$s\n"; + public static final String MESSAGE_UNMARK_TASK_FAIL = "Task is already unmarked!\n"; + + private final int[] targetIndices; + private ReadOnlySavvyTasker original; + + public UnmarkCommand(int[] targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute() { + + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + LinkedList tasksToUnmark = new LinkedList(); + for(int targetIndex : targetIndices) { + if (lastShownList.size() < targetIndex || targetIndex <= 0) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + tasksToUnmark.add((Task) lastShownList.get(targetIndex - 1)); + } + + original = new SavvyTasker(model.getSavvyTasker()); + + StringBuilder resultSb = new StringBuilder(); + try { + for(Task taskToUnmark : tasksToUnmark) { + if (taskToUnmark.isArchived()){ + taskToUnmark.setArchived(false); + model.modifyTask(taskToUnmark, taskToUnmark); + model.updateFilteredListToShowArchived(); + resultSb.append(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark)); + } else { + resultSb.append(String.format(MESSAGE_UNMARK_TASK_FAIL, taskToUnmark)); + } + } + } catch (TaskNotFoundException pnfe) { + assert false : "The target task cannot be missing"; + } catch (InvalidDateException e) { + assert false : "The target task should be valid, only the archived flag is set"; + } + return new CommandResult(resultSb.toString()); + } + + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the unmark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + + /** + * Undo the unmark command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + assert model != null; + model.resetData(original); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java new file mode 100644 index 000000000000..afd504f48a72 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java @@ -0,0 +1,66 @@ +//@@author A0139916U +// Please see CommandParser interface for documentation for many of the overridden methods +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.AddCommand; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +public class AddCommandParser extends TaskFieldParser { + private static final String HEADER = "add"; + private static final String READABLE_FORMAT = HEADER+" TASK_NAME [s/START_DATE] " + + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_TASK_NAME+">([^/]+?(\\s+|$))+)((?<=\\s)(" + + "(s/(?<"+REGEX_REF_START_DATE+">[^/]+)(?!.*\\ss/))|" + + "(e/(?<"+REGEX_REF_END_DATE+">[^/]+)(?!.*\\se/))|" + + "(l/(?<"+REGEX_REF_LOCATION+">[^/]+)(?!.*\\sl/))|" + + "(p/(?<"+REGEX_REF_PRIORITY_LEVEL+">[^/]+)(?!.*\\sp/))|" + + "(r/(?<"+REGEX_REF_RECURRING_TYPE+">[^/]+)(?!.*\\sr/))|" + + "(n/(?<"+REGEX_REF_NUMBER_OF_RECURRENCE+">[^/]+)(?!.*\\sn/))|" + + "(c/(?<"+REGEX_REF_CATEGORY+">[^/]+)(?!.*\\sc/))|" + + "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]+)(?!.*\\sd/))" + + ")(\\s|$)){0,10}", Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public AddCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + InferredDate startDate = parseStartDate(matcher.group(REGEX_REF_START_DATE)); + InferredDate endDate = parseEndDate(matcher.group(REGEX_REF_END_DATE)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + + return new AddCommand(taskName, startDate, + endDate, location, priority, + recurrence, nrOfRecurrence, + category, description); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java new file mode 100644 index 000000000000..9166b8903414 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java @@ -0,0 +1,73 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.AliasCommand; + +public class AliasCommandParser implements CommandParser { + private static final String HEADER = "alias"; + private static final String READABLE_FORMAT = HEADER+" k/KEYWORD r/REPRESENTATION"; + + private static final String REGEX_REF_REPRESENTATION = "Text"; + private static final String REGEX_REF_KEYWORD = "Keyword"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+((?<=\\s)(" + + "(r/(?<"+REGEX_REF_REPRESENTATION+">[^/]+)(?!.*\\sr/))|" + + "(k/(?<"+REGEX_REF_KEYWORD+">[^/]+)(?!.*\\sk/))" + + ")(\\s|$)){2}", + Pattern.CASE_INSENSITIVE + ); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public boolean shouldPreprocess() { + return false; + } + + @Override + public AliasCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String representation = parseRepresentation(matcher.group(REGEX_REF_REPRESENTATION)); + String keyword = parseKeyword(matcher.group(REGEX_REF_KEYWORD)); + return new AliasCommand(keyword, representation); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private String parseRepresentation(String originalText) throws ParseException { + String trimmedText = originalText.trim(); + + if (trimmedText.isEmpty()) { + throw new ParseException(trimmedText, "REPRESENTATION: Needs to be at least one character!"); + } + + return trimmedText; + } + + private String parseKeyword(String keywordText) throws ParseException { + String trimmedKeywordText = keywordText.trim(); + if (trimmedKeywordText.length() < 2) { + throw new ParseException(trimmedKeywordText, "KEYWORD: Needs to consist at least 2 character."); + } + if (trimmedKeywordText.matches(".*\\s+.*")) { + throw new ParseException(trimmedKeywordText, "KEYWORD: Needs to be a single word without spaces."); + } + return trimmedKeywordText; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/ClearCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/ClearCommandParser.java new file mode 100644 index 000000000000..71526b7cb365 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/ClearCommandParser.java @@ -0,0 +1,31 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ClearCommand; + +public class ClearCommandParser implements CommandParser { + private static final String HEADER = "clear"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ClearCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new ClearCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/CommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/CommandParser.java new file mode 100644 index 000000000000..cb7d0e5b19ee --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/CommandParser.java @@ -0,0 +1,60 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.logic.commands.Command; + +/** + * This interface should be implemented by specialized parsers that want to + * parse a specific command. The text passed to such a parser will be + * guaranteed to have its header (the leading word) matching the String + * returned by the abstract getHeader() method supplied by its implementation. + * + * @param A Command that this Parser is going to produce upon successful parsing + */ +public interface CommandParser { + /** + * Indicates whether this parser would like to have the text supplied to it + * preprocessed. The default behaviour is to allow preprocessing. + * Subclasses should override this method to return false if they do not want + * the text to be preprocessed. + * + * @return true if preprocessing is desired, false otherwise + */ + public default boolean shouldPreprocess() { + return true; + } + + /** + * Gets the header of this command parser. + * The header is the first token of a command text, used to identify which + * CommandParser the command text will be dispatched to. Subclasses should + * implement this method to indicate their header so that any command text that + * starts with such a header will be given to them. This method should always + * return the same value, must return a string as a single token with no whitespaces + * in between, and must not return null or empty string. + * + * @return header string + */ + public String getHeader(); + + /** + * Gets the human-readable format of the string required by this command parser to + * parse correctly. Subclasses should implement this method to provide the above described. + * This method should not return null, and should return the same value every time. + * + * @return a human-readable format required by this parser + */ + public String getRequiredFormat(); + + /** + * Parses the command text and returns the resulting command built from the parse. + * Subclasses should implement this method to do their parsing. If the command text + * is not as what they expect, subclasses should throw a ParseException. + * + * @param commandText the full command text, including the header + * @return the resulting command object which can then be executed + * @throws ParseException if the commandText has an incorrect syntax + */ + public T parse(String commandText) throws ParseException; + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/DateParser.java b/src/main/java/seedu/savvytasker/logic/parser/DateParser.java new file mode 100644 index 000000000000..42ff9b2448d8 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/DateParser.java @@ -0,0 +1,103 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import com.joestelmach.natty.DateGroup; + +public class DateParser { + /** + * Represents a date-time that may be partially inferred on its + * date component or time component. If both components are not + * inferred, then the date-time is exactly as the user has specified + * it to be. + */ + public class InferredDate { + private final Date inferredDateTime; + private final boolean dateInferred; + private final boolean timeInferred; + + public InferredDate(Date inferredDateTime, boolean dateInferred, boolean timeInferred) { + this.inferredDateTime = inferredDateTime; + this.dateInferred = dateInferred; + this.timeInferred = timeInferred; + } + + /** + * Gets the inferred date-time. + * @return the inferred date-time + */ + public Date getInferredDateTime() { + return this.inferredDateTime; + } + + /** + * Checks if the date component is inferred + * @return true if inferred, false otherwise + */ + public boolean isDateInferred() { + return this.dateInferred; + } + + /** + * Checks if the time component is inferred + * @return true if inferred, false otherwise + */ + public boolean isTimeInferred() { + return this.timeInferred; + } + } + + private com.joestelmach.natty.Parser nattyParser; + + public DateParser() { + this.nattyParser = new com.joestelmach.natty.Parser(); + } + + /** + * Parses an input for a single date. It is considered a parsing error + * if multiple dates are specified in the given input. + * + * @param input the input to be parsed + * @return an InferredDate object resulting from the parsing + * @throws ParseException if the input contains multiple dates or no dates. + */ + public InferredDate parseSingle(String input) throws ParseException { + assert input != null; + + // Temporary workaround for natty's failure at adapting to locales dd-mm-yyyy, until + // their issue is closed. + if (!Locale.getDefault().equals(Locale.US)) { + input = input.replaceAll("(\\d{1,2})-(\\d{1,2})-((?:\\d\\d){1,2})", "$2-$1-$3"); + } + + List dateGroups = this.nattyParser.parse(input); + int totalDates = countDates(dateGroups); + + if (totalDates == 0) { + throw new ParseException(input, "Failed to understand given date."); + } + + if (totalDates > 1) { + throw new ParseException(input, "Too many dates entered."); + } + + DateGroup group = dateGroups.get(0); + + return new InferredDate( + group.getDates().get(0), + group.isDateInferred(), + group.isTimeInferred()); + + } + + private int countDates(List dateGroups) { + int totalNumOfDates = 0; + for(DateGroup group : dateGroups) { + totalNumOfDates += group.getDates().size(); + } + return totalNumOfDates; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/DeleteCommandParser.java new file mode 100644 index 000000000000..53a5ae195b96 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/DeleteCommandParser.java @@ -0,0 +1,51 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.DeleteCommand; + +public class DeleteCommandParser implements CommandParser { + private static final String HEADER = "delete"; + private static final String READABLE_FORMAT = HEADER+" INDEX [MORE_INDEX]"; + + private static final String REGEX_REF_INDICES = "Indices"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDICES+">[^/]+)", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public DeleteCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + int[] indices = parseIndices(matcher.group(REGEX_REF_INDICES)); + return new DeleteCommand(indices); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int[] parseIndices(String indicesText) throws ParseException { + try { + return INDEX_PARSER.parseMultiple(indicesText); + } catch (ParseException ex) { + throw new ParseException(indicesText, String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat() + ": " + ex.getFailureDetails())); + } + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/ExitCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/ExitCommandParser.java new file mode 100644 index 000000000000..6a3d432283c0 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/ExitCommandParser.java @@ -0,0 +1,31 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ExitCommand; + +public class ExitCommandParser implements CommandParser { + private static final String HEADER = "exit"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ExitCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new ExitCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/FindCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/FindCommandParser.java new file mode 100644 index 000000000000..d5c6867219b5 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/FindCommandParser.java @@ -0,0 +1,98 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.FindCommand; +import seedu.savvytasker.model.task.FindType; + +public class FindCommandParser implements CommandParser { + private static final String HEADER = "find"; + private static final String READABLE_FORMAT = "find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]"; + + private static final String REGEX_REF_KEYWORDS_BEFORE_TYPE = "Before"; + private static final String REGEX_REF_FIND_TYPE = "FindType"; + private static final String REGEX_REF_KEYWORDS_AFTER_TYPE = "After"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_KEYWORDS_BEFORE_TYPE+">([^/]+(\\s+|$))*)" + + "(t/(?<"+REGEX_REF_FIND_TYPE+">[^/]+?)(?!.*\\st/)(\\s+|$))?" + + "(?<"+REGEX_REF_KEYWORDS_AFTER_TYPE+">([^/]+(\\s+|$))*)" + , Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + //@@author A0139915W + @Override + public FindCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + FindType findType = parseFindType(matcher.group(REGEX_REF_FIND_TYPE)); + String[] keywords = parseKeywords(matcher.group(REGEX_REF_KEYWORDS_BEFORE_TYPE), + matcher.group(REGEX_REF_KEYWORDS_AFTER_TYPE)); + + return new FindCommand(findType, keywords); + } + + throw new ParseException(commandText, String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + //@@author + + private FindType parseFindType(String findTypeText) throws ParseException { + if (findTypeText == null) { + return null; + } + + String trimmedFindTypeText = findTypeText.trim(); + try { + return FindType.valueOfIgnoreCase(trimmedFindTypeText); + } catch (IllegalArgumentException ex) { + throw new ParseException(trimmedFindTypeText, "FIND_TYPE: Unknown type '" + findTypeText + "'"); + } + } + + private String[] parseKeywords(String keywordsBefore, String keywordsAfter) throws ParseException { + String trimmedKeywordsBefore = keywordsBefore.trim(); + String trimmedKeywordsAfter = keywordsAfter.trim(); + + String[] keywordsArr1 = new String[0]; + String[] keywordsArr2 = new String[0]; + if (!trimmedKeywordsBefore.isEmpty()) { + keywordsArr1 = trimmedKeywordsBefore.split("\\s+"); + } + if (!trimmedKeywordsAfter.isEmpty()) { + keywordsArr2 = trimmedKeywordsAfter.split("\\s+"); + } + + if (keywordsArr1.length == 0 && keywordsArr2.length == 0) { + throw new ParseException(trimmedKeywordsBefore + " ... " + trimmedKeywordsAfter, + "KEYWORD: Need to specify at least one keyword!"); + } + + return concatArray(keywordsArr1, keywordsArr2); + } + + /** + * Concatenates 2 arrays into 1 array, in the order the arrays are given. + * + * @param array1 the first array + * @param array2 the second array + * @return the new array with the values from each of the given array added into it in order + */ + private String[] concatArray(String[] array1, String[] array2) { + String[] newArray = Arrays.copyOf(array1, array1.length + array2.length); + System.arraycopy(array2, 0, newArray, array1.length, array2.length); + return newArray; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/HelpCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/HelpCommandParser.java new file mode 100644 index 000000000000..0b5e16eda059 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/HelpCommandParser.java @@ -0,0 +1,31 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.HelpCommand; + +public class HelpCommandParser implements CommandParser { + private static final String HEADER = "help"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public HelpCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new HelpCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/IndexParser.java b/src/main/java/seedu/savvytasker/logic/parser/IndexParser.java new file mode 100644 index 000000000000..866ee418e60e --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/IndexParser.java @@ -0,0 +1,77 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.Arrays; + +/** + * A class for parsing a single index or multiple indices. An index is + * a positive integer that may be used by commands to indicate which task + * they act on. + */ +public class IndexParser { + public static String INDEX_MUST_BE_POSITIVE = "Must be positive whole numbers."; + /** + * Parses a single index. + * + * @param indexText the text to parse + * @return the resulting index + * @throws ParseException if there are more than one integer in the text, or if + * the integer given is not positive + */ + public int parseSingle(String indexText) throws ParseException { + boolean parseError = false; + + String trimmedIndexText = indexText.trim(); + int index = 0; + try { + index = Integer.parseInt(trimmedIndexText); + + if (index <= 0) { + parseError = true; + } + } catch (NumberFormatException ex) { + parseError = true; + } + + if (parseError) { + throw new ParseException(trimmedIndexText, "Must be a positive whole number."); + } + + return index; + } + + /** + * Parses multiple indices, delimited by space. + * + * @param indicesText the text to parse + * @return an array of the resulting indices + * @throws ParseException if any of the indices is not a positive integer + */ + public int[] parseMultiple(String indicesText) throws ParseException { + boolean parseError = false; + + String trimmedIndicesText = indicesText.trim(); + int[] indices = null; + try { + indices = Arrays + .stream(trimmedIndicesText.split("\\s+")) + .mapToInt(Integer::parseInt) + .toArray(); + + for(int index : indices) { + if (index <= 0) { + parseError = true; + break; + } + } + } catch (NumberFormatException ex) { + parseError = true; + } + + if (parseError) { + throw new ParseException(trimmedIndicesText, INDEX_MUST_BE_POSITIVE); + } + + return indices; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java new file mode 100644 index 000000000000..f484abc8cb9c --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java @@ -0,0 +1,56 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ListCommand; +import seedu.savvytasker.model.ListType; + +public class ListCommandParser implements CommandParser { + private static final String HEADER = "list"; + private static final String READABLE_FORMAT = HEADER+" [LIST_TYPE]"; + + private static final String REGEX_REF_LIST_TYPE = "ListType"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s*((?<=\\s)(?<"+REGEX_REF_LIST_TYPE+">[^/]+))?", + Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ListCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + ListType listType = parseListType(matcher.group(REGEX_REF_LIST_TYPE)); + + return new ListCommand(listType); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private ListType parseListType(String listTypeText) throws ParseException { + if (listTypeText == null) { + return null; + } + + try { + listTypeText = listTypeText.trim(); + return ListType.valueOfIgnoreCase(listTypeText.replaceAll("\\s", "")); + } catch (IllegalArgumentException ex) { + throw new ParseException(listTypeText, "LIST_TYPE: Unknown type '" + listTypeText + "'"); + } + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/MarkCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/MarkCommandParser.java new file mode 100644 index 000000000000..465921f73759 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/MarkCommandParser.java @@ -0,0 +1,51 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.MarkCommand; + +public class MarkCommandParser implements CommandParser { + private static final String HEADER = "mark"; + private static final String READABLE_FORMAT = HEADER+" INDEX [MORE_INDEX]"; + + private static final String REGEX_REF_INDICES = "Indices"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDICES+">[^/]+)", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public MarkCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + int[] indices = parseIndices(matcher.group(REGEX_REF_INDICES)); + return new MarkCommand(indices); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int[] parseIndices(String indicesText) throws ParseException { + try { + return INDEX_PARSER.parseMultiple(indicesText); + } catch (ParseException ex) { + throw new ParseException(indicesText, "INDEX [MORE_INDEX]: " + ex.getFailureDetails()); + } + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java b/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java new file mode 100644 index 000000000000..77ba0d698247 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java @@ -0,0 +1,278 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import static seedu.savvytasker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.logic.commands.IncorrectCommand; +import seedu.savvytasker.model.alias.AliasSymbol; + +/** + * Represents the master parser that is used by Logic. This is a parser containing + * all subparsers that will do the actual parsing to produce Command objects. This + * parser replaces keywords of the input with aliased representations before selecting + * a subparser to do the parsing. The selection of the subparser is based on the first + * word of the input, called the header, and matching it to the subparser that declares + * that it parses it. + */ +public class MasterParser { + private static final Pattern KEYWORD_PATTERN = + Pattern.compile("([^\\s/]+)([\\s/]+|$)"); + + private final Map> commandParsers; + private final Map aliasingSymbols; + private final ObservableList aliasList = FXCollections.observableArrayList(); + + + public MasterParser() { + this.commandParsers = new HashMap>(); + this.aliasingSymbols = new HashMap(); + } + + /** + * Parses the input text, selecting an appropriate registered parser to parse it. + * The parser selected is based on the first header word of the input text. The text + * is preprocessed, replacing any of its tokens that are keywords to an alias, before + * being passed to the parser. + * + * @param userInput the text to be parse + * @return the command that was parsed if successful, or IncorrectCommand if there is no + * parser that can parse the text or if there is a format error with the text. + */ + public Command parse(String userInput) { + String[] pieces = preprocessInitial(userInput.trim()); + if (pieces == null) { + return new IncorrectCommand(userInput, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + String header = pieces[0]; + String body = pieces[1]; + CommandParser parser = selectParser(extractTrueHeader(header)); + if (parser == null) { + return new IncorrectCommand(header + body, + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + + if (parser.shouldPreprocess()) { + return makeParserParse(parser, header + preprocessBody(body)); + } else { + return makeParserParse(parser, header + body); + } + } + + /** + * Makes the parser parse the preprocessed text. + * + * @param parser the parser to use to parse + * @param preprocessedText the text to parse + * @return the output Command from the parsing, or IncorrectCommand object if the parse failed + */ + private Command makeParserParse(CommandParser parser, String preprocessedText) { + try { + return parser.parse(preprocessedText); + } catch (ParseException pe) { + return new IncorrectCommand(preprocessedText, String.format(pe.getFailureDetails())); + } + } + + private CommandParser selectParser(String header) { + return commandParsers.get(header); + } + + /** + * Does an initial preprocessing of a command text in case the header is aliased. + * Returns a string array with 2 elements: the first is the header which is possibly aliased, + * which will be replaced with the representation of the aliasing, whereas the + * second is the body. If no header is found, this method returns null. + * + * @param commandText the command text + * @return a string array containing 2 elements, first is the header, second is the body; or null if + * there is no header in the command text + */ + private String[] preprocessInitial(String commandText) { + Matcher matcher = KEYWORD_PATTERN.matcher(commandText); + + if (matcher.find()) { + String header = matcher.group(1); + String spaces = matcher.group(2); + + AliasSymbol symbol = aliasingSymbols.get(header); + if (symbol != null) { + header = symbol.getRepresentation(); + } + + String body = commandText.substring(matcher.end()); + return new String[] {header + spaces, body}; + } + + return null; + } + + /** + * Gets the true header from the preprocessed header as a preprocessed header may contain + * several tokens. + * + * @param preprocessedHeader the preprocessed header + * @return the true header + */ + private String extractTrueHeader(String preprocessedHeader) { + Matcher matcher = KEYWORD_PATTERN.matcher(preprocessedHeader); + + if (matcher.find()) { + return matcher.group(1); + } + + return null; + } + + /** + * Preprocess the body to replace keywords with their replacement if they are aliased. + * @param bodyText the body text + * @return the preprocessed body text + */ + private String preprocessBody(String bodyText) { + StringBuilder builder = new StringBuilder(); + Matcher matcher = KEYWORD_PATTERN.matcher(bodyText); + + while (matcher.find()) { + String keyword = matcher.group(1); + String spaces = matcher.group(2); // Preserves the amount of spaces as that may be what user wants + + AliasSymbol symbol = aliasingSymbols.get(keyword); + if (symbol != null) { + keyword = symbol.getRepresentation(); + } + + builder.append(keyword); + builder.append(spaces); + } + + return builder.toString(); + } + + /** + * Registers a command parser that will be used by the master parser, and return true if it + * is successfully registered. The header of this command parser should not be used by any + * other registered command parsers or used by any AliasSymbol whose keyword is the registered + * with the same name, or false will be return and the parser will not be added. Use + * {@link #isCommandParserRegistered(String) isCommandParserRegistered } method to check if + * a command parser is already registered, and {@link #doesAliasSymbolExist(String) doesAliasSymbolExist} + * + * Parameter commandParser should not be null. + * + * @param commandParser the command parser + * @return true if successfully registered, false if there is an parser with the same header + * already registered or if an alias with the same keyword is previously added. + */ + public boolean registerCommandParser(CommandParser commandParser) { + assert commandParser != null; + + if (commandParsers.containsKey(commandParser.getHeader())) { + return false; + } + if (aliasingSymbols.containsKey(commandParser.getHeader())) { + return false; + } + + commandParsers.put(commandParser.getHeader(), commandParser); + return true; + } + + /** + * Checks if a command parser with the specified header is already + * registered into the master parser. + * + * @param header the header to check against + * @return true if such a command parser is registered, false otherwise + */ + public boolean isCommandParserRegistered(String header) { + return commandParsers.containsKey(header); + } + + /** + * Unregisters and returns the command parser that uses the specified header. + * If such a parser is not registered, null is returned. + * + * @param header the header to check against + * @return the CommandParser object that uses the specified header. + */ + public CommandParser unregisterCommandParser(String header) { + return commandParsers.remove(header); + } + + /** + * Adds an aliasing symbol to be used by the parser to replace all such the symbol's keyword with + * its representation before parsing. If a symbol with an identical keyword exists, calling this + * method has no effect and just returns false. + * + * @param symbol the symbol, cannot be null. + * @return true if this symbol is added successfully, false if another symbol with the same keyword + * already exists or the keyword is used by a command and this symbol cannot be added. + */ + public boolean addAliasSymbol(AliasSymbol symbol) { + assert symbol != null; + + if (aliasingSymbols.containsKey(symbol.getKeyword())) { + return false; + } + if (isCommandParserRegistered(symbol.getKeyword())) { + return false; + } + + aliasList.add(symbol); + aliasingSymbols.put(symbol.getKeyword(), symbol); + return true; + } + + /** + * Removes an aliasing symbol, identified by its keyword. The parser will no longer replace the + * keyword of this symbol with its representation before parsing. + * + * @param symbol the symbol to remove, cannot be null + * @return true if the symbol exists and is removed, false otherwise + */ + public boolean removeAliasSymbol(String symbolKeyword) { + assert symbolKeyword != null; + + AliasSymbol symbol = aliasingSymbols.remove(symbolKeyword); + if (symbol != null) { + return aliasList.remove(symbol); + } else { + return false; + } + } + + /** + * Returns true if a symbol with the specified keyword exists, false otherwise. + * @param symbolKeyword the keyword to check for, cannot be null + * @return true if the symbol exists, false otherwise + */ + public boolean doesAliasSymbolExist(String symbolKeyword) { + assert symbolKeyword != null; + + return aliasingSymbols.containsKey(symbolKeyword); + } + + /** + * Clears all existing symbols. + * @see #removeAliasSymbol + */ + public void clearAllAliasSymbols() { + aliasList.clear(); + aliasingSymbols.clear(); + } + + public ObservableList getAliasSymbolList() { + return aliasList; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java new file mode 100644 index 000000000000..6592d486ee0b --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java @@ -0,0 +1,88 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.ModifyCommand; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +public class ModifyCommandParser extends TaskFieldParser { + private static final String HEADER = "modify"; + private static final String READABLE_FORMAT = HEADER+" INDEX [t/TASK_NAME] [s/START_DATE] " + + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; + + private static final String REGEX_REF_INDEX = "Index"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDEX+">([^/]+?(\\s+|$))+)((?<=\\s)(" + + "(t/(?<"+REGEX_REF_TASK_NAME+">[^/]*)(?!.*\\st/))|" + + "(s/(?<"+REGEX_REF_START_DATE+">[^/]*)(?!.*\\ss/))|" + + "(e/(?<"+REGEX_REF_END_DATE+">[^/]*)(?!.*\\se/))|" + + "(l/(?<"+REGEX_REF_LOCATION+">[^/]*)(?!.*\\sl/))|" + + "(p/(?<"+REGEX_REF_PRIORITY_LEVEL+">[^/]+)(?!.*\\sp/))|" + + "(r/(?<"+REGEX_REF_RECURRING_TYPE+">[^/]+)(?!.*\\sr/))|" + + "(n/(?<"+REGEX_REF_NUMBER_OF_RECURRENCE+">[^/]+)(?!.*\\sn/))|" + + "(c/(?<"+REGEX_REF_CATEGORY+">[^/]*)(?!.*\\sc/))|" + + "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]*)(?!.*\\sd/))" + + ")(\\s|$)){0,11}", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public ModifyCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + + int index = parseIndex(matcher.group(REGEX_REF_INDEX)); + InferredDate startDate = parseDate(matcher.group(REGEX_REF_START_DATE)); + InferredDate endDate = parseDate(matcher.group(REGEX_REF_END_DATE)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + + return new ModifyCommand(index, taskName, startDate, + endDate, location, priority, + recurrence, nrOfRecurrence, + category, description); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int parseIndex(String indexText) throws ParseException { + try { + return INDEX_PARSER.parseSingle(indexText); + } catch (ParseException ex) { + throw new ParseException(indexText, "INDEX: " + ex.getFailureDetails()); + } + } + + private InferredDate parseDate(String dateText) throws ParseException { + if (dateText != null && dateText.trim().isEmpty()) { + return dateParser.new InferredDate(new Date(), true, true); + } + + return parseStartDate(dateText); + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/ParseException.java b/src/main/java/seedu/savvytasker/logic/parser/ParseException.java new file mode 100644 index 000000000000..6c859022d159 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/ParseException.java @@ -0,0 +1,45 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +/** + * This exception should be thrown by parsers when they are unable to + * parse a string of tokens. + */ +public class ParseException extends Exception { + private static final long serialVersionUID = -1157747299012674373L; + private final String parsedString; + private final String failureDetails; + + /** + * Constructs a ParseException object with the original string that + * the parser failed to parse, with no further details to why the + * parser failed to parse the string + * + * @param parsedString the erroneous string + */ + public ParseException(String parsedString) { + this(parsedString, ""); + } + + /** + * Constructs a ParseException object with the original string that + * the parser failed to parse, and further details to why the parser + * failed to parse the string. + * + * @param parsedString the erroneous string + * @param failureDetails the failure details + */ + public ParseException(String parsedString, String failureDetails) { + super("Failed to parse " + parsedString + ". " + failureDetails); + this.parsedString = parsedString; + this.failureDetails = failureDetails; + } + + public String getParsedString() { + return this.parsedString; + } + + public String getFailureDetails() { + return this.failureDetails; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/RedoCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/RedoCommandParser.java new file mode 100644 index 000000000000..4ac4554f2828 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/RedoCommandParser.java @@ -0,0 +1,31 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.RedoCommand; + +public class RedoCommandParser implements CommandParser { + private static final String HEADER = "redo"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public RedoCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new RedoCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/StorageCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/StorageCommandParser.java new file mode 100644 index 000000000000..c97a391c1431 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/StorageCommandParser.java @@ -0,0 +1,45 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.StorageCommand; + +public class StorageCommandParser implements CommandParser { + private static final String HEADER = "storage"; + private static final String READABLE_FORMAT = HEADER+" NEW_PATH"; + + private static final String REGEX_REF_PATH = "Path"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_PATH+">.*)", + Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public StorageCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String path = matcher.group(REGEX_REF_PATH).trim(); + + if (!path.isEmpty()) { + return new StorageCommand(path); + } + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java b/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java new file mode 100644 index 000000000000..a15ace1d987a --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java @@ -0,0 +1,150 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +/** + * This class contains common parsing methods for parsing Task fields. + * Each of the parse method takes in a string which can be null, and return + * the respective parsed object. If null is provided to each of the parse methods, + * null will be returned. + */ +public abstract class TaskFieldParser implements CommandParser { + /* + * Provides standard regex ref names for various task fields that might be used by subclasses. + */ + protected static final String REGEX_REF_TASK_NAME = "TaskName"; + protected static final String REGEX_REF_START_DATE = "StartDate"; + protected static final String REGEX_REF_END_DATE = "EndDate"; + protected static final String REGEX_REF_LOCATION = "Location"; + protected static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; + protected static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; + protected static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; + protected static final String REGEX_REF_CATEGORY = "Category"; + protected static final String REGEX_REF_DESCRIPTION = "Description"; + + protected final DateParser dateParser; + + protected TaskFieldParser() { + this.dateParser = new DateParser(); + } + + protected String parseTaskName(String taskNameText) throws ParseException { + if (taskNameText == null) { + return null; + } + return taskNameText.trim(); + } + + protected InferredDate parseStartDate(String dateText) throws ParseException { + return parseDate(dateText, "START_DATE: "); + } + + protected InferredDate parseEndDate(String dateText) throws ParseException { + return parseDate(dateText, "END_DATE: "); + } + + private InferredDate parseDate(String dateText, String errorField) throws ParseException { + if (dateText == null) { + return null; + } + String trimmedDateText = dateText.trim(); + try { + return dateParser.parseSingle(trimmedDateText); + } catch (ParseException ex) { + throw new ParseException(trimmedDateText, errorField + ex.getFailureDetails()); + } + } + + protected String parseLocation(String locationText) throws ParseException { + if (locationText == null) { + return null; + } + return locationText.trim(); + } + + protected PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseException { + if (priorityLevelText == null) { + return null; + } + + String trimmedPriorityLevelText = priorityLevelText.trim(); + try { + return PriorityLevel.valueOfIgnoreCase(trimmedPriorityLevelText); + } catch (IllegalArgumentException ex) { + throw new ParseException(trimmedPriorityLevelText, "PRIORITY_LEVEL: Unknown type '" + priorityLevelText + "'"); + } + } + + protected RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws ParseException { + if (recurrenceTypeText == null) { + return null; + } + + String trimmedRecurrenceTypeText = recurrenceTypeText.trim(); + try { + return RecurrenceType.valueOfIgnoreCase(trimmedRecurrenceTypeText); + } catch (IllegalArgumentException ex) { + throw new ParseException(trimmedRecurrenceTypeText, "RECURRING_TYPE: Unknown type '" + recurrenceTypeText + "'"); + } + } + + protected Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseException { + if (numRecurrenceText == null) { + return null; + } + + String trimmedNumRecurrenceText = numRecurrenceText.trim(); + int numRecurrence = 0; + boolean parseError = false; + + try { + numRecurrence = Integer.parseInt(trimmedNumRecurrenceText); + if (numRecurrence < 0) { + parseError = true; + } + } catch (NumberFormatException ex) { + parseError = true; + } + + if (parseError) { + throw new ParseException(trimmedNumRecurrenceText, "NUMBER_OF_RECURRENCE: Must be a nonnegative whole number!"); + } + + return numRecurrence; + } + + protected String parseCategory(String categoryText) throws ParseException { + if (categoryText == null) { + return null; + } + return categoryText.trim(); + } + + protected String parseDescription(String descriptionText) throws ParseException { + if (descriptionText == null) { + return null; + } + return descriptionText.trim(); + } + //@@author + + //@@author A0138431L + public String parsefilePath(String filePathText) throws ParseException { + if (filePathText == null) { + return null; + } + return filePathText.trim(); + } + + public String parsefileName(String fileNameText) throws ParseException { + if (fileNameText == null) { + return null; + } + return fileNameText.trim(); + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java new file mode 100644 index 000000000000..809f57552b6e --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java @@ -0,0 +1,56 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.UnaliasCommand; + +public class UnaliasCommandParser implements CommandParser { + private static final String HEADER = "unalias"; + private static final String READABLE_FORMAT = HEADER+" KEYWORD"; + + private static final String REGEX_REF_KEYWORD = "Keyword"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_KEYWORD+">[^/]+)", + Pattern.CASE_INSENSITIVE + ); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public boolean shouldPreprocess() { + return false; + } + + @Override + public UnaliasCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String keyword = parseKeyword(matcher.group(REGEX_REF_KEYWORD)); + return new UnaliasCommand(keyword); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private String parseKeyword(String keywordText) throws ParseException { + String trimmedKeywordText = keywordText.trim(); + if (trimmedKeywordText.isEmpty()) { + throw new ParseException(trimmedKeywordText, "KEYWORD: Cannot be empty."); + } + return trimmedKeywordText; + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/UndoCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/UndoCommandParser.java new file mode 100644 index 000000000000..a668f65157b5 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/UndoCommandParser.java @@ -0,0 +1,31 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.UndoCommand; + +public class UndoCommandParser implements CommandParser { + private static final String HEADER = "undo"; + private static final String READABLE_FORMAT = HEADER; + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public UndoCommand parse(String commandText) throws ParseException { + if (commandText.trim().equalsIgnoreCase(HEADER)) { + return new UndoCommand(); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/UnmarkCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/UnmarkCommandParser.java new file mode 100644 index 000000000000..26499b9accbb --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/UnmarkCommandParser.java @@ -0,0 +1,53 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.UnmarkCommand; + +public class UnmarkCommandParser implements CommandParser { + private static final String HEADER = "unmark"; + private static final String READABLE_FORMAT = HEADER+" INDEX [MORE_INDEX]"; + + private static final String REGEX_REF_INDICES = "Indices"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_INDICES+">[^/]+)", Pattern.CASE_INSENSITIVE); + + private static final IndexParser INDEX_PARSER = new IndexParser(); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public UnmarkCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + int[] indices = parseIndices(matcher.group(REGEX_REF_INDICES)); + + // TODO: Integrate UnmarkCommand properly! + return new UnmarkCommand(indices); + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + + private int[] parseIndices(String indicesText) throws ParseException { + try { + return INDEX_PARSER.parseMultiple(indicesText); + } catch (ParseException ex) { + throw new ParseException(indicesText, "INDEX [MORE_INDEX]: " + ex.getFailureDetails()); + } + } + +} diff --git a/src/main/java/seedu/savvytasker/model/ListType.java b/src/main/java/seedu/savvytasker/model/ListType.java new file mode 100644 index 000000000000..2e7fda37e229 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/ListType.java @@ -0,0 +1,42 @@ +//@@author A0139916U +package seedu.savvytasker.model; + +/** + * This enum represents what and how to list tasks/symbols. + */ +public enum ListType { + /** + * List tasks by due date. + */ + DueDate, + + /** + * List tasks by priority level. + */ + PriorityLevel, + + /** + * List archived tasks. + */ + Archived, + + /** + * List aliases. + */ + Alias; + + /** + * Gets a ListType enum object from its enum name, ignoring cases. + * @param name the name of the ListType enum object + * @return the corresponding ListType enum object + */ + public static ListType valueOfIgnoreCase(String name) { + for (ListType type : ListType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown list type: " + name); + } +} diff --git a/src/main/java/seedu/savvytasker/model/Model.java b/src/main/java/seedu/savvytasker/model/Model.java new file mode 100644 index 000000000000..fd42b55aae28 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/Model.java @@ -0,0 +1,108 @@ +package seedu.savvytasker.model; + +import java.util.LinkedList; +import java.util.Date; + +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; +import seedu.savvytasker.model.task.FindType; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +/** + * The API of the Model component. + */ +public interface Model { + /** Clears existing backing model and replaces with the provided new data. */ + void resetData(ReadOnlySavvyTasker newData); + + /** Returns Savvy Tasker */ + ReadOnlySavvyTasker getSavvyTasker(); + + //@@author A0139915W + /** + * Deletes the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. + * */ + Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException; + + /** + * Modifies the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the modify operation is successful, an exception is thrown otherwise. + * */ + Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException; + + /** Adds the given Task. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the add operation is successful, an exception is thrown otherwise. + * */ + Task addTask(Task task) throws InvalidDateException; + + /** Adds the given Task as a recurring task. The task's recurrence type must not be null. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of Tasks added if the add operation is successful, an exception is thrown otherwise. + * */ + LinkedList addRecurringTask(Task task) throws InvalidDateException; + + /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredTaskList(); + + /** Updates the filter of the filtered task list to show all active tasks sorted by due date */ + void updateFilteredListToShowActiveSortedByDueDate(); + + /** Updates the filter of the filtered task list to show all active tasks sorted by priority level */ + void updateFilteredListToShowActiveSortedByPriorityLevel(); + + /** Updates the filter of the filtered task list to show all active tasks */ + void updateFilteredListToShowActive(); + + /** Updates the filter of the filtered task list to show all archived tasks */ + void updateFilteredListToShowArchived(); + + /** Updates the filter of the filtered task list to filter by the given keywords*/ + void updateFilteredTaskList(FindType findType, String[] keywords); + //@@author + + /** Adds the given AliasSymbol */ + void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException; + + /** Removes an the given AliasSymbol. */ + void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException; + + /** Gets the number of aliases */ + int getAliasSymbolCount(); + + //@@author A0138431L + + /** Returns the filtered task list of floating task as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredFloatingTasks(); + + /** Returns the filtered task list of daily task as an {@code UnmodifiableObservableList} + * as of expected date */ + UnmodifiableObservableList getFilteredDailyTasks(int dayOfWeek, Date date); + + /** Returns the filtered task list of upcoming task as an {@code UnmodifiableObservableList} + * as of expected date */ + UnmodifiableObservableList getFilteredUpcomingTasks(Date date); + + /** Updates the filter of the filtered task list to show all floating tasks */ + void updateFilteredListToShowFloating(); + + /** Updates the filter of the filtered task list to show all tasks of the selected week*/ + void updateFilteredListToShowDaily(int i); + + /** Updates the filter of the filtered task list to show all upcoming tasks after the selected week*/ + void updateFilteredListToShowUpcoming(); + + //@@author +} diff --git a/src/main/java/seedu/savvytasker/model/ModelManager.java b/src/main/java/seedu/savvytasker/model/ModelManager.java new file mode 100755 index 000000000000..b58f9ae4ec01 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/ModelManager.java @@ -0,0 +1,871 @@ +package seedu.savvytasker.model; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +import org.apache.commons.lang.time.DateUtils; + +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import seedu.savvytasker.commons.core.ComponentManager; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.model.AliasSymbolChangedEvent; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.util.StringUtil; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; +import seedu.savvytasker.model.task.FindType; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +/** + * Represents the in-memory model of the savvy tasker data. + * All changes to any model should be synchronized. + */ +public class ModelManager extends ComponentManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + Date onDate = new Date(); + Date firstDayOfSelectedWeek = new Date(); + + //@@author A0138431L + private final SavvyTasker savvyTasker; + private final FilteredList filteredTasks; + private final SortedList sortedAndFilteredTasks; + private final FilteredList filteredFloatingTasks; + private final SortedList sortedAndFilteredFloatingTasks; + private final FilteredList filteredDay1Tasks; + private final SortedList sortedAndFilteredDay1Tasks; + private final FilteredList filteredDay2Tasks; + private final SortedList sortedAndFilteredDay2Tasks; + private final FilteredList filteredDay3Tasks; + private final SortedList sortedAndFilteredDay3Tasks; + private final FilteredList filteredDay4Tasks; + private final SortedList sortedAndFilteredDay4Tasks; + private final FilteredList filteredDay5Tasks; + private final SortedList sortedAndFilteredDay5Tasks; + private final FilteredList filteredDay6Tasks; + private final SortedList sortedAndFilteredDay6Tasks; + private final FilteredList filteredDay7Tasks; + private final SortedList sortedAndFilteredDay7Tasks; + private final FilteredList filteredUpcomingTasks; + private final SortedList sortedAndFilteredUpcomingTasks; + + /** + * Initializes a ModelManager with the given SavvyTasker + * and its variables should not be null + */ + public ModelManager(SavvyTasker src) { + super(); + assert src != null; + + logger.fine("Initializing with savvy tasker: " + src); + + savvyTasker = new SavvyTasker(src); + filteredTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + + filteredFloatingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredFloatingTasks = new SortedList<>(filteredFloatingTasks, new TaskSortedByDefault()); + + filteredDay1Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay1Tasks = new SortedList<>(filteredDay1Tasks, new TaskSortedByDefault()); + filteredDay2Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay2Tasks = new SortedList<>(filteredDay2Tasks, new TaskSortedByDefault()); + filteredDay3Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay3Tasks = new SortedList<>(filteredDay3Tasks, new TaskSortedByDefault()); + filteredDay4Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay4Tasks = new SortedList<>(filteredDay4Tasks, new TaskSortedByDefault()); + filteredDay5Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay5Tasks = new SortedList<>(filteredDay5Tasks, new TaskSortedByDefault()); + filteredDay6Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay6Tasks = new SortedList<>(filteredDay6Tasks, new TaskSortedByDefault()); + filteredDay7Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay7Tasks = new SortedList<>(filteredDay7Tasks, new TaskSortedByDefault()); + + filteredUpcomingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredUpcomingTasks = new SortedList<>(filteredUpcomingTasks, new TaskSortedByDefault()); + + updateFilteredListToShowActive(); // shows only active tasks on start + } + + public ModelManager() { + this(new SavvyTasker()); + } + + public ModelManager(ReadOnlySavvyTasker initialData) { + savvyTasker = new SavvyTasker(initialData); + filteredTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + + filteredFloatingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredFloatingTasks = new SortedList<>(filteredFloatingTasks, new TaskSortedByDefault()); + + filteredDay1Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay1Tasks = new SortedList<>(filteredDay1Tasks, new TaskSortedByDefault()); + filteredDay2Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay2Tasks = new SortedList<>(filteredDay2Tasks, new TaskSortedByDefault()); + filteredDay3Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay3Tasks = new SortedList<>(filteredDay3Tasks, new TaskSortedByDefault()); + filteredDay4Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay4Tasks = new SortedList<>(filteredDay4Tasks, new TaskSortedByDefault()); + filteredDay5Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay5Tasks = new SortedList<>(filteredDay5Tasks, new TaskSortedByDefault()); + filteredDay6Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay6Tasks = new SortedList<>(filteredDay6Tasks, new TaskSortedByDefault()); + filteredDay7Tasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredDay7Tasks = new SortedList<>(filteredDay7Tasks, new TaskSortedByDefault()); + + filteredUpcomingTasks = new FilteredList<>(savvyTasker.getTasks()); + sortedAndFilteredUpcomingTasks = new SortedList<>(filteredUpcomingTasks, new TaskSortedByDefault()); + + updateFilteredListToShowActive(); // shows only active tasks on start + } + //@@author + + @Override + public void resetData(ReadOnlySavvyTasker newData) { + savvyTasker.resetData(newData); + indicateSavvyTaskerChanged(); + } + + @Override + public ReadOnlySavvyTasker getSavvyTasker() { + return savvyTasker; + } + + /** Raises an event to indicate the model has changed */ + private void indicateSavvyTaskerChanged() { + raise(new SavvyTaskerChangedEvent(savvyTasker)); + } + //@@author A0139916U + + private void indicateAliasSymbolAdded(AliasSymbol symbol) { + raise(new AliasSymbolChangedEvent(symbol, AliasSymbolChangedEvent.Action.Added)); + } + + private void indicateAliasSymbolRemoved(AliasSymbol symbol) { + raise(new AliasSymbolChangedEvent(symbol, AliasSymbolChangedEvent.Action.Removed)); + } + //@@author + + + //@@author A0139915W + @Override + public synchronized Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException { + Task taskDeleted = savvyTasker.removeTask(target); + indicateSavvyTaskerChanged(); + return taskDeleted; + } + + @Override + public synchronized Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException { + Task taskModified = savvyTasker.replaceTask(target, replacement); + indicateSavvyTaskerChanged(); + return taskModified; + } + + @Override + public synchronized Task addTask(Task t) throws InvalidDateException { + Task taskAdded = savvyTasker.addTask(t); + updateFilteredListToShowActive(); + indicateSavvyTaskerChanged(); + return taskAdded; + } + + @Override + public synchronized LinkedList addRecurringTask(Task recurringTask) throws InvalidDateException { + LinkedList recurringTasks = savvyTasker.addRecurringTasks(recurringTask); + updateFilteredListToShowActive(); + indicateSavvyTaskerChanged(); + return recurringTasks; + } + //@@author + + //@@author A0139916U + @Override + public synchronized void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException { + savvyTasker.addAliasSymbol(symbol); + indicateSavvyTaskerChanged(); + indicateAliasSymbolAdded(symbol); + } + + @Override + public synchronized void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException { + savvyTasker.removeAliasSymbol(symbol); + indicateSavvyTaskerChanged(); + indicateAliasSymbolRemoved(symbol); + } + + @Override + public int getAliasSymbolCount() { + return savvyTasker.getAliasSymbolCount(); + } + //@@author + + //=========== Filtered/Sorted Task List Accessors =============================================================== + + //@@author A0139915W + @Override + public UnmodifiableObservableList getFilteredTaskList() { + return new UnmodifiableObservableList(sortedAndFilteredTasks); + } + + @Override + public void updateFilteredListToShowActiveSortedByDueDate() { + updateFilteredListToShowActive(new TaskSortedByDueDate()); + } + + @Override + public void updateFilteredListToShowActiveSortedByPriorityLevel() { + updateFilteredListToShowActive(new TaskSortedByPriorityLevel()); + } + + @Override + public void updateFilteredListToShowActive() { + updateFilteredTaskList(new PredicateExpression(new TaskIsActiveQualifier())); + } + + private void updateFilteredListToShowActive(Comparator comparator) { + updateFilteredTaskList( + new PredicateExpression(new TaskIsActiveQualifier()), + comparator); + } + + @Override + public void updateFilteredListToShowArchived() { + updateFilteredTaskList(new PredicateExpression(new TaskIsArchivedQualifier())); + } + + @Override + public void updateFilteredTaskList(FindType findType, String[] keywords) { + assert findType != null; + Qualifier qualifier = null; + switch (findType) + { + case Partial: + qualifier = new TaskNamePartialMatchQualifier(keywords); + break; + case Full: + qualifier = new TaskNameFullMatchQualifier(keywords); + break; + case Exact: + qualifier = new TaskNameExactMatchQualifier(keywords); + break; + case Category: + qualifier = new CategoryPartialMatchQualifier(keywords); + break; + default: + assert false; // should never get here. + break; + } + updateFilteredTaskList(new PredicateExpression(qualifier)); + } + + private void updateFilteredTaskList(Expression expression) { + updateFilteredTaskList(expression, new TaskSortedByDefault()); + } + + private void updateFilteredTaskList(Expression expression, Comparator comparator) { + filteredTasks.setPredicate(expression::satisfies); + sortedAndFilteredTasks.setComparator(comparator); + } + //@@author + + //@author A0138431L + //Get filtered task list according to date category + + @Override + public UnmodifiableObservableList getFilteredFloatingTasks() { + updateFilteredListToShowFloating(); + return new UnmodifiableObservableList(filteredFloatingTasks); + } + + @Override + public UnmodifiableObservableList getFilteredDailyTasks(int dayOfWeek, Date date) { + this.onDate = date; + updateFilteredListToShowDaily(dayOfWeek); + switch(dayOfWeek) { + case 0: + return new UnmodifiableObservableList(filteredDay1Tasks); + case 1: + return new UnmodifiableObservableList(filteredDay2Tasks); + case 2: + return new UnmodifiableObservableList(filteredDay3Tasks); + case 3: + return new UnmodifiableObservableList(filteredDay4Tasks); + case 4: + return new UnmodifiableObservableList(filteredDay5Tasks); + case 5: + return new UnmodifiableObservableList(filteredDay6Tasks); + case 6: + return new UnmodifiableObservableList(filteredDay7Tasks); + default: + return new UnmodifiableObservableList(filteredDay1Tasks); + } + } + + @Override + public UnmodifiableObservableList getFilteredUpcomingTasks(Date date) { + this.firstDayOfSelectedWeek = date; + updateFilteredListToShowUpcoming(); + return new UnmodifiableObservableList(filteredUpcomingTasks); + } + + + //Binding isFloating quantifier predicate to filtered list + @Override + public void updateFilteredListToShowFloating() { + updateFilteredFloatingTaskList(new PredicateExpression(new TaskIsFloatingQualifier())); + } + + private void updateFilteredFloatingTaskList(Expression expression) { + updateFilteredFloatingTaskList(expression, new TaskSortedByDefault()); + } + + private void updateFilteredFloatingTaskList(Expression expression, Comparator comparator) { + filteredFloatingTasks.setPredicate(expression::satisfies); + sortedAndFilteredFloatingTasks.setComparator(comparator); + } + + //Binding isOnDate quantifier predicate to filtered list + @Override + public void updateFilteredListToShowDaily(int dayOfWeek) { + updateFilteredDailyTaskList(new PredicateExpression(new TaskIsOnDateQualifier()), dayOfWeek); + } + + private void updateFilteredDailyTaskList(Expression expression, int dayOfWeek) { + updateFilteredDailyTaskList(expression, new TaskSortedByDefault(), dayOfWeek); + } + + private void updateFilteredDailyTaskList(Expression expression, Comparator comparator, int dayOfWeek) { + switch(dayOfWeek) { + case 0: + filteredDay1Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay1Tasks.setComparator(comparator); + case 1: + filteredDay2Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay2Tasks.setComparator(comparator); + case 2: + filteredDay3Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay3Tasks.setComparator(comparator); + case 3: + filteredDay4Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay4Tasks.setComparator(comparator); + case 4: + filteredDay5Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay5Tasks.setComparator(comparator); + case 5: + filteredDay6Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay6Tasks.setComparator(comparator); + case 6: + filteredDay7Tasks.setPredicate(expression::satisfies); + sortedAndFilteredDay7Tasks.setComparator(comparator); + + } + } + + //Binding isUpcoming quantifier predicate to filtered list + @Override + public void updateFilteredListToShowUpcoming() { + updateFilteredUpcomingTaskList(new PredicateExpression(new TaskIsUpcomingQualifier())); + } + + private void updateFilteredUpcomingTaskList(Expression expression) { + updateFilteredUpcomingTaskList(expression, new TaskSortedByDefault()); + } + + private void updateFilteredUpcomingTaskList(Expression expression, Comparator comparator) { + filteredUpcomingTasks.setPredicate(expression::satisfies); + sortedAndFilteredUpcomingTasks.setComparator(comparator); + } + + //@@author + + //========== Inner classes/interfaces used for filtering ================================================== + + interface Expression { + boolean satisfies(ReadOnlyTask task); + String toString(); + } + + private class PredicateExpression implements Expression { + + private final Qualifier qualifier; + + PredicateExpression(Qualifier qualifier) { + this.qualifier = qualifier; + } + + @Override + public boolean satisfies(ReadOnlyTask task) { + return qualifier.run(task); + } + + @Override + public String toString() { + return qualifier.toString(); + } + } + + interface Qualifier { + boolean run(ReadOnlyTask task); + String toString(); + + /** + * Helper method to build Set from String[] + * @param keywords list of keywords + */ + default Set createSet(String[] keywords) { + HashSet _keywords = new HashSet(); + for (String keyword : keywords) { + _keywords.add(keyword); + } + return _keywords; + } + } + + //@@author A0139915W + /** + * Qualifier matching a partial word from the set of keywords + */ + private class CategoryPartialMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + CategoryPartialMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = createSet(keyWordsToMatch); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsPartialIgnoreCase(task.getCategory(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "category(PartialMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier matching a partial word from the set of keywords + */ + private class TaskNamePartialMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + TaskNamePartialMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = createSet(keyWordsToMatch); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsPartialIgnoreCase(task.getTaskName(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "taskName(PartialMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier matching a full word from the set of keywords + */ + private class TaskNameFullMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + TaskNameFullMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = createSet(keyWordsToMatch); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsIgnoreCase(task.getTaskName(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "taskName(FullMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier matching a exactly from the set of keywords + */ + private class TaskNameExactMatchQualifier implements Qualifier { + private Set keyWordsToMatch; + + TaskNameExactMatchQualifier(String[] keyWordsToMatch) { + this.keyWordsToMatch = new HashSet(); + this.keyWordsToMatch.add(buildSingleString(keyWordsToMatch)); + } + + /** + * Builds a single string to be matched exactly against the task name. + * @param keyWordsToMatch list of keywords to match. + * @return A single string built from the list of keywords. + */ + private String buildSingleString(String[] keyWordsToMatch) { + StringBuilder sb = new StringBuilder(); + List keywords = Arrays.asList(keyWordsToMatch); + Iterator itr = keywords.iterator(); + while (itr.hasNext()) { + sb.append(itr.next()); + if (itr.hasNext()) { + sb.append(" "); + } + } + return sb.toString(); + } + + @Override + public boolean run(ReadOnlyTask task) { + return keyWordsToMatch.stream() + .filter(keyword -> StringUtil.containsExactIgnoreCase(task.getTaskName(), keyword)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "taskName(ExactMatch)=" + String.join(", ", keyWordsToMatch); + } + } + + /** + * Qualifier for checking if {@link Task} is active. Tasks that are not archived are active. + * + */ + private class TaskIsActiveQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + return !task.isArchived(); + } + + @Override + public String toString() { + return "isArchived=false"; + } + } + + /** + * Qualifier for checking if {@link Task} is archived + * + */ + private class TaskIsArchivedQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + return task.isArchived(); + } + + @Override + public String toString() { + return "isArchived=true"; + } + }//@@author + + //@@author A0138431L + /** + * Qualifier for checking if {@link Task} is an overdue task + * + * A overdue task is a deadline or event task with end dateTime after current dateTime + * + * @return true if the task is overdue + * + */ + private class TaskIsOverdueQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + Date today = new Date(); + + boolean isOverdue = false; + + if (task.getEndDateTime() != null) { + + Date endDateTime = task.getEndDateTime(); + + if (endDateTime.compareTo(today)<0 && task.isArchived() == false) { + + isOverdue = true; + } + + } + + return isOverdue; + + } + + @Override + public String toString() { + return "isOverdue=true"; + } + } + + /** + * Qualifier for checking if {@link Task} is a floating task + * + * A floating task do not have start or end time + * + * @return true if the task falls on the date specified. else return false + * + */ + private class TaskIsFloatingQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + boolean isFloating = false; + + if(task.getStartDateTime() == null && task.getEndDateTime() == null && task.isArchived() == false) { + + isFloating = true; + + } + + return isFloating; + + } + + @Override + public String toString() { + return "isFloating=true"; + } + } + + /** + * Qualifier for checking if {@link Task} falls on the selected date + * + * Check whether the task is on the date specified (for deadline tasks) + * Check whether the date specified is within the range of date the task (for event tasks) + * Includes task that are completed. + * + * @return true if the task falls on the date specified. else return false + * + */ + private class TaskIsOnDateQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + Date expectedDate = onDate; + + boolean isOnDate = false; + + //Archived Task + if(task.isArchived() == true){ + + isOnDate = false; + + } + //Deadline Task + else if(task.getStartDateTime() == null && task.getEndDateTime() != null) { + + Date endDateTime = task.getEndDateTime(); + + if (DateUtils.isSameDay(endDateTime, expectedDate)) { + + isOnDate = true; + + } + + } + //Event Task + else if(task.getStartDateTime() != null && task.getEndDateTime() != null) { + + Date startDateTime = task.getStartDateTime(); + Date endDateTime = task.getEndDateTime(); + + if (DateUtils.isSameDay(startDateTime, expectedDate)) { + + isOnDate = true; + + } else if (DateUtils.isSameDay(endDateTime, expectedDate)) { + + isOnDate = true; + + } else if (startDateTime.compareTo(expectedDate)<0 && expectedDate.compareTo(endDateTime)<0) { + + isOnDate = true; + + } + } + + return isOnDate; + } + + @Override + public String toString() { + return "isOnDate=true"; + } + } + + /** + * Qualifier for checking if {@link Task} task is upcoming + * + * A upcoming task is a task that will happen after the last day, 2359 of selected week + * + * @return true if the task is a upcoming task + * + */ + private class TaskIsUpcomingQualifier implements Qualifier { + + @Override + public boolean run(ReadOnlyTask task) { + + Date lastDateOfExpectedWeek = firstDayOfSelectedWeek; + + //convert date object to calendar object and add 7 days, last day of the selected week + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(lastDateOfExpectedWeek); + calendarExpectedDate.add(Calendar.DAY_OF_MONTH, 7); + calendarExpectedDate.set(Calendar.HOUR_OF_DAY,23); + calendarExpectedDate.set(Calendar.MINUTE,59); + calendarExpectedDate.set(Calendar.SECOND,59); + + //convert calendar object back to date object + lastDateOfExpectedWeek = calendarExpectedDate.getTime(); + + boolean isUpcoming = true; + + //Archived Task + if(task.isArchived() == true){ + + isUpcoming = false; + + } + + //Floating Task + else if(task.getStartDateTime() == null && task.getEndDateTime() == null) { + + isUpcoming = false; + + } + //Deadline Task + else if(task.getStartDateTime() == null && task.getEndDateTime() != null) { + + + if (task.getEndDateTime().compareTo(lastDateOfExpectedWeek)<0) { + + isUpcoming = false; + + } + + } + //Event Task + else { + + if (task.getStartDateTime().compareTo(lastDateOfExpectedWeek)<0) { + + isUpcoming = false; + + } + + } + + return isUpcoming; + } + + @Override + public String toString() { + return "isUpcoming=true"; + } + }//@@author + + //@@author A0139915W + + //========== Inner classes/interfaces used for sorting ================================================== + + /** + * Compares {@link Task} by their default field, id + */ + private class TaskSortedByDefault implements Comparator { + + @Override + public int compare(Task task1, Task task2) { + if (task1 == null && task2 == null) { + return 0; + } else if (task1 == null) { + return 1; + } else if (task2 == null) { + return -1; + } else { + return task1.getId() - task2.getId(); + } + } + + } + + /** + * Compares {@link Task} by their DueDate + */ + private class TaskSortedByDueDate implements Comparator { + + @Override + public int compare(Task task1, Task task2) { + if (task1 == null && task2 == null) { + return 0; + } else if (task1 == null) { + return 1; + } else if (task2 == null) { + return -1; + } else { + // End dates can be nulls (floating tasks) + // Check for existence of endDateTime before comparing + if (task1.getEndDateTime() == null && + task2.getEndDateTime() == null) { + return 0; + } else if (task1.getEndDateTime() == null) { + return 1; + } else if (task2.getEndDateTime() == null) { + return -1; + } else { + return task1.getEndDateTime().compareTo(task2.getEndDateTime()); + } + } + } + + } + + /** + * Compares {@link Task} by their PriorityLevel + */ + private class TaskSortedByPriorityLevel implements Comparator { + + @Override + public int compare(Task task1, Task task2) { + if (task1 == null && task2 == null) return 0; + else if (task1 == null) return 1; + else if (task2 == null) return -1; + else { + return task2.getPriority().compareTo(task1.getPriority()); + } + } + + } + //@@author + +} \ No newline at end of file diff --git a/src/main/java/seedu/savvytasker/model/ReadOnlySavvyTasker.java b/src/main/java/seedu/savvytasker/model/ReadOnlySavvyTasker.java new file mode 100644 index 000000000000..be6e6cdc8719 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/ReadOnlySavvyTasker.java @@ -0,0 +1,41 @@ +package seedu.savvytasker.model; + +import java.util.List; + +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.AliasSymbolList; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.TaskList; + +/** + * Unmodifiable view of a task list + */ +public interface ReadOnlySavvyTasker { + //@@author A0139915W + /** + * Returns a defensively copied task list. + */ + TaskList getTaskList(); + //@@author + //@@author A0139916U + + /** + * Returns a defensively copied alias symbol list. + */ + + AliasSymbolList getAliasSymbolList(); + //@@author + //@@author A0139915W + /** + * Returns an unmodifiable view of task list + */ + List getReadOnlyListOfTasks(); + //@@author + //@@author A0139916U + + /** + * Returns unmodifiable view of symbols list + */ + List getReadOnlyListOfAliasSymbols(); + //@@author +} diff --git a/src/main/java/seedu/savvytasker/model/SavvyTasker.java b/src/main/java/seedu/savvytasker/model/SavvyTasker.java new file mode 100644 index 000000000000..f85f4307df2d --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/SavvyTasker.java @@ -0,0 +1,310 @@ +package seedu.savvytasker.model; + +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.AliasSymbolList; +import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; +import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.RecurrenceType; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList; +import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +/** + * Wraps all data at the savvy-tasker level + * Duplicates are not allowed (by .equals comparison) + */ +public class SavvyTasker implements ReadOnlySavvyTasker { + + private final TaskList tasks; + private final AliasSymbolList symbols; + + public SavvyTasker() { + this.tasks = new TaskList(); + this.symbols = new AliasSymbolList(); + } + + public SavvyTasker(ReadOnlySavvyTasker toBeCopied) { + this(toBeCopied.getTaskList(), toBeCopied.getAliasSymbolList()); + } + + public SavvyTasker(TaskList tasks, AliasSymbolList symbols) { + this(); + resetData(tasks.getInternalList()); + this.symbols.reset(symbols); + } + + public static ReadOnlySavvyTasker getEmptySavvyTasker() { + return new SavvyTasker(); + } + +//// task list overwrite operations + + public ObservableList getTasks() { + return tasks.getInternalList(); + } + + public void setTasks(List tasks) { + this.tasks.getInternalList().setAll(tasks); + } + + public void resetData(Collection newTasks) { + setTasks(newTasks.stream().map(Task::new).collect(Collectors.toList())); + } + + public void resetData(ReadOnlySavvyTasker newData) { + resetData(newData.getReadOnlyListOfTasks()); + } + +//// symbol/task-level operations + + //@@author A0139915W + + /** + * Adds a task to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the task added if the operation succeeds, an exception is thrown otherwise. + */ + public Task addTask(Task t) throws InvalidDateException { + // guarantees unique ID + t.setId(tasks.getNextId()); + try { + return tasks.add(t); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } + } + + /** + * Adds a group of recurring tasks to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of recurring tasks if the operation succeeds, an exception is thrown otherwise + */ + public LinkedList addRecurringTasks(Task recurringTask) throws InvalidDateException { + LinkedList tasksToAdd = + createRecurringTasks(recurringTask, recurringTask.getRecurringType(), + recurringTask.getNumberOfRecurrence()); + Iterator itr = tasksToAdd.iterator(); + + while(itr.hasNext()) { + // this will be an atomic operation + // guaranteed no duplicates + // if the start/end dates are invalid, + // the first task to be added will fail immediately, + // subsequent tasks will not be added + try { + tasks.add(itr.next()); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } + } + return tasksToAdd; + } + + /** + * Creates a list of recurring tasks to be added into savvy tasker. + * @param recurringTask the task that recurs + * @param recurringType the type of recurrence + * @param numberOfRecurrences the number of recurrences + * @return A list of tasks that represents a recurring task. + */ + private LinkedList createRecurringTasks(Task recurringTask, RecurrenceType recurringType, int numberOfRecurrences) { + assert recurringTask.getRecurringType() != null; + assert recurringTask.getNumberOfRecurrence() > 0; + + LinkedList listOfTasks = new LinkedList(); + recurringTask.setGroupId(tasks.getNextGroupId()); + + for (int i = 0; i < numberOfRecurrences; ++i) { + Task t = recurringTask.clone(); + // guarantees uniqueness + t.setId(tasks.getNextId()); + listOfTasks.add(setDatesForRecurringType(t, recurringType, i)); + } + + return listOfTasks; + } + + /** + * Helper function for createRecurringTasks(). Sets the respective start/end datetime for the + * i-th recurring task to be added + * @param t The first recurring task + * @param recurringType the type of recurrence + * @param index the index of the loop + * @return A task with its respective datetime set. + */ + private Task setDatesForRecurringType(Task t, RecurrenceType recurringType, int index) { + Date startDate = t.getStartDateTime(); + Date endDate = t.getEndDateTime(); + Calendar calendar = Calendar.getInstance(); + switch (recurringType) { + case Daily: // add one day to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 1 * index); + endDate = calendar.getTime(); + } + break; + case Weekly: // add 7 days to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 7 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 7 * index); + endDate = calendar.getTime(); + } + break; + case Monthly: // add 1 month to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.MONTH, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.MONTH, 1 * index); + endDate = calendar.getTime(); + } + break; + case Yearly: // add 1 year to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.YEAR, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.YEAR, 1 * index); + endDate = calendar.getTime(); + } + break; + case None: + //fall through + default: + assert false; // should not come here + break; + } + t.setStartDateTime(startDate); + t.setEndDateTime(endDate); + return t; + } + + /** + * Removes a task from savvy tasker. + * @param key the task to be removed + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the remove operation is successful, an exception is thrown otherwise. + */ + public Task removeTask(ReadOnlyTask key) throws TaskNotFoundException { + return tasks.remove(key); + } + + /** + * Replaces a task from savvy tasker. + * @param key the task to be replaced + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return true if the task is removed successfully + */ + public Task replaceTask(ReadOnlyTask key, Task replacement) throws TaskNotFoundException, InvalidDateException { + return tasks.replace(key, replacement); + } + //@@author + //@@author A0139916U + + /** + * Adds an alias symbol to savvy tasker. + * @param symbol the symbol to add + * @throws DuplicateSymbolKeywordException if another symbol with the same keyword already exists + */ + public void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException { + symbols.addAliasSymbol(symbol); + } + + /** + * Removes an alias symbol from savvy tasker. + * @param symbol the symbol to remove + * @throws SymbolKeywordNotFoundException if there is no such symbol + */ + public void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException { + symbols.removeAliasSymbol(symbol); + } + //@@author + + public int getAliasSymbolCount() { + return symbols.size(); + } + +//// util methods + + @Override + public String toString() { + return tasks.getInternalList().size() + " tasks, " + symbols.size() + " symbols"; + // TODO: refine later + } + + //@@author A0139915W + @Override + public List getReadOnlyListOfTasks() { + return Collections.unmodifiableList(tasks.getInternalList()); + } + + @Override + public TaskList getTaskList() { + TaskList defensiveCopy = new TaskList(); + defensiveCopy.getInternalList().addAll(tasks.getInternalList()); + return defensiveCopy; + } + //@@author + + //@@author A0139916U + @Override + public List getReadOnlyListOfAliasSymbols() { + return symbols.asReadonly(); + } + + @Override + public AliasSymbolList getAliasSymbolList() { + return new AliasSymbolList(symbols); + } + //@@author + + //@@author A0139915W + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SavvyTasker // instanceof handles nulls + && this.tasks.equals(((SavvyTasker) other).tasks) + && this.symbols.equals(((SavvyTasker) other).symbols)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(tasks, symbols); + } + //@@author +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/savvytasker/model/UserPrefs.java similarity index 92% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/savvytasker/model/UserPrefs.java index da9c8037f495..1da734046bcd 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/savvytasker/model/UserPrefs.java @@ -1,9 +1,9 @@ -package seedu.address.model; - -import seedu.address.commons.core.GuiSettings; +package seedu.savvytasker.model; import java.util.Objects; +import seedu.savvytasker.commons.core.GuiSettings; + /** * Represents User's preferences. */ diff --git a/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java b/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java new file mode 100644 index 000000000000..52fbaddfc592 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java @@ -0,0 +1,55 @@ +//@@author A0139916U +package seedu.savvytasker.model.alias; + +import java.util.Objects; + +/** + * A class that represents a keyword-representation pair. This pair can be used + * by the parser for substituting keywords found in a commandText with its + * representation. Instances of this class are immutable. + */ +public class AliasSymbol { + private final String keyword; + private final String representation; + + /** + * Creates a Symbol object with the specified keyword and its representation. + * The keyword must be a single word without spaces while the representation + * can be any non-empty string. Both parameters cannot be null. + * + * @param keyword + * @param representation + */ + public AliasSymbol(String keyword, String representation) { + assert keyword != null && !keyword.matches(".*\\s+.*"); + assert representation != null && !representation.isEmpty(); + + this.keyword = keyword.toLowerCase(); + this.representation = representation; + } + + public String getKeyword() { + return this.keyword; + } + + public String getRepresentation() { + return this.representation; + } + + @Override + public String toString() { + return "[Keyword: " + this.keyword + ", Representation: " + this.representation + "]"; + } + + @Override + public boolean equals(Object object) { + return this == object || (object instanceof AliasSymbol && + this.keyword.equals(((AliasSymbol)object).keyword) && + this.representation.equals(((AliasSymbol)object).representation)); + } + + @Override + public int hashCode() { + return Objects.hash(keyword, representation); + } +} diff --git a/src/main/java/seedu/savvytasker/model/alias/AliasSymbolList.java b/src/main/java/seedu/savvytasker/model/alias/AliasSymbolList.java new file mode 100644 index 000000000000..13e87673adc9 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/alias/AliasSymbolList.java @@ -0,0 +1,127 @@ +//@@author A0139916U +package seedu.savvytasker.model.alias; + +import java.util.Iterator; +import java.util.List; + +import edu.emory.mathcs.backport.java.util.Collections; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +public class AliasSymbolList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** Default constructor */ + public AliasSymbolList() {} + + /** + * Copy constructor + * @param src the list to copy from, cannot be null + */ + public AliasSymbolList(AliasSymbolList src) { + assert src != null; + internalList.addAll(src.internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Returns true if a symbol with the specified symbolKeyword exists in the list + * @param symbolKeyword the symbolKeyword to check for + * @return true if exists, false otherwise + */ + public boolean contains(String symbolKeyword) { + for (AliasSymbol symbol : internalList) { + if (symbol.getKeyword().equals(symbolKeyword)) + return true; + } + + return false; + } + + /** + * Returns true if the symbol exists in the list. Both keyword and representation must match. + * @param symbol the symol to check for + * @return true if exists, false otherwise + */ + public boolean contains(AliasSymbol symbol) { + return internalList.contains(symbol); + } + + /** + * Adds a symbol to the list. The symbol being added must not have a keyword that clashes with another + * symbol in the list. + * @param symbol the symbol to add, cannot be null + * @throws DuplicateSymbolKeywordException if a symbol with the same keyword already exists + */ + public void addAliasSymbol(AliasSymbol symbol) throws DuplicateSymbolKeywordException { + assert symbol != null; + if (contains(symbol.getKeyword())) + throw new DuplicateSymbolKeywordException(); + internalList.add(symbol); + } + + /** + * Removes a symbol from the list. + * @param symbol the symbol to remove, cannot be null + * @throws SymbolKeywordNotFoundException if no such symbol can be found. + */ + public void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException { + assert symbol != null; + if (!contains(symbol)) + throw new SymbolKeywordNotFoundException(); + internalList.remove(symbol); + } + + /** + * Replace an old symbol with a new symbol. The new symbol's keyword must equal to the old + * symbol's keyword. + * + * @param oldSymbol the old symbol to be replaced, cannot be null + * @param newSymbol the new symbol to used to replace, cannot be null + * @throws SymbolKeywordNotFoundException if an old symbol with the keyword is not found. + */ + public void replaceAliasSymbol(AliasSymbol oldSymbol, AliasSymbol newSymbol) throws SymbolKeywordNotFoundException { + assert oldSymbol != null && newSymbol != null; + assert oldSymbol.getKeyword().equals(newSymbol.getKeyword()); + + removeAliasSymbol(oldSymbol); + internalList.add(newSymbol); + } + + /** + * Clears this list and copy all elements from the other list to this. + * @param other the other list + */ + public void reset(AliasSymbolList other) { + assert other != null; + internalList.addAll(other.internalList); // AliasSymbol is immutable, no need for deep copy. + } + + /** + * Gets the size of this list. + * @return the size of this list + */ + public int size() { + return internalList.size(); + } + + public List asReadonly() { + return Collections.unmodifiableList(internalList); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AliasSymbolList // instanceof handles nulls + && this.internalList.equals( ((AliasSymbolList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/savvytasker/model/alias/DuplicateSymbolKeywordException.java b/src/main/java/seedu/savvytasker/model/alias/DuplicateSymbolKeywordException.java new file mode 100644 index 000000000000..72d933bd4063 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/alias/DuplicateSymbolKeywordException.java @@ -0,0 +1,12 @@ +//@@author A0139916U +package seedu.savvytasker.model.alias; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; + +public class DuplicateSymbolKeywordException extends IllegalValueException { + private static final long serialVersionUID = -5516547118656055929L; + + public DuplicateSymbolKeywordException() { + super("Operation will result in two symbols with the same keyword defined."); + } +} diff --git a/src/main/java/seedu/savvytasker/model/alias/SymbolKeywordNotFoundException.java b/src/main/java/seedu/savvytasker/model/alias/SymbolKeywordNotFoundException.java new file mode 100644 index 000000000000..f37dbd7a871e --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/alias/SymbolKeywordNotFoundException.java @@ -0,0 +1,12 @@ +//@@author A0139916U +package seedu.savvytasker.model.alias; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; + +public class SymbolKeywordNotFoundException extends IllegalValueException { + private static final long serialVersionUID = -5516547118656055929L; + + public SymbolKeywordNotFoundException() { + super("Unable to find a symbol with the specified keyword."); + } +} diff --git a/src/main/java/seedu/savvytasker/model/task/FindType.java b/src/main/java/seedu/savvytasker/model/task/FindType.java new file mode 100644 index 000000000000..aab2e4e9b111 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/task/FindType.java @@ -0,0 +1,48 @@ +//@@author A0139916U +package seedu.savvytasker.model.task; + +/** + * This enum represents the type of matching used when trying to find tasks + * from keywords. + */ +public enum FindType { + /** + * Specifies partial matching of a single keyword. + * Searches by Task's name. + * E.g. 'ap' matches 'happy' + */ + Partial, + /** + * Specifies full matching of a single keyword. + * Searches by Task's name. + * E.g. 'ap' does not match 'happy', 'happy' matches 'very happy' + */ + Full, + /** + * Specifies full matching of all keywords in a set of keywords. + * Searches by Task's name. + * E.g. 'happy' does not match 'very happy', 'very happy' matches 'very happy' + */ + Exact, + /** + * Specifies partial matching of all keywords in a set of keywords. + * Searches by Task's category. + * E.g. 'happy' does not match 'very happy', 'very happy' matches 'very happy' + */ + Category; + + /** + * Gets a FindType enum object from its enum name, ignoring cases. + * @param name the name of the FindType enum object + * @return the corresponding FindType enum object + */ + public static FindType valueOfIgnoreCase(String name) { + for (FindType type : FindType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown find type: " + name); + } +} diff --git a/src/main/java/seedu/savvytasker/model/task/PriorityLevel.java b/src/main/java/seedu/savvytasker/model/task/PriorityLevel.java new file mode 100644 index 000000000000..836b8faeedfb --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/task/PriorityLevel.java @@ -0,0 +1,26 @@ +//@@author A0139916U +package seedu.savvytasker.model.task; + +/** + * This enum represents the priority level of tasks. + */ +public enum PriorityLevel { + Low, + Medium, + High; + + /** + * Gets a PriorityLevel enum object from its enum name, ignoring cases. + * @param name the name of the PriorityLevel enum object + * @return the corresponding PriorityLevel enum object + */ + public static PriorityLevel valueOfIgnoreCase(String name) { + for (PriorityLevel type : PriorityLevel.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown priority level: " + name); + } +} diff --git a/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java b/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java new file mode 100644 index 000000000000..d29526f9e35a --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java @@ -0,0 +1,200 @@ +package seedu.savvytasker.model.task; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.commons.lang.time.DateUtils; + +//@@author A0139915W +/** + * A read-only immutable interface for a Task in the TaskList. + * Implementations should guarantee: details are present and not null, field values are validated. + */ +public interface ReadOnlyTask { + + int getId(); + int getGroupId(); + boolean isMarked(); + boolean isArchived(); + String getTaskName(); + Date getStartDateTime(); + Date getEndDateTime(); + String getLocation(); + PriorityLevel getPriority(); + RecurrenceType getRecurringType(); + int getNumberOfRecurrence(); + String getCategory(); + String getDescription(); + + /** + * Returns true if both tasks have the same state. (interfaces cannot override .equals) + */ + default boolean isSameStateAs(ReadOnlyTask other) { + return getId() == other.getId(); + } + + /** + * Formats the task as text, showing all task details. + */ + default String getAsText() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Id: ") + .append(getId()) + .append(" Task Name: ") + .append(getTaskName()) + .append(" Archived: ") + .append(isArchived()); + if (getStartDateTime() != null) { + builder.append(" Start: ") + .append(getStartDateTime()); + } + if (getEndDateTime() != null) { + builder.append(" End: ") + .append(getEndDateTime()); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()); + } + builder.append(" Priority: ") + .append(getPriority()); + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + .append(getDescription()); + } + builder.append(" Archived: ") + .append(isArchived()); + return builder.toString(); + } + + + /** + * Formats the task as text, showing all task details, formatted for the UI. + */ + default String getTextForUi() { + final StringBuilder builder = new StringBuilder(); + if (getStartDateTime() != null || getEndDateTime() != null) { + builder.append(generateDateTime(getStartDateTime(), getEndDateTime())) + .append("\n"); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()) + .append("\n"); + } + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()) + .append("\n"); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + .append(getDescription()) + .append("\n"); + } + return builder.toString(); + } + //@@author + + //@@author A0138431L + static final String EMPTY_FIELD = " "; + + static String DATE_PATTERN = "dd MMM yy"; + static String TIME_PATTERN = "hh:mm a"; + + // String format for deadline tasks dateTime format + static String DEADLINE_FORMAT = "by %1$s, %2$s"; + + // String format for event tasks dateTime format + static String EVENT_DIFF_START_END_DATE_FORMAT = "%1$s, %2$s to %3$s, %4$s"; + static String EVENT_SAME_START_END_DATE_FORMAT = "%1$s, %2$s to %3$s"; + + static Date lastDayOfSelectedWeek = new Date(); + + /** + * Generates the DateTime Format for all tasks with time. + * + * @param task the task to have its DateTime Format generated + * + * @return DateTime Format (e.g. (31 Oct 16, 10:00PM) + **/ + default String generateDateTime(Date start, Date end) { + String dateTimeFormat; + //Floating Task + if(start == null && end == null) { + dateTimeFormat = EMPTY_FIELD; + } + //Deadline Task + else if(start == null && end != null) { + dateTimeFormat = generateDeadlineDateTime(end); + //Event Task + }else { + dateTimeFormat = generateEventDateTime(start, end); + } + + return dateTimeFormat; + + } + + /** + * Generates the dateTimeFormat for deadline tasks + * + * @param task the task to have its dateTimeFormat generated + * + * @return dateTimeFormat (e.g. 30 Oct 16, 10:00PM) + */ + default String generateDeadlineDateTime(Date end) { + + String dateTimeFormat; + + SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_PATTERN); + SimpleDateFormat timeFormatter = new SimpleDateFormat(TIME_PATTERN); + + String taskEndDateFormat = dateFormatter.format(end.getTime()); + String taskEndTimeFormat = timeFormatter.format(end.getTime()); + + dateTimeFormat = String.format(DEADLINE_FORMAT, taskEndDateFormat, taskEndTimeFormat); + + return dateTimeFormat; + + } + + /** + * Generates the dateTimeFormat for ranged tasks + * + * @param task the task to have its dateTimeFormat generated + * + * @return dateTimeFormat (e.g. 30 Oct 16, 8:00AM to 9:00PM) + */ + default String generateEventDateTime(Date start, Date end) { + + String dateTimeFormat; + + SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_PATTERN); + SimpleDateFormat timeFormatter = new SimpleDateFormat(TIME_PATTERN); + + String taskStartDateFormat = dateFormatter.format(start.getTime()); + String taskStartTimeFormat = timeFormatter.format(start.getTime()); + + String taskEndDateFormat = dateFormatter.format(end.getTime()); + String taskEndTimeFormat = timeFormatter.format(end.getTime()); + + if(DateUtils.isSameDay(start, end) == false) { + + dateTimeFormat = String.format(EVENT_DIFF_START_END_DATE_FORMAT, taskStartDateFormat, taskStartTimeFormat, taskEndDateFormat, taskEndTimeFormat); + + } else { + + dateTimeFormat = String.format(EVENT_SAME_START_END_DATE_FORMAT, taskEndDateFormat, taskStartTimeFormat, taskEndTimeFormat); + + } + + return dateTimeFormat; + + } +} +//@@author diff --git a/src/main/java/seedu/savvytasker/model/task/RecurrenceType.java b/src/main/java/seedu/savvytasker/model/task/RecurrenceType.java new file mode 100644 index 000000000000..cb4e32e59f16 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/task/RecurrenceType.java @@ -0,0 +1,43 @@ +//@@author A0139916U +package seedu.savvytasker.model.task; + +/** + * This enum represents the type of recurrence of a recurring task. + */ +public enum RecurrenceType { + /** + * Specifies no recurrence i.e. the task is a one-time task. + */ + None, + /** + * Specifies daily recurrence. + */ + Daily, + /** + * Specifies weekly recurrence. + */ + Weekly, + /** + * Specifies monthly recurrence. + */ + Monthly, + /** + * Specifies yearly recurrence. + */ + Yearly; + + /** + * Gets a RecurrenceType enum object from its enum name, ignoring cases. + * @param name the name of the RecurrenceType enum object + * @return the corresponding RecurrenceType enum object + */ + public static RecurrenceType valueOfIgnoreCase(String name) { + for (RecurrenceType type : RecurrenceType.values()) { + if (type.toString().equalsIgnoreCase(name)) { + return type; + } + } + + throw new IllegalArgumentException("Unknown recurrence type: " + name); + } +} diff --git a/src/main/java/seedu/savvytasker/model/task/Task.java b/src/main/java/seedu/savvytasker/model/task/Task.java new file mode 100644 index 000000000000..361060f26140 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/task/Task.java @@ -0,0 +1,333 @@ +package seedu.savvytasker.model.task; + +import java.util.Date; +import java.util.Objects; + +import seedu.savvytasker.commons.util.SmartDefaultDates; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; + +//@@author A0139915W +/** + * Represents a Task in the task list. + */ +public class Task implements ReadOnlyTask { + + private int id; + private int groupId; + private String taskName; + private Date startDateTime; + private Date endDateTime; + private String location; + private PriorityLevel priority; + private RecurrenceType recurringType; + private int numberOfRecurrence; + private String category; + private String description; + private boolean isArchived; + + /** + * Constructor with smart defaults + */ + public Task(int id, int groupId, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, + String category, String description, boolean isArchived) { + + SmartDefaultDates sdd = new SmartDefaultDates(startDateTime, endDateTime); + this.id = id; + this.groupId = groupId; + this.taskName = taskName; + this.startDateTime = sdd.getStartDate(); + this.endDateTime = sdd.getEndDate(); + this.location = location; + if (priority == null) { + this.priority = PriorityLevel.Medium; + } else { + this.priority = priority; + } + if (recurringType == null) { + this.recurringType = RecurrenceType.None; + } else { + this.recurringType = recurringType; + } + if (numberOfRecurrence == null) { + this.numberOfRecurrence = 1; + } else { + this.numberOfRecurrence = numberOfRecurrence.intValue(); + } + this.category = category; + this.description = description; + this.isArchived = isArchived; + } + + /** + * Constructor without smart defaults + */ + public Task(int id, int groupId, String taskName, Date startDateTime, Date endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, + String category, String description, boolean isArchived) { + + this.id = id; + this.groupId = groupId; + this.taskName = taskName; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.location = location; + if (priority == null) { + this.priority = PriorityLevel.Medium; + } else { + this.priority = priority; + } + if (recurringType == null) { + this.recurringType = RecurrenceType.None; + } else { + this.recurringType = recurringType; + } + if (numberOfRecurrence == null) { + this.numberOfRecurrence = 1; + } else { + this.numberOfRecurrence = numberOfRecurrence.intValue(); + } + this.category = category; + this.description = description; + this.isArchived = isArchived; + } + + public Task(String taskName) { + this.taskName = taskName; + // sets initial default values + this.priority = PriorityLevel.Medium; + } + + /** + * Copy constructor. + */ + public Task(ReadOnlyTask source) { + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), + source.getEndDateTime(), source.getLocation(), source.getPriority(), + source.getRecurringType(), source.getNumberOfRecurrence(), + source.getCategory(), source.getDescription(), source.isArchived()); + } + + /** + * Copy and modify constructor + */ + public Task(ReadOnlyTask source, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, + String description) { + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), + source.getEndDateTime(), source.getLocation(), source.getPriority(), + source.getRecurringType(), source.getNumberOfRecurrence(), + source.getCategory(), source.getDescription(), source.isArchived()); + + //this.id should follow that of the source. + //this.groupId should follow that of the source. + //this.isArchived should follow that of the source. + this.taskName = taskName == null ? this.taskName : taskName; + setStartDate(startDateTime); + setEndDate(endDateTime); + this.location = location == null ? this.location : location; + this.priority = priority == null ? this.priority : priority; + this.recurringType = recurringType == null ? this.recurringType : recurringType; + this.numberOfRecurrence = numberOfRecurrence == null ? this.numberOfRecurrence : numberOfRecurrence.intValue(); + this.category = category == null ? this.category : category; + this.description = description == null ? this.description : description; + } + + private void setStartDate(InferredDate inferredDate) { + if (inferredDate != null) { + if (inferredDate.isDateInferred() && inferredDate.isTimeInferred()) { + // user specified s/ but with nothing tagged to it + // remove existing start date + this.startDateTime = null; + } else { + // user specified s/ with something tagged to it + // update existing start date + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + this.startDateTime = sdd.getStart(inferredDate); + } + } + } + + private void setEndDate(InferredDate inferredDate) { + if (inferredDate != null) { + if (inferredDate.isDateInferred() && inferredDate.isTimeInferred()) { + // user specified e/ but with nothing tagged to it + // remove existing end date + this.endDateTime = null; + } else { + // user specified e/ with something tagged to it + // update existing end date + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + this.endDateTime = sdd.getStart(inferredDate); + } + } + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getGroupId() { + return this.groupId; + } + + @Override + public boolean isMarked() { + return isArchived(); // all marked tasks are archived + } + + @Override + public boolean isArchived() { + return this.isArchived; + } + + @Override + public String getTaskName() { + return taskName; + } + + @Override + public Date getStartDateTime() { + return startDateTime; + } + + @Override + public Date getEndDateTime() { + return endDateTime; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public PriorityLevel getPriority() { + return priority; + } + + @Override + public RecurrenceType getRecurringType() { + return recurringType; + } + + @Override + public int getNumberOfRecurrence() { + return numberOfRecurrence; + } + + @Override + public String getCategory() { + return category; + } + + @Override + public String getDescription() { + return description; + } + + public void setId(int id) { + this.id = id; + } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public void setStartDateTime(Date startDateTime) { + this.startDateTime = startDateTime; + } + + public void setEndDateTime(Date endDateTime) { + this.endDateTime = endDateTime; + } + + public void setLocation(String location) { + this.location = location; + } + + public void setPriority(PriorityLevel priority) { + this.priority = priority; + } + + public void setRecurringType(RecurrenceType recurringType) { + this.recurringType = recurringType; + } + + public void setNumberOfRecurrence(int numberOfRecurrence) { + this.numberOfRecurrence = numberOfRecurrence; + } + + public void setCategory(String category) { + this.category = category; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setArchived(boolean isArchived) { + if (isArchived) { + mark(); + } else { + unmark(); + } + } + + /** + * Marks the task as complete, also moves this task from the active list + * to the archived list. + */ + public void mark() { + if (!isMarked()) { + this.isArchived = true; + } + } + + /** + * Unmarks the task as complete, also moves this task from the archived list + * to the active list. + */ + public void unmark() { + if (isMarked()) { + this.isArchived = false; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReadOnlyTask // instanceof handles nulls + && this.isSameStateAs((ReadOnlyTask) other)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(taskName); + } + + @Override + public String toString() { + return getAsText(); + } + + /** + * Creates a deep copy of this object. + */ + public Task clone() { + Task t = new Task(id, groupId, taskName, + (Date)startDateTime.clone(), (Date)endDateTime.clone(), + location, priority, recurringType, numberOfRecurrence, + category, description, isArchived); + return t; + } + +} +//@@author diff --git a/src/main/java/seedu/savvytasker/model/task/TaskList.java b/src/main/java/seedu/savvytasker/model/task/TaskList.java new file mode 100644 index 000000000000..2165115f7c17 --- /dev/null +++ b/src/main/java/seedu/savvytasker/model/task/TaskList.java @@ -0,0 +1,211 @@ +package seedu.savvytasker.model.task; + +import java.util.Iterator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.savvytasker.commons.exceptions.DuplicateDataException; +import seedu.savvytasker.commons.exceptions.IllegalValueException; + +//@@author A0139915W +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Task#equals(Object) + */ +public class TaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private int nextId = 0; + private boolean isNextIdInitialized = false; + private int nextGroupId = 0; + private boolean isNextGroupIdInitialized = false; + + /** + * Signals that an operation would have violated the 'end time earlier than start time' property of the list. + */ + public static class InvalidDateException extends IllegalValueException { + + /** + * Generated serial ID + */ + private static final long serialVersionUID = 8933810750762846403L; + + protected InvalidDateException() { + super("Operation would result in invalid start/end dates"); + } + } + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateTaskException extends DuplicateDataException { + + /** + * Generated serial ID + */ + private static final long serialVersionUID = 8933810750762846403L; + + protected DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } + } + + /** + * Signals that an operation targeting a specified task in the list would fail because + * there is no such matching task in the list. + */ + public static class TaskNotFoundException extends Exception { + + /** + * Generated serial ID + */ + private static final long serialVersionUID = -7591982407764643511L; + } + + /** + * Gets the next available id for uniquely identifying a task in + * Savvy Tasker. + * @return The next available id; + */ + public int getNextId() { + if (!isNextIdInitialized) { + int nextLowest = 0; // first id to be used is 1. Start finding with 0 + for (Task t : internalList) { + if (t.getId() > nextLowest) { + nextLowest = t.getId(); + } + } + // assumption that the number of tasks < 2^31 + // implementation will be buggy if nextId exceeds 2^31 + nextId = nextLowest; + assert nextId < Integer.MAX_VALUE; + isNextIdInitialized = true; + } + nextId++; + return nextId; + } + + /** + * Gets the next available group id for uniquely identifying a group of recurring tasks in + * Savvy Tasker. + * @return The next available group id; + */ + public int getNextGroupId() { + if (!isNextGroupIdInitialized) { + int nextLowest = 0; // first id to be used is 1. Start finding with 0 + for (Task t : internalList) { + if (t.getId() > nextLowest) { + nextLowest = t.getGroupId(); + } + } + // assumption that the number of tasks < 2^31 + // implementation will be buggy if nextId exceeds 2^31 + nextGroupId = nextLowest; + assert nextGroupId < Integer.MAX_VALUE; + isNextGroupIdInitialized = true; + } + nextGroupId++; + return nextGroupId; + } + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(ReadOnlyTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Returns true if the end time is not earlier than the start time. + */ + public boolean isValidStartEnd(ReadOnlyTask toCheck) { + assert toCheck != null; + return toCheck.getStartDateTime() == null || toCheck.getEndDateTime() == null || + toCheck.getStartDateTime().compareTo(toCheck.getEndDateTime()) < 0; + } + + /** + * Adds a task to the list. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task added if the add is successful, an exception is thrown otherwise. + */ + public Task add(Task toAdd) throws DuplicateTaskException, InvalidDateException { + assert toAdd != null; + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + if (!isValidStartEnd(toAdd)) { + throw new InvalidDateException(); + } + internalList.add(toAdd); + return toAdd; + } + + /** + * Removes the equivalent task from the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. + */ + public Task remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + assert toRemove != null; + int index = internalList.indexOf(toRemove); + if (index >= 0) { + final Task taskToDelete = internalList.get(index); + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskToDelete; + } else { + throw new TaskNotFoundException(); + } + } + + /** + * Replaces the equivalent task from the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task replaced if the replace is successful, an exception is thrown otherwise. + */ + public Task replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotFoundException, InvalidDateException { + assert toReplace != null; + assert replacement != null; + if (internalList.contains(toReplace)) { + if (!isValidStartEnd(replacement)) { + throw new InvalidDateException(); + } + internalList.set(internalList.indexOf(toReplace), replacement); + return replacement; + } + else { + throw new TaskNotFoundException(); + } + } + + public ObservableList getInternalList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskList // instanceof handles nulls + && this.internalList.equals( ((TaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +//@@author diff --git a/src/main/java/seedu/savvytasker/storage/ConfigStorage.java b/src/main/java/seedu/savvytasker/storage/ConfigStorage.java new file mode 100644 index 000000000000..72ac5051173e --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/ConfigStorage.java @@ -0,0 +1,29 @@ +//@@author A0138431L +package seedu.savvytasker.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.exceptions.DataConversionException; + +/** + * Represents a storage for {@link seedu.savvytasker.commons.core.Config}. + */ +public interface ConfigStorage { + /** + * Returns Config data from storage. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readConfigFile() throws DataConversionException, IOException; + + /** + * Saves the given {@link seedu.savvytasker.commons.core.Config} to the storage. + * @param config cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveConfigFile(Config config) throws IOException; + +} \ No newline at end of file diff --git a/src/main/java/seedu/savvytasker/storage/JsonConfigStorage.java b/src/main/java/seedu/savvytasker/storage/JsonConfigStorage.java new file mode 100644 index 000000000000..271de40c6e9b --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/JsonConfigStorage.java @@ -0,0 +1,90 @@ +//@@author A0138431L + +package seedu.savvytasker.storage; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.FileUtil; + +/** + * A class to access Config stored in the hard disk as a json file + */ +public class JsonConfigStorage implements ConfigStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonConfigStorage.class); + + private String filePath; + + public JsonConfigStorage(String filePath) { + + this.filePath = filePath; + } + + @Override + public Optional readConfigFile() throws DataConversionException, IOException { + + return readConfig(filePath); + } + + @Override + public void saveConfigFile(Config config) throws IOException { + + saveConfig(config, filePath); + } + + /** + * Similar to {@link #readConfigFile()} + * @param configFilePath location of the data. Cannot be null. + * @throws DataConversionException if the file format is not as expected. + */ + public Optional readConfig(String configFilePath) throws DataConversionException { + + assert configFilePath != null; + + File configFile = new File(configFilePath); + + if (!configFile.exists()) { + + logger.info("Config file " + configFile + " not found"); + + return Optional.empty(); + + } + + Config config; + + try { + + config = FileUtil.deserializeObjectFromJsonFile(configFile, Config.class); + + } catch (IOException e) { + + logger.warning("Error reading from config file " + configFile + ": " + e); + + throw new DataConversionException(e); + + } + + return Optional.of(config); + } + + /** + * Similar to {@link #saveConfigFile(Config)} + * @param configFilePath location of the data. Cannot be null. + */ + private void saveConfig(Config config, String configFilePath) throws IOException { + + assert config != null; + assert configFilePath != null; + assert !configFilePath.isEmpty(); + + FileUtil.serializeObjectToJsonFile(new File(configFilePath), config); + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/savvytasker/storage/JsonUserPrefsStorage.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/savvytasker/storage/JsonUserPrefsStorage.java index 1efa8288e4f6..176317984931 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/savvytasker/storage/JsonUserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.UserPrefs; +package seedu.savvytasker.storage; import java.io.File; import java.io.IOException; import java.util.Optional; import java.util.logging.Logger; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.model.UserPrefs; + /** * A class to access UserPrefs stored in the hard disk as a json file */ diff --git a/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java b/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java new file mode 100644 index 000000000000..60a25ac03ea6 --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java @@ -0,0 +1,51 @@ +package seedu.savvytasker.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + +/** + * Represents a storage for {@link seedu.savvytasker.model.SavvyTasker}. + */ +public interface SavvyTaskerStorage { + + /** + * Returns the file path of the data file. + */ + String getSavvyTaskerFilePath(); + + //@@author A0139915W + /** + * Sets the file path of the data file. Returns true if successful, false otherwise + */ + boolean setSavvyTaskerFilePath(String path); + //@@author + + /** + * Returns SavvyTasker data as a {@link ReadOnlySavvyTasker}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readSavvyTasker() throws DataConversionException, IOException; + + /** + * @see #getSavvyTaskerFilePath() + */ + Optional readSavvyTasker(String filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlySavvyTasker} to the storage. + * @param savvyTasker cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker) throws IOException; + + /** + * @see #saveSavvyTasker(ReadOnlySavvyTasker) + */ + void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker, String filePath) throws IOException; + +} diff --git a/src/main/java/seedu/savvytasker/storage/Storage.java b/src/main/java/seedu/savvytasker/storage/Storage.java new file mode 100644 index 000000000000..4ccbb1b5e76a --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/Storage.java @@ -0,0 +1,49 @@ +package seedu.savvytasker.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends SavvyTaskerStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(UserPrefs userPrefs) throws IOException; + + @Override + String getSavvyTaskerFilePath(); + + @Override + boolean setSavvyTaskerFilePath(String path); + + @Override + Optional readSavvyTasker() throws DataConversionException, IOException; + + @Override + void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker) throws IOException; + + /** + * Changes the storage location as requested + * Creates the data file at the new location. + * Raises {@link DataSavingExceptionEvent} if there was an error during saving. + */ + void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce); + + /** + * Saves the current version of the Savvy Tasker to the hard disk. + * Creates the data file if it is missing. + * Raises {@link DataSavingExceptionEvent} if there was an error during saving. + */ + void handleSavvyTaskerChangedEvent(SavvyTaskerChangedEvent stce); +} diff --git a/src/main/java/seedu/savvytasker/storage/StorageManager.java b/src/main/java/seedu/savvytasker/storage/StorageManager.java new file mode 100644 index 000000000000..5510e67eaef1 --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/StorageManager.java @@ -0,0 +1,111 @@ +package seedu.savvytasker.storage; + +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import seedu.savvytasker.commons.core.ComponentManager; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.UserPrefs; + +/** + * Manages storage of AddressBook data in local storage. + */ +public class StorageManager extends ComponentManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private SavvyTaskerStorage savvyTaskerStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(SavvyTaskerStorage savvyTaskerStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.savvyTaskerStorage = savvyTaskerStorage; + this.userPrefsStorage = userPrefsStorage; + } + + public StorageManager(String savvyTaskerFilePath, String userPrefsFilePath) { + this(new XmlSavvyTaskerStorage(savvyTaskerFilePath), new JsonUserPrefsStorage(userPrefsFilePath)); + } + + // ================ UserPrefs methods ============================== + + @Override + public Optional readUserPrefs() throws DataConversionException, IOException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(UserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ SavvyTasker methods ============================== + + @Override + public String getSavvyTaskerFilePath() { + return savvyTaskerStorage.getSavvyTaskerFilePath(); + } + + //@@author A0139915W + @Override + public boolean setSavvyTaskerFilePath(String path) { + return savvyTaskerStorage.setSavvyTaskerFilePath(path); + } + //@@author + + @Override + public Optional readSavvyTasker() throws DataConversionException, IOException { + return readSavvyTasker(savvyTaskerStorage.getSavvyTaskerFilePath()); + } + + @Override + public Optional readSavvyTasker(String filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return savvyTaskerStorage.readSavvyTasker(filePath); + } + + @Override + public void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker) throws IOException { + saveSavvyTasker(savvyTasker, savvyTaskerStorage.getSavvyTaskerFilePath()); + } + + @Override + public void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker, String filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + savvyTaskerStorage.saveSavvyTasker(savvyTasker, filePath); + } + + //@@author A0138431L + @Override + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + logger.info(LogsCenter.getEventHandlingLogMessage(dslce, "Local storage location changed.")); + try { + saveSavvyTasker(dslce.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + //@@author + + @Override + @Subscribe + public void handleSavvyTaskerChangedEvent(SavvyTaskerChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); + try { + saveSavvyTasker(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/savvytasker/storage/UserPrefsStorage.java similarity index 69% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/savvytasker/storage/UserPrefsStorage.java index ad2dc935187c..6905b018dfd4 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/savvytasker/storage/UserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; +package seedu.savvytasker.storage; import java.io.IOException; import java.util.Optional; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.model.UserPrefs; + /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link seedu.savvytasker.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -20,7 +20,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * Saves the given {@link seedu.savvytasker.model.UserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/savvytasker/storage/XmlAdaptedAliasSymbol.java b/src/main/java/seedu/savvytasker/storage/XmlAdaptedAliasSymbol.java new file mode 100644 index 000000000000..2292a5e39494 --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/XmlAdaptedAliasSymbol.java @@ -0,0 +1,40 @@ +//@@author A0139916U +package seedu.savvytasker.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.alias.AliasSymbol; + +public class XmlAdaptedAliasSymbol { + + @XmlElement(required = true) + private String keyword; + @XmlElement(required = true) + private String representation; + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedAliasSymbol() {} + + + /** + * Converts a given AliasSymbol into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedAliasSymbol + */ + public XmlAdaptedAliasSymbol(AliasSymbol source) { + keyword = source.getKeyword(); + representation = source.getRepresentation(); + } + + /** + * Converts this jaxb-friendly adapted alias symbol object into the model's AliasSymbol object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted AliasSymbol + */ + public AliasSymbol toModelType() throws IllegalValueException { + return new AliasSymbol(keyword, representation); + } +} diff --git a/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java b/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java new file mode 100644 index 000000000000..7df5aab8cf31 --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java @@ -0,0 +1,82 @@ +package seedu.savvytasker.storage; + +import java.util.Date; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; + +//@@author A0139915W +/** + * JAXB-friendly version of the Task. + */ +public class XmlAdaptedTask { + + @XmlElement(required = true) + private int id; + @XmlElement(required = false) + private int groupId; + @XmlElement(required = true) + private String taskName; + @XmlElement(required = false) + private Date startDateTime; + @XmlElement(required = false) + private Date endDateTime; + @XmlElement(required = false) + private String location; + @XmlElement(required = false) + private PriorityLevel priority; + @XmlElement(required = false) + private String category; + @XmlElement(required = false) + private String description; + @XmlElement(required = true) + private boolean isArchived; + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedTask() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedTask(ReadOnlyTask source) { + id = source.getId(); + groupId = source.getGroupId(); + taskName = source.getTaskName(); + startDateTime = source.getStartDateTime(); + endDateTime = source.getEndDateTime(); + location = source.getLocation(); + priority = source.getPriority(); + category = source.getCategory(); + description = source.getDescription(); + 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 int id = this.id; + final String taskName = this.taskName; + final Date startDateTime = this.startDateTime; + final Date endDateTime = this.endDateTime; + final String location = this.location; + final PriorityLevel priority = this.priority; + final String category = this.category; + final String description = this.description; + final boolean isArchived = this.isArchived; + return new Task(id, groupId, taskName, startDateTime, endDateTime, location, priority, + null, null, category, description, isArchived); + } +} +//@@author diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/savvytasker/storage/XmlFileStorage.java similarity index 65% rename from src/main/java/seedu/address/storage/XmlFileStorage.java rename to src/main/java/seedu/savvytasker/storage/XmlFileStorage.java index 27a5210cadaf..6848873f4bc6 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/savvytasker/storage/XmlFileStorage.java @@ -1,23 +1,24 @@ -package seedu.address.storage; +package seedu.savvytasker.storage; -import seedu.address.commons.util.XmlUtil; -import seedu.address.commons.exceptions.DataConversionException; - -import javax.xml.bind.JAXBException; import java.io.File; import java.io.FileNotFoundException; +import javax.xml.bind.JAXBException; + +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.XmlUtil; + /** - * Stores addressbook data in an XML file + * Stores savvytasker data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the given savvytasker data to the specified file. */ - public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(File file, XmlSerializableSavvyTasker savvyTasker) throws FileNotFoundException { try { - XmlUtil.saveDataToFile(file, addressBook); + XmlUtil.saveDataToFile(file, savvyTasker); } catch (JAXBException e) { assert false : "Unexpected exception " + e.getMessage(); } @@ -26,10 +27,10 @@ public static void saveDataToFile(File file, XmlSerializableAddressBook addressB /** * Returns address book in the file or an empty address book */ - public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, + public static XmlSerializableSavvyTasker loadDataFromSaveFile(File file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableSavvyTasker.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java b/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java new file mode 100644 index 000000000000..29092ceb7cc6 --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java @@ -0,0 +1,95 @@ +package seedu.savvytasker.storage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + +/** + * A class to access SavvyTasker data stored as an xml file on the hard disk. + */ +public class XmlSavvyTaskerStorage implements SavvyTaskerStorage { + + private static final Logger logger = LogsCenter.getLogger(XmlSavvyTaskerStorage.class); + + private String filePath; + + public XmlSavvyTaskerStorage(String filePath){ + this.filePath = filePath; + } + + @Override + public String getSavvyTaskerFilePath() { + return filePath; + } + + //@@author A0139915W + @Override + /** + * Changes the path of the storage location if the file at newPath can be successfully created. + */ + public boolean setSavvyTaskerFilePath(String newPath) { + try { + File file = new File(newPath); + if (FileUtil.createIfMissing(file)) { + this.filePath = newPath; + return true; + } else { + return false; + } + } catch (IOException e) { + // do nothing, can't set the path to new path + } + return false; + } + //@@author + + /** + * Similar to {@link #readSavvyTasker()} + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readSavvyTasker(String filePath) throws DataConversionException, FileNotFoundException { + assert filePath != null; + + File savvyTaskerFile = new File(filePath); + + if (!savvyTaskerFile.exists()) { + logger.info("SavvyTasker file " + savvyTaskerFile + " not found"); + return Optional.empty(); + } + + ReadOnlySavvyTasker savvyTaskerOptional = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + + return Optional.of(savvyTaskerOptional); + } + + /** + * Similar to {@link #saveSavvyTasker(ReadOnlySavvyTasker)} + * @param filePath location of the data. Cannot be null + */ + public void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker, String filePath) throws IOException { + assert savvyTasker != null; + assert filePath != null; + + File file = new File(filePath); + FileUtil.createIfMissing(file); + XmlFileStorage.saveDataToFile(file, new XmlSerializableSavvyTasker(savvyTasker)); + } + + @Override + public Optional readSavvyTasker() throws DataConversionException, IOException { + return readSavvyTasker(filePath); + } + + @Override + public void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker) throws IOException { + saveSavvyTasker(savvyTasker, filePath); + } +} diff --git a/src/main/java/seedu/savvytasker/storage/XmlSerializableSavvyTasker.java b/src/main/java/seedu/savvytasker/storage/XmlSerializableSavvyTasker.java new file mode 100644 index 000000000000..0c9e71e69558 --- /dev/null +++ b/src/main/java/seedu/savvytasker/storage/XmlSerializableSavvyTasker.java @@ -0,0 +1,100 @@ +package seedu.savvytasker.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.alias.AliasSymbol; +import seedu.savvytasker.model.alias.AliasSymbolList; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.TaskList; + +/** + * An Immutable SavvyTasker that is serializable to XML format + */ +@XmlRootElement(name = "savvytasker") +public class XmlSerializableSavvyTasker implements ReadOnlySavvyTasker { + private static final Logger logger = LogsCenter.getLogger(XmlSerializableSavvyTasker.class); + + @XmlElement + private List tasks; + @XmlElement + private List symbols; + + /** + * Empty constructor required for marshalling + */ + public XmlSerializableSavvyTasker() { + tasks = new ArrayList<>(); + symbols = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableSavvyTasker(ReadOnlySavvyTasker src) { + this(); + tasks.addAll(src.getReadOnlyListOfTasks().stream().map(XmlAdaptedTask::new).collect(Collectors.toList())); + symbols.addAll(src.getReadOnlyListOfAliasSymbols().stream().map(XmlAdaptedAliasSymbol::new).collect(Collectors.toList())); + } + + @Override + public TaskList getTaskList() { + TaskList lists = new TaskList(); + for (XmlAdaptedTask t : tasks) { + try { + lists.add(t.toModelType()); + } catch (IllegalValueException e) { + logger.warning("Failed to convert XmlAdaptedTask to Task or add it to TaskList: " + + e.getMessage()); + } + } + return lists; + } + + @Override + public List getReadOnlyListOfTasks() { + return tasks.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + logger.warning("Failed to convert XmlAdaptedTask to Task: " + e.getMessage()); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public AliasSymbolList getAliasSymbolList() { + AliasSymbolList lists = new AliasSymbolList(); + for (XmlAdaptedAliasSymbol s : symbols) { + try { + lists.addAliasSymbol(s.toModelType()); + } catch (IllegalValueException e) { + logger.warning("Failed to convert XmlAliasSymbol to AliasSymbol " + + "or add it to AliasSymbolList: " + e.getMessage() ); + } + } + return lists; + + } + + @Override + public List getReadOnlyListOfAliasSymbols() { + return symbols.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + logger.warning("Failed to convert XmlAliasSymbol to AliasSymbol: " + e.getMessage()); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } +} diff --git a/src/main/java/seedu/savvytasker/ui/AliasSymbolCard.java b/src/main/java/seedu/savvytasker/ui/AliasSymbolCard.java new file mode 100644 index 000000000000..77ca5e554d54 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/AliasSymbolCard.java @@ -0,0 +1,52 @@ +package seedu.savvytasker.ui; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import seedu.savvytasker.model.alias.AliasSymbol; + +public class AliasSymbolCard extends UiPart{ + + private static final String FXML = "AliasSymbolListCard.fxml"; + + @FXML + private HBox cardPane; + @FXML + private Label aliasName; + @FXML + private Label id; + @FXML + private Label details; + + private AliasSymbol symbol; + private int displayedIndex; + + public static AliasSymbolCard load(AliasSymbol symbol, int displayedIndex){ + AliasSymbolCard card = new AliasSymbolCard(); + card.symbol = symbol; + card.displayedIndex = displayedIndex; + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + aliasName.setText(symbol.getKeyword()); + id.setText(displayedIndex + ". "); + details.setText(symbol.getRepresentation()); + } + + public HBox getLayout() { + return cardPane; + } + + @Override + public void setNode(Node node) { + cardPane = (HBox)node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} diff --git a/src/main/java/seedu/savvytasker/ui/AliasSymbolListPanel.java b/src/main/java/seedu/savvytasker/ui/AliasSymbolListPanel.java new file mode 100644 index 000000000000..85f8949f1b1e --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/AliasSymbolListPanel.java @@ -0,0 +1,104 @@ +package seedu.savvytasker.ui; + +import java.util.logging.Logger; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.model.alias.AliasSymbol; + +/** + * Panel containing the list of persons. + */ +public class AliasSymbolListPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(AliasSymbolListPanel.class); + private static final String FXML = "AliasSymbolListPanel.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView aliasSymbolListView; + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + public Node getNode() { + return panel; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static AliasSymbolListPanel load(Stage primaryStage, AnchorPane aliasSymbolListPlaceholder, + ObservableList aliasList) { + AliasSymbolListPanel aliasListPanel = + UiPartLoader.loadUiPart(primaryStage, aliasSymbolListPlaceholder, new AliasSymbolListPanel()); + aliasListPanel.configure(aliasList); + return aliasListPanel; + } + + private void configure(ObservableList aliasList) { + setConnections(aliasList); + addToPlaceholder(); + } + + private void setConnections(ObservableList aliasList) { + aliasSymbolListView.setItems(aliasList); + aliasSymbolListView.setCellFactory(listView -> new AliasSymbolListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + aliasSymbolListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in alias list panel changed to : '" + newValue + "'"); + //raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + aliasSymbolListView.scrollTo(index); + aliasSymbolListView.getSelectionModel().clearAndSelect(index); + }); + } + + class AliasSymbolListViewCell extends ListCell { + + @Override + protected void updateItem(AliasSymbol symbol, boolean empty) { + super.updateItem(symbol, empty); + + if (empty || symbol == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(AliasSymbolCard.load(symbol, getIndex() + 1).getLayout()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/savvytasker/ui/BrowserPanel.java similarity index 83% rename from src/main/java/seedu/address/ui/BrowserPanel.java rename to src/main/java/seedu/savvytasker/ui/BrowserPanel.java index 54b88318019b..3475f9fde5ac 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/savvytasker/ui/BrowserPanel.java @@ -1,14 +1,14 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; + +import java.util.logging.Logger; import javafx.event.Event; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; import javafx.scene.web.WebView; -import seedu.address.commons.util.FxViewUtil; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.util.FxViewUtil; +import seedu.savvytasker.model.task.ReadOnlyTask; /** * The Browser Panel of the App. @@ -50,8 +50,8 @@ public static BrowserPanel load(AnchorPane placeholder){ return browserPanel; } - public void loadPersonPage(ReadOnlyPerson person) { - loadPage("https://www.google.com.sg/#safe=off&q=" + person.getName().fullName.replaceAll(" ", "+")); + public void loadPersonPage(ReadOnlyTask task) { + loadPage("https://www.google.com.sg/#safe=off&q=" + task.getTaskName().replaceAll(" ", "+")); } public void loadPage(String url){ diff --git a/src/main/java/seedu/savvytasker/ui/CommandBox.java b/src/main/java/seedu/savvytasker/ui/CommandBox.java new file mode 100755 index 000000000000..1f5e2eb15ecf --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/CommandBox.java @@ -0,0 +1,375 @@ +//@@author A0138431L +package seedu.savvytasker.ui; + +import java.io.File; +import java.util.Calendar; +import java.util.Date; +import java.util.Stack; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.ui.IncorrectCommandAttemptedEvent; +import seedu.savvytasker.commons.events.ui.ShowCheatsheetEvent; +import seedu.savvytasker.commons.events.ui.WeekSelectionChangedEvent; +import seedu.savvytasker.commons.util.FxViewUtil; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.logic.commands.CommandResult; + +public class CommandBox extends UiPart { + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + private static final String FXML = "CommandBox.fxml"; + + private AnchorPane placeHolderPane; + private AnchorPane commandPane; + private ResultDisplay resultDisplay; + String previousCommandTest; + private Date date = new Date(); + private static int DAYS_OF_WEEK = 7; + + private final String UNDO_COMMAND = "undo"; + private final String REDO_COMMAND = "redo"; + private final String HELP_COMMAND = "help"; + private final String EXIT_COMMAND = "exit"; + private final String LIST_COMMAND = "list"; + private final String LIST_ARCHIVED_COMMAND = "list archived"; + private final String LIST_PRIORITY_COMMAND = "list priorityLevel"; + private final String LIST_ALIAS_COMMAND = "list alias"; + private final String CLEAR_COMMAND = "clear"; + private final String STORAGE_COMMAND = "storage ."; + + KeyCombination saveKey = new KeyCodeCombination(KeyCode.S, KeyCombination.META_DOWN); + KeyCombination undoKey = new KeyCodeCombination(KeyCode.Z, KeyCombination.META_DOWN); + KeyCombination redoKey = new KeyCodeCombination(KeyCode.Y, KeyCombination.META_DOWN); + KeyCombination helpKey = new KeyCodeCombination(KeyCode.H, KeyCombination.META_DOWN); + KeyCombination exitKey = new KeyCodeCombination(KeyCode.Q, KeyCombination.META_DOWN); + KeyCombination listKey = new KeyCodeCombination(KeyCode.L, KeyCombination.META_DOWN); + KeyCombination listArchivedKey = new KeyCodeCombination(KeyCode.A, KeyCombination.META_DOWN); + KeyCombination listPriorityKey = new KeyCodeCombination(KeyCode.P, KeyCombination.META_DOWN); + KeyCombination listAliasKey = new KeyCodeCombination(KeyCode.I, KeyCombination.META_DOWN); + KeyCombination clearKey = new KeyCodeCombination(KeyCode.D, KeyCombination.META_DOWN); + KeyCombination leftKey = new KeyCodeCombination(KeyCode.LEFT, KeyCombination.META_DOWN); + KeyCombination rightKey = new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.META_DOWN); + + // stack to store commands history + private static Stack COMMAND_HISTORY_STACK = new Stack(); + private static Stack COMMAND_FUTURE_STACK = new Stack(); + + private Logic logic; + + @FXML + private TextField commandTextField; + private CommandResult mostRecentResult; + + public static CommandBox load(Stage primaryStage, AnchorPane commandBoxPlaceholder, + ResultDisplay resultDisplay, Logic logic) { + CommandBox commandBox = UiPartLoader.loadUiPart(primaryStage, commandBoxPlaceholder, new CommandBox()); + commandBox.configure(resultDisplay, logic); + commandBox.addToPlaceholder(); + return commandBox; + } + + public void configure(ResultDisplay resultDisplay, Logic logic) { + this.resultDisplay = resultDisplay; + this.logic = logic; + registerAsAnEventHandler(this); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(commandTextField); + FxViewUtil.applyAnchorBoundaryParameters(commandPane, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, 0.0, 0.0); + } + + @Override + public void setNode(Node node) { + commandPane = (AnchorPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + + @FXML + private void handleCommandInputChanged() { + //Take a copy of the command text + previousCommandTest = commandTextField.getText(); + + COMMAND_HISTORY_STACK.push(previousCommandTest); + /* We assume the command is correct. If it is incorrect, the command box will be changed accordingly + * in the event handling code {@link #handleIncorrectCommandAttempted} + */ + setStyleToIndicateCorrectCommand(); + mostRecentResult = logic.execute(previousCommandTest); + resultDisplay.postMessage(mostRecentResult.feedbackToUser); + logger.info("Result: " + mostRecentResult.feedbackToUser); + } + + + /** + * Sets the command box style to indicate a correct command. + */ + private void setStyleToIndicateCorrectCommand() { + commandTextField.getStyleClass().remove("error"); + commandTextField.setText(""); + } + + @Subscribe + private void handleIncorrectCommandAttempted(IncorrectCommandAttemptedEvent event){ + logger.info(LogsCenter.getEventHandlingLogMessage(event,"Invalid command: " + previousCommandTest)); + setStyleToIndicateIncorrectCommand(); + restoreCommandText(); + } + + /** + * Restores the command box text to the previously entered command + */ + private void restoreCommandText() { + commandTextField.setText(""); + } + + /** + * Sets the command box style to indicate an error + */ + private void setStyleToIndicateIncorrectCommand() { + commandTextField.getStyleClass().add("error"); + } + + public Node getCommandTextField() { + return commandTextField; + } + + //==================== Keyboard Shortcuts Code ================================================================= + /** + * Key pressed handler for text box. + * + * @param keyEvent key event for the button that is being pressed. + */ + public void commandTextFieldOnKeyPressedHandler(KeyEvent keyEvent) { + + String userInput = commandTextField.getText().trim(); + + try { + + KeyCode keyCode = keyEvent.getCode(); + + if(saveKey.match(keyEvent)) { + + processSave(); + + }else if (keyCode == KeyCode.ESCAPE) { + + showCheatsheet(); + + } else if (keyCode == KeyCode.UP) { + + processUp(userInput); + + } else if (keyCode == KeyCode.DOWN) { + + processDown(userInput); + + } else if (leftKey.match(keyEvent)) { + + processDate(-1); + + } else if (rightKey.match(keyEvent)) { + + processDate(1); + + } else if (undoKey.match(keyEvent)) { + + executeCommand(UNDO_COMMAND); + + } else if (redoKey.match(keyEvent)) { + + executeCommand(REDO_COMMAND); + + } else if (helpKey.match(keyEvent)) { + + executeCommand(HELP_COMMAND); + + } else if (exitKey.match(keyEvent)) { + + executeCommand(EXIT_COMMAND); + + } else if (listKey.match(keyEvent)) { + + executeCommand(LIST_COMMAND); + + } else if (listArchivedKey.match(keyEvent)) { + + executeCommand(LIST_ARCHIVED_COMMAND); + + } else if (listPriorityKey.match(keyEvent)) { + + executeCommand(LIST_PRIORITY_COMMAND); + + } else if (listAliasKey.match(keyEvent)) { + + executeCommand(LIST_ALIAS_COMMAND); + + } else if (clearKey.match(keyEvent)) { + + executeCommand(CLEAR_COMMAND); + + } + + } catch (IllegalArgumentException e) { + + commandTextField.setText(""); + + COMMAND_HISTORY_STACK.add(userInput); + + this.logger.info("Illegal Argument has been entered."); + + } catch (Exception e) { + + e.printStackTrace(); + commandTextField.setText(""); + + this.logger.info("Illegal Argument has been entered."); + + } + + } + + /** + * Process the event that occurs after the user presses the ctrl-S button. + * + * @param userInput the command keyed in by the user. + */ + public void processSave() { + + //Execute the save command + DirectoryChooser directoryChooser = new DirectoryChooser(); + File selectedFile = directoryChooser.showDialog(primaryStage); + String filepath = selectedFile.getAbsolutePath(); + executeCommand(STORAGE_COMMAND + filepath + "/savvytasker.xml"); + + } + + /** + * Process the event that occurs after the user presses the [UP] button. + * + * @param userInput the command keyed in by the user. + */ + public void processUp(String userInput) { + + if (!COMMAND_HISTORY_STACK.isEmpty()) { + + String previousCommand = COMMAND_HISTORY_STACK.pop(); + + if (!userInput.equals("")) { + + COMMAND_FUTURE_STACK.push(userInput); + + } + + commandTextField.setText(previousCommand); + + } + + commandTextField.positionCaret(commandTextField.getText().length()); + + } + + /** + * Process the event that occurs after the user presses the down button. + * + * @param userInput the command keyed in by the user. + */ + public void processDown(String userInput) { + + if (!COMMAND_FUTURE_STACK.isEmpty()) { + + String nextCommand = COMMAND_FUTURE_STACK.pop(); + + COMMAND_HISTORY_STACK.push(userInput); + commandTextField.setText(nextCommand); + + } else if (!userInput.equals("")) { + + COMMAND_HISTORY_STACK.push(userInput); + commandTextField.setText(""); + + } + + commandTextField.positionCaret(commandTextField.getText().length()); + + } + + /** + * Process the event that occurs after the user presses the left or right button. + * + * @param numbers of week to be added to the current selected week to be displayed in the daily task list view + */ + public void processDate(int numberOfWeek) { + + date = addWeek(numberOfWeek, date); + indicateWeekSelectionChanged(); + } + + /** Raises an event to indicate the week to be displayed has changed */ + private void indicateWeekSelectionChanged() { + raise(new WeekSelectionChangedEvent()); + } + + /** Raises an event to indicate the week to be displayed has changed */ + private void showCheatsheet() { + raise(new ShowCheatsheetEvent()); + } + + /** + * Execute commands + * + * @param command to be executed + */ + public void executeCommand(String commandInput) { + CommandResult commandResult = logic.execute(commandInput); + resultDisplay.postMessage(commandResult.feedbackToUser); + logger.info("Result: " + commandResult.feedbackToUser); + } + + private Date addWeek(int numberOfWeek, Date date) { + + //convert date object to calendar object and add 1 days + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(date); + + calendarExpectedDate.add(Calendar.DATE, (numberOfWeek*DAYS_OF_WEEK)); + + //convert calendar object back to date object + date = calendarExpectedDate.getTime(); + + return date; + } + + public Date getDate() { + return date; + } +} + diff --git a/src/main/java/seedu/savvytasker/ui/DailyPanel.java b/src/main/java/seedu/savvytasker/ui/DailyPanel.java new file mode 100644 index 000000000000..a252b9c33ea2 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/DailyPanel.java @@ -0,0 +1,184 @@ +package seedu.savvytasker.ui; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.logging.Logger; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.time.DateUtils; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.ui.TaskPanelSelectionChangedEvent; +import seedu.savvytasker.commons.util.FxViewUtil; +import seedu.savvytasker.model.task.ReadOnlyTask; + +//@@author A0138431L + +/** + * Panel containing the list overdue task. + * @author A0138431L + * + */ +public class DailyPanel extends UiPart { + + private static String TODAY_TITLE = "Today"; + private static String TOMORROW_TITLE = "Tomorrow"; + private static String DAY_PATTERN = "EEEE"; + private static String DATE_PATTERN = "dd MMM yy"; + private static String DAY_DATE_FORMAT = "%1$s, %2$s"; + + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "DailyList.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + + @FXML + private TextField header; + + @FXML + private ListView taskListView; + + public DailyPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static DailyPanel load(Stage primaryStage, AnchorPane DailyListPlaceholder, + ObservableList taskList, int dayOfTheWeek, Date date) { + DailyPanel dailyPanel = + UiPartLoader.loadUiPart(primaryStage, DailyListPlaceholder, new DailyPanel()); + dailyPanel.configure(taskList, dayOfTheWeek, date); + return dailyPanel; + } + + private void configure(ObservableList taskList, int dayOfTheWeek, Date date) { + + String dateHeader = generateHeader(dayOfTheWeek, date); + Date today = new Date(); + if(date == today) { + placeHolderPane.setStyle("-fx-background-color:#FF0000"); + header.setStyle("-fx-text-fill:#FF0000"); + } + setConnections(taskList, dateHeader); + addToPlaceholder(); + + } + + private void setConnections(ObservableList taskList, String dateHeader) { + header.clear(); + header.setText(dateHeader); + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in daily task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + private String generateHeader(int dayOfTheWeek, Date date) { + + SimpleDateFormat dayFormatter = new SimpleDateFormat(DAY_PATTERN); + SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_PATTERN); + + Date today = new Date(); + Date tomorrow = new Date(); + tomorrow = addDay(1, tomorrow); + + String day; + + if(DateUtils.isSameDay(date, today)) { + + day = TODAY_TITLE; + + } else if (DateUtils.isSameDay(date, tomorrow)) { + + day = TOMORROW_TITLE; + + } else { + + day = dayFormatter.format(date); + + } + String header = String.format(DAY_DATE_FORMAT, day, dateFormatter.format(date)); + + return header; + } + + private Date addDay(int i, Date date) { + + //convert date object to calendar object and add 1 days + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(date); + + calendarExpectedDate.add(Calendar.DATE, i); + + //convert calendar object back to date object + date = calendarExpectedDate.getTime(); + + return date; + } + + class TaskListViewCell extends ListCell { + + public TaskListViewCell() { + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, 0, false).getLayout()); + } + } + } + +} diff --git a/src/main/java/seedu/savvytasker/ui/FloatingPanel.java b/src/main/java/seedu/savvytasker/ui/FloatingPanel.java new file mode 100644 index 000000000000..2074179901d2 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/FloatingPanel.java @@ -0,0 +1,112 @@ +package seedu.savvytasker.ui; + +import java.util.logging.Logger; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.ui.TaskPanelSelectionChangedEvent; +import seedu.savvytasker.model.task.ReadOnlyTask; + +//@@author A0138431L + +/** + * Panel containing the list floating task. + * @author A0138431L + * + */ +public class FloatingPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "FloatingList.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView taskListView; + + public FloatingPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static FloatingPanel load(Stage primaryStage, AnchorPane floatingListPlaceholder, + ObservableList taskList) { + FloatingPanel floatingPanel = + UiPartLoader.loadUiPart(primaryStage, floatingListPlaceholder, new FloatingPanel()); + floatingPanel.configure(taskList); + return floatingPanel; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in floating task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + class TaskListViewCell extends ListCell { + + public TaskListViewCell() { + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, 0, false).getLayout()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/savvytasker/ui/HelpWindow.java old mode 100644 new mode 100755 similarity index 87% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/seedu/savvytasker/ui/HelpWindow.java index 45b765ab6a0c..f98e4d5b0357 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/savvytasker/ui/HelpWindow.java @@ -1,14 +1,14 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; + +import java.util.logging.Logger; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.scene.web.WebView; import javafx.stage.Stage; -import seedu.address.commons.util.FxViewUtil; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.util.FxViewUtil; /** * Controller for a help page @@ -20,7 +20,7 @@ public class HelpWindow extends UiPart { private static final String FXML = "HelpWindow.fxml"; private static final String TITLE = "Help"; private static final String USERGUIDE_URL = - "https://github.com/se-edu/addressbook-level4/blob/master/docs/UserGuide.md"; + "https://github.com/CS2103AUG2016-T14-C2/main/blob/master/docs/UserGuide.md"; private AnchorPane mainPane; @@ -59,4 +59,10 @@ private void configure(){ public void show() { dialogStage.showAndWait(); } + + public void hide() { + + dialogStage.hide(); + + } } diff --git a/src/main/java/seedu/savvytasker/ui/MainWindow.java b/src/main/java/seedu/savvytasker/ui/MainWindow.java new file mode 100755 index 000000000000..4f6a94758057 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/MainWindow.java @@ -0,0 +1,407 @@ +//@@author A0138431L + +package seedu.savvytasker.ui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.MenuItem; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.GuiSettings; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.ui.ExitAppRequestEvent; +import seedu.savvytasker.commons.events.ui.ShowCheatsheetEvent; +import seedu.savvytasker.commons.events.ui.WeekSelectionChangedEvent; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.model.UserPrefs; +import seedu.savvytasker.model.task.ReadOnlyTask; + +/** + * The Main Window. Provides the basic application layout containing + * a sorting and filtered list that display the result of the user command + * on the left and a week's view of the task + * + * The week's view contains 4 lists, namely the floating list, + * days of the week list and upcoming list + * + * Floating list contains task without start and end dateTime + * Days of the week list contains task that falls on the respective day of the selected week + * Upcoming list contains task with start date after the last day of selected week + * + * @author A0138431L + * + */ +public class MainWindow extends UiPart { + + private static final String ICON = "/images/savvytasker-icon.png"; + private static final Image image = new Image(MainWindow.class.getResourceAsStream(ICON)); + private static final String CHEATSHEET = "/images/cheatsheet.png"; + private static final Image imageOverlay = new Image(MainWindow.class.getResourceAsStream(CHEATSHEET)); + private static final String FXML = "MainWindow.fxml"; + public static final int MIN_HEIGHT = 700; + public static final int MIN_WIDTH = 1150; + + private Logic logic; + Date firstDayOfSelectedWeek = new Date(); + private static int DAYS_OF_WEEK = 7; + private boolean isShown = false; + + // Independent Ui parts residing in this Ui container + //private BrowserPanel browserPanel; + private TaskListPanel taskListPanel; + private AliasSymbolListPanel aliasSymbolListPanel; + private ResultDisplay resultDisplay; + private StatusBarFooter statusBarFooter; + private CommandBox commandBox; + private Config config; + private UserPrefs userPrefs; + @FXML + private FloatingPanel floatingPanel; + @FXML + private DailyPanel dailyPanel; + @FXML + private UpcomingPanel upcomingPanel; + + // Handles to elements of this Ui container + private VBox rootLayout; + private Scene scene; + + private String addressBookName; + + @FXML + private AnchorPane browserPlaceholder; + + @FXML + private AnchorPane commandBoxPlaceholder; + + @FXML + private ImageView imageIcon; + + @FXML + private ImageView cheatsheet; + + @FXML + private AnchorPane taskListPanelPlaceholder; + + @FXML + private AnchorPane aliasSymbolListPanelPlaceholder; + + @FXML + private AnchorPane resultDisplayPlaceholder; + + @FXML + private AnchorPane statusbarPlaceholder; + + @FXML + private VBox listPanel; + + @FXML + private AnchorPane floatingPanelPlaceholder; + + @FXML + private AnchorPane day1PanelPlaceholder; + @FXML + private AnchorPane day2PanelPlaceholder; + @FXML + private AnchorPane day3PanelPlaceholder; + @FXML + private AnchorPane day4PanelPlaceholder; + @FXML + private AnchorPane day5PanelPlaceholder; + @FXML + private AnchorPane day6PanelPlaceholder; + @FXML + private AnchorPane day7PanelPlaceholder; + + @FXML + private AnchorPane upcomingPanelPlaceholder; + + public MainWindow() { + super(); + } + + @Override + public void setNode(Node node) { + rootLayout = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { + + MainWindow mainWindow = UiPartLoader.loadUiPart(primaryStage, new MainWindow()); + mainWindow.configure(config.getAppTitle(), config.getSavvyTaskerListName(), config, prefs, logic); + return mainWindow; + } + + private void configure(String appTitle, String addressBookName, Config config, UserPrefs prefs, + Logic logic) { + + //Set dependencies + this.logic = logic; + this.addressBookName = addressBookName; + this.config = config; + this.userPrefs = prefs; + registerAsAnEventHandler(this); + + //Configure the UI + setTitle(appTitle); + setIcon(ICON); + setWindowMinSize(); + setWindowDefaultSize(prefs); + scene = new Scene(rootLayout); + primaryStage.setScene(scene); + + } + + void fillInnerParts() { + imageIcon.setImage(image); + taskListPanel = TaskListPanel.load(primaryStage, getTaskListPlaceholder(), logic.getFilteredTaskList()); + aliasSymbolListPanel = AliasSymbolListPanel.load(primaryStage, getAliasSymbolPlaceholder(), logic.getAliasSymbolList()); + setDefaultView(); + resultDisplay = ResultDisplay.load(primaryStage, getResultDisplayPlaceholder()); + statusBarFooter = StatusBarFooter.load(primaryStage, getStatusbarPlaceholder(), config.getSavvyTaskerFilePath()); + commandBox = CommandBox.load(primaryStage, getCommandBoxPlaceholder(), resultDisplay, logic); + commandBox.getCommandTextField().requestFocus(); + floatingPanel = FloatingPanel.load(primaryStage, getFloatingPanelPlaceholder(), logic.getFilteredFloatingTasks()); + loadDailyPanel(); + upcomingPanel = UpcomingPanel.load(primaryStage, getUpcomingPanelPlaceholder(), logic.getFilteredUpcomingTasks(firstDayOfSelectedWeek)); + cheatsheet.setImage(imageOverlay); + } + + private void loadDailyPanel() { + firstDayOfSelectedWeek = commandBox.getDate(); + for (int i = 0; i < DAYS_OF_WEEK; i++) { + Date onDate = new Date(); + onDate.setTime(firstDayOfSelectedWeek.getTime()); + onDate = addDay(i, onDate); + dailyPanel = DailyPanel.load(primaryStage, getDailyPanelPlaceholder(i), + logic.getFilteredDailyTasks(i, onDate), i, onDate); + } + } + + /** + * Removes all the children in the taskPanel VBox + * Shows the default list, which is the list of tasks + */ + private void setDefaultView() { + getListPanel().getChildren().remove(getAliasSymbolPlaceholder()); + getListPanel().getChildren().remove(getTaskListPlaceholder()); + getListPanel().getChildren().add(getTaskListPlaceholder()); + } + + /** + * Set to true to show the list of tasks. Set to false to show the list of alias + * @param isShown + */ + public void showTaskList(boolean isShown) { + getListPanel().getChildren().remove(getAliasSymbolPlaceholder()); + getListPanel().getChildren().remove(getTaskListPlaceholder()); + if (isShown) { + getListPanel().getChildren().add(getTaskListPlaceholder()); + } else { + getListPanel().getChildren().add(getAliasSymbolPlaceholder()); + } + } + + private VBox getListPanel() { + return listPanel; + } + + private VBox getRootLayout() { + return rootLayout; + } + + private AnchorPane getCommandBoxPlaceholder() { + return commandBoxPlaceholder; + } + + private AnchorPane getStatusbarPlaceholder() { + return statusbarPlaceholder; + } + + private AnchorPane getResultDisplayPlaceholder() { + return resultDisplayPlaceholder; + } + + public AnchorPane getTaskListPlaceholder() { + return taskListPanelPlaceholder; + } + + public AnchorPane getAliasSymbolPlaceholder() { + return aliasSymbolListPanelPlaceholder; + } + + private AnchorPane getFloatingPanelPlaceholder() { + return floatingPanelPlaceholder; + } + + private AnchorPane getDailyPanelPlaceholder(int index) { + + switch(index) { + + case 0: + + return day1PanelPlaceholder; + + case 1: + + return day2PanelPlaceholder; + + case 2: + + return day3PanelPlaceholder; + + case 3: + + return day4PanelPlaceholder; + + case 4: + + return day5PanelPlaceholder; + + case 5: + + return day6PanelPlaceholder; + + case 6: + default: + + return day7PanelPlaceholder; + + } + + } + + private AnchorPane getUpcomingPanelPlaceholder() { + return upcomingPanelPlaceholder; + } + + private Date addDay(int i, Date date) { + + //convert date object to calendar object and add 1 days + Calendar calendarExpectedDate = Calendar.getInstance(); + calendarExpectedDate.setTime(date); + + calendarExpectedDate.add(Calendar.DATE, i); + + //convert calendar object back to date object + date = calendarExpectedDate.getTime(); + + return date; + } + + public void hide() { + primaryStage.hide(); + } + + private void setTitle(String appTitle) { + primaryStage.setTitle(appTitle); + } + + /** + * Sets the default size based on user preferences. + */ + protected void setWindowDefaultSize(UserPrefs prefs) { + primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); + primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); + if (prefs.getGuiSettings().getWindowCoordinates() != null) { + primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); + primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); + } + } + + private void setWindowMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + + /** + * Returns the current size and the position of the main Window. + */ + public GuiSettings getCurrentGuiSetting() { + return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + } + + @FXML + public void handleHelp() { + HelpWindow helpWindow = HelpWindow.load(primaryStage); + helpWindow.show(); + } + + public void hideHelp() { + HelpWindow helpWindow = HelpWindow.load(primaryStage); + helpWindow.hide(); + } + + public void show() { + primaryStage.show(); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + raise(new ExitAppRequestEvent()); + } + + public AliasSymbolListPanel getAliasSymbolListPanel() { + return this.aliasSymbolListPanel; + } + + public TaskListPanel getTaskListPanel() { + return this.taskListPanel; + } + + public void loadPersonPage(ReadOnlyTask task) { + //feature removed + //browserPanel.loadPersonPage(task); + } + + public void releaseResources() { + //feature removed + //browserPanel.freeResources(); + } + + @Subscribe + public void handleSavvyTaskerChangedEvent(SavvyTaskerChangedEvent stce) { + loadDailyPanel(); + } + + @Subscribe + public void handleWeekSelectionChangedEvent(WeekSelectionChangedEvent stce) { + loadDailyPanel(); + } + + @Subscribe + public void handleCheatsheetDisplayToggledEvent(ShowCheatsheetEvent stce) { + + if(isShown == false) { + cheatsheet.setVisible(true); + isShown = true; + } else { + cheatsheet.setVisible(false); + isShown = false; + } + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/savvytasker/ui/ResultDisplay.java similarity index 93% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/savvytasker/ui/ResultDisplay.java index 37284ee6c696..ca5b0396047b 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/savvytasker/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -6,7 +6,7 @@ import javafx.scene.control.TextArea; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import seedu.address.commons.util.FxViewUtil; +import seedu.savvytasker.commons.util.FxViewUtil; /** * A ui for the status bar that is displayed at the header of the application. @@ -37,6 +37,7 @@ public void configure() { resultDisplayArea.getStyleClass().add(STATUS_BAR_STYLE_SHEET); resultDisplayArea.setText(""); resultDisplayArea.textProperty().bind(displayed); + resultDisplayArea.setStyle("-fx-text-fill: white;"); FxViewUtil.applyAnchorBoundaryParameters(resultDisplayArea, 0.0, 0.0, 0.0, 0.0); mainPane.getChildren().add(resultDisplayArea); FxViewUtil.applyAnchorBoundaryParameters(mainPane, 0.0, 0.0, 0.0, 0.0); diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/savvytasker/ui/StatusBarFooter.java similarity index 80% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/savvytasker/ui/StatusBarFooter.java index f74f66be6fc9..0b1df76eea51 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/savvytasker/ui/StatusBarFooter.java @@ -1,18 +1,21 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; + +import java.util.Date; +import java.util.logging.Logger; + +import org.controlsfx.control.StatusBar; import com.google.common.eventbus.Subscribe; + import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.stage.Stage; -import org.controlsfx.control.StatusBar; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.util.FxViewUtil; - -import java.util.Date; -import java.util.logging.Logger; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; +import seedu.savvytasker.commons.util.FxViewUtil; /** * A ui for the status bar that is displayed at the footer of the application. @@ -45,7 +48,7 @@ public void configure(String saveLocation) { addSyncStatus(); setSyncStatus("Not updated yet in this session"); addSaveLocation(); - setSaveLocation("./" + saveLocation); + setSaveLocation(saveLocation); registerAsAnEventHandler(this); } @@ -89,10 +92,17 @@ public String getFxmlPath() { return FXML; } + //@@author A0138431L + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + setSaveLocation(dslce.newPath); + } + //@@author + @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleSavvyTaskerChangedEvent(SavvyTaskerChangedEvent stce) { String lastUpdated = (new Date()).toString(); - logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); + logger.info(LogsCenter.getEventHandlingLogMessage(stce, "Setting last updated status to " + lastUpdated)); setSyncStatus("Last Updated: " + lastUpdated); } } diff --git a/src/main/java/seedu/savvytasker/ui/TaskCard.java b/src/main/java/seedu/savvytasker/ui/TaskCard.java new file mode 100644 index 000000000000..1646d1a1b142 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/TaskCard.java @@ -0,0 +1,112 @@ +//@@author A0138431L + +package seedu.savvytasker.ui; + +import java.util.Date; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import seedu.savvytasker.model.task.ReadOnlyTask; + +public class TaskCard extends UiPart{ + + private static final String FXML = "TaskListCard.fxml"; + + private static final String ICON = "/images/overdue.png"; + private static final Image OVERDUE_IMAGE = new Image(MainWindow.class.getResourceAsStream(ICON)); + + public static final String LOW_PRIORITY_BACKGROUND = "-fx-background-color:#CEFFDC"; + public static final String MEDIUM_PRIORITY_BACKGROUND = "-fx-background-color:#FFFED8"; + public static final String HIGH_PRIORITY_BACKGROUND = "-fx-background-color:#FF8180"; + + @FXML + private HBox cardPane; + @FXML + private Label taskName; + @FXML + private Label id; + @FXML + private Label details; + @FXML + private ImageView overdueIcon; + + private boolean isShowingIndex; + private ReadOnlyTask task; + private int displayedIndex; + + public TaskCard(boolean isShowingIndex){ + this.isShowingIndex = isShowingIndex; + } + + public static TaskCard load(ReadOnlyTask task, int displayedIndex, boolean isShowingIndex){ + TaskCard card = new TaskCard(isShowingIndex); + card.task = task; + card.displayedIndex = displayedIndex; + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + + taskName.setText(task.getTaskName()); + if (isShowingIndex) { + id.setText(displayedIndex + ". "); + } + details.setText(task.getTextForUi()); + setCardBackground(); + setOverdue(); + + } + + public HBox getLayout() { + return cardPane; + } + + private void setOverdue() { + + Date today = new Date(); + + if (task.getEndDateTime() != null) { + + Date endDateTime = task.getEndDateTime(); + + if (endDateTime.compareTo(today)<0 && task.isArchived() == false) { + + overdueIcon.setImage(OVERDUE_IMAGE); + } + } + + } + private void setCardBackground() { + + if (task.getPriority().toString().equals("High")) { + + cardPane.setStyle(HIGH_PRIORITY_BACKGROUND); + + } else if (task.getPriority().toString().equals("Medium")) { + + cardPane.setStyle(MEDIUM_PRIORITY_BACKGROUND); + + } else if (task.getPriority().toString().equals("Low")) { + + cardPane.setStyle(LOW_PRIORITY_BACKGROUND); + + } + + } + + + @Override + public void setNode(Node node) { + cardPane = (HBox)node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} diff --git a/src/main/java/seedu/savvytasker/ui/TaskListPanel.java b/src/main/java/seedu/savvytasker/ui/TaskListPanel.java new file mode 100644 index 000000000000..659260270feb --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/TaskListPanel.java @@ -0,0 +1,115 @@ +package seedu.savvytasker.ui; + +import java.util.logging.Logger; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.ui.TaskPanelSelectionChangedEvent; +import seedu.savvytasker.model.task.ReadOnlyTask; + +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "TaskListPanel.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView taskListView; + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + public Node getNode() { + return panel; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static TaskListPanel load(Stage primaryStage, AnchorPane personListPlaceholder, + ObservableList taskList) { + TaskListPanel personListPanel = + UiPartLoader.loadUiPart(primaryStage, personListPlaceholder, new TaskListPanel()); + personListPanel.configure(taskList); + return personListPanel; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + setBackground(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setBackground() { + if (taskListView.getItems().size() > 0) { + taskListView.setStyle("-fx-background-color: transparent"); + } else { + taskListView.setStyle("-fx-background-color: white"); + } + } + + private void setEventHandlerForSelectionChangeEvent() { + setBackground(); + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + class TaskListViewCell extends ListCell { + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, getIndex() + 1, true).getLayout()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/savvytasker/ui/Ui.java similarity index 87% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/savvytasker/ui/Ui.java index e6a67fe8c027..73319c96daf2 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/savvytasker/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/savvytasker/ui/UiManager.java similarity index 75% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/savvytasker/ui/UiManager.java index 4a4dba3a2f6e..f5b5835b2868 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/savvytasker/ui/UiManager.java @@ -1,24 +1,27 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; + +import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; + import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; - -import java.util.logging.Logger; +import seedu.savvytasker.MainApp; +import seedu.savvytasker.commons.core.ComponentManager; +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent.DisplayedList; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; +import seedu.savvytasker.commons.events.ui.ShowHelpRequestEvent; +import seedu.savvytasker.commons.events.ui.TaskPanelSelectionChangedEvent; +import seedu.savvytasker.commons.util.StringUtil; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.model.UserPrefs; /** * The manager of the UI component. @@ -111,14 +114,24 @@ private void handleShowHelpEvent(ShowHelpRequestEvent event) { mainWindow.handleHelp(); } + @Subscribe + private void handleChangeListRequestEvent(ChangeListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + if (event.displayedList == DisplayedList.Task) { + mainWindow.showTaskList(true); + } else { + mainWindow.showTaskList(false); + } + } + @Subscribe private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getPersonListPanel().scrollTo(event.targetIndex); + mainWindow.getTaskListPanel().scrollTo(event.targetIndex); } @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event){ + private void handlePersonPanelSelectionChangedEvent(TaskPanelSelectionChangedEvent event){ logger.info(LogsCenter.getEventHandlingLogMessage(event)); mainWindow.loadPersonPage(event.getNewSelection()); } diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/savvytasker/ui/UiPart.java similarity index 93% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/savvytasker/ui/UiPart.java index 0a4ceb33e9b7..0de4ed9e4780 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/savvytasker/ui/UiPart.java @@ -1,13 +1,13 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.stage.Modality; import javafx.stage.Stage; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.BaseEvent; -import seedu.address.commons.util.AppUtil; +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.commons.util.AppUtil; /** * Base class for UI parts. diff --git a/src/main/java/seedu/address/ui/UiPartLoader.java b/src/main/java/seedu/savvytasker/ui/UiPartLoader.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPartLoader.java rename to src/main/java/seedu/savvytasker/ui/UiPartLoader.java index f880685a5b15..aaf697376c6d 100644 --- a/src/main/java/seedu/address/ui/UiPartLoader.java +++ b/src/main/java/seedu/savvytasker/ui/UiPartLoader.java @@ -1,10 +1,10 @@ -package seedu.address.ui; +package seedu.savvytasker.ui; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import seedu.address.MainApp; +import seedu.savvytasker.MainApp; /** * A utility class to load UiParts from FXML files. diff --git a/src/main/java/seedu/savvytasker/ui/UpcomingPanel.java b/src/main/java/seedu/savvytasker/ui/UpcomingPanel.java new file mode 100644 index 000000000000..7862582b15fa --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/UpcomingPanel.java @@ -0,0 +1,112 @@ +package seedu.savvytasker.ui; + +import java.util.logging.Logger; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.commons.events.ui.TaskPanelSelectionChangedEvent; +import seedu.savvytasker.model.task.ReadOnlyTask; + +//@@author A0138431L + +/** +* Panel containing the list overdue task. +* @author A0138431L +* +*/ +public class UpcomingPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + private static final String FXML = "UpcomingList.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView taskListView; + + public UpcomingPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static UpcomingPanel load(Stage primaryStage, AnchorPane UpcomingListPlaceholder, + ObservableList taskList) { + UpcomingPanel upcomingPanel = + UiPartLoader.loadUiPart(primaryStage, UpcomingListPlaceholder, new UpcomingPanel()); + upcomingPanel.configure(taskList); + return upcomingPanel; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in upcoming task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + class TaskListViewCell extends ListCell { + + public TaskListViewCell() { + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(TaskCard.load(task, 0, false).getLayout()); + } + } + } + +} diff --git a/src/main/resources/images/cheatsheet.png b/src/main/resources/images/cheatsheet.png new file mode 100644 index 000000000000..4c4c2bbfdc0b Binary files /dev/null and b/src/main/resources/images/cheatsheet.png differ diff --git a/src/main/resources/images/overdue.png b/src/main/resources/images/overdue.png new file mode 100644 index 000000000000..ed1b6b13e27a Binary files /dev/null and b/src/main/resources/images/overdue.png differ diff --git a/src/main/resources/images/savvytasker-icon.png b/src/main/resources/images/savvytasker-icon.png new file mode 100644 index 000000000000..a809380abe90 Binary files /dev/null and b/src/main/resources/images/savvytasker-icon.png differ diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/AliasSymbolListCard.fxml similarity index 70% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/AliasSymbolListCard.fxml index 13d4b149651b..7f205ec4b5b1 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/AliasSymbolListCard.fxml @@ -12,7 +12,7 @@ - + @@ -26,14 +26,14 @@ - - - diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/AliasSymbolListPanel.fxml similarity index 58% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/AliasSymbolListPanel.fxml index 000c4c999b65..9149a98c6ab4 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/AliasSymbolListPanel.fxml @@ -3,12 +3,12 @@ - + - + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 575de420b994..1b27d75c643b 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,8 @@ - - - - + + + + diff --git a/src/main/resources/view/DailyList.fxml b/src/main/resources/view/DailyList.fxml new file mode 100644 index 000000000000..6a359692d8bb --- /dev/null +++ b/src/main/resources/view/DailyList.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css old mode 100644 new mode 100755 index 8043b344253a..f46f4b2772e4 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,3 +1,4 @@ +/*@@author A0138431L */ .background { -fx-background-color: derive(#1d1d1d, 20%); } @@ -94,16 +95,18 @@ .list-cell .label { -fx-text-fill: #010504; + -fx-font-family:'Helvetica Condensed'; } .cell_big_label { - -fx-font-size: 16px; + -fx-font-size: 12px; -fx-text-fill: #010504; + -fx-font-family:'Helvetica Condensed'; } .cell_small_label { - -fx-font-size: 11px; - -fx-text-fill: #010504; + -fx-font-size: 10px; + -fx-text-fill: #696969; } .anchor-pane { @@ -122,7 +125,11 @@ } .result-display { - -fx-background-color: #ffffff; + -fx-background-color: transparent; +} + +.result-display .content { + -fx-background-color: #383838; } .result-display .label { @@ -272,10 +279,9 @@ -fx-padding: 8 1 8 1; } -#cardPane { - -fx-background-color: transparent; - -fx-border-color: #d6d6d6; - -fx-border-width: 1 1 1 1; +.cardPane { + -fx-border-color: #ffffff; + -fx-border-radius: 5px; } #commandTypeLabel { @@ -285,4 +291,109 @@ #filterField, #personListPanel, #personWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); -} \ No newline at end of file +} + +#taskListView { + -fx-background-color: transparent; +} + +#header { + fx-font-size: 11px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Condensed'; +} + +/*------------------------------------------ FloatingPanel Styling ------------------------------------------*/ + +.floating-scrollpane, .floating-panel { + -fx-background-color:#ACEDFF; + -fx-border-radius: 15px; + -fx-font-size: 14px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Bold'; +} + +.floating-scrollpane { + -fx-border-radius: 15px; +} + +.floating-scrollpane > .scroll-bar:horizontal .thumb, +.floating-scrollpane > .scroll-bar:vertical .thumb { + -fx-background-color:#406C7F; +} + +.floating-panel .title, .floating-panel .taskname { + -fx-text-fill:#000000; +} + +.floating-panel .subtitle, .floating-panel .timestamp { + -fx-text-fill:#5997BS2; +} + +/*------------------------------------------ DailyPanel Styling ------------------------------------------*/ + +.daily-scrollpane, .daily-panel { + -fx-background-color:#99D3FF; + -fx-border-radius: 15px; + -fx-font-size: 14px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Bold'; +} + +.daily-scrollpane > .scroll-bar:horizontal .thumb, +.daily-scrollpane > .scroll-bar:vertical .thumb { + -fx-background-color:#7E7E45; +} + +.daily-panel .title, .daily-panel .taskname { + -fx-text-fill:#5D5D33; +} + +.daily-panel .subtitle, .daily-panel .timestamp { + -fx-text-fill:#B2B262; +} + +/*------------------------------------------ UpcomingPanel Styling ------------------------------------------*/ +.upcoming-scrollpane, .upcoming-panel { + -fx-background-color:#D1E0FF; + -fx-border-radius: 15px; + -fx-font-size: 14px; + -fx-text-fill: #000000; + -fx-font-family:'Helvetica Bold'; +} + +.upcoming-scrollpane > .scroll-bar:horizontal .thumb, +.upcoming-scrollpane > .scroll-bar:vertical .thumb { + -fx-background-color:#657E47; +} + +.upcoming-panel .check-box > .box { + -fx-border-color:#657E47; +} + +.upcoming-panel .check-box { + -fx-font-family:'Helvetica'; + -fx-text-fill:#485A33; + -fx-font-size:10; + -fx-font-weight:bold; +} + +.upcoming-panel .title, .upcoming-panel .taskname { + -fx-text-fill:#485A33; +} + +.upcoming-panel .subtitle, .upcoming-panel .timestamp { + -fx-text-fill:#8EB264; +} + +/*------------------------------------------ ArchivedPanel Styling ------------------------------------------*/ + +.archived-panel .title { + -fx-font-family:'Helvetica Condensed'; + -fx-font-size:24; + -fx-font-weight:normal; +} + +.archived-panel .taskname, .archived-panel .timestamp { + -fx-text-fill:#FFFFFF; +} diff --git a/src/main/resources/view/FloatingList.fxml b/src/main/resources/view/FloatingList.fxml new file mode 100644 index 000000000000..b6e353e0d1fc --- /dev/null +++ b/src/main/resources/view/FloatingList.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index c4cbd84cac28..d4bc0ef9364a 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,7 +1,7 @@ - + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml old mode 100644 new mode 100755 index 2f9235c621d8..6c3ab490740a --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,54 +1,122 @@ - - - - + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + - + + + + - - - - - - + - + - + - + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index cc650d739e22..b456bcb1ac70 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,7 @@ + fx:controller="seedu.savvytasker.ui.ResultDisplay"> diff --git a/src/main/resources/view/StatusBarFooter.fxml b/src/main/resources/view/StatusBarFooter.fxml index 2656558b6eb7..b6e43d96dc4b 100644 --- a/src/main/resources/view/StatusBarFooter.fxml +++ b/src/main/resources/view/StatusBarFooter.fxml @@ -1,7 +1,7 @@ - + diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100755 index 000000000000..7077986c6f44 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 000000000000..2ba5b1ab0454 --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/UpcomingList.fxml b/src/main/resources/view/UpcomingList.fxml new file mode 100644 index 000000000000..c305cd526ca9 --- /dev/null +++ b/src/main/resources/view/UpcomingList.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/test/data/ConfigUtilTest/SomeFile.json b/src/test/data/ConfigUtilTest/SomeFile.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/src/test/data/ConfigUtilTest/SomeFile.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/src/test/data/JsonUserPrefsStorageTest/SomeFile.json b/src/test/data/JsonUserPrefsStorageTest/SomeFile.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/src/test/data/JsonUserPrefsStorageTest/SomeFile.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/src/test/data/ManualTesting/SampleData.xml b/src/test/data/ManualTesting/SampleData.xml new file mode 100644 index 000000000000..c88f59971511 --- /dev/null +++ b/src/test/data/ManualTesting/SampleData.xml @@ -0,0 +1,505 @@ + + + + 1 + 0 + Attend project meeting + 2016-11-16T00:10:00+08:00 + 2016-11-16T00:12:00+08:00 + High + priority + false + + + 2 + 0 + Go to the gym + Medium + priority + false + + + 3 + 0 + Swimming session + Low + priority + false + + + 4 + 0 + Project deadline + 2016-12-01T00:00:00+08:00 + High + false + + + 5 + 0 + Homework submission deadline + 2016-11-16T00:00:00+08:00 + Medium + false + + + 6 + 0 + Christmas shopping + 2016-12-05T00:13:00+08:00 + 2016-12-05T00:16:00+08:00 + Medium + false + + + 7 + 0 + Send out party invites + 2016-11-29T00:11:00+08:00 + 2016-11-29T00:13:00+08:00 + Medium + false + + + 8 + 0 + Create presentation slides + 2016-01-01T00:00:00+08:00 + Medium + true + + + 9 + 0 + Run marathon + 2016-01-01T00:00:00+08:00 + City Hall + Medium + Standard Chartered Marathon + false + + + 10 + 0 + Make demo video + 2016-11-19T00:10:00+08:00 + 2016-11-19T00:12:00+08:00 + Medium + false + + + 11 + 0 + Finish CS2010 PS6 + 2016-11-09T00:00:00+08:00 + CodeCrunch + High + Complete search problem + false + + + 12 + 0 + Swimming Lesson + 2016-11-26T12:00:00+08:00 + 2016-11-26T15:00:00+08:00 + Tampines Swimming Pool + Medium + Teach butterfly stroke to students + false + + + 13 + 0 + Study for CS2105 midterms + 2016-10-15T00:00:00+08:00 + National Library + High + Lectures 1 to 5 + true + + + 14 + 0 + Study for CS2010 midterms + 2016-10-26T15:00:00+08:00 + NUS Central Library + High + Lectures 1 to 6 + true + + + 15 + 0 + Watch YouTube videos + 2016-05-01T16:00:00+08:00 + At home + Low + true + + + 16 + 0 + Watch Dr Strange movie + 2016-11-09T16:00:00+08:00 + 2016-11-09T18:30:00+08:00 + Cathay JEM + Low + Stay back for 2 end credit scenes + true + + + 17 + 0 + Practice piano + 2016-11-14T15:00:00+08:00 + 2016-11-14T16:30:00+08:00 + Home + Medium + Chopin, Beethoven, Mozart scores + false + + + 18 + 0 + Bring brother to playground + 2016-11-24T12:00:00+08:00 + 2016-11-24T15:00:00+08:00 + Woodlands Blk 401 + Medium + Pick up his friends along the way + false + + + 19 + 0 + Study for CS2105 finals + 2016-11-26T00:00:00+08:00 + National Library + High + Lectures 1 to 11 + false + + + 20 + 0 + Study for CS2010 finals + 2016-11-28T00:00:00+08:00 + COM1 Level 2 + High + Lectures 1 to 11 + false + + + 21 + 0 + Practice for Math Olympiad + 2016-12-08T00:00:00+08:00 + S17 + High + Do past year papers + false + + + 22 + 0 + Bring grandparents out + 2016-12-14T12:00:00+08:00 + 2016-12-14T15:00:00+08:00 + Gardens by the Bay + Medium + Visit the Flower Dome + false + + + 23 + 0 + Set up dinner with OG + 2016-12-02T18:00:00+08:00 + 2016-12-02T20:00:00+08:00 + Bugis + Medium + Steamboat, get table for 9 + false + + + 24 + 0 + Play Call of Duty + 2016-12-03T12:00:00+08:00 + 2016-12-03T15:00:00+08:00 + Home + Low + on Playstation 4 + false + + + 25 + 0 + Print out lecture notes + 2016-10-28T12:00:00+08:00 + 2016-10-28T12:30:00+08:00 + COM1 Level 1 + Medium + Print for CS2106 + true + + + 26 + 0 + Do Math Homework 4 + 2016-11-07T19:00:00+08:00 + 2016-11-07T21:00:00+08:00 + PGP study room + High + Practice MATLAB + false + + + 27 + 0 + Buy chocolates for Easter + 2016-11-11T12:00:00+08:00 + 2016-11-11T13:00:00+08:00 + Takashimaya + Medium + Find Japanese chocolates + false + + + 28 + 0 + Pay hostel fees + 2016-11-07T12:00:00+08:00 + 2016-11-07T12:15:00+08:00 + Hostel Management Office + High + Bring credit card + false + + + 29 + 0 + Apply for vacation stay + 2016-11-18T00:00:00+08:00 + Medium + false + + + 30 + 0 + Make poster for Orbital + 2016-08-12T09:00:00+08:00 + 2016-08-12T13:00:00+08:00 + COM1 + High + Print out A3 size at printing area + true + + + 31 + 0 + Tea appreciation session + 2016-12-12T12:00:00+08:00 + 2016-12-12T16:00:00+08:00 + Teahouse @ Bugis + Medium + 300 different teas to try + false + + + 32 + 0 + Go prawning + 2016-12-19T17:00:00+08:00 + 2016-12-19T19:00:00+08:00 + Bugis + Medium + Get 2 rods and live bait + false + + + 33 + 0 + Buy new iPhone + 2016-12-21T12:00:00+08:00 + 2016-12-21T13:00:00+08:00 + Orchard Road + Medium + get iPhone 7 Plus 128GB + false + + + 34 + 0 + Deposit money in bank + 2016-12-04T13:00:00+08:00 + 2016-12-04T13:30:00+08:00 + DBS Bank + Medium + Deposit paycheck + false + + + 35 + 0 + Book flight tickets + 2016-11-22T15:00:00+08:00 + Online + Medium + Book 2 tickets to San Francisco + false + + + 36 + 0 + Buy new camera for trip + 2016-11-22T17:00:00+08:00 + Orchard Road + Medium + Get a GoPro Hero 5 + false + + + 37 + 0 + Go to motor exhibition + 2016-12-19T10:00:00+08:00 + 2016-12-19T15:00:00+08:00 + Suntec Convention Center + Low + Bring $50 for tickets + false + + + 38 + 0 + Go grocery shopping + 2016-11-10T13:00:00+08:00 + 2016-11-10T16:00:00+08:00 + NTUC Fairprice + Medium + Replace toiletries and food + false + + + 39 + 0 + Buy souvenirs for foreign guests + 2016-12-27T12:00:00+08:00 + 2016-12-27T15:00:00+08:00 + Marina Bay Sands + Medium + Get Singapore specific items + false + + + 40 + 0 + Watch concert + 2016-12-29T19:00:00+08:00 + Esplanade + Low + Bring jacket + false + + + 41 + 0 + Submit assignment + 2016-09-01T00:00:00+08:00 + High + true + + + 42 + 0 + Finish tutorial + 2016-09-04T00:00:00+08:00 + Medium + true + + + 43 + 0 + Buy Turkey for Thanksgiving + 2016-12-22T00:00:00+08:00 + Cold Storage + Medium + false + + + 44 + 0 + Buy new furniture + 2016-12-26T12:00:00+08:00 + 2016-12-26T15:00:00+08:00 + IKEA Tampines + Medium + Get a chair and study lamp + false + + + 45 + 0 + Play badminton + 2016-12-06T12:00:00+08:00 + 2016-12-06T14:00:00+08:00 + Kovan Sports Hub + Low + Bring own shuttlecocks + false + + + 46 + 0 + Play tennis + 2016-12-05T12:00:00+08:00 + 2016-12-05T14:00:00+08:00 + Kovan Sports Hub + Low + Bring own tennis balls + false + + + 47 + 0 + Go for fish spa + 2016-12-08T13:00:00+08:00 + 2016-12-08T15:00:00+08:00 + Bugis + Low + Bring towel + false + + + 48 + 0 + Go for massage + 2016-12-02T12:00:00+08:00 + 2016-12-02T15:00:00+08:00 + JB + Low + Get premium package + false + + + 49 + 0 + Bring dog to vet + 2016-11-09T13:00:00+08:00 + 2016-11-09T14:00:00+08:00 + Clementi Vet + High + Dog displaying allergy symptoms + false + + + 50 + 0 + Return library books + 2016-12-07T00:00:00+08:00 + High + false + + diff --git a/src/test/data/ManualTesting/TestScript.md b/src/test/data/ManualTesting/TestScript.md new file mode 100644 index 000000000000..6790cc9b8465 --- /dev/null +++ b/src/test/data/ManualTesting/TestScript.md @@ -0,0 +1,143 @@ +# Manual Scripted Testing + +## How to load the sample data + +1. Download saavytasker.jar +1. Download the [SampleData.xml](/src/test/data/ManualTesting/SampleData.xml) file +2. Make a copy, and rename it is to `savvytasker.xml` +3. Copy `savvytasker.xml` and overwrite the same named file \data\savvytasker.xml +4. Launch savvytasker.jar + +## Test script + +### List tasks + +- List all tasks: `list` or Ctrl + L + +> Lists all currently unmarked/ongoing tasks, according to due date. +> All floating tasks are listed after tasks with dates, according to the order they were entered. + +- List all tasks by priority level: `list priority level` or Ctrl + P + +- List all marked tasks: `list archived` or Ctrl + A + +> Lists all marked/completed tasks. + +### Adding new tasks + +- Add a task: `add Buy groceries` + +> New task will be added to end of unmarked list, with a default `Medium` priority level (yellow in color). +> Task will be added as a `Floating Task`. + +- Add a task with a deadline and High priority level: `add Submit assignment e/11-11-16 8pm p/High` + +> New task will be added, with a `High` priority level (red in color), and end date of 11 Nov 2016, 8pm. +> Start date will be defaulted to current date 12am. + +- Add a task with a Low priority level: `add Watch a movie p/Low` + +> New task will be added, with a `Low` priority level (green in color). +> Task wil be added as a `Floating Task`. + +- Add a task with a start date, end date, location, and description: `add Attend dance class s/8-11-16 5pm e/8-11-16 7pm l/Bugis dance studio d/Salsa class` + +> New task will be added, with respective start date, end date, location, and description. +> Task will be added to the respective dates on the calendar. + +- Add an overdue task: `add Study for midterms s/6-10-16` + +> New task will be added, end date will be defaulted to 23:59:59 of the start date. +> Task will be added as an overdue task. + +### Deleting tasks + +- Delete a task: `delete 1` + +> Removes the task listed at index 1 (Go to the gym). + +- Delete multiple tasks: `delete 1 2 3` + +> Removes the tasks listed at indexes 1, 2 and 3 (Swimming session, Project deadline, Homework submission deadline). + +### Undo commands + +- Undo last command: `undo` or Ctrl + Z + +> The last 'delete 1 2 3' command is undone, and all 3 deleted tasks are added back. + +### Redo commands + +- Redo the last undone command: `redo` or Ctrl + Y
+ +> The last undone `delete 1 2 3` command is redone, so all 3 tasks are deleted again. + +### Modify tasks + +- Modify a task: `modify 1 t/Buy working supplies s/11-10-16 3pm e/11-10-16 4pm l/Popular Bookstore d/Pens p/Low`
+ +> Changes the task listed at index 1 (Christmas shopping) to Buy working supplies, with new start date Nov 10 3pm and end date Nov 10 4pm, and priority level changed to 'Low'. +> Also adds a new location: Popular Bookstore, and new description: Pens. + +### Alias command + +- Alias a keyword: `alias k/pjm r/Project Meeting` `add pjm` + +> Future instances of `pjm` will be recognized as `Project Meeting`. +> A new task 'Project Meeting' will be added to the end of the list. + +- Alias a command: `alias k/++ r/add` `++ pjm` + +> Future instances of `++` will be recognized as an `add` command. +> A new task `Project Meeting` will be added to the end of the list. + +- View aliased keys: `list alias` or Ctrl + I +> Lists all aliased keys (pjm, ++). + +### Unalias command + +- Unalias an aliased keyword: `unalias pjm` `add pjm` + +> Future instances of `pjm` will no longer be recognized as `Project Meeting`. +> A new task `pjm` will be added to the end of the list. + +### Mark a task + +- Mark a completed task: `mark 1` + +> Marks the task listed at index 1 (Buy working supplies). +> The newly marked task is relisted on the archived list. + +### Unmark a task + +- Unmark a previously marked task: `list archived` `unmark 1` + +> Shows the list of marked/archived tasks. +> Unmarks the task listed at index 1 (Buy working supplies). +> The newly unmarked task relists on the unmarked task list. + +### Find a task + +- Find tasks containing keywords: `find gro` + +> Shows all unmarked/unarchived tasks containing the keyword `gro` (Bring brother to playground, Go grocery shopping, Buy groceries). + +- Find tasks containing exact match: `find t/exact buy groceries` + +> Shows the task(s) containing the exact phrase: `buy groceries`, not case sensitive. + +### Clear all tasks + +- Clear all tasks: `clear` or Ctrl + D + +> Deletes all data from all lists. + +### Other commands + +- View help: `help` or Ctrl + H + +> Opens the help window. + +- Exit the app: `exit` or Ctrl + Q + +> Exits the SaavyTasker app. diff --git a/src/test/data/XmlAddressBookStorageTest/SomeFile.xml b/src/test/data/XmlAddressBookStorageTest/SomeFile.xml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempSavvyTasker.xml similarity index 100% rename from src/test/data/XmlUtilTest/tempAddressBook.xml rename to src/test/data/XmlUtilTest/tempSavvyTasker.xml diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml deleted file mode 100644 index eafca730fb1e..000000000000 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - Hans Muster - 9482424 - hans@google.com -
4th street
-
- - Ruth Mueller - 87249245 - ruth@google.com -
81th street
-
- - Heinz Kurz - 95352563 - heinz@yahoo.com -
wall street
-
- - Cornelia Meier - 87652533 - cornelia@google.com -
10th street
-
- - Werner Meyer - 9482224 - werner@gmail.com -
michegan ave
-
- - Lydia Kunz - 9482427 - lydia@gmail.com -
little tokyo
-
- - Anna Best - 9482442 - anna@google.com -
4th street
-
- - Stefan Meier - 8482424 - stefan@mail.com -
little india
-
- - Martin Mueller - 8482131 - hans@google.com -
chicago ave
-
-
diff --git a/src/test/data/XmlUtilTest/validSavvyTasker.xml b/src/test/data/XmlUtilTest/validSavvyTasker.xml new file mode 100644 index 000000000000..9edf3b9b64b9 --- /dev/null +++ b/src/test/data/XmlUtilTest/validSavvyTasker.xml @@ -0,0 +1,19 @@ + + + + Hello World + 0 + A test description + + + Hello + 277387621-03-16T04:42:25.152+08:00 + 277387608-03-17T04:42:25.152+08:00 + 0 + I have a date + + + new task + 0 + + diff --git a/src/test/java/guitests/AddCommandTest.java b/src/test/java/guitests/AddCommandTest.java index 3b2e1844bd0d..ae4fc28569d2 100644 --- a/src/test/java/guitests/AddCommandTest.java +++ b/src/test/java/guitests/AddCommandTest.java @@ -1,53 +1,86 @@ package guitests; -import guitests.guihandles.PersonCardHandle; +import guitests.guihandles.TaskCardHandle; + import org.junit.Test; -import seedu.address.logic.commands.AddCommand; -import seedu.address.commons.core.Messages; -import seedu.address.testutil.TestPerson; -import seedu.address.testutil.TestUtil; + +import seedu.savvytasker.logic.commands.AddCommand; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +//@@author A0139915W +public class AddCommandTest extends SavvyTaskerGuiTest { -public class AddCommandTest extends AddressBookGuiTest { + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); @Test public void add() { - //add one person - TestPerson[] currentList = td.getTypicalPersons(); - TestPerson personToAdd = td.hoon; - assertAddSuccess(personToAdd, currentList); - currentList = TestUtil.addPersonsToList(currentList, personToAdd); - - //add another person - personToAdd = td.ida; - assertAddSuccess(personToAdd, currentList); - currentList = TestUtil.addPersonsToList(currentList, personToAdd); - - //add duplicate person - commandBox.runCommand(td.hoon.getAddCommand()); - assertResultMessage(AddCommand.MESSAGE_DUPLICATE_PERSON); - assertTrue(personListPanel.isListMatching(currentList)); + //add one task + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToAdd = td.happy; + assertAddSuccess(taskToAdd, currentList); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + //add another task + taskToAdd = td.haloween; + assertAddSuccess(taskToAdd, currentList); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); //add to empty list commandBox.runCommand("clear"); - assertAddSuccess(td.alice); + assertAddSuccess(td.highPriority); //invalid command - commandBox.runCommand("adds Johnny"); - assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + commandBox.runCommand("adds Bad Command Task"); + assertResultMessage("Input: adds Bad Command Task\n" + + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + + //invalid start end date + Date start = getDate("31/12/2015"); + Date end = getDate("30/12/2015"); + commandBox.runCommand("add bad start-end pair s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(AddCommand.MESSAGE_INVALID_START_END)); } - private void assertAddSuccess(TestPerson personToAdd, TestPerson... currentList) { - commandBox.runCommand(personToAdd.getAddCommand()); + private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { + commandBox.runCommand(taskToAdd.getAddCommand()); //confirm the new card contains the right data - PersonCardHandle addedCard = personListPanel.navigateToPerson(personToAdd.getName().fullName); - assertMatching(personToAdd, addedCard); + TaskCardHandle addedCard = taskListPanel.navigateToTask(taskToAdd.getTaskName()); + assertMatching(taskToAdd, addedCard); - //confirm the list now contains all previous persons plus the new person - TestPerson[] expectedList = TestUtil.addPersonsToList(currentList, personToAdd); - assertTrue(personListPanel.isListMatching(expectedList)); + //confirm the list now contains all previous tasks plus the new task + TestTask[] expectedList = TestUtil.addTasksToList(currentList, taskToAdd); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; } + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } } +//@@author diff --git a/src/test/java/guitests/ClearCommandTest.java b/src/test/java/guitests/ClearCommandTest.java index 9d52b427659c..b34b5c5c0c79 100644 --- a/src/test/java/guitests/ClearCommandTest.java +++ b/src/test/java/guitests/ClearCommandTest.java @@ -4,18 +4,18 @@ import static org.junit.Assert.assertTrue; -public class ClearCommandTest extends AddressBookGuiTest { +public class ClearCommandTest extends SavvyTaskerGuiTest { @Test public void clear() { //verify a non-empty list can be cleared - assertTrue(personListPanel.isListMatching(td.getTypicalPersons())); + assertTrue(taskListPanel.isListMatching(td.getTypicalTasks())); assertClearCommandSuccess(); //verify other commands can work after a clear command - commandBox.runCommand(td.hoon.getAddCommand()); - assertTrue(personListPanel.isListMatching(td.hoon)); + commandBox.runCommand(td.happy.getAddCommand()); + assertTrue(taskListPanel.isListMatching(td.happy)); commandBox.runCommand("delete 1"); assertListSize(0); @@ -26,6 +26,6 @@ public void clear() { private void assertClearCommandSuccess() { commandBox.runCommand("clear"); assertListSize(0); - assertResultMessage("Address book has been cleared!"); + assertResultMessage("Savvy Tasker has been cleared!"); } } diff --git a/src/test/java/guitests/CommandBoxTest.java b/src/test/java/guitests/CommandBoxTest.java index 1379198bf8b0..ef2111d47438 100644 --- a/src/test/java/guitests/CommandBoxTest.java +++ b/src/test/java/guitests/CommandBoxTest.java @@ -4,18 +4,18 @@ import static org.junit.Assert.assertEquals; -public class CommandBoxTest extends AddressBookGuiTest { +public class CommandBoxTest extends SavvyTaskerGuiTest { @Test public void commandBox_commandSucceeds_textCleared() { - commandBox.runCommand(td.benson.getAddCommand()); + commandBox.runCommand(td.haloween.getAddCommand()); assertEquals(commandBox.getCommandInput(), ""); } @Test - public void commandBox_commandFails_textStays(){ + public void commandBox_commandFails_textClears(){ commandBox.runCommand("invalid command"); - assertEquals(commandBox.getCommandInput(), "invalid command"); + assertEquals(commandBox.getCommandInput(), ""); //TODO: confirm the text box color turns to red } diff --git a/src/test/java/guitests/DeleteCommandTest.java b/src/test/java/guitests/DeleteCommandTest.java index 10c7b9e0dbea..bb0f13f9b730 100644 --- a/src/test/java/guitests/DeleteCommandTest.java +++ b/src/test/java/guitests/DeleteCommandTest.java @@ -1,35 +1,37 @@ package guitests; import org.junit.Test; -import seedu.address.testutil.TestPerson; -import seedu.address.testutil.TestUtil; + +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; import static org.junit.Assert.assertTrue; -import static seedu.address.logic.commands.DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS; +import static seedu.savvytasker.logic.commands.DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS; -public class DeleteCommandTest extends AddressBookGuiTest { +//@@author A0139915W +public class DeleteCommandTest extends SavvyTaskerGuiTest { @Test public void delete() { //delete the first in the list - TestPerson[] currentList = td.getTypicalPersons(); + TestTask[] currentList = td.getTypicalTasks(); int targetIndex = 1; assertDeleteSuccess(targetIndex, currentList); //delete the last in the list - currentList = TestUtil.removePersonFromList(currentList, targetIndex); + currentList = TestUtil.removeTaskFromList(currentList, targetIndex); targetIndex = currentList.length; assertDeleteSuccess(targetIndex, currentList); //delete from the middle of the list - currentList = TestUtil.removePersonFromList(currentList, targetIndex); + currentList = TestUtil.removeTaskFromList(currentList, targetIndex); targetIndex = currentList.length/2; assertDeleteSuccess(targetIndex, currentList); //invalid index commandBox.runCommand("delete " + currentList.length + 1); - assertResultMessage("The person index provided is invalid"); + assertResultMessage("The task index provided is invalid"); } @@ -38,17 +40,18 @@ public void delete() { * @param targetIndexOneIndexed e.g. to delete the first person in the list, 1 should be given as the target index. * @param currentList A copy of the current list of persons (before deletion). */ - private void assertDeleteSuccess(int targetIndexOneIndexed, final TestPerson[] currentList) { - TestPerson personToDelete = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing - TestPerson[] expectedRemainder = TestUtil.removePersonFromList(currentList, targetIndexOneIndexed); + private void assertDeleteSuccess(int targetIndexOneIndexed, final TestTask[] currentList) { + TestTask taskToDelete = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndexOneIndexed); commandBox.runCommand("delete " + targetIndexOneIndexed); //confirm the list now contains all previous persons except the deleted person - assertTrue(personListPanel.isListMatching(expectedRemainder)); + assertTrue(taskListPanel.isListMatching(expectedRemainder)); //confirm the result message is correct - assertResultMessage(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + assertResultMessage(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); } } +//@@author diff --git a/src/test/java/guitests/FindCommandTest.java b/src/test/java/guitests/FindCommandTest.java index 441a6dbed666..8183ef85bc17 100644 --- a/src/test/java/guitests/FindCommandTest.java +++ b/src/test/java/guitests/FindCommandTest.java @@ -1,39 +1,64 @@ package guitests; import org.junit.Test; -import seedu.address.commons.core.Messages; -import seedu.address.testutil.TestPerson; + +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -public class FindCommandTest extends AddressBookGuiTest { +//@@author A0139915W +public class FindCommandTest extends SavvyTaskerGuiTest { @Test public void find_nonEmptyList() { - assertFindResult("find Mark"); //no results - assertFindResult("find Meier", td.benson, td.daniel); //multiple results + assertFindResult("find Zoo"); //no results + assertFindResult("find Priority", td.highPriority, td.medPriority, td.lowPriority); //multiple results //find after deleting one result commandBox.runCommand("delete 1"); - assertFindResult("find Meier",td.daniel); + assertFindResult("find Priority", td.medPriority, td.lowPriority); + } + + @Test + public void find_nonEmptyList_byPartialMatch() { + // covered by find_nonEmptyList() + } + + @Test + public void find_nonEmptyList_byFullMatch() { + assertFindResult("find t/full Due", td.furthestDue, td.nearerDue, + td.notSoNearerDue, td.earliestDue, td.longDue); //multiple results + } + + @Test + public void find_nonEmptyList_byExactMatch() { + assertFindResult("find t/exact Nearer Due Task", td.nearerDue); // one matching result only + } + + @Test + public void find_nonEmptyList_byCategory() { + assertFindResult("find t/category priority", td.highPriority, td.medPriority, td.lowPriority); // matching 3 results } @Test public void find_emptyList(){ commandBox.runCommand("clear"); - assertFindResult("find Jean"); //no results + assertFindResult("find Shopping"); //no results } @Test public void find_invalidCommand_fail() { - commandBox.runCommand("findgeorge"); - assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + commandBox.runCommand("findmyring"); + assertResultMessage("Input: findmyring\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); } - private void assertFindResult(String command, TestPerson... expectedHits ) { + private void assertFindResult(String command, TestTask... expectedHits) { commandBox.runCommand(command); assertListSize(expectedHits.length); - assertResultMessage(expectedHits.length + " persons listed!"); - assertTrue(personListPanel.isListMatching(expectedHits)); + assertResultMessage(expectedHits.length + " tasks listed!"); + assertTrue(taskListPanel.isListMatching(expectedHits)); } } +//@@author diff --git a/src/test/java/guitests/GuiRobot.java b/src/test/java/guitests/GuiRobot.java index 44aa9edb48aa..fd161ab0c4fe 100644 --- a/src/test/java/guitests/GuiRobot.java +++ b/src/test/java/guitests/GuiRobot.java @@ -1,8 +1,9 @@ package guitests; import javafx.scene.input.KeyCodeCombination; +import seedu.savvytasker.testutil.TestUtil; + import org.testfx.api.FxRobot; -import seedu.address.testutil.TestUtil; /** * Robot used to simulate user actions on the GUI. diff --git a/src/test/java/guitests/HelpWindowTest.java b/src/test/java/guitests/HelpWindowTest.java index 258d9d628d80..7b549a9b7b83 100644 --- a/src/test/java/guitests/HelpWindowTest.java +++ b/src/test/java/guitests/HelpWindowTest.java @@ -5,17 +5,11 @@ import static org.junit.Assert.assertTrue; -public class HelpWindowTest extends AddressBookGuiTest { +public class HelpWindowTest extends SavvyTaskerGuiTest { @Test public void openHelpWindow() { - personListPanel.clickOnListView(); - - assertHelpWindowOpen(mainMenu.openHelpWindowUsingAccelerator()); - - assertHelpWindowOpen(mainMenu.openHelpWindowUsingMenu()); - assertHelpWindowOpen(commandBox.runHelpCommand()); } diff --git a/src/test/java/guitests/ListCommandTest.java b/src/test/java/guitests/ListCommandTest.java new file mode 100644 index 000000000000..c8989ee4d9f6 --- /dev/null +++ b/src/test/java/guitests/ListCommandTest.java @@ -0,0 +1,66 @@ +package guitests; + +import org.junit.Test; + +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; + +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +//@@author A0139915W +public class ListCommandTest extends SavvyTaskerGuiTest { + + @Test + public void list_nonEmptyList() { + assertListResult("list", td.earliestDue, td.nearerDue, td.notSoNearerDue, td.furthestDue, + td.highPriority, td.medPriority, td.lowPriority); //multiple results + + //list after deleting one result + commandBox.runCommand("delete 1"); + assertListResult("list", td.nearerDue, td.notSoNearerDue, td.furthestDue, + td.highPriority, td.medPriority, td.lowPriority); + } + + @Test + public void list_nonEmptyList_byInvalidSwitch() { + commandBox.runCommand("list badswitch"); + assertResultMessage("Input: list badswitch\nLIST_TYPE: Unknown type \'badswitch\'"); + } + + @Test + public void list_nonEmptyList_byDueDate() { + // covered by list_nonEmptyList() + } + + @Test + public void list_nonEmptyList_byPriority() { + assertListResult("list PriorityLevel", td.highPriority, td.medPriority, + td.furthestDue, td.nearerDue, td.notSoNearerDue, td.earliestDue, td.lowPriority); //multiple results + } + + @Test + public void list_nonEmptyList_byArchived() { + assertListResult("list Archived", td.longDue); // one matching result only + } + + @Test + public void list_emptyList(){ + commandBox.runCommand("clear"); + assertListResult("list"); //no results + } + + @Test + public void find_invalidCommand_fail() { + commandBox.runCommand("listmytasks"); + assertResultMessage("Input: listmytasks\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + + private void assertListResult(String command, TestTask... expectedHits ) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + assertResultMessage(expectedHits.length + " tasks listed!"); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } +} +//@@author diff --git a/src/test/java/guitests/ModifyCommandTest.java b/src/test/java/guitests/ModifyCommandTest.java new file mode 100644 index 000000000000..a57ed8f57d2d --- /dev/null +++ b/src/test/java/guitests/ModifyCommandTest.java @@ -0,0 +1,85 @@ +package guitests; + +import guitests.guihandles.TaskCardHandle; + +import org.junit.Test; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; + +import static org.junit.Assert.assertTrue; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +//@@author A0139915W +public class ModifyCommandTest extends SavvyTaskerGuiTest { + + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + + @Test + public void add() { + //modify task + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToModify = currentList[0]; + Date start = getDate("30/12/2016"); + Date end = getDate("31/12/2016"); + taskToModify.setStartDateTime(start); + taskToModify.setEndDateTime(end); + assertModifySuccess("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end), taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + taskToModify.setStartDateTime(null); + taskToModify.setEndDateTime(null); + assertModifySuccess("modify 1 s/ e/", taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + //modify invalid index + commandBox.runCommand("modify " + currentList.length + "1" + " s/sat"); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX)); + + //modify with invalid end date + start = getDate("31/12/2016"); + end = getDate("30/12/2016"); + commandBox.runCommand("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_START_END)); + } + + private void assertModifySuccess(String command, TestTask taskToModify, TestTask... currentList) { + commandBox.runCommand(command); + + //confirm the new card contains the right data + TaskCardHandle modifiedCard = taskListPanel.navigateToTask(taskToModify.getTaskName()); + assertMatching(taskToModify, modifiedCard); + + //confirm the list now contains all previous persons plus the new person + TestTask[] expectedList = TestUtil.replaceTaskFromList(currentList, taskToModify); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + +} +//@@author diff --git a/src/test/java/guitests/RedoCommandTest.java b/src/test/java/guitests/RedoCommandTest.java new file mode 100644 index 000000000000..00a77efc1dbc --- /dev/null +++ b/src/test/java/guitests/RedoCommandTest.java @@ -0,0 +1,152 @@ +//@@author A0097627N +package guitests; + +import guitests.guihandles.TaskCardHandle; + +import org.junit.Test; + +import seedu.savvytasker.logic.commands.UndoCommand; +import seedu.savvytasker.logic.commands.RedoCommand; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; + +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.savvytasker.logic.commands.RedoCommand.MESSAGE_REDO_ACKNOWLEDGEMENT; + +public class RedoCommandTest extends SavvyTaskerGuiTest { + + TestTask[] expectedList = td.getTypicalTasks(); + TestTask[] currentList = td.getTypicalTasks(); + TestTask firstTaskToAdd = td.happy; + TestTask secondTaskToAdd = td.haloween; + TestTask pjmTaskToAdd = td.pjm; + TestTask projectMeetingTaskToAdd = td.projectMeeting; + + @Test + // redo one add command + public void redoAddTest() { + expectedList = TestUtil.addTasksToList(currentList, firstTaskToAdd); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + @Test + // redo a delete command + public void redoDeleteTest() { + expectedList = TestUtil.removeTaskFromList(currentList, 1); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + @Test + // redo clear command + public void redoClearTest() { + commandBox.runCommand("clear"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertListSize(0); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + @Test + // redo alias command + public void redoAliasTest() { + expectedList = td.getTypicalTasks(); + expectedList = TestUtil.addTasksToList(expectedList, projectMeetingTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + @Test + // redo unalias command + public void redoUnaliasTest() { + expectedList = TestUtil.addTasksToList(currentList, pjmTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("unalias pjm"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + // redo two add commands + @Test + public void redoTwoAddTest() { + expectedList = TestUtil.addTasksToList(currentList, firstTaskToAdd); + expectedList = TestUtil.addTasksToList(expectedList, secondTaskToAdd); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand(secondTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // redo two delete commands + @Test + public void redoTwoDeleteTest() { + expectedList = TestUtil.removeTaskFromList(currentList, 1); + expectedList = TestUtil.removeTaskFromList(expectedList, 1); + commandBox.runCommand("delete 1"); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // redo a delete command followed by an add command + @Test + public void redoDeleteAddTest() { + expectedList = TestUtil.addTasksToList(currentList, firstTaskToAdd); + expectedList = TestUtil.removeTaskFromList(expectedList, 1); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // redo an add command followed by a delete command + @Test + public void redoAddDeleteTest() { + expectedList = TestUtil.removeTaskFromList(currentList, 1); + expectedList = TestUtil.addTasksToList(expectedList, firstTaskToAdd); + commandBox.runCommand("delete 1"); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + commandBox.runCommand("redo"); + commandBox.runCommand("redo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_REDO_ACKNOWLEDGEMENT); + } + + // invalid command + @Test + public void invalidTest() { + commandBox.runCommand("redos"); + assertResultMessage("Input: redos\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } +} +//@@author \ No newline at end of file diff --git a/src/test/java/guitests/AddressBookGuiTest.java b/src/test/java/guitests/SavvyTaskerGuiTest.java similarity index 74% rename from src/test/java/guitests/AddressBookGuiTest.java rename to src/test/java/guitests/SavvyTaskerGuiTest.java index 35734932f11c..3db0a8a67871 100644 --- a/src/test/java/guitests/AddressBookGuiTest.java +++ b/src/test/java/guitests/SavvyTaskerGuiTest.java @@ -8,12 +8,13 @@ import org.junit.Rule; import org.junit.rules.TestName; import org.testfx.api.FxToolkit; -import seedu.address.TestApp; -import seedu.address.commons.core.EventsCenter; -import seedu.address.model.AddressBook; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.testutil.TestUtil; -import seedu.address.testutil.TypicalTestPersons; + +import seedu.savvytasker.TestApp; +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.testutil.TestUtil; +import seedu.savvytasker.testutil.TypicalTestTasks; import java.util.concurrent.TimeoutException; @@ -23,7 +24,7 @@ /** * A GUI Test class for AddressBook. */ -public abstract class AddressBookGuiTest { +public abstract class SavvyTaskerGuiTest { /* The TestName Rule makes the current test name available inside test methods */ @Rule @@ -31,7 +32,7 @@ public abstract class AddressBookGuiTest { TestApp testApp; - protected TypicalTestPersons td = new TypicalTestPersons(); + protected TypicalTestTasks td = new TypicalTestTasks(); /* * Handles to GUI elements present at the start up are created in advance @@ -39,7 +40,7 @@ public abstract class AddressBookGuiTest { */ protected MainGuiHandle mainGui; protected MainMenuHandle mainMenu; - protected PersonListPanelHandle personListPanel; + protected TaskListPanelHandle taskListPanel; protected ResultDisplayHandle resultDisplay; protected CommandBoxHandle commandBox; private Stage stage; @@ -59,7 +60,7 @@ public void setup() throws Exception { FxToolkit.setupStage((stage) -> { mainGui = new MainGuiHandle(new GuiRobot(), stage); mainMenu = mainGui.getMainMenu(); - personListPanel = mainGui.getPersonListPanel(); + taskListPanel = mainGui.getTaskListPanel(); resultDisplay = mainGui.getResultDisplay(); commandBox = mainGui.getCommandBox(); this.stage = stage; @@ -75,10 +76,10 @@ public void setup() throws Exception { * Override this in child classes to set the initial local data. * Return null to use the data in the file specified in {@link #getDataFileLocation()} */ - protected AddressBook getInitialData() { - AddressBook ab = TestUtil.generateEmptyAddressBook(); - TypicalTestPersons.loadAddressBookWithSampleData(ab); - return ab; + protected SavvyTasker getInitialData() { + SavvyTasker st = TestUtil.generateEmptySavvyTasker(); + TypicalTestTasks.loadSavvyTaskerWithSampleData(st); + return st; } /** @@ -96,16 +97,16 @@ public void cleanup() throws TimeoutException { /** * Asserts the person shown in the card is same as the given person */ - public void assertMatching(ReadOnlyPerson person, PersonCardHandle card) { - assertTrue(TestUtil.compareCardAndPerson(card, person)); + public void assertMatching(ReadOnlyTask task, TaskCardHandle card) { + assertTrue(TestUtil.compareCardAndTask(card, task)); } /** * Asserts the size of the person list is equal to the given number. */ protected void assertListSize(int size) { - int numberOfPeople = personListPanel.getNumberOfPeople(); - assertEquals(size, numberOfPeople); + int numberOfTask = taskListPanel.getNumberOfTasks(); + assertEquals(size, numberOfTask); } /** diff --git a/src/test/java/guitests/SelectCommandTest.java b/src/test/java/guitests/SelectCommandTest.java index 5273552056ce..04a823f9147f 100644 --- a/src/test/java/guitests/SelectCommandTest.java +++ b/src/test/java/guitests/SelectCommandTest.java @@ -1,13 +1,9 @@ package guitests; -import org.junit.Test; -import seedu.address.model.person.ReadOnlyPerson; - -import static org.junit.Assert.assertEquals; - -public class SelectCommandTest extends AddressBookGuiTest { +public class SelectCommandTest extends SavvyTaskerGuiTest { + /** Not running the select test for now, not implemented @Test public void selectPerson_nonEmptyList() { @@ -15,7 +11,7 @@ public void selectPerson_nonEmptyList() { assertNoPersonSelected(); assertSelectionSuccess(1); //first person in the list - int personCount = td.getTypicalPersons().length; + int personCount = td.getTypicalTasks().length; assertSelectionSuccess(personCount); //last person in the list int middleIndex = personCount / 2; assertSelectionSuccess(middleIndex); //a person in the middle of the list @@ -23,7 +19,7 @@ public void selectPerson_nonEmptyList() { assertSelectionInvalid(personCount + 1); //invalid index assertPersonSelected(middleIndex); //assert previous selection remains - /* Testing other invalid indexes such as -1 should be done when testing the SelectCommand */ + //Testing other invalid indexes such as -1 should be done when testing the SelectCommand } @Test @@ -35,24 +31,24 @@ public void selectPerson_emptyList(){ private void assertSelectionInvalid(int index) { commandBox.runCommand("select " + index); - assertResultMessage("The person index provided is invalid"); + assertResultMessage("The task index provided is invalid"); } private void assertSelectionSuccess(int index) { commandBox.runCommand("select " + index); - assertResultMessage("Selected Person: "+index); + assertResultMessage("Selected Task: "+index); assertPersonSelected(index); } private void assertPersonSelected(int index) { - assertEquals(personListPanel.getSelectedPersons().size(), 1); - ReadOnlyPerson selectedPerson = personListPanel.getSelectedPersons().get(0); - assertEquals(personListPanel.getPerson(index-1), selectedPerson); + assertEquals(taskListPanel.getSelectedTasks().size(), 1); + ReadOnlyTask selectedTask = taskListPanel.getSelectedTasks().get(0); + assertEquals(taskListPanel.getTask(index-1), selectedTask); //TODO: confirm the correct page is loaded in the Browser Panel } private void assertNoPersonSelected() { - assertEquals(personListPanel.getSelectedPersons().size(), 0); - } + assertEquals(taskListPanel.getSelectedTasks().size(), 0); + }*/ } diff --git a/src/test/java/guitests/StorageCommandTest.java b/src/test/java/guitests/StorageCommandTest.java new file mode 100644 index 000000000000..0a0eda4104a2 --- /dev/null +++ b/src/test/java/guitests/StorageCommandTest.java @@ -0,0 +1,52 @@ +package guitests; + +import org.junit.Test; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.storage.JsonConfigStorage; +import seedu.savvytasker.testutil.TestTask; + +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.io.IOException; + +import java.util.Optional; + +//@@author A0138431L +public class StorageCommandTest extends SavvyTaskerGuiTest { + + private static final String CONFIG_JSON = "config.json"; + private static final String CONFIG_LOCATION = "./src/test/data/SaveLocationCommandTest"; + + @Test + public void saveToValidFilePath_success() throws DataConversionException, IOException, DuplicateTaskException { + String testFilePath = "./src/test/data/StorageCommandTest/newStorageLocation/"; + commandBox.runCommand("storage " + testFilePath); + assertWriteToJsonSuccess(); + assertWriteToXmlSuccess(); + } + private void assertWriteToJsonSuccess() throws DataConversionException { + JsonConfigStorage jsonConfigStorage = new JsonConfigStorage(CONFIG_LOCATION); + Optional config = jsonConfigStorage.readConfig(CONFIG_JSON); + assert(config.isPresent()); + } + + private void assertWriteToXmlSuccess() { + TestTask[] currentList = td.getTypicalTasks(); + assertTrue(taskListPanel.isListMatching(currentList)); + } + + @Test + public void storage() { + //invalid command + commandBox.runCommand("store"); + assertResultMessage("Input: store\n" + + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + +} +//@@author \ No newline at end of file diff --git a/src/test/java/guitests/UndoCommandTest.java b/src/test/java/guitests/UndoCommandTest.java new file mode 100644 index 000000000000..813c06479a81 --- /dev/null +++ b/src/test/java/guitests/UndoCommandTest.java @@ -0,0 +1,145 @@ +//@@author A0097627N +package guitests; + +import guitests.guihandles.TaskCardHandle; + +import org.junit.Test; + +import seedu.savvytasker.logic.commands.UndoCommand; +import seedu.savvytasker.logic.commands.HelpCommand; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; + +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.savvytasker.logic.commands.UndoCommand.MESSAGE_UNDO_ACKNOWLEDGEMENT; + +public class UndoCommandTest extends SavvyTaskerGuiTest { + + TestTask[] expectedList = td.getTypicalTasks(); + TestTask[] currentList = td.getTypicalTasks(); + TestTask firstTaskToAdd = td.happy; + TestTask secondTaskToAdd = td.haloween; + TestTask pjmTaskToAdd = td.pjm; + TestTask projectMeetingTaskToAdd = td.projectMeeting; + + @Test + // undo one add command + public void undoAddTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + @Test + // undo a delete command + public void undoDeleteTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + @Test + // undo clear command + public void undoClearTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("clear"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + @Test + // undo alias command + public void undoAliasTest() { + expectedList = td.getTypicalTasks(); + expectedList = TestUtil.addTasksToList(expectedList, pjmTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("undo"); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + @Test + // undo unalias command + public void undoUnaliasTest() { + expectedList = TestUtil.addTasksToList(currentList, projectMeetingTaskToAdd); + commandBox.runCommand("alias k/pjm r/Project Meeting"); + commandBox.runCommand("unalias pjm"); + commandBox.runCommand("undo"); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + commandBox.runCommand(pjmTaskToAdd.getAddCommand()); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + // undo mark command + @Test + public void undoMarkTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("mark 1"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo two add commands + @Test + public void undoTwoAddTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand(secondTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo two delete commands + @Test + public void undoTwoDeleteTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("delete 1"); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo a delete command followed by an add command + @Test + public void undoDeleteAddTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("delete 1"); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // undo an add command followed by a delete command + @Test + public void undoAddDeleteTest() { + expectedList = td.getTypicalTasks(); + commandBox.runCommand("delete 1"); + commandBox.runCommand(firstTaskToAdd.getAddCommand()); + commandBox.runCommand("undo"); + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_UNDO_ACKNOWLEDGEMENT); + } + + // invalid command + @Test + public void invalidTest() { + commandBox.runCommand("undos"); + assertResultMessage("Input: undos\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } +} +// @@author diff --git a/src/test/java/guitests/guihandles/GuiHandle.java b/src/test/java/guitests/guihandles/GuiHandle.java index 5e7e0f6de911..693fbe432892 100644 --- a/src/test/java/guitests/guihandles/GuiHandle.java +++ b/src/test/java/guitests/guihandles/GuiHandle.java @@ -7,8 +7,8 @@ import javafx.scene.input.KeyCode; import javafx.stage.Stage; import javafx.stage.Window; -import seedu.address.TestApp; -import seedu.address.commons.core.LogsCenter; +import seedu.savvytasker.TestApp; +import seedu.savvytasker.commons.core.LogsCenter; import java.util.logging.Logger; diff --git a/src/test/java/guitests/guihandles/MainGuiHandle.java b/src/test/java/guitests/guihandles/MainGuiHandle.java index 45802c5135c7..198d6376e724 100644 --- a/src/test/java/guitests/guihandles/MainGuiHandle.java +++ b/src/test/java/guitests/guihandles/MainGuiHandle.java @@ -2,7 +2,7 @@ import guitests.GuiRobot; import javafx.stage.Stage; -import seedu.address.TestApp; +import seedu.savvytasker.TestApp; /** * Provides a handle for the main GUI. @@ -12,9 +12,9 @@ public class MainGuiHandle extends GuiHandle { public MainGuiHandle(GuiRobot guiRobot, Stage primaryStage) { super(guiRobot, primaryStage, TestApp.APP_TITLE); } - - public PersonListPanelHandle getPersonListPanel() { - return new PersonListPanelHandle(guiRobot, primaryStage); + + public TaskListPanelHandle getTaskListPanel() { + return new TaskListPanelHandle(guiRobot, primaryStage); } public ResultDisplayHandle getResultDisplay() { diff --git a/src/test/java/guitests/guihandles/MainMenuHandle.java b/src/test/java/guitests/guihandles/MainMenuHandle.java index 0aeb047a0e1d..9941cc22293f 100644 --- a/src/test/java/guitests/guihandles/MainMenuHandle.java +++ b/src/test/java/guitests/guihandles/MainMenuHandle.java @@ -3,7 +3,7 @@ import guitests.GuiRobot; import javafx.scene.input.KeyCode; import javafx.stage.Stage; -import seedu.address.TestApp; +import seedu.savvytasker.TestApp; import java.util.Arrays; diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java deleted file mode 100644 index fae22a45ae2f..000000000000 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ /dev/null @@ -1,63 +0,0 @@ -package guitests.guihandles; - -import guitests.GuiRobot; -import javafx.scene.Node; -import javafx.stage.Stage; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * Provides a handle to a person card in the person list panel. - */ -public class PersonCardHandle extends GuiHandle { - private static final String NAME_FIELD_ID = "#name"; - private static final String ADDRESS_FIELD_ID = "#address"; - private static final String PHONE_FIELD_ID = "#phone"; - private static final String EMAIL_FIELD_ID = "#email"; - - private Node node; - - public PersonCardHandle(GuiRobot guiRobot, Stage primaryStage, Node node){ - super(guiRobot, primaryStage, null); - this.node = node; - } - - protected String getTextFromLabel(String fieldId) { - return getTextFromLabel(fieldId, node); - } - - public String getFullName() { - return getTextFromLabel(NAME_FIELD_ID); - } - - public String getAddress() { - return getTextFromLabel(ADDRESS_FIELD_ID); - } - - public String getPhone() { - return getTextFromLabel(PHONE_FIELD_ID); - } - - public String getEmail() { - return getTextFromLabel(EMAIL_FIELD_ID); - } - - public boolean isSamePerson(ReadOnlyPerson person){ - return getFullName().equals(person.getName().fullName) && getPhone().equals(person.getPhone().value) - && getEmail().equals(person.getEmail().value) && getAddress().equals(person.getAddress().value); - } - - @Override - public boolean equals(Object obj) { - if(obj instanceof PersonCardHandle) { - PersonCardHandle handle = (PersonCardHandle) obj; - return getFullName().equals(handle.getFullName()) - && getAddress().equals(handle.getAddress()); //TODO: compare the rest - } - return super.equals(obj); - } - - @Override - public String toString() { - return getFullName() + " " + getAddress(); - } -} diff --git a/src/test/java/guitests/guihandles/ResultDisplayHandle.java b/src/test/java/guitests/guihandles/ResultDisplayHandle.java index 110b4682b184..8478fcfffb63 100644 --- a/src/test/java/guitests/guihandles/ResultDisplayHandle.java +++ b/src/test/java/guitests/guihandles/ResultDisplayHandle.java @@ -3,7 +3,7 @@ import guitests.GuiRobot; import javafx.scene.control.TextArea; import javafx.stage.Stage; -import seedu.address.TestApp; +import seedu.savvytasker.TestApp; /** * A handler for the ResultDisplay of the UI diff --git a/src/test/java/guitests/guihandles/TaskCardHandle.java b/src/test/java/guitests/guihandles/TaskCardHandle.java new file mode 100644 index 000000000000..58771f3d2091 --- /dev/null +++ b/src/test/java/guitests/guihandles/TaskCardHandle.java @@ -0,0 +1,54 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.scene.Node; +import javafx.stage.Stage; +import seedu.savvytasker.model.task.ReadOnlyTask; + +//@@author A0139915W +/** + * Provides a handle to a person card in the person list panel. + */ +public class TaskCardHandle extends GuiHandle { + private static final String TASKNAME_FIELD_ID = "#taskName"; + private static final String DETAILS_FIELD_ID = "#details"; + + private Node node; + + public TaskCardHandle(GuiRobot guiRobot, Stage primaryStage, Node node){ + super(guiRobot, primaryStage, null); + this.node = node; + } + + protected String getTextFromLabel(String fieldId) { + return getTextFromLabel(fieldId, node); + } + + public String getTaskName() { + return getTextFromLabel(TASKNAME_FIELD_ID); + } + + public String getDetails() { + return getTextFromLabel(DETAILS_FIELD_ID); + } + + public boolean isSameTask(ReadOnlyTask task) { + return getTaskName().equals(task.getTaskName()) && getDetails().equals(task.getTextForUi()); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof TaskCardHandle) { + TaskCardHandle handle = (TaskCardHandle) obj; + return getTaskName().equals(handle.getTaskName()) && + getDetails().equals(handle.getDetails()); + } + return super.equals(obj); + } + + @Override + public String toString() { + return getTaskName(); + } +} +//@@author diff --git a/src/test/java/guitests/guihandles/PersonListPanelHandle.java b/src/test/java/guitests/guihandles/TaskListPanelHandle.java similarity index 51% rename from src/test/java/guitests/guihandles/PersonListPanelHandle.java rename to src/test/java/guitests/guihandles/TaskListPanelHandle.java index 3451992cf735..f94f5a0aebc8 100644 --- a/src/test/java/guitests/guihandles/PersonListPanelHandle.java +++ b/src/test/java/guitests/guihandles/TaskListPanelHandle.java @@ -6,10 +6,10 @@ import javafx.scene.Node; import javafx.scene.control.ListView; import javafx.stage.Stage; -import seedu.address.TestApp; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.testutil.TestUtil; +import seedu.savvytasker.TestApp; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.testutil.TestUtil; import java.util.List; import java.util.Optional; @@ -17,35 +17,38 @@ import static org.junit.Assert.assertTrue; +//@@author A0139915W /** * Provides a handle for the panel containing the person list. */ -public class PersonListPanelHandle extends GuiHandle { +public class TaskListPanelHandle extends GuiHandle { public static final int NOT_FOUND = -1; public static final String CARD_PANE_ID = "#cardPane"; - private static final String PERSON_LIST_VIEW_ID = "#personListView"; + private static final String TASK_LIST_VIEW_ID = "#taskListView"; - public PersonListPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + public TaskListPanelHandle(GuiRobot guiRobot, Stage primaryStage) { super(guiRobot, primaryStage, TestApp.APP_TITLE); } - public List getSelectedPersons() { - ListView personList = getListView(); - return personList.getSelectionModel().getSelectedItems(); + public List getSelectedTasks() { + ListView taskList = getListView(); + return taskList.getSelectionModel().getSelectedItems(); } - public ListView getListView() { - return (ListView) getNode(PERSON_LIST_VIEW_ID); + @SuppressWarnings("unchecked") + public ListView getListView() { + // Should be a listview of readonlytask only. + return (ListView) getNode(TASK_LIST_VIEW_ID); } /** * Returns true if the list is showing the person details correctly and in correct order. * @param persons A list of person in the correct order. */ - public boolean isListMatching(ReadOnlyPerson... persons) { - return this.isListMatching(0, persons); + public boolean isListMatching(ReadOnlyTask... tasks) { + return this.isListMatching(0, tasks); } /** @@ -59,17 +62,17 @@ public void clickOnListView() { /** * Returns true if the {@code persons} appear as the sub list (in that order) at position {@code startPosition}. */ - public boolean containsInOrder(int startPosition, ReadOnlyPerson... persons) { - List personsInList = getListView().getItems(); + public boolean containsInOrder(int startPosition, ReadOnlyTask... tasks) { + List tasksInList = getListView().getItems(); // Return false if the list in panel is too short to contain the given list - if (startPosition + persons.length > personsInList.size()){ + if (startPosition + tasks.length > tasksInList.size()){ return false; } // Return false if any of the persons doesn't match - for (int i = 0; i < persons.length; i++) { - if (!personsInList.get(startPosition + i).getName().fullName.equals(persons[i].getName().fullName)){ + for (int i = 0; i < tasks.length; i++) { + if (!tasksInList.get(startPosition + i).getTaskName().equals(tasks[i].getTaskName())){ return false; } } @@ -82,17 +85,17 @@ public boolean containsInOrder(int startPosition, ReadOnlyPerson... persons) { * @param startPosition The starting position of the sub list. * @param persons A list of person in the correct order. */ - public boolean isListMatching(int startPosition, ReadOnlyPerson... persons) throws IllegalArgumentException { - if (persons.length + startPosition != getListView().getItems().size()) { + public boolean isListMatching(int startPosition, ReadOnlyTask... tasks) throws IllegalArgumentException { + if (tasks.length + startPosition != getListView().getItems().size()) { throw new IllegalArgumentException("List size mismatched\n" + - "Expected " + (getListView().getItems().size() - 1) + " persons"); + "Expected " + (getListView().getItems().size()) + " tasks"); } - assertTrue(this.containsInOrder(startPosition, persons)); - for (int i = 0; i < persons.length; i++) { + assertTrue(this.containsInOrder(startPosition, tasks)); + for (int i = 0; i < tasks.length; i++) { final int scrollTo = i + startPosition; guiRobot.interact(() -> getListView().scrollTo(scrollTo)); guiRobot.sleep(200); - if (!TestUtil.compareCardAndPerson(getPersonCardHandle(startPosition + i), persons[i])) { + if (!TestUtil.compareCardAndTask(getTaskCardHandle(startPosition + i), tasks[i])) { return false; } } @@ -100,21 +103,21 @@ public boolean isListMatching(int startPosition, ReadOnlyPerson... persons) thro } - public PersonCardHandle navigateToPerson(String name) { + public TaskCardHandle navigateToTask(String name) { guiRobot.sleep(500); //Allow a bit of time for the list to be updated - final Optional person = getListView().getItems().stream().filter(p -> p.getName().fullName.equals(name)).findAny(); - if (!person.isPresent()) { + final Optional task = getListView().getItems().stream().filter(p -> p.getTaskName().equals(name)).findAny(); + if (!task.isPresent()) { throw new IllegalStateException("Name not found: " + name); } - return navigateToPerson(person.get()); + return navigateToTask(task.get()); } /** * Navigates the listview to display and select the person. */ - public PersonCardHandle navigateToPerson(ReadOnlyPerson person) { - int index = getPersonIndex(person); + public TaskCardHandle navigateToTask(ReadOnlyTask task) { + int index = getTaskIndex(task); guiRobot.interact(() -> { getListView().scrollTo(index); @@ -122,17 +125,17 @@ public PersonCardHandle navigateToPerson(ReadOnlyPerson person) { getListView().getSelectionModel().select(index); }); guiRobot.sleep(100); - return getPersonCardHandle(person); + return getTaskCardHandle(task); } /** * Returns the position of the person given, {@code NOT_FOUND} if not found in the list. */ - public int getPersonIndex(ReadOnlyPerson targetPerson) { - List personsInList = getListView().getItems(); - for (int i = 0; i < personsInList.size(); i++) { - if(personsInList.get(i).getName().equals(targetPerson.getName())){ + public int getTaskIndex(ReadOnlyTask targetTask) { + List tasksInList = getListView().getItems(); + for (int i = 0; i < tasksInList.size(); i++) { + if(tasksInList.get(i).getTaskName().equals(targetTask.getTaskName())){ return i; } } @@ -142,21 +145,21 @@ public int getPersonIndex(ReadOnlyPerson targetPerson) { /** * Gets a person from the list by index */ - public ReadOnlyPerson getPerson(int index) { + public ReadOnlyTask getTask(int index) { return getListView().getItems().get(index); } - public PersonCardHandle getPersonCardHandle(int index) { - return getPersonCardHandle(new Person(getListView().getItems().get(index))); + public TaskCardHandle getTaskCardHandle(int index) { + return getTaskCardHandle(new Task(getListView().getItems().get(index))); } - public PersonCardHandle getPersonCardHandle(ReadOnlyPerson person) { + public TaskCardHandle getTaskCardHandle(ReadOnlyTask task) { Set nodes = getAllCardNodes(); - Optional personCardNode = nodes.stream() - .filter(n -> new PersonCardHandle(guiRobot, primaryStage, n).isSamePerson(person)) + Optional taskCardNode = nodes.stream() + .filter(n -> new TaskCardHandle(guiRobot, primaryStage, n).isSameTask(task)) .findFirst(); - if (personCardNode.isPresent()) { - return new PersonCardHandle(guiRobot, primaryStage, personCardNode.get()); + if (taskCardNode.isPresent()) { + return new TaskCardHandle(guiRobot, primaryStage, taskCardNode.get()); } else { return null; } @@ -166,7 +169,8 @@ protected Set getAllCardNodes() { return guiRobot.lookup(CARD_PANE_ID).queryAll(); } - public int getNumberOfPeople() { + public int getNumberOfTasks() { return getListView().getItems().size(); } } +//@@author diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java deleted file mode 100644 index e1ee0cfb4051..000000000000 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ /dev/null @@ -1,512 +0,0 @@ -package seedu.address.logic; - -import com.google.common.eventbus.Subscribe; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import seedu.address.commons.core.EventsCenter; -import seedu.address.logic.commands.*; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.*; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; -import seedu.address.storage.StorageManager; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static seedu.address.commons.core.Messages.*; - -public class LogicManagerTest { - - /** - * See https://github.com/junit-team/junit4/wiki/rules#temporaryfolder-rule - */ - @Rule - public TemporaryFolder saveFolder = new TemporaryFolder(); - - private Model model; - private Logic logic; - - //These are for checking the correctness of the events raised - private ReadOnlyAddressBook latestSavedAddressBook; - private boolean helpShown; - private int targetedJumpIndex; - - @Subscribe - private void handleLocalModelChangedEvent(AddressBookChangedEvent abce) { - latestSavedAddressBook = new AddressBook(abce.data); - } - - @Subscribe - private void handleShowHelpRequestEvent(ShowHelpRequestEvent she) { - helpShown = true; - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent je) { - targetedJumpIndex = je.targetIndex; - } - - @Before - public void setup() { - model = new ModelManager(); - String tempAddressBookFile = saveFolder.getRoot().getPath() + "TempAddressBook.xml"; - String tempPreferencesFile = saveFolder.getRoot().getPath() + "TempPreferences.json"; - logic = new LogicManager(model, new StorageManager(tempAddressBookFile, tempPreferencesFile)); - EventsCenter.getInstance().registerHandler(this); - - latestSavedAddressBook = new AddressBook(model.getAddressBook()); // last saved assumed to be up to date before. - helpShown = false; - targetedJumpIndex = -1; // non yet - } - - @After - public void teardown() { - EventsCenter.clearSubscribers(); - } - - @Test - public void execute_invalid() throws Exception { - String invalidCommand = " "; - assertCommandBehavior(invalidCommand, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - /** - * Executes the command and confirms that the result message is correct. - * Both the 'address book' and the 'last shown list' are expected to be empty. - * @see #assertCommandBehavior(String, String, ReadOnlyAddressBook, List) - */ - private void assertCommandBehavior(String inputCommand, String expectedMessage) throws Exception { - assertCommandBehavior(inputCommand, expectedMessage, new AddressBook(), Collections.emptyList()); - } - - /** - * Executes the command and confirms that the result message is correct and - * also confirms that the following three parts of the LogicManager object's state are as expected:
- * - the internal address book data are same as those in the {@code expectedAddressBook}
- * - the backing list shown by UI matches the {@code shownList}
- * - {@code expectedAddressBook} was saved to the storage file.
- */ - private void assertCommandBehavior(String inputCommand, String expectedMessage, - ReadOnlyAddressBook expectedAddressBook, - List expectedShownList) throws Exception { - - //Execute the command - CommandResult result = logic.execute(inputCommand); - - //Confirm the ui display elements should contain the right data - assertEquals(expectedMessage, result.feedbackToUser); - assertEquals(expectedShownList, model.getFilteredPersonList()); - - //Confirm the state of data (saved and in-memory) is as expected - assertEquals(expectedAddressBook, model.getAddressBook()); - assertEquals(expectedAddressBook, latestSavedAddressBook); - } - - - @Test - public void execute_unknownCommandWord() throws Exception { - String unknownCommand = "uicfhmowqewca"; - assertCommandBehavior(unknownCommand, MESSAGE_UNKNOWN_COMMAND); - } - - @Test - public void execute_help() throws Exception { - assertCommandBehavior("help", HelpCommand.SHOWING_HELP_MESSAGE); - assertTrue(helpShown); - } - - @Test - public void execute_exit() throws Exception { - assertCommandBehavior("exit", ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT); - } - - @Test - public void execute_clear() throws Exception { - TestDataHelper helper = new TestDataHelper(); - model.addPerson(helper.generatePerson(1)); - model.addPerson(helper.generatePerson(2)); - model.addPerson(helper.generatePerson(3)); - - assertCommandBehavior("clear", ClearCommand.MESSAGE_SUCCESS, new AddressBook(), Collections.emptyList()); - } - - - @Test - public void execute_add_invalidArgsFormat() throws Exception { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); - assertCommandBehavior( - "add wrong args wrong args", expectedMessage); - assertCommandBehavior( - "add Valid Name 12345 e/valid@email.butNoPhonePrefix a/valid, address", expectedMessage); - assertCommandBehavior( - "add Valid Name p/12345 valid@email.butNoPrefix a/valid, address", expectedMessage); - assertCommandBehavior( - "add Valid Name p/12345 e/valid@email.butNoAddressPrefix valid, address", expectedMessage); - } - - @Test - public void execute_add_invalidPersonData() throws Exception { - assertCommandBehavior( - "add []\\[;] p/12345 e/valid@e.mail a/valid, address", Name.MESSAGE_NAME_CONSTRAINTS); - assertCommandBehavior( - "add Valid Name p/not_numbers e/valid@e.mail a/valid, address", Phone.MESSAGE_PHONE_CONSTRAINTS); - assertCommandBehavior( - "add Valid Name p/12345 e/notAnEmail a/valid, address", Email.MESSAGE_EMAIL_CONSTRAINTS); - assertCommandBehavior( - "add Valid Name p/12345 e/valid@e.mail a/valid, address t/invalid_-[.tag", Tag.MESSAGE_TAG_CONSTRAINTS); - - } - - @Test - public void execute_add_successful() throws Exception { - // setup expectations - TestDataHelper helper = new TestDataHelper(); - Person toBeAdded = helper.adam(); - AddressBook expectedAB = new AddressBook(); - expectedAB.addPerson(toBeAdded); - - // execute command and verify result - assertCommandBehavior(helper.generateAddCommand(toBeAdded), - String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded), - expectedAB, - expectedAB.getPersonList()); - - } - - @Test - public void execute_addDuplicate_notAllowed() throws Exception { - // setup expectations - TestDataHelper helper = new TestDataHelper(); - Person toBeAdded = helper.adam(); - AddressBook expectedAB = new AddressBook(); - expectedAB.addPerson(toBeAdded); - - // setup starting state - model.addPerson(toBeAdded); // person already in internal address book - - // execute command and verify result - assertCommandBehavior( - helper.generateAddCommand(toBeAdded), - AddCommand.MESSAGE_DUPLICATE_PERSON, - expectedAB, - expectedAB.getPersonList()); - - } - - - @Test - public void execute_list_showsAllPersons() throws Exception { - // prepare expectations - TestDataHelper helper = new TestDataHelper(); - AddressBook expectedAB = helper.generateAddressBook(2); - List expectedList = expectedAB.getPersonList(); - - // prepare address book state - helper.addToModel(model, 2); - - assertCommandBehavior("list", - ListCommand.MESSAGE_SUCCESS, - expectedAB, - expectedList); - } - - - /** - * Confirms the 'invalid argument index number behaviour' for the given command - * targeting a single person in the shown list, using visible index. - * @param commandWord to test assuming it targets a single person in the last shown list based on visible index. - */ - private void assertIncorrectIndexFormatBehaviorForCommand(String commandWord, String expectedMessage) throws Exception { - assertCommandBehavior(commandWord , expectedMessage); //index missing - assertCommandBehavior(commandWord + " +1", expectedMessage); //index should be unsigned - assertCommandBehavior(commandWord + " -1", expectedMessage); //index should be unsigned - assertCommandBehavior(commandWord + " 0", expectedMessage); //index cannot be 0 - assertCommandBehavior(commandWord + " not_a_number", expectedMessage); - } - - /** - * Confirms the 'invalid argument index number behaviour' for the given command - * targeting a single person in the shown list, using visible index. - * @param commandWord to test assuming it targets a single person in the last shown list based on visible index. - */ - private void assertIndexNotFoundBehaviorForCommand(String commandWord) throws Exception { - String expectedMessage = MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; - TestDataHelper helper = new TestDataHelper(); - List personList = helper.generatePersonList(2); - - // set AB state to 2 persons - model.resetData(new AddressBook()); - for (Person p : personList) { - model.addPerson(p); - } - - assertCommandBehavior(commandWord + " 3", expectedMessage, model.getAddressBook(), personList); - } - - @Test - public void execute_selectInvalidArgsFormat_errorMessageShown() throws Exception { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE); - assertIncorrectIndexFormatBehaviorForCommand("select", expectedMessage); - } - - @Test - public void execute_selectIndexNotFound_errorMessageShown() throws Exception { - assertIndexNotFoundBehaviorForCommand("select"); - } - - @Test - public void execute_select_jumpsToCorrectPerson() throws Exception { - TestDataHelper helper = new TestDataHelper(); - List threePersons = helper.generatePersonList(3); - - AddressBook expectedAB = helper.generateAddressBook(threePersons); - helper.addToModel(model, threePersons); - - assertCommandBehavior("select 2", - String.format(SelectCommand.MESSAGE_SELECT_PERSON_SUCCESS, 2), - expectedAB, - expectedAB.getPersonList()); - assertEquals(1, targetedJumpIndex); - assertEquals(model.getFilteredPersonList().get(1), threePersons.get(1)); - } - - - @Test - public void execute_deleteInvalidArgsFormat_errorMessageShown() throws Exception { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE); - assertIncorrectIndexFormatBehaviorForCommand("delete", expectedMessage); - } - - @Test - public void execute_deleteIndexNotFound_errorMessageShown() throws Exception { - assertIndexNotFoundBehaviorForCommand("delete"); - } - - @Test - public void execute_delete_removesCorrectPerson() throws Exception { - TestDataHelper helper = new TestDataHelper(); - List threePersons = helper.generatePersonList(3); - - AddressBook expectedAB = helper.generateAddressBook(threePersons); - expectedAB.removePerson(threePersons.get(1)); - helper.addToModel(model, threePersons); - - assertCommandBehavior("delete 2", - String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, threePersons.get(1)), - expectedAB, - expectedAB.getPersonList()); - } - - - @Test - public void execute_find_invalidArgsFormat() throws Exception { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE); - assertCommandBehavior("find ", expectedMessage); - } - - @Test - public void execute_find_onlyMatchesFullWordsInNames() throws Exception { - TestDataHelper helper = new TestDataHelper(); - Person pTarget1 = helper.generatePersonWithName("bla bla KEY bla"); - Person pTarget2 = helper.generatePersonWithName("bla KEY bla bceofeia"); - Person p1 = helper.generatePersonWithName("KE Y"); - Person p2 = helper.generatePersonWithName("KEYKEYKEY sduauo"); - - List fourPersons = helper.generatePersonList(p1, pTarget1, p2, pTarget2); - AddressBook expectedAB = helper.generateAddressBook(fourPersons); - List expectedList = helper.generatePersonList(pTarget1, pTarget2); - helper.addToModel(model, fourPersons); - - assertCommandBehavior("find KEY", - Command.getMessageForPersonListShownSummary(expectedList.size()), - expectedAB, - expectedList); - } - - @Test - public void execute_find_isNotCaseSensitive() throws Exception { - TestDataHelper helper = new TestDataHelper(); - Person p1 = helper.generatePersonWithName("bla bla KEY bla"); - Person p2 = helper.generatePersonWithName("bla KEY bla bceofeia"); - Person p3 = helper.generatePersonWithName("key key"); - Person p4 = helper.generatePersonWithName("KEy sduauo"); - - List fourPersons = helper.generatePersonList(p3, p1, p4, p2); - AddressBook expectedAB = helper.generateAddressBook(fourPersons); - List expectedList = fourPersons; - helper.addToModel(model, fourPersons); - - assertCommandBehavior("find KEY", - Command.getMessageForPersonListShownSummary(expectedList.size()), - expectedAB, - expectedList); - } - - @Test - public void execute_find_matchesIfAnyKeywordPresent() throws Exception { - TestDataHelper helper = new TestDataHelper(); - Person pTarget1 = helper.generatePersonWithName("bla bla KEY bla"); - Person pTarget2 = helper.generatePersonWithName("bla rAnDoM bla bceofeia"); - Person pTarget3 = helper.generatePersonWithName("key key"); - Person p1 = helper.generatePersonWithName("sduauo"); - - List fourPersons = helper.generatePersonList(pTarget1, p1, pTarget2, pTarget3); - AddressBook expectedAB = helper.generateAddressBook(fourPersons); - List expectedList = helper.generatePersonList(pTarget1, pTarget2, pTarget3); - helper.addToModel(model, fourPersons); - - assertCommandBehavior("find key rAnDoM", - Command.getMessageForPersonListShownSummary(expectedList.size()), - expectedAB, - expectedList); - } - - - /** - * A utility class to generate test data. - */ - class TestDataHelper{ - - Person adam() throws Exception { - Name name = new Name("Adam Brown"); - Phone privatePhone = new Phone("111111"); - Email email = new Email("adam@gmail.com"); - Address privateAddress = new Address("111, alpha street"); - Tag tag1 = new Tag("tag1"); - Tag tag2 = new Tag("tag2"); - UniqueTagList tags = new UniqueTagList(tag1, tag2); - return new Person(name, privatePhone, email, privateAddress, tags); - } - - /** - * Generates a valid person using the given seed. - * Running this function with the same parameter values guarantees the returned person will have the same state. - * Each unique seed will generate a unique Person object. - * - * @param seed used to generate the person data field values - */ - Person generatePerson(int seed) throws Exception { - return new Person( - new Name("Person " + seed), - new Phone("" + Math.abs(seed)), - new Email(seed + "@email"), - new Address("House of " + seed), - new UniqueTagList(new Tag("tag" + Math.abs(seed)), new Tag("tag" + Math.abs(seed + 1))) - ); - } - - /** Generates the correct add command based on the person given */ - String generateAddCommand(Person p) { - StringBuffer cmd = new StringBuffer(); - - cmd.append("add "); - - cmd.append(p.getName().toString()); - cmd.append(" p/").append(p.getPhone()); - cmd.append(" e/").append(p.getEmail()); - cmd.append(" a/").append(p.getAddress()); - - UniqueTagList tags = p.getTags(); - for(Tag t: tags){ - cmd.append(" t/").append(t.tagName); - } - - return cmd.toString(); - } - - /** - * Generates an AddressBook with auto-generated persons. - */ - AddressBook generateAddressBook(int numGenerated) throws Exception{ - AddressBook addressBook = new AddressBook(); - addToAddressBook(addressBook, numGenerated); - return addressBook; - } - - /** - * Generates an AddressBook based on the list of Persons given. - */ - AddressBook generateAddressBook(List persons) throws Exception{ - AddressBook addressBook = new AddressBook(); - addToAddressBook(addressBook, persons); - return addressBook; - } - - /** - * Adds auto-generated Person objects to the given AddressBook - * @param addressBook The AddressBook to which the Persons will be added - */ - void addToAddressBook(AddressBook addressBook, int numGenerated) throws Exception{ - addToAddressBook(addressBook, generatePersonList(numGenerated)); - } - - /** - * Adds the given list of Persons to the given AddressBook - */ - void addToAddressBook(AddressBook addressBook, List personsToAdd) throws Exception{ - for(Person p: personsToAdd){ - addressBook.addPerson(p); - } - } - - /** - * Adds auto-generated Person objects to the given model - * @param model The model to which the Persons will be added - */ - void addToModel(Model model, int numGenerated) throws Exception{ - addToModel(model, generatePersonList(numGenerated)); - } - - /** - * Adds the given list of Persons to the given model - */ - void addToModel(Model model, List personsToAdd) throws Exception{ - for(Person p: personsToAdd){ - model.addPerson(p); - } - } - - /** - * Generates a list of Persons based on the flags. - */ - List generatePersonList(int numGenerated) throws Exception{ - List persons = new ArrayList<>(); - for(int i = 1; i <= numGenerated; i++){ - persons.add(generatePerson(i)); - } - return persons; - } - - List generatePersonList(Person... persons) { - return Arrays.asList(persons); - } - - /** - * Generates a Person object with given name. Other fields will have some dummy values. - */ - Person generatePersonWithName(String name) throws Exception { - return new Person( - new Name(name), - new Phone("1"), - new Email("1@email"), - new Address("House of 1"), - new UniqueTagList(new Tag("tag")) - ); - } - } -} diff --git a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java b/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java deleted file mode 100644 index 04b0db1ce1c7..000000000000 --- a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package seedu.address.storage; - - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.testutil.TypicalTestPersons; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -public class XmlAddressBookStorageTest { - private static String TEST_DATA_FOLDER = FileUtil.getPath("./src/test/data/XmlAddressBookStorageTest/"); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - public TemporaryFolder testFolder = new TemporaryFolder(); - - @Test - public void readAddressBook_nullFilePath_assertionFailure() throws Exception { - thrown.expect(AssertionError.class); - readAddressBook(null); - } - - private java.util.Optional readAddressBook(String filePath) throws Exception { - return new XmlAddressBookStorage(filePath).readAddressBook(addToTestDataPathIfNotNull(filePath)); - } - - private String addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { - return prefsFileInTestDataFolder != null - ? TEST_DATA_FOLDER + prefsFileInTestDataFolder - : null; - } - - @Test - public void read_missingFile_emptyResult() throws Exception { - assertFalse(readAddressBook("NonExistentFile.xml").isPresent()); - } - - @Test - public void read_notXmlFormat_exceptionThrown() throws Exception { - - thrown.expect(DataConversionException.class); - readAddressBook("NotXmlFormatAddressBook.xml"); - - /* IMPORTANT: Any code below an exception-throwing line (like the one above) will be ignored. - * That means you should not have more than one exception test in one method - */ - } - - @Test - public void readAndSaveAddressBook_allInOrder_success() throws Exception { - String filePath = testFolder.getRoot().getPath() + "TempAddressBook.xml"; - TypicalTestPersons td = new TypicalTestPersons(); - AddressBook original = td.getTypicalAddressBook(); - XmlAddressBookStorage xmlAddressBookStorage = new XmlAddressBookStorage(filePath); - - //Save in new file and read back - xmlAddressBookStorage.saveAddressBook(original, filePath); - ReadOnlyAddressBook readBack = xmlAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - //Modify data, overwrite exiting file, and read back - original.addPerson(new Person(TypicalTestPersons.hoon)); - original.removePerson(new Person(TypicalTestPersons.alice)); - xmlAddressBookStorage.saveAddressBook(original, filePath); - readBack = xmlAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - //Save and read without specifying file path - original.addPerson(new Person(TypicalTestPersons.ida)); - xmlAddressBookStorage.saveAddressBook(original); //file path not specified - readBack = xmlAddressBookStorage.readAddressBook().get(); //file path not specified - assertEquals(original, new AddressBook(readBack)); - - } - - @Test - public void saveAddressBook_nullAddressBook_assertionFailure() throws IOException { - thrown.expect(AssertionError.class); - saveAddressBook(null, "SomeFile.xml"); - } - - private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - new XmlAddressBookStorage(filePath).saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); - } - - @Test - public void saveAddressBook_nullFilePath_assertionFailure() throws IOException { - thrown.expect(AssertionError.class); - saveAddressBook(new AddressBook(), null); - } - - -} diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java deleted file mode 100644 index a623b81c878f..000000000000 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ /dev/null @@ -1,35 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * A utility class to help with building Addressbook objects. - * Example usage:
- * {@code AddressBook ab = new AddressBookBuilder().withPerson("John", "Doe").withTag("Friend").build();} - */ -public class AddressBookBuilder { - - private AddressBook addressBook; - - public AddressBookBuilder(AddressBook addressBook){ - this.addressBook = addressBook; - } - - public AddressBookBuilder withPerson(Person person) throws UniquePersonList.DuplicatePersonException { - addressBook.addPerson(person); - return this; - } - - public AddressBookBuilder withTag(String tagName) throws IllegalValueException { - addressBook.addTag(new Tag(tagName)); - return this; - } - - public AddressBook build(){ - return addressBook; - } -} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java deleted file mode 100644 index 8b02a1668ef6..000000000000 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; -import seedu.address.model.person.*; - -/** - * - */ -public class PersonBuilder { - - private TestPerson person; - - public PersonBuilder() { - this.person = new TestPerson(); - } - - public PersonBuilder withName(String name) throws IllegalValueException { - this.person.setName(new Name(name)); - return this; - } - - public PersonBuilder withTags(String ... tags) throws IllegalValueException { - for (String tag: tags) { - person.getTags().add(new Tag(tag)); - } - return this; - } - - public PersonBuilder withAddress(String address) throws IllegalValueException { - this.person.setAddress(new Address(address)); - return this; - } - - public PersonBuilder withPhone(String phone) throws IllegalValueException { - this.person.setPhone(new Phone(phone)); - return this; - } - - public PersonBuilder withEmail(String email) throws IllegalValueException { - this.person.setEmail(new Email(email)); - return this; - } - - public TestPerson build() { - return this.person; - } - -} diff --git a/src/test/java/seedu/address/testutil/TestPerson.java b/src/test/java/seedu/address/testutil/TestPerson.java deleted file mode 100644 index 19ee5ded1cd3..000000000000 --- a/src/test/java/seedu/address/testutil/TestPerson.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.model.tag.UniqueTagList; -import seedu.address.model.person.*; - -/** - * A mutable person object. For testing only. - */ -public class TestPerson implements ReadOnlyPerson { - - private Name name; - private Address address; - private Email email; - private Phone phone; - private UniqueTagList tags; - - public TestPerson() { - tags = new UniqueTagList(); - } - - public void setName(Name name) { - this.name = name; - } - - public void setAddress(Address address) { - this.address = address; - } - - public void setEmail(Email email) { - this.email = email; - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - @Override - public Name getName() { - return name; - } - - @Override - public Phone getPhone() { - return phone; - } - - @Override - public Email getEmail() { - return email; - } - - @Override - public Address getAddress() { - return address; - } - - @Override - public UniqueTagList getTags() { - return tags; - } - - @Override - public String toString() { - return getAsText(); - } - - public String getAddCommand() { - StringBuilder sb = new StringBuilder(); - sb.append("add " + this.getName().fullName + " "); - sb.append("p/" + this.getPhone().value + " "); - sb.append("e/" + this.getEmail().value + " "); - sb.append("a/" + this.getAddress().value + " "); - this.getTags().getInternalList().stream().forEach(s -> sb.append("t/" + s.tagName + " ")); - return sb.toString(); - } -} diff --git a/src/test/java/seedu/address/testutil/TypicalTestPersons.java b/src/test/java/seedu/address/testutil/TypicalTestPersons.java deleted file mode 100644 index 773f64a98cc3..000000000000 --- a/src/test/java/seedu/address/testutil/TypicalTestPersons.java +++ /dev/null @@ -1,61 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.person.*; - -/** - * - */ -public class TypicalTestPersons { - - public static TestPerson alice, benson, carl, daniel, elle, fiona, george, hoon, ida; - - public TypicalTestPersons() { - try { - alice = new PersonBuilder().withName("Alice Pauline").withAddress("123, Jurong West Ave 6, #08-111") - .withEmail("alice@gmail.com").withPhone("85355255") - .withTags("friends").build(); - benson = new PersonBuilder().withName("Benson Meier").withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@gmail.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); - carl = new PersonBuilder().withName("Carl Kurz").withPhone("95352563").withEmail("heinz@yahoo.com").withAddress("wall street").build(); - daniel = new PersonBuilder().withName("Daniel Meier").withPhone("87652533").withEmail("cornelia@google.com").withAddress("10th street").build(); - elle = new PersonBuilder().withName("Elle Meyer").withPhone("9482224").withEmail("werner@gmail.com").withAddress("michegan ave").build(); - fiona = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427").withEmail("lydia@gmail.com").withAddress("little tokyo").build(); - george = new PersonBuilder().withName("George Best").withPhone("9482442").withEmail("anna@google.com").withAddress("4th street").build(); - - //Manually added - hoon = new PersonBuilder().withName("Hoon Meier").withPhone("8482424").withEmail("stefan@mail.com").withAddress("little india").build(); - ida = new PersonBuilder().withName("Ida Mueller").withPhone("8482131").withEmail("hans@google.com").withAddress("chicago ave").build(); - } catch (IllegalValueException e) { - e.printStackTrace(); - assert false : "not possible"; - } - } - - public static void loadAddressBookWithSampleData(AddressBook ab) { - - try { - ab.addPerson(new Person(alice)); - ab.addPerson(new Person(benson)); - ab.addPerson(new Person(carl)); - ab.addPerson(new Person(daniel)); - ab.addPerson(new Person(elle)); - ab.addPerson(new Person(fiona)); - ab.addPerson(new Person(george)); - } catch (UniquePersonList.DuplicatePersonException e) { - assert false : "not possible"; - } - } - - public TestPerson[] getTypicalPersons() { - return new TestPerson[]{alice, benson, carl, daniel, elle, fiona, george}; - } - - public AddressBook getTypicalAddressBook(){ - AddressBook ab = new AddressBook(); - loadAddressBookWithSampleData(ab); - return ab; - } -} diff --git a/src/test/java/seedu/address/TestApp.java b/src/test/java/seedu/savvytasker/TestApp.java similarity index 71% rename from src/test/java/seedu/address/TestApp.java rename to src/test/java/seedu/savvytasker/TestApp.java index 756642b6c180..2da3b7709f80 100644 --- a/src/test/java/seedu/address/TestApp.java +++ b/src/test/java/seedu/savvytasker/TestApp.java @@ -1,13 +1,14 @@ -package seedu.address; +package seedu.savvytasker; import javafx.stage.Screen; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.storage.XmlSerializableAddressBook; -import seedu.address.testutil.TestUtil; +import seedu.savvytasker.MainApp; +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.core.GuiSettings; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.UserPrefs; +import seedu.savvytasker.storage.XmlSerializableSavvyTasker; +import seedu.savvytasker.testutil.TestUtil; import java.util.function.Supplier; @@ -21,13 +22,13 @@ public class TestApp extends MainApp { protected static final String DEFAULT_PREF_FILE_LOCATION_FOR_TESTING = TestUtil.getFilePathInSandboxFolder("pref_testing.json"); public static final String APP_TITLE = "Test App"; protected static final String ADDRESS_BOOK_NAME = "Test"; - protected Supplier initialDataSupplier = () -> null; + protected Supplier initialDataSupplier = () -> null; protected String saveFileLocation = SAVE_LOCATION_FOR_TESTING; public TestApp() { } - public TestApp(Supplier initialDataSupplier, String saveFileLocation) { + public TestApp(Supplier initialDataSupplier, String saveFileLocation) { super(); this.initialDataSupplier = initialDataSupplier; this.saveFileLocation = saveFileLocation; @@ -35,7 +36,7 @@ public TestApp(Supplier initialDataSupplier, String saveFil // If some initial local data has been provided, write those to the file if (initialDataSupplier.get() != null) { TestUtil.createDataFileWithData( - new XmlSerializableAddressBook(this.initialDataSupplier.get()), + new XmlSerializableSavvyTasker(this.initialDataSupplier.get()), this.saveFileLocation); } } @@ -44,9 +45,9 @@ public TestApp(Supplier initialDataSupplier, String saveFil protected Config initConfig(String configFilePath) { Config config = super.initConfig(configFilePath); config.setAppTitle(APP_TITLE); - config.setAddressBookFilePath(saveFileLocation); + config.setSavvyTaskerFilePath(saveFileLocation); config.setUserPrefsFilePath(DEFAULT_PREF_FILE_LOCATION_FOR_TESTING); - config.setAddressBookName(ADDRESS_BOOK_NAME); + config.setSavvyTaskerName(ADDRESS_BOOK_NAME); return config; } @@ -55,11 +56,10 @@ protected UserPrefs initPrefs(Config config) { UserPrefs userPrefs = super.initPrefs(config); double x = Screen.getPrimary().getVisualBounds().getMinX(); double y = Screen.getPrimary().getVisualBounds().getMinY(); - userPrefs.updateLastUsedGuiSetting(new GuiSettings(600.0, 600.0, (int) x, (int) y)); + userPrefs.updateLastUsedGuiSetting(new GuiSettings(1150.0, 700.0, (int) x, (int) y)); return userPrefs; } - - + @Override public void start(Stage primaryStage) { ui.start(primaryStage); diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/savvytasker/commons/core/ConfigTest.java similarity index 74% rename from src/test/java/seedu/address/commons/core/ConfigTest.java rename to src/test/java/seedu/savvytasker/commons/core/ConfigTest.java index 62d58646f736..3f0c0642d746 100644 --- a/src/test/java/seedu/address/commons/core/ConfigTest.java +++ b/src/test/java/seedu/savvytasker/commons/core/ConfigTest.java @@ -1,9 +1,11 @@ -package seedu.address.commons.core; +package seedu.savvytasker.commons.core; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import seedu.savvytasker.commons.core.Config; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -14,11 +16,11 @@ public class ConfigTest { @Test public void toString_defaultObject_stringReturned() { - String defaultConfigAsString = "App title : Address App\n" + + String defaultConfigAsString = "App title : Savvy Tasker\n" + "Current log level : INFO\n" + "Preference file Location : preferences.json\n" + - "Local data file location : data/addressbook.xml\n" + - "AddressBook name : MyAddressBook"; + "Local data file location : data/savvytasker.xml\n" + + "Savvy Tasker List name : MyTaskList"; assertEquals(defaultConfigAsString, new Config().toString()); } diff --git a/src/test/java/seedu/address/commons/core/VersionTest.java b/src/test/java/seedu/savvytasker/commons/core/VersionTest.java similarity index 98% rename from src/test/java/seedu/address/commons/core/VersionTest.java rename to src/test/java/seedu/savvytasker/commons/core/VersionTest.java index 87ac01f6c92d..4af9ffbc3e64 100644 --- a/src/test/java/seedu/address/commons/core/VersionTest.java +++ b/src/test/java/seedu/savvytasker/commons/core/VersionTest.java @@ -1,9 +1,11 @@ -package seedu.address.commons.core; +package seedu.savvytasker.commons.core; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import seedu.savvytasker.commons.core.Version; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/AppUtilTest.java similarity index 85% rename from src/test/java/seedu/address/commons/util/AppUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/AppUtilTest.java index fbea1d0c1e8e..405662595b86 100644 --- a/src/test/java/seedu/address/commons/util/AppUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/AppUtilTest.java @@ -1,9 +1,11 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import seedu.savvytasker.commons.util.AppUtil; + import static org.junit.Assert.assertNotNull; public class AppUtilTest { diff --git a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java similarity index 82% rename from src/test/java/seedu/address/commons/util/ConfigUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java index 6699343c4a82..0e853b22d958 100644 --- a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java @@ -1,12 +1,15 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; + +import seedu.savvytasker.commons.core.Config; +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.ConfigUtil; +import seedu.savvytasker.commons.util.FileUtil; import java.io.File; import java.io.IOException; @@ -76,14 +79,15 @@ private Config getTypicalConfig() { config.setAppTitle("Typical App Title"); config.setLogLevel(Level.INFO); config.setUserPrefsFilePath("C:\\preferences.json"); - config.setAddressBookFilePath("addressbook.xml"); - config.setAddressBookName("TypicalAddressBookName"); + config.setSavvyTaskerFilePath("data/savvytasker.xml"); + config.setSavvyTaskerName("MyTaskList"); return config; } private Optional read(String configFileInTestDataFolder) throws DataConversionException { String configFilePath = addToTestDataPathIfNotNull(configFileInTestDataFolder); - return new ConfigUtil().readConfig(configFilePath); + new ConfigUtil(); + return ConfigUtil.readConfig(configFilePath); } @Test @@ -103,24 +107,25 @@ public void saveConfig_allInOrder_success() throws DataConversionException, IOEx Config original = getTypicalConfig(); String configFilePath = testFolder.getRoot() + File.separator + "TempConfig.json"; - ConfigUtil configStorage = new ConfigUtil(); + new ConfigUtil(); //Try writing when the file doesn't exist - configStorage.saveConfig(original, configFilePath); - Config readBack = configStorage.readConfig(configFilePath).get(); + ConfigUtil.saveConfig(original, configFilePath); + Config readBack = ConfigUtil.readConfig(configFilePath).get(); assertEquals(original, readBack); //Try saving when the file exists original.setAppTitle("Updated Title"); original.setLogLevel(Level.FINE); - configStorage.saveConfig(original, configFilePath); - readBack = configStorage.readConfig(configFilePath).get(); + ConfigUtil.saveConfig(original, configFilePath); + readBack = ConfigUtil.readConfig(configFilePath).get(); assertEquals(original, readBack); } private void save(Config config, String configFileInTestDataFolder) throws IOException { String configFilePath = addToTestDataPathIfNotNull(configFileInTestDataFolder); - new ConfigUtil().saveConfig(config, configFilePath); + new ConfigUtil(); + ConfigUtil.saveConfig(config, configFilePath); } private String addToTestDataPathIfNotNull(String configFileInTestDataFolder) { diff --git a/src/test/java/seedu/address/commons/util/FileUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/FileUtilTest.java similarity index 91% rename from src/test/java/seedu/address/commons/util/FileUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/FileUtilTest.java index 8de2621799cf..54d7fe3decfd 100644 --- a/src/test/java/seedu/address/commons/util/FileUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/FileUtilTest.java @@ -1,11 +1,13 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import seedu.address.testutil.SerializableTestClass; -import seedu.address.testutil.TestUtil; + +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.testutil.SerializableTestClass; +import seedu.savvytasker.testutil.TestUtil; import java.io.File; import java.io.IOException; diff --git a/src/test/java/seedu/address/commons/util/JsonUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/JsonUtilTest.java similarity index 84% rename from src/test/java/seedu/address/commons/util/JsonUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/JsonUtilTest.java index fc3902188807..f5709c374264 100644 --- a/src/test/java/seedu/address/commons/util/JsonUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/JsonUtilTest.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; /** * Tests JSON Read and Write diff --git a/src/test/java/seedu/savvytasker/commons/util/SmartDefaultDatesTest.java b/src/test/java/seedu/savvytasker/commons/util/SmartDefaultDatesTest.java new file mode 100644 index 000000000000..d8598a8eecf2 --- /dev/null +++ b/src/test/java/seedu/savvytasker/commons/util/SmartDefaultDatesTest.java @@ -0,0 +1,240 @@ +package seedu.savvytasker.commons.util; + +import org.junit.Test; + +import seedu.savvytasker.logic.parser.DateParser; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.logic.parser.ParseException; + +import static org.junit.Assert.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +//@@author A0139915W +public class SmartDefaultDatesTest { + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HHmmss"); + + @Test + public void smartDefaultDates_parseStart() { + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredEnd = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start date, assumed to on the given date at 12am + // and to end on the given date at 2359:59 + assertStartEndEquals( + getDate("31/12/2016 000000"), getDate("31/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start time, assumed to start today at the given time + // and to end today 2359:59 + assertStartEndEquals( + getDate(sdf.format(today) + " 150000"), getDate(sdf.format(today) + " 235959"), + sdd.getStartDate(), sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseEnd() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredStart = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date, assumed to start today at 12am + // and to end on the given date at 2359:59 + assertStartEndEquals( + getDate(sdf.format(today) + " 000000"), getDate("31/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end time, assumed to start today at 12am + // and to end at the given time today + assertStartEndEquals( + getDate(sdf.format(today) + " 000000"), getDate(sdf.format(today) + " 150000"), + sdd.getStartDate(), sdd.getEndDate()); + + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2000"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date in the past, start date will be null + // and to end on the given date at 2359:59 + assertStartEndEquals( + null, getDate("31/12/2000 235959"), + sdd.getStartDate(), sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseStartEnd() { + // START_TIME + // date not supplied -> today + // time not supplied -> 0000 + // END_TIME + // date not supplied -> today + // time not supplied -> 2359 + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end + // start defaults to 0000 + // end defaults to 2359:59 + assertStartEndEquals( + getDate("31/12/2016 000000"), getDate("31/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/30/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end, end date earlier than start + // start defaults to 0000 + // end defaults to 2359:59 + // no restrictions imposed on end time earlier than start time + assertStartEndEquals( + getDate("31/12/2016 000000"), getDate("30/12/2016 235959"), + sdd.getStartDate(), sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10am"); + inferredEnd = dateParser.parseSingle("10pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end + // start and end defaults to the given time today + assertStartEndEquals( + getDate(sdf.format(today) + " 100000"), getDate(sdf.format(today) + " 220000"), + sdd.getStartDate(), sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10pm"); + inferredEnd = dateParser.parseSingle("10am"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end, end time ends before start time + // start and end defaults to the given time today + // no restrictions imposed on end time being earlier + assertStartEndEquals( + getDate(sdf.format(today) + " 220000"), getDate(sdf.format(today) + " 100000"), + sdd.getStartDate(), sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_defaultParse() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + Date actualStart = sdd.getStart(dateParser.new InferredDate(new Date(), true, true)); + Date actualEnd = sdd.getEnd(dateParser.new InferredDate(new Date(), true, true)); + assertStartEndEquals(null, null, + actualStart, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("10pm")); + actualEnd = sdd.getEnd(dateParser.parseSingle("10am")); + } catch (ParseException e) { + assert false; //won't get here + } + assertStartEndEquals( + getDate(sdf.format(today) + " 220000"), getDate(sdf.format(today) + " 100000"), + actualStart, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("12/31/2016")); + actualEnd = sdd.getEnd(dateParser.parseSingle("12/31/2016")); + } catch (ParseException e) { + assert false; //won't get here + } + assertStartEndEquals(getDate("31/12/2016 000000"), getDate("31/12/2016 235959"), + actualStart, actualEnd); + } + + private void assertStartEndEquals(Date expectedStart, Date expectedEnd, + Date actualStart, Date actualEnd) { + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date today(int hours_24, int minute_60) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.set(Calendar.HOUR_OF_DAY, hours_24); + calendar.set(Calendar.MINUTE, minute_60); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } +} +//@@author diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/StringUtilTest.java similarity index 95% rename from src/test/java/seedu/address/commons/util/StringUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/StringUtilTest.java index 194dd71d2c3f..91725258a9c6 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/StringUtilTest.java @@ -1,14 +1,15 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import seedu.savvytasker.commons.util.StringUtil; + import java.io.FileNotFoundException; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/src/test/java/seedu/address/commons/util/UrlUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/UrlUtilTest.java similarity index 95% rename from src/test/java/seedu/address/commons/util/UrlUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/UrlUtilTest.java index 58efab5fd499..1ef267e9e519 100644 --- a/src/test/java/seedu/address/commons/util/UrlUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/UrlUtilTest.java @@ -1,7 +1,9 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import org.junit.Test; +import seedu.savvytasker.commons.util.UrlUtil; + import java.net.MalformedURLException; import java.net.URL; diff --git a/src/test/java/seedu/address/commons/util/XmlUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/XmlUtilTest.java similarity index 59% rename from src/test/java/seedu/address/commons/util/XmlUtilTest.java rename to src/test/java/seedu/savvytasker/commons/util/XmlUtilTest.java index dc4fd886c23e..515df40c1155 100644 --- a/src/test/java/seedu/address/commons/util/XmlUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/XmlUtilTest.java @@ -1,12 +1,15 @@ -package seedu.address.commons.util; +package seedu.savvytasker.commons.util; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import seedu.address.model.AddressBook; -import seedu.address.storage.XmlSerializableAddressBook; -import seedu.address.testutil.AddressBookBuilder; -import seedu.address.testutil.TestUtil; + +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.commons.util.XmlUtil; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.storage.XmlSerializableSavvyTasker; +import seedu.savvytasker.testutil.SavvyTaskerBuilder; +import seedu.savvytasker.testutil.TestUtil; import javax.xml.bind.JAXBException; import java.io.File; @@ -19,8 +22,8 @@ public class XmlUtilTest { private static final String TEST_DATA_FOLDER = FileUtil.getPath("src/test/data/XmlUtilTest/"); private static final File EMPTY_FILE = new File(TEST_DATA_FOLDER + "empty.xml"); private static final File MISSING_FILE = new File(TEST_DATA_FOLDER + "missing.xml"); - private static final File VALID_FILE = new File(TEST_DATA_FOLDER + "validAddressBook.xml"); - private static final File TEMP_FILE = new File(TestUtil.getFilePathInSandboxFolder("tempAddressBook.xml")); + private static final File VALID_FILE = new File(TEST_DATA_FOLDER + "validSavvyTasker.xml"); + private static final File TEMP_FILE = new File(TestUtil.getFilePathInSandboxFolder("tempSavvyTasker.xml")); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -28,7 +31,7 @@ public class XmlUtilTest { @Test public void getDataFromFile_nullFile_AssertionError() throws Exception { thrown.expect(AssertionError.class); - XmlUtil.getDataFromFile(null, AddressBook.class); + XmlUtil.getDataFromFile(null, SavvyTasker.class); } @Test @@ -40,26 +43,25 @@ public void getDataFromFile_nullClass_AssertionError() throws Exception { @Test public void getDataFromFile_missingFile_FileNotFoundException() throws Exception { thrown.expect(FileNotFoundException.class); - XmlUtil.getDataFromFile(MISSING_FILE, AddressBook.class); + XmlUtil.getDataFromFile(MISSING_FILE, SavvyTasker.class); } @Test public void getDataFromFile_emptyFile_DataFormatMismatchException() throws Exception { thrown.expect(JAXBException.class); - XmlUtil.getDataFromFile(EMPTY_FILE, AddressBook.class); + XmlUtil.getDataFromFile(EMPTY_FILE, SavvyTasker.class); } @Test public void getDataFromFile_validFile_validResult() throws Exception { - XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(VALID_FILE, XmlSerializableAddressBook.class); - assertEquals(9, dataFromFile.getPersonList().size()); - assertEquals(0, dataFromFile.getTagList().size()); + XmlSerializableSavvyTasker dataFromFile = XmlUtil.getDataFromFile(VALID_FILE, XmlSerializableSavvyTasker.class); + assertEquals(3, dataFromFile.getReadOnlyListOfTasks().size()); } @Test public void saveDataToFile_nullFile_AssertionError() throws Exception { thrown.expect(AssertionError.class); - XmlUtil.saveDataToFile(null, new AddressBook()); + XmlUtil.saveDataToFile(null, new SavvyTasker()); } @Test @@ -71,23 +73,23 @@ public void saveDataToFile_nullClass_AssertionError() throws Exception { @Test public void saveDataToFile_missingFile_FileNotFoundException() throws Exception { thrown.expect(FileNotFoundException.class); - XmlUtil.saveDataToFile(MISSING_FILE, new AddressBook()); + XmlUtil.saveDataToFile(MISSING_FILE, new SavvyTasker()); } @Test public void saveDataToFile_validFile_dataSaved() throws Exception { TEMP_FILE.createNewFile(); - XmlSerializableAddressBook dataToWrite = new XmlSerializableAddressBook(new AddressBook()); + XmlSerializableSavvyTasker dataToWrite = new XmlSerializableSavvyTasker(new SavvyTasker()); XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); - XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableAddressBook.class); - assertEquals((new AddressBook(dataToWrite)).toString(),(new AddressBook(dataFromFile)).toString()); + XmlSerializableSavvyTasker dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableSavvyTasker.class); + assertEquals((new SavvyTasker(dataToWrite)).toString(),(new SavvyTasker(dataFromFile)).toString()); //TODO: use equality instead of string comparisons - AddressBookBuilder builder = new AddressBookBuilder(new AddressBook()); - dataToWrite = new XmlSerializableAddressBook(builder.withPerson(TestUtil.generateSamplePersonData().get(0)).withTag("Friends").build()); + SavvyTaskerBuilder builder = new SavvyTaskerBuilder(new SavvyTasker()); + dataToWrite = new XmlSerializableSavvyTasker(builder.withTask(TestUtil.generateSampleTaskData().get(0)).build()); XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); - dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableAddressBook.class); - assertEquals((new AddressBook(dataToWrite)).toString(),(new AddressBook(dataFromFile)).toString()); + dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableSavvyTasker.class); + assertEquals((new SavvyTasker(dataToWrite)).toString(),(new SavvyTasker(dataFromFile)).toString()); } } diff --git a/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java b/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java new file mode 100644 index 000000000000..a5682f25e361 --- /dev/null +++ b/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java @@ -0,0 +1,464 @@ +package seedu.savvytasker.logic; + +import com.google.common.eventbus.Subscribe; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; +import seedu.savvytasker.commons.events.ui.ShowHelpRequestEvent; +import seedu.savvytasker.logic.Logic; +import seedu.savvytasker.logic.LogicManager; +import seedu.savvytasker.logic.commands.*; +import seedu.savvytasker.logic.parser.IndexParser; +import seedu.savvytasker.model.Model; +import seedu.savvytasker.model.ModelManager; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.storage.StorageManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static seedu.savvytasker.commons.core.Messages.*; + +public class LogicManagerTest { + + /** + * See https://github.com/junit-team/junit4/wiki/rules#temporaryfolder-rule + */ + @Rule + public TemporaryFolder saveFolder = new TemporaryFolder(); + + private Model model; + private Logic logic; + + //These are for checking the correctness of the events raised + private ReadOnlySavvyTasker latestSavedSavvyTasker; + private boolean helpShown; + //private int targetedJumpIndex; + + @Subscribe + private void handleLocalModelChangedEvent(SavvyTaskerChangedEvent stce) { + latestSavedSavvyTasker = new SavvyTasker(stce.data); + } + + @Subscribe + private void handleShowHelpRequestEvent(ShowHelpRequestEvent she) { + helpShown = true; + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent je) { + //targetedJumpIndex = je.targetIndex; + } + + @Before + public void setup() { + model = new ModelManager(); + String tempAddressBookFile = saveFolder.getRoot().getPath() + "TempAddressBook.xml"; + String tempPreferencesFile = saveFolder.getRoot().getPath() + "TempPreferences.json"; + logic = new LogicManager(model, new StorageManager(tempAddressBookFile, tempPreferencesFile)); + EventsCenter.getInstance().registerHandler(this); + + latestSavedSavvyTasker = new SavvyTasker(model.getSavvyTasker()); // last saved assumed to be up to date before. + helpShown = false; + //targetedJumpIndex = -1; // non yet + } + + @After + public void teardown() { + EventsCenter.clearSubscribers(); + } + + @Test + public void execute_invalid() throws Exception { + String invalidCommand = " "; + assertCommandBehavior(invalidCommand, + "Input: \n" + + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + /** + * Executes the command and confirms that the result message is correct. + * Both the 'address book' and the 'last shown list' are expected to be empty. + * @see #assertCommandBehavior(String, String, ReadOnlyAddressBook, List) + */ + private void assertCommandBehavior(String inputCommand, String expectedMessage) throws Exception { + assertCommandBehavior(inputCommand, expectedMessage, new SavvyTasker(), Collections.emptyList()); + } + + /** + * Executes the command and confirms that the result message is correct and + * also confirms that the following three parts of the LogicManager object's state are as expected:
+ * - the internal address book data are same as those in the {@code expectedAddressBook}
+ * - the backing list shown by UI matches the {@code shownList}
+ * - {@code expectedAddressBook} was saved to the storage file.
+ */ + private void assertCommandBehavior(String inputCommand, String expectedMessage, + ReadOnlySavvyTasker expectedSavvyTasker, + List expectedShownList) throws Exception { + + //Execute the command + CommandResult result = logic.execute(inputCommand); + + //Confirm the ui display elements should contain the right data + assertEquals(expectedMessage, result.feedbackToUser); + UnmodifiableObservableList tasks = model.getFilteredTaskList(); + assertEquals(expectedShownList, tasks); + + //Confirm the state of data (saved and in-memory) is as expected + assertEquals(expectedSavvyTasker, model.getSavvyTasker()); + assertEquals(expectedSavvyTasker, latestSavedSavvyTasker); + } + + + @Test + public void execute_unknownCommandWord() throws Exception { + String unknownCommand = "uicfhmowqewca"; + assertCommandBehavior(unknownCommand, "Input: uicfhmowqewca\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_help() throws Exception { + assertCommandBehavior("help", HelpCommand.SHOWING_HELP_MESSAGE); + assertTrue(helpShown); + } + + @Test + public void execute_exit() throws Exception { + assertCommandBehavior("exit", ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT); + } + + @Test + public void execute_clear() throws Exception { + TestDataHelper helper = new TestDataHelper(); + model.addTask(helper.generateTask(1)); + model.addTask(helper.generateTask(2)); + model.addTask(helper.generateTask(3)); + + assertCommandBehavior("clear", ClearCommand.MESSAGE_SUCCESS, new SavvyTasker(), Collections.emptyList()); + } + + + /* NOT ENFORCED, NOT RUNNING TEST CASE + @Test + public void execute_add_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); + assertCommandBehavior( + "add wrong args wrong args", expectedMessage); + assertCommandBehavior( + "add Valid Name 12345 e/valid@email.butNoPhonePrefix a/valid, address", expectedMessage); + assertCommandBehavior( + "add Valid Name p/12345 valid@email.butNoPrefix a/valid, address", expectedMessage); + assertCommandBehavior( + "add Valid Name p/12345 e/valid@email.butNoAddressPrefix valid, address", expectedMessage); + } + */ + + @Test + public void execute_add_successful() throws Exception { + // setup expectations + TestDataHelper helper = new TestDataHelper(); + Task toBeAdded = helper.demo(); + SavvyTasker expectedST = new SavvyTasker(); + expectedST.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded), + expectedST, + expectedST.getReadOnlyListOfTasks()); + + } + + + /** + * Confirms the 'invalid argument index number behaviour' for the given command + * targeting a single person in the shown list, using visible index. + * @param commandWord to test assuming it targets a single person in the last shown list based on visible index. + */ + private void assertIncorrectIndexFormatBehaviorForCommand(String commandWord, String expectedMessage) throws Exception { + assertCommandBehavior(commandWord , expectedMessage); //index missing + + // the following commands outputs a different expected message dealing with + // invalid indices. + String newExpectedMessage = "Input: delete -1\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; + assertCommandBehavior(commandWord + " -1", newExpectedMessage); //index should be unsigned + newExpectedMessage = "Input: delete 0\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; + assertCommandBehavior(commandWord + " 0", newExpectedMessage); //index cannot be 0 + newExpectedMessage = "Input: delete not_a_number\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; + assertCommandBehavior(commandWord + " not_a_number", newExpectedMessage); + } + + /** + * Confirms the 'invalid argument index number behaviour' for the given command + * targeting a single person in the shown list, using visible index. + * @param commandWord to test assuming it targets a single person in the last shown list based on visible index. + */ + private void assertIndexNotFoundBehaviorForCommand(String commandWord) throws Exception { + String expectedMessage = MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + TestDataHelper helper = new TestDataHelper(); + List taskList = helper.generateTaskList(2); + + // set AB state to 2 persons + model.resetData(new SavvyTasker()); + for (Task t : taskList) { + model.addTask(t); + } + + assertCommandBehavior(commandWord + " 3", expectedMessage, model.getSavvyTasker(), taskList); + } + + /* NOT IMPLEMENTED, NOT RUNNING TEST CASE + @Test + public void execute_selectInvalidArgsFormat_errorMessageShown() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE); + assertIncorrectIndexFormatBehaviorForCommand("select", expectedMessage); + } + + @Test + public void execute_selectIndexNotFound_errorMessageShown() throws Exception { + assertIndexNotFoundBehaviorForCommand("select"); + } + + @Test + public void execute_select_jumpsToCorrectPerson() throws Exception { + TestDataHelper helper = new TestDataHelper(); + List threeTasks = helper.generateTaskList(3); + + SavvyTasker expectedST = helper.generateSavvyTasker(threeTasks); + helper.addToModel(model, threeTasks); + + assertCommandBehavior("select 2", + String.format(SelectCommand.MESSAGE_SELECT_PERSON_SUCCESS, 2), + expectedST, + expectedST.getReadOnlyListOfTasks()); + assertEquals(1, targetedJumpIndex); + assertEquals(model.getFilteredTaskList().get(1), threeTasks.get(1)); + } + */ + + + @Test + public void execute_deleteInvalidArgsFormat_errorMessageShown() throws Exception { + String expectedMessage = "Input: delete\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT); + assertIncorrectIndexFormatBehaviorForCommand("delete", expectedMessage); + } + + @Test + public void execute_deleteIndexNotFound_errorMessageShown() throws Exception { + assertIndexNotFoundBehaviorForCommand("delete"); + } + + @Test + public void execute_delete_removesCorrectTask() throws Exception { + TestDataHelper helper = new TestDataHelper(); + List threeTasks = helper.generateTaskList(3); + + SavvyTasker expectedST = helper.generateSavvyTasker(threeTasks); + expectedST.removeTask(threeTasks.get(1)); + helper.addToModel(model, threeTasks); + + assertCommandBehavior("delete 2", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, threeTasks.get(1)), + expectedST, + expectedST.getReadOnlyListOfTasks()); + } + + + @Test + public void execute_find_invalidArgsFormat() throws Exception { + String expectedMessage = "Input: find\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.COMMAND_FORMAT); + assertCommandBehavior("find ", expectedMessage); + } + + @Test + public void execute_find_onlyMatchesFullWordsInNames() throws Exception { + TestDataHelper helper = new TestDataHelper(); + Task pTarget1 = helper.generateTaskWithName("bla bla KEY bla"); + pTarget1.setId(0); + Task pTarget2 = helper.generateTaskWithName("bla KEY bla bceofeia"); + pTarget2.setId(1); + Task p1 = helper.generateTaskWithName("KE Y"); + p1.setId(2); + Task p2 = helper.generateTaskWithName("KEYKEYKEY sduauo"); + p2.setId(3); + + List fourTasks = helper.generateTaskList(p1, pTarget1, p2, pTarget2); + SavvyTasker expectedST = helper.generateSavvyTasker(fourTasks); + List expectedList = helper.generateTaskList(pTarget1, pTarget2); + helper.addToModel(model, fourTasks); + + assertCommandBehavior("find t/full KEY", + Command.getMessageForTaskListShownSummary(expectedList.size()), + expectedST, + expectedList); + } + + @Test + public void execute_find_isNotCaseSensitive() throws Exception { + TestDataHelper helper = new TestDataHelper(); + Task p1 = helper.generateTaskWithName("bla bla KEY bla"); + p1.setId(0); + Task p2 = helper.generateTaskWithName("bla KEY bla bceofeia"); + p2.setId(1); + Task p3 = helper.generateTaskWithName("key key"); + p3.setId(2); + Task p4 = helper.generateTaskWithName("KEy sduauo"); + p4.setId(3); + + List fourTasks = helper.generateTaskList(p3, p1, p4, p2); + SavvyTasker expectedST = helper.generateSavvyTasker(fourTasks); + List expectedList = fourTasks; + helper.addToModel(model, fourTasks); + + assertCommandBehavior("find KEY", + Command.getMessageForTaskListShownSummary(expectedList.size()), + expectedST, + expectedList); + } + + @Test + public void execute_find_matchesIfAnyKeywordPresent() throws Exception { + TestDataHelper helper = new TestDataHelper(); + Task pTarget1 = helper.generateTaskWithName("bla bla KEY bla"); + pTarget1.setId(0); + Task pTarget2 = helper.generateTaskWithName("bla rAnDoM bla bceofeia"); + pTarget2.setId(1); + Task pTarget3 = helper.generateTaskWithName("key key"); + pTarget3.setId(2); + Task p1 = helper.generateTaskWithName("sduauo"); + p1.setId(3); + + List fourTasks = helper.generateTaskList(pTarget1, p1, pTarget2, pTarget3); + SavvyTasker expectedST = helper.generateSavvyTasker(fourTasks); + List expectedList = helper.generateTaskList(pTarget1, pTarget2, pTarget3); + helper.addToModel(model, fourTasks); + + assertCommandBehavior("find key rAnDoM", + Command.getMessageForTaskListShownSummary(expectedList.size()), + expectedST, + expectedList); + } + + + /** + * A utility class to generate test data. + */ + class TestDataHelper{ + + Task demo() { + return new Task("Demo task"); + } + + /** + * Generates a valid task using the given seed. + * Running this function with the same parameter values guarantees the returned person will have the same state. + * Each unique seed will generate a unique Task object. + * + * @param seed used to generate the person data field values + */ + Task generateTask(int seed) { + Task t = new Task("Task " + seed); + t.setId(seed); + return t; + } + + /** Generates the correct add command based on the person given */ + String generateAddCommand(Task t) { + StringBuffer cmd = new StringBuffer(); + + cmd.append("add "); + cmd.append(t.getTaskName().toString()); + + return cmd.toString(); + } + + /** + * Generates an AddressBook with auto-generated persons. + */ + SavvyTasker generateSavvyTasker(int numGenerated) throws Exception{ + SavvyTasker savvyTasker = new SavvyTasker(); + addToSavvyTasker(savvyTasker, numGenerated); + return savvyTasker; + } + + /** + * Generates an AddressBook based on the list of Persons given. + */ + SavvyTasker generateSavvyTasker(List tasks) throws Exception{ + SavvyTasker savvyTasker = new SavvyTasker(); + addToSavvyTasker(savvyTasker, tasks); + return savvyTasker; + } + + /** + * Adds auto-generated Person objects to the given AddressBook + * @param addressBook The AddressBook to which the Persons will be added + */ + void addToSavvyTasker(SavvyTasker savvyTasker, int numGenerated) throws Exception{ + addToSavvyTasker(savvyTasker, generateTaskList(numGenerated)); + } + + /** + * Adds the given list of Persons to the given AddressBook + */ + void addToSavvyTasker(SavvyTasker savvyTasker, List tasksToAdd) throws Exception{ + for(Task t : tasksToAdd){ + savvyTasker.addTask(t); + } + } + + /** + * Adds auto-generated Person objects to the given model + * @param model The model to which the Persons will be added + */ + void addToModel(Model model, int numGenerated) throws Exception{ + addToModel(model, generateTaskList(numGenerated)); + } + + /** + * Adds the given list of Persons to the given model + */ + void addToModel(Model model, List tasksToAdd) throws Exception{ + for(Task t: tasksToAdd){ + model.addTask(t); + } + } + + /** + * Generates a list of Task based on the flags. + */ + List generateTaskList(int numGenerated) throws Exception{ + List tasks = new ArrayList<>(); + for(int i = 0; i < numGenerated; i++){ + tasks.add(generateTask(i)); + } + return tasks; + } + + List generateTaskList(Task... tasks) { + return Arrays.asList(tasks); + } + + /** + * Generates a Task object with given name. Other fields will have some dummy values. + */ + Task generateTaskWithName(String taskName) throws Exception { + return new Task(taskName); + } + } +} diff --git a/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java b/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java new file mode 100644 index 000000000000..a1856f5245ba --- /dev/null +++ b/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java @@ -0,0 +1,518 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.savvytasker.logic.commands.AddCommand; +import seedu.savvytasker.logic.commands.Command; +import seedu.savvytasker.logic.commands.IncorrectCommand; +import seedu.savvytasker.model.alias.AliasSymbol; + +public class ParserTest { + private AddCommandParser addParser; + private DeleteCommandParser deleteParser; + private ModifyCommandParser modifyParser; + private ClearCommandParser clearParser; + private ListCommandParser listParser; + private FindCommandParser findParser; + private HelpCommandParser helpParser; + private ExitCommandParser exitParser; + private MarkCommandParser markParser; + private UnmarkCommandParser unmarkParser; + private UndoCommandParser undoParser; + private RedoCommandParser redoParser; + private AliasCommandParser aliasParser; + private UnaliasCommandParser unaliasParser; + private StorageCommandParser storageParser; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setup() { + addParser = new AddCommandParser(); + deleteParser = new DeleteCommandParser(); + modifyParser = new ModifyCommandParser(); + clearParser = new ClearCommandParser(); + listParser = new ListCommandParser(); + findParser = new FindCommandParser(); + helpParser = new HelpCommandParser(); + exitParser = new ExitCommandParser(); + markParser = new MarkCommandParser(); + unmarkParser = new UnmarkCommandParser(); + undoParser = new UndoCommandParser(); + redoParser = new RedoCommandParser(); + aliasParser = new AliasCommandParser(); + unaliasParser = new UnaliasCommandParser(); + storageParser = new StorageCommandParser(); + } + + @Test + public void parseAdd_reorder() throws ParseException { + assertNotNull(addParser.parse("add task l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); + } + + @Test + public void parseAdd_multipleSpaces() throws ParseException { + assertNotNull(addParser.parse("add Multiple Spaces s/ 2pm")); + } + + @Test + public void parseAdd_sameOptionMultipleTimes_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task s/ tomorrow 3pm s/ tomorrow 10pm"); + } + + @Test + public void parseAdd_missingTaskName_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add s/ tomorrow 3pm"); + } + + @Test + public void parseAdd_arbitrarySlash_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task s/ tomorrow 2pm/3pm e/ sunday"); + } + + @Test + public void parseAdd_fullValid() throws ParseException { + assertNotNull(addParser.parse("add task s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); + } + + @Test + public void parseAdd_invalidRecurrenceType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task r/ Error "); + } + + @Test + public void parseAdd_invalidPriorityLevel_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + addParser.parse("add task p/ Error "); + } + + //================================================================================== + + @Test + public void parseDelete_noIndexSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + deleteParser.parse("delete"); + } + + @Test + public void parseDelete_oneIndex() throws ParseException { + assertNotNull(deleteParser.parse("delete 1")); + } + + @Test + public void parseDelete_multipleIndices() throws ParseException { + assertNotNull(deleteParser.parse("delete 1 2 3")); + } + + @Test + public void parseDelete_multipleSpacesIndices() throws ParseException { + assertNotNull(deleteParser.parse("delete 1 2 3")); + } + + @Test + public void parseDelete_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + deleteParser.parse("delete -1"); + } + + @Test + public void parseDelete_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + deleteParser.parse("delete 0"); + } + + //================================================================================== + + @Test + public void parseModify_noIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify t/ newtask"); + } + + @Test + public void parseModify_multipleIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify 1 2 3 t/ newtask"); + } + + @Test + public void parseModify_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify -1 t/ newtask"); + } + + @Test + public void parseModify_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + modifyParser.parse("modify 0 t/ newtask"); + } + + @Test + public void parseModify_onlySpecifyIndex() throws ParseException { + assertNotNull(modifyParser.parse("modify 1")); + } + + @Test + public void parseModify_fullValid() throws ParseException { + assertNotNull(modifyParser.parse("modify 3 t/ newtask s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); + } + + @Test + public void parseModify_reorder() throws ParseException { + assertNotNull(modifyParser.parse("modify 1 l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); + } + + @Test + public void parseModify_multipleSpaces() throws ParseException { + assertNotNull(modifyParser.parse("modify 1 t/ Multiple Spaces s/ 2pm")); + } + + //================================================================================== + + @Test + public void parseClear_spaces() throws ParseException { + assertNotNull(clearParser.parse("clear ")); + } + + @Test + public void parseClear_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + clearParser.parse("clear 1"); + } + + @Test + public void parseClear_valid() throws ParseException { + assertNotNull(clearParser.parse("clear")); + } + + //================================================================================== + + @Test + public void parseList_noParameters() throws ParseException { + assertNotNull(listParser.parse("list")); + } + + @Test + public void parseList_noParametersSpaces() throws ParseException { + assertNotNull(listParser.parse("list ")); + } + + @Test + public void parse_list_valid() throws ParseException { + assertNotNull(listParser.parse("list Priority Level ")); + } + + @Test + public void parseList_invalidType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + listParser.parse("list Error "); + } + + //================================================================================== + + @Test + public void parseFind_noKeywords_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find"); + } + + @Test + public void parseFind_noKeywordsSpaces_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find "); + } + + @Test + public void parseFind_noKeywordsButWithType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find t/ Exact "); + } + + @Test + public void parseFind_invalidType_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + findParser.parse("find t/ Error some words"); + } + + @Test + public void parseFind_validAfter() throws ParseException { + assertNotNull(findParser.parse("find t/ Exact this word ")); + } + + @Test + public void parseFind_validBefore() throws ParseException { + assertNotNull(findParser.parse("find some words t/ Partial ")); + } + + @Test + public void parseFind_validBeforeAndAfter() throws ParseException { + assertNotNull(findParser.parse("find some words t/ Full some words after ")); + } + + //================================================================================== + + @Test + public void parseHelp_spaces() throws ParseException { + assertNotNull(helpParser.parse("help ")); + } + + @Test + public void parseHelp_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("help 1"); + } + + @Test + public void parseHelp_valid() throws ParseException { + assertNotNull(helpParser.parse("help")); + } + + //================================================================================== + + @Test + public void parseExit_spaces() throws ParseException { + assertNotNull(exitParser.parse("exit ")); + } + + @Test + public void parseExit_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("exit 1"); + } + + @Test + public void parseExit_valid() throws ParseException { + assertNotNull(exitParser.parse("exit")); + } + + //================================================================================== + + @Test + public void parseMark_noIndexSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + markParser.parse("mark"); + } + + @Test + public void parseMark_oneIndex() throws ParseException { + assertNotNull(markParser.parse("mark 1")); + } + + @Test + public void parseMark_multipleIndices() throws ParseException { + assertNotNull(markParser.parse("mark 1 2 3")); + } + + @Test + public void parseMark_multipleSpacesIndices() throws ParseException { + assertNotNull(markParser.parse("mark 1 2 3")); + } + + @Test + public void parseMark_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + markParser.parse("mark -1"); + } + + @Test + public void parseMark_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + markParser.parse("mark 0"); + } + + //================================================================================== + + @Test + public void parseUnmark_noIndexSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unmarkParser.parse("unmark"); + } + + @Test + public void parseUnmark_oneIndex() throws ParseException { + assertNotNull(unmarkParser.parse("unmark 1")); + } + + @Test + public void parseUnmark_multipleIndices() throws ParseException { + assertNotNull(unmarkParser.parse("unmark 1 2 3")); + } + + @Test + public void parseUnmark_multipleSpacesIndices() throws ParseException { + assertNotNull(unmarkParser.parse("unmark 1 2 3")); + } + + @Test + public void parseUnmark_negativeIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unmarkParser.parse("unmark -1"); + } + + @Test + public void parseUnmark_zeroIndex_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unmarkParser.parse("unmark 0"); + } + + //================================================================================== + + @Test + public void parseUndo_spaces() throws ParseException { + assertNotNull(undoParser.parse("undo ")); + } + + @Test + public void parseUndo_invalid() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("undo 1"); + } + + @Test + public void parseUndo_valid() throws ParseException { + assertNotNull(undoParser.parse("undo")); + } + + //================================================================================== + + @Test + public void parseRedo_spaces() throws ParseException { + assertNotNull(redoParser.parse("redo ")); + } + + @Test + public void parseRedo_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + helpParser.parse("redo 1"); + } + + @Test + public void parseRedo_valid() throws ParseException { + assertNotNull(redoParser.parse("redo")); + } + + //================================================================================== + + @Test + public void parseAlias_keywordUnspecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias r/ a string of things"); + } + + @Test + public void parseAlias_textUnspecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ xyz"); + } + + @Test + public void parseAlias_noSwitchesSpecified_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias power overwhelming"); + } + + @Test + public void parseAlias_keywordNotSingleWord_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ not a single word r/ project management"); + } + + @Test + public void parseAlias_keywordEmpty_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ r/ project management"); + } + + @Test + public void parseAlias_textEmpty_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + aliasParser.parse("alias k/ pjm r/ "); + } + + @Test + public void parseAlias_fullValid() throws ParseException { + assertNotNull(aliasParser.parse("alias k/ pjm r/ project management ")); + } + + //================================================================================== + + @Test + public void parseUnalias_emptyKeyword_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + unaliasParser.parse("unalias "); + } + + @Test + public void parseUnalias_valid() throws ParseException { + assertNotNull(unaliasParser.parse("unalias something ")); + } + + //================================================================================== + + @Test + public void parseStorage_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + storageParser.parse("storage "); + } + + @Test + public void parseStorage_valid() throws ParseException { + assertNotNull(storageParser.parse("storage C:/Users/Brown/Desktop/file.xml ")); + } + + //================================================================================== + @Test + public void masterParser_subparserParsing_returnParsedCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + assertTrue(parser.parse(" add A New Task s/ tomorrow e/ the day after tomorrow, l/ SR10 ") instanceof AddCommand); + } + + @Test + public void masterParser_subparserRemoved_returnIncorrectCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + parser.unregisterCommandParser("add"); + assertTrue(parser.parse(" add A New Task s/ tomorrow e/ the day after tomorrow, l/ SR10 ") instanceof IncorrectCommand); + } + + @Test + public void masterParser_alias_returnParsedCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); + parser.addAliasSymbol(new AliasSymbol("pqr", "s/ tomorrow e/ 30 september 3pm")); + assertTrue(parser.parse("xyz pqr") instanceof AddCommand); + } + + @Test + public void masterParser_invalidAlias_returnFalse() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + assertFalse(parser.addAliasSymbol(new AliasSymbol("add", "add A New Task"))); + } + + @Test + public void masterParser_removedAlias_returnIncorrectCommand() throws ParseException { + MasterParser parser = new MasterParser(); + parser.registerCommandParser(new AddCommandParser()); + parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); + parser.removeAliasSymbol("xyz"); + assertTrue(parser.parse("xyz pqr") instanceof IncorrectCommand); + } +} diff --git a/src/test/java/seedu/address/model/UnmodifiableObservableListTest.java b/src/test/java/seedu/savvytasker/model/UnmodifiableObservableListTest.java similarity index 93% rename from src/test/java/seedu/address/model/UnmodifiableObservableListTest.java rename to src/test/java/seedu/savvytasker/model/UnmodifiableObservableListTest.java index 0334d7e42073..aaa1d5b4b7b6 100644 --- a/src/test/java/seedu/address/model/UnmodifiableObservableListTest.java +++ b/src/test/java/seedu/savvytasker/model/UnmodifiableObservableListTest.java @@ -1,16 +1,17 @@ -package seedu.address.model; +package seedu.savvytasker.model; import javafx.collections.FXCollections; +import seedu.savvytasker.commons.core.UnmodifiableObservableList; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import seedu.address.commons.core.UnmodifiableObservableList; import java.util.*; import static org.junit.Assert.assertSame; -import static seedu.address.testutil.TestUtil.assertThrows; +import static seedu.savvytasker.testutil.TestUtil.assertThrows; public class UnmodifiableObservableListTest { diff --git a/src/test/java/seedu/savvytasker/model/task/TaskListTest.java b/src/test/java/seedu/savvytasker/model/task/TaskListTest.java new file mode 100644 index 000000000000..eac412d95d72 --- /dev/null +++ b/src/test/java/seedu/savvytasker/model/task/TaskListTest.java @@ -0,0 +1,92 @@ +package seedu.savvytasker.model.task; + +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.savvytasker.model.task.TaskList; +import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +import static org.junit.Assert.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.junit.Rule; + + +//@@author A0139915W +public class TaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void taskList_addDuplicate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(DuplicateTaskException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + tasks.add(t); // passes + assertEquals(1, tasks.getInternalList().size()); + tasks.add(t); // fails + } + + @Test + public void taskList_addInvalidDate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.add(t); // fails, end date earlier than start date + } + + @Test + public void taskList_removeNonExistent() throws TaskNotFoundException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.remove(t); // fails + } + + @Test + public void taskList_replaceNonExistent() throws TaskNotFoundException, InvalidDateException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.replace(t, t); // fails + } + + @Test + public void taskList_replaceInvalidDate() throws TaskNotFoundException, InvalidDateException, DuplicateTaskException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("30/12/2016")); + t.setEndDateTime(getDate("31/12/2016")); + tasks.add(t); + assertEquals(1, tasks.getInternalList().size()); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.replace(t, t); // fails, end date earlier than start date + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +//@@author diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/savvytasker/storage/JsonUserPrefsStorageTest.java similarity index 94% rename from src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java rename to src/test/java/seedu/savvytasker/storage/JsonUserPrefsStorageTest.java index 4e87203611be..d25f26f037a6 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java +++ b/src/test/java/seedu/savvytasker/storage/JsonUserPrefsStorageTest.java @@ -1,13 +1,15 @@ -package seedu.address.storage; +package seedu.savvytasker.storage; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.UserPrefs; + +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.model.UserPrefs; +import seedu.savvytasker.storage.JsonUserPrefsStorage; import java.io.File; import java.io.IOException; diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/savvytasker/storage/StorageManagerTest.java similarity index 63% rename from src/test/java/seedu/address/storage/StorageManagerTest.java rename to src/test/java/seedu/savvytasker/storage/StorageManagerTest.java index 6780feab6afc..d6c69996208c 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/savvytasker/storage/StorageManagerTest.java @@ -1,17 +1,22 @@ -package seedu.address.storage; +package seedu.savvytasker.storage; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.testutil.TypicalTestPersons; -import seedu.address.testutil.EventsCollector; + +import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.UserPrefs; +import seedu.savvytasker.storage.JsonUserPrefsStorage; +import seedu.savvytasker.storage.Storage; +import seedu.savvytasker.storage.StorageManager; +import seedu.savvytasker.storage.XmlSavvyTaskerStorage; +import seedu.savvytasker.testutil.EventsCollector; +import seedu.savvytasker.testutil.TypicalTestTasks; import java.io.IOException; @@ -54,25 +59,25 @@ public void prefsReadSave() throws Exception { } @Test - public void addressBookReadSave() throws Exception { - AddressBook original = new TypicalTestPersons().getTypicalAddressBook(); - storageManager.saveAddressBook(original); - ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); - assertEquals(original, new AddressBook(retrieved)); + public void savvyTaskerReadSave() throws Exception { + SavvyTasker original = new TypicalTestTasks().getTypicalSavvyTasker(); + storageManager.saveSavvyTasker(original); + ReadOnlySavvyTasker retrieved = storageManager.readSavvyTasker().get(); + assertEquals(original, new SavvyTasker(retrieved)); //More extensive testing of AddressBook saving/reading is done in XmlAddressBookStorageTest } @Test public void getAddressBookFilePath(){ - assertNotNull(storageManager.getAddressBookFilePath()); + assertNotNull(storageManager.getSavvyTaskerFilePath()); } @Test - public void handleAddressBookChangedEvent_exceptionThrown_eventRaised() throws IOException { + public void handleSavvyTaskerChangedEvent_exceptionThrown_eventRaised() throws IOException { //Create a StorageManager while injecting a stub that throws an exception when the save method is called Storage storage = new StorageManager(new XmlAddressBookStorageExceptionThrowingStub("dummy"), new JsonUserPrefsStorage("dummy")); EventsCollector eventCollector = new EventsCollector(); - storage.handleAddressBookChangedEvent(new AddressBookChangedEvent(new AddressBook())); + storage.handleSavvyTaskerChangedEvent(new SavvyTaskerChangedEvent(new SavvyTasker())); assertTrue(eventCollector.get(0) instanceof DataSavingExceptionEvent); } @@ -80,14 +85,14 @@ public void handleAddressBookChangedEvent_exceptionThrown_eventRaised() throws I /** * A Stub class to throw an exception when the save method is called */ - class XmlAddressBookStorageExceptionThrowingStub extends XmlAddressBookStorage{ + class XmlAddressBookStorageExceptionThrowingStub extends XmlSavvyTaskerStorage{ public XmlAddressBookStorageExceptionThrowingStub(String filePath) { super(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { + public void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker, String filePath) throws IOException { throw new IOException("dummy exception"); } } diff --git a/src/test/java/seedu/savvytasker/storage/XmlAddressBookStorageTest.java b/src/test/java/seedu/savvytasker/storage/XmlAddressBookStorageTest.java new file mode 100644 index 000000000000..11fb099cfba1 --- /dev/null +++ b/src/test/java/seedu/savvytasker/storage/XmlAddressBookStorageTest.java @@ -0,0 +1,107 @@ +package seedu.savvytasker.storage; + + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +import seedu.savvytasker.commons.exceptions.DataConversionException; +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.model.ReadOnlySavvyTasker; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.storage.XmlSavvyTaskerStorage; +import seedu.savvytasker.testutil.TypicalTestTasks; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class XmlAddressBookStorageTest { + private static String TEST_DATA_FOLDER = FileUtil.getPath("./src/test/data/XmlAddressBookStorageTest/"); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Test + public void readSavvyTasker_nullFilePath_assertionFailure() throws Exception { + thrown.expect(AssertionError.class); + readSavvyTasker(null); + } + + private java.util.Optional readSavvyTasker(String filePath) throws Exception { + return new XmlSavvyTaskerStorage(filePath).readSavvyTasker(addToTestDataPathIfNotNull(filePath)); + } + + private String addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { + return prefsFileInTestDataFolder != null + ? TEST_DATA_FOLDER + prefsFileInTestDataFolder + : null; + } + + @Test + public void read_missingFile_emptyResult() throws Exception { + assertFalse(readSavvyTasker("NonExistentFile.xml").isPresent()); + } + + @Test + public void read_notXmlFormat_exceptionThrown() throws Exception { + + thrown.expect(DataConversionException.class); + readSavvyTasker("NotXmlFormatAddressBook.xml"); + + /* IMPORTANT: Any code below an exception-throwing line (like the one above) will be ignored. + * That means you should not have more than one exception test in one method + */ + } + + @Test + public void readAndSaveAddressBook_allInOrder_success() throws Exception { + String filePath = testFolder.getRoot().getPath() + "TempAddressBook.xml"; + TypicalTestTasks td = new TypicalTestTasks(); + SavvyTasker original = td.getTypicalSavvyTasker(); + XmlSavvyTaskerStorage xmlSavvyTaskerStorage = new XmlSavvyTaskerStorage(filePath); + + //Save in new file and read back + xmlSavvyTaskerStorage.saveSavvyTasker(original, filePath); + ReadOnlySavvyTasker readBack = xmlSavvyTaskerStorage.readSavvyTasker(filePath).get(); + assertEquals(original, new SavvyTasker(readBack)); + + //Modify data, overwrite exiting file, and read back + original.addTask(new Task(td.happy)); + original.removeTask(new Task(td.happy)); + xmlSavvyTaskerStorage.saveSavvyTasker(original, filePath); + readBack = xmlSavvyTaskerStorage.readSavvyTasker(filePath).get(); + assertEquals(original, new SavvyTasker(readBack)); + + //Save and read without specifying file path + original.addTask(new Task(td.happy)); + xmlSavvyTaskerStorage.saveSavvyTasker(original); //file path not specified + readBack = xmlSavvyTaskerStorage.readSavvyTasker().get(); //file path not specified + assertEquals(original, new SavvyTasker(readBack)); + + } + + @Test + public void saveSavvyTasker_nullSavvyTasker_assertionFailure() throws IOException { + thrown.expect(AssertionError.class); + saveSavvyTasker(null, "SomeFile.xml"); + } + + private void saveSavvyTasker(ReadOnlySavvyTasker addressBook, String filePath) throws IOException { + new XmlSavvyTaskerStorage(filePath).saveSavvyTasker(addressBook, addToTestDataPathIfNotNull(filePath)); + } + + @Test + public void saveSavvyTasker_nullFilePath_assertionFailure() throws IOException { + thrown.expect(AssertionError.class); + saveSavvyTasker(new SavvyTasker(), null); + } + + +} diff --git a/src/test/java/seedu/address/testutil/EventsCollector.java b/src/test/java/seedu/savvytasker/testutil/EventsCollector.java similarity index 84% rename from src/test/java/seedu/address/testutil/EventsCollector.java rename to src/test/java/seedu/savvytasker/testutil/EventsCollector.java index c44d6ca6f95a..9a2b13950ce4 100644 --- a/src/test/java/seedu/address/testutil/EventsCollector.java +++ b/src/test/java/seedu/savvytasker/testutil/EventsCollector.java @@ -1,8 +1,9 @@ -package seedu.address.testutil; +package seedu.savvytasker.testutil; import com.google.common.eventbus.Subscribe; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.BaseEvent; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.BaseEvent; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/seedu/savvytasker/testutil/SavvyTaskerBuilder.java b/src/test/java/seedu/savvytasker/testutil/SavvyTaskerBuilder.java new file mode 100644 index 000000000000..3e5851de46cc --- /dev/null +++ b/src/test/java/seedu/savvytasker/testutil/SavvyTaskerBuilder.java @@ -0,0 +1,31 @@ +package seedu.savvytasker.testutil; + +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; + +//@@author A0139915W +/** + * A utility class to help with building SavvyTasker objects. + * Example usage:
+ * {@code SavvyTasker st = new SavvyTaskerBuilder().withTask("Hello Task").build();} + */ +public class SavvyTaskerBuilder { + + private SavvyTasker savvyTasker; + + public SavvyTaskerBuilder(SavvyTasker savvyTasker){ + this.savvyTasker = savvyTasker; + } + + public SavvyTaskerBuilder withTask(Task task) throws DuplicateTaskException, InvalidDateException { + savvyTasker.addTask(task); + return this; + } + + public SavvyTasker build(){ + return savvyTasker; + } +} +//@@author diff --git a/src/test/java/seedu/address/testutil/SerializableTestClass.java b/src/test/java/seedu/savvytasker/testutil/SerializableTestClass.java similarity index 98% rename from src/test/java/seedu/address/testutil/SerializableTestClass.java rename to src/test/java/seedu/savvytasker/testutil/SerializableTestClass.java index ef58ef857179..7057cefc0aa6 100644 --- a/src/test/java/seedu/address/testutil/SerializableTestClass.java +++ b/src/test/java/seedu/savvytasker/testutil/SerializableTestClass.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package seedu.savvytasker.testutil; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/src/test/java/seedu/savvytasker/testutil/TaskBuilder.java b/src/test/java/seedu/savvytasker/testutil/TaskBuilder.java new file mode 100644 index 000000000000..600b424a41c8 --- /dev/null +++ b/src/test/java/seedu/savvytasker/testutil/TaskBuilder.java @@ -0,0 +1,81 @@ +package seedu.savvytasker.testutil; + +import java.util.Date; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.RecurrenceType; + +//@@author A0139915W +/** + * Helper to build Task objects + */ +public class TaskBuilder { + + private TestTask task; + + public TaskBuilder() { + this.task = new TestTask(); + } + + public TaskBuilder withId(int id) { + this.task.setId(id); + return this; + } + + public TaskBuilder withTaskName(String taskName) throws IllegalValueException { + this.task.setTaskName(taskName); + return this; + } + + public TaskBuilder withStartDateTime(Date startDateTime) { + this.task.setStartDateTime(startDateTime); + return this; + } + + public TaskBuilder withEndDateTime(Date endDateTime) { + this.task.setEndDateTime(endDateTime); + return this; + } + + public TaskBuilder withLocation(String location) { + this.task.setLocation(location); + return this; + } + + public TaskBuilder withPriority(PriorityLevel priority) { + this.task.setPriority(priority); + return this; + } + + public TaskBuilder withRecurringType(RecurrenceType recurringType) { + this.task.setRecurringType(recurringType); + return this; + } + + public TaskBuilder withNumberOfRecurrence(int numberOfRecurrence) { + this.task.setNumberOfRecurrence(numberOfRecurrence); + return this; + } + + public TaskBuilder withCategory(String category) { + this.task.setCategory(category); + return this; + } + + public TaskBuilder withDescription(String description) { + this.task.setDescription(description); + return this; + } + + public TaskBuilder withArchived(boolean isArchived) { + this.task.setArchived(isArchived); + return this; + } + + public TestTask build() { + return this.task; + } + +} +//@@author diff --git a/src/test/java/seedu/savvytasker/testutil/TestTask.java b/src/test/java/seedu/savvytasker/testutil/TestTask.java new file mode 100644 index 000000000000..40b75a26f5da --- /dev/null +++ b/src/test/java/seedu/savvytasker/testutil/TestTask.java @@ -0,0 +1,184 @@ +package seedu.savvytasker.testutil; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.RecurrenceType; + +//@@author A0139915W +/** + * A mutable task object. For testing only. + */ +public class TestTask implements ReadOnlyTask { + + private int id; + private int groupId; + private String taskName; + private Date startDateTime; + private Date endDateTime; + private String location; + private PriorityLevel priority; + private RecurrenceType recurringType; + private int numberOfRecurrence; + private String category; + private String description; + private boolean isArchived; + + public TestTask() { + // sets initial default values + this.priority = PriorityLevel.Medium; + this.recurringType = RecurrenceType.None; + this.numberOfRecurrence = 0; + } + + @Override + public int getId() { + return id; + } + + @Override + public int getGroupId() { + return groupId; + } + + @Override + public String getTaskName() { + return taskName; + } + + @Override + public Date getStartDateTime() { + return startDateTime; + } + + @Override + public Date getEndDateTime() { + return endDateTime; + } + + @Override + public String getLocation() { + return location; + } + + @Override + public PriorityLevel getPriority() { + return priority; + } + + @Override + public RecurrenceType getRecurringType() { + return recurringType; + } + + @Override + public int getNumberOfRecurrence() { + return numberOfRecurrence; + } + + @Override + public String getCategory() { + return category; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public boolean isMarked() { + return isArchived(); // all marked tasks are archived + } + + @Override + public boolean isArchived() { + return isArchived; + } + + public void setId(int id) { + this.id = id; + } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public void setStartDateTime(Date startDateTime) { + this.startDateTime = startDateTime; + } + + public void setEndDateTime(Date endDateTime) { + this.endDateTime = endDateTime; + } + + public void setLocation(String location) { + this.location = location; + } + + public void setPriority(PriorityLevel priority) { + this.priority = priority; + } + + public void setRecurringType(RecurrenceType recurringType) { + this.recurringType = recurringType; + } + + public void setNumberOfRecurrence(int numberOfRecurrence) { + this.numberOfRecurrence = numberOfRecurrence; + } + + public void setCategory(String category) { + this.category = category; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setArchived(boolean isArchived) { + this.isArchived = isArchived; + } + + @Override + public String toString() { + return getAsText(); + } + + public String getAddCommand() { + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HHmm"); + StringBuilder sb = new StringBuilder(); + sb.append("add " + this.getTaskName()); + if (startDateTime != null) { + sb.append(" s/ ").append(sdf.format(startDateTime)); + } + if (endDateTime != null) { + sb.append(" e/ ").append(sdf.format(endDateTime)); + } + if (location != null && !location.isEmpty()) { + sb.append(" l/ ").append(location); + } + if (priority != null && priority != PriorityLevel.Medium) { + // p/ defaults to medium, if set to medium, take as non-existent + sb.append(" p/ ").append(priority.toString()); + } + if (recurringType != null && recurringType != RecurrenceType.None) { + // r/ defaults to none, if set to none, take as non-existent + sb.append(" r/ ").append(recurringType.toString()); + } + if (category != null && !category.isEmpty()) { + sb.append(" c/ ").append(category); + } + if (description != null && !description.isEmpty()) { + sb.append(" d/ ").append(description); + } + return sb.toString(); + } +} +//@@author diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/savvytasker/testutil/TestUtil.java similarity index 58% rename from src/test/java/seedu/address/testutil/TestUtil.java rename to src/test/java/seedu/savvytasker/testutil/TestUtil.java index 17c92d66398a..3ced67abe8ab 100644 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ b/src/test/java/seedu/savvytasker/testutil/TestUtil.java @@ -1,7 +1,7 @@ -package seedu.address.testutil; +package seedu.savvytasker.testutil; import com.google.common.io.Files; -import guitests.guihandles.PersonCardHandle; +import guitests.guihandles.TaskCardHandle; import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.scene.Node; @@ -12,15 +12,16 @@ import junit.framework.AssertionFailedError; import org.loadui.testfx.GuiTest; import org.testfx.api.FxToolkit; -import seedu.address.TestApp; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.XmlUtil; -import seedu.address.model.AddressBook; -import seedu.address.model.person.*; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; -import seedu.address.storage.XmlSerializableAddressBook; + +import seedu.savvytasker.TestApp; +import seedu.savvytasker.commons.util.FileUtil; +import seedu.savvytasker.commons.util.XmlUtil; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.alias.AliasSymbolList; +import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList; +import seedu.savvytasker.storage.XmlSerializableSavvyTasker; import java.io.File; import java.io.IOException; @@ -37,7 +38,11 @@ * A utility class for test cases. */ public class TestUtil { - + /** + * Folder used for temp files created during testing. Ignored by Git. + */ + public static String SANDBOX_FOLDER = FileUtil.getPath("./src/test/data/sandbox/"); + public static final Task[] sampleTaskData = getSampleTaskData(); public static String LS = System.lineSeparator(); public static void assertThrows(Class expected, Runnable executable) { @@ -55,50 +60,20 @@ public static void assertThrows(Class expected, Runnable ex String.format("Expected %s to be thrown, but nothing was thrown.", expected.getName())); } - /** - * Folder used for temp files created during testing. Ignored by Git. - */ - public static String SANDBOX_FOLDER = FileUtil.getPath("./src/test/data/sandbox/"); + //@@author A0139915W - public static final Person[] samplePersonData = getSamplePersonData(); - - private static Person[] getSamplePersonData() { - try { - return new Person[]{ - new Person(new Name("Ali Muster"), new Phone("9482424"), new Email("hans@google.com"), new Address("4th street"), new UniqueTagList()), - new Person(new Name("Boris Mueller"), new Phone("87249245"), new Email("ruth@google.com"), new Address("81th street"), new UniqueTagList()), - new Person(new Name("Carl Kurz"), new Phone("95352563"), new Email("heinz@yahoo.com"), new Address("wall street"), new UniqueTagList()), - new Person(new Name("Daniel Meier"), new Phone("87652533"), new Email("cornelia@google.com"), new Address("10th street"), new UniqueTagList()), - new Person(new Name("Elle Meyer"), new Phone("9482224"), new Email("werner@gmail.com"), new Address("michegan ave"), new UniqueTagList()), - new Person(new Name("Fiona Kunz"), new Phone("9482427"), new Email("lydia@gmail.com"), new Address("little tokyo"), new UniqueTagList()), - new Person(new Name("George Best"), new Phone("9482442"), new Email("anna@google.com"), new Address("4th street"), new UniqueTagList()), - new Person(new Name("Hoon Meier"), new Phone("8482424"), new Email("stefan@mail.com"), new Address("little india"), new UniqueTagList()), - new Person(new Name("Ida Mueller"), new Phone("8482131"), new Email("hans@google.com"), new Address("chicago ave"), new UniqueTagList()) - }; - } catch (IllegalValueException e) { - assert false; - //not possible - return null; - } + private static Task[] getSampleTaskData() { + return new Task[]{ + new Task("Sample Task Leh"), + new Task("Sample Task La"), + new Task("Sample Task Lo"), + new Task("Sample Task Ah") + }; } - - public static final Tag[] sampleTagData = getSampleTagData(); - - private static Tag[] getSampleTagData() { - try { - return new Tag[]{ - new Tag("relatives"), - new Tag("friends") - }; - } catch (IllegalValueException e) { - assert false; - return null; - //not possible - } - } - - public static List generateSamplePersonData() { - return Arrays.asList(samplePersonData); + //@@author + + public static List generateSampleTaskData() { + return Arrays.asList(sampleTaskData); } /** @@ -117,7 +92,7 @@ public static String getFilePathInSandboxFolder(String fileName) { } public static void createDataFileWithSampleData(String filePath) { - createDataFileWithData(generateSampleStorageAddressBook(), filePath); + createDataFileWithData(generateSampleStorageSavvyTasker(), filePath); } public static void createDataFileWithData(T data, String filePath) { @@ -134,12 +109,12 @@ public static void main(String... s) { createDataFileWithSampleData(TestApp.SAVE_LOCATION_FOR_TESTING); } - public static AddressBook generateEmptyAddressBook() { - return new AddressBook(new UniquePersonList(), new UniqueTagList()); + public static SavvyTasker generateEmptySavvyTasker() { + return new SavvyTasker(new TaskList(), new AliasSymbolList()); } - public static XmlSerializableAddressBook generateSampleStorageAddressBook() { - return new XmlSerializableAddressBook(generateEmptyAddressBook()); + public static XmlSerializableSavvyTasker generateSampleStorageSavvyTasker() { + return new XmlSerializableSavvyTasker(generateEmptySavvyTasker()); } /** @@ -211,6 +186,7 @@ public static void tearDownRuntime() throws Exception { * * Caveat: only find method declared in the current Class, not inherited from supertypes */ + @SuppressWarnings({ "unchecked", "rawtypes" }) public static Method getPrivateMethod(Class objectClass, String methodName) throws NoSuchMethodException { Method method = objectClass.getDeclaredMethod(methodName); method.setAccessible(true); @@ -272,51 +248,58 @@ public static Object getLastElement(List list) { return list.get(list.size() - 1); } + //@@author A0139915W /** - * Removes a subset from the list of persons. - * @param persons The list of persons - * @param personsToRemove The subset of persons. - * @return The modified persons after removal of the subset from persons. + * Removes a subset from the list of tasks. + * @param tasks The list of tasks + * @param tasksToRemove The subset of tasjs. + * @return The modified tasks after removal of the subset from tasks. */ - public static TestPerson[] removePersonsFromList(final TestPerson[] persons, TestPerson... personsToRemove) { - List listOfPersons = asList(persons); - listOfPersons.removeAll(asList(personsToRemove)); - return listOfPersons.toArray(new TestPerson[listOfPersons.size()]); + public static TestTask[] removeTasksFromList(final TestTask[] tasks, TestTask... tasksToRemove) { + List listOfTasks = asList(tasks); + listOfTasks.removeAll(asList(tasksToRemove)); + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); } /** - * Returns a copy of the list with the person at specified index removed. + * Returns a copy of the list with the task at specified index removed. * @param list original list to copy from * @param targetIndexInOneIndexedFormat e.g. if the first element to be removed, 1 should be given as index. */ - public static TestPerson[] removePersonFromList(final TestPerson[] list, int targetIndexInOneIndexedFormat) { - return removePersonsFromList(list, list[targetIndexInOneIndexedFormat-1]); + public static TestTask[] removeTaskFromList(final TestTask[] list, int targetIndexInOneIndexedFormat) { + return removeTasksFromList(list, list[targetIndexInOneIndexedFormat-1]); } /** - * Replaces persons[i] with a person. - * @param persons The array of persons. - * @param person The replacement person - * @param index The index of the person to be replaced. + * Replaces tasks[i] with a task. + * @param tasks The array of tasks. + * @param task The replacement task + * @param index The index of the task to be replaced. * @return */ - public static TestPerson[] replacePersonFromList(TestPerson[] persons, TestPerson person, int index) { - persons[index] = person; - return persons; + public static TestTask[] replaceTaskFromList(TestTask[] tasks, TestTask task) { + for (int i = 0; i < tasks.length; ++i) { + if (tasks[i].getId() == task.getId()) { + tasks[i] = task; + break; + } + } + return tasks; } /** - * Appends persons to the array of persons. - * @param persons A array of persons. - * @param personsToAdd The persons that are to be appended behind the original array. - * @return The modified array of persons. + * Appends tasks to the array of tasks. + * @param tasks A array of tasks. + * @param tasksToAdd The tasks that are to be appended behind the original array. + * @return The modified array of tasks. */ - public static TestPerson[] addPersonsToList(final TestPerson[] persons, TestPerson... personsToAdd) { - List listOfPersons = asList(persons); - listOfPersons.addAll(asList(personsToAdd)); - return listOfPersons.toArray(new TestPerson[listOfPersons.size()]); + public static TestTask[] addTasksToList(final TestTask[] tasks, TestTask... tasksToAdd) { + List listOfTasks = asList(tasks); + listOfTasks.addAll(asList(tasksToAdd)); + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); } + //@@author private static List asList(T[] objs) { List list = new ArrayList<>(); @@ -326,29 +309,10 @@ private static List asList(T[] objs) { return list; } - public static boolean compareCardAndPerson(PersonCardHandle card, ReadOnlyPerson person) { - return card.isSamePerson(person); - } - - public static Tag[] getTagList(String tags) { - - if (tags.equals("")) { - return new Tag[]{}; - } - - final String[] split = tags.split(", "); - - final List collect = Arrays.asList(split).stream().map(e -> { - try { - return new Tag(e.replaceFirst("Tag: ", "")); - } catch (IllegalValueException e1) { - //not possible - assert false; - return null; - } - }).collect(Collectors.toList()); - - return collect.toArray(new Tag[split.length]); + //@@author A0139915W + public static boolean compareCardAndTask(TaskCardHandle card, ReadOnlyTask task) { + return card.isSameTask(task); } + //@@author } diff --git a/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java b/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java new file mode 100644 index 000000000000..bfff6d3faa63 --- /dev/null +++ b/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java @@ -0,0 +1,88 @@ +package seedu.savvytasker.testutil; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import seedu.savvytasker.commons.exceptions.IllegalValueException; +import seedu.savvytasker.model.SavvyTasker; +import seedu.savvytasker.model.task.PriorityLevel; +import seedu.savvytasker.model.task.Task; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; + +//@@author A0139915W +/** + * Test tasks used to test cases + */ +public class TypicalTestTasks { + + public TestTask highPriority, medPriority, lowPriority, furthestDue, + nearerDue, notSoNearerDue, earliestDue, longDue, happy, haloween, pjm, projectMeeting; + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + + public TypicalTestTasks() { + try { + highPriority = new TaskBuilder().withId(1).withTaskName("High Priority Task") + .withPriority(PriorityLevel.High).withCategory("priority").build(); + medPriority = new TaskBuilder().withId(2).withTaskName("Medium Priority Task") + .withPriority(PriorityLevel.Medium).withCategory("priority").build(); + lowPriority = new TaskBuilder().withId(3).withTaskName("Low Priority Task") + .withPriority(PriorityLevel.Low).withCategory("priority").build(); + furthestDue = new TaskBuilder().withId(4).withTaskName("Furthest Due Task") + .withEndDateTime(getDate("01/12/2016")).build(); + nearerDue = new TaskBuilder().withId(5).withTaskName("Nearer Due Task") + .withEndDateTime(getDate("01/11/2016")).build(); + notSoNearerDue = new TaskBuilder().withId(6).withTaskName("Not So Nearer Due Task") + .withEndDateTime(getDate("02/11/2016")).build(); + earliestDue = new TaskBuilder().withId(7).withTaskName("Earliest Due Task") + .withEndDateTime(getDate("01/10/2016")).build(); + longDue = new TaskBuilder().withId(8).withTaskName("Long Due Task") + .withEndDateTime(getDate("01/1/2016")).withArchived(true).build(); + + //Manually added + happy = new TaskBuilder().withId(9).withTaskName("Happy Task").build(); + haloween = new TaskBuilder().withId(10).withTaskName("Haloween Task").build(); + pjm = new TaskBuilder().withId(11).withTaskName("pjm").build(); + projectMeeting = new TaskBuilder().withId(12).withTaskName("Project Meeting").build(); + } catch (IllegalValueException e) { + e.printStackTrace(); + assert false : "not possible"; + } + } + + public static void loadSavvyTaskerWithSampleData(SavvyTasker st) { + TypicalTestTasks td = new TypicalTestTasks(); + try { + st.addTask(new Task(td.highPriority)); + st.addTask(new Task(td.medPriority)); + st.addTask(new Task(td.lowPriority)); + st.addTask(new Task(td.furthestDue)); + st.addTask(new Task(td.nearerDue)); + st.addTask(new Task(td.notSoNearerDue)); + st.addTask(new Task(td.earliestDue)); + st.addTask(new Task(td.longDue)); + } catch (InvalidDateException e) { + assert false : "not possible"; + } + } + + public TestTask[] getTypicalTasks() { + return new TestTask[]{ highPriority, medPriority, lowPriority, + furthestDue, nearerDue, notSoNearerDue, earliestDue }; + } + + public SavvyTasker getTypicalSavvyTasker(){ + SavvyTasker st = new SavvyTasker(); + loadSavvyTaskerWithSampleData(st); + return st; + } + + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +//@@author