diff --git a/.project b/.project index 1c9339c5f927..3e66785fd728 100644 --- a/.project +++ b/.project @@ -1,7 +1,7 @@ - addressbook-level4 - Project addressbook-level4 created by Buildship. + TARS + Task And Remember Stuff - Productivity tool created by F10-C1. 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/.travis.yml b/.travis.yml index a9d9e9b47d87..87d62f572586 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,6 @@ addons: apt: packages: - oracle-java8-installer + +notifications: + slack: cs2103aug2016-f10-c1:5ptG2TxXzNoNzKGzo3ymxl31 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..334bc8aed867 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,30 @@ -[![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-F10-C1/main.svg?branch=develop)](https://travis-ci.org/CS2103AUG2016-F10-C1/main.svg?branch=develop) +[![Coverage Status](https://coveralls.io/repos/github/CS2103AUG2016-F10-C1/main/badge.svg?branch=develop)](https://coveralls.io/github/CS2103AUG2016-F10-C1/main?branch=develop) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/45df166d305f42fc85fefe45651c568e)](https://www.codacy.com/app/weikangchia/main?utm_source=github.com&utm_medium=referral&utm_content=CS2103AUG2016-F10-C1/main&utm_campaign=Badge_Grade) +# TARS: Task And Remember Stuff +### A Productivity Tool by CS2103AUG2016-F10-C1 -# Address Book (Level 4) +
-
- -* This is a desktop Address Book 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. +* This is a desktop To-Do List application. It has a GUI but most of the user interactions happen using + a CLI (Command Line Interface). +* This application is a CS2103 project and is still undergoing development. +* Our latest release: [v0.5](https://github.com/CS2103AUG2016-F10-C1/main/releases/tag/v0.5) #### Site Map * [User Guide](docs/UserGuide.md) -* [Developer Guide](docs/DeveloperGuide.md) -* [Learning Outcomes](docs/LearningOutcomes.md) +* [Developer Guide](docs/DeveloperGuide.md) * [About Us](docs/AboutUs.md) * [Contact Us](docs/ContactUs.md) #### Acknowledgements -* Some parts of this sample application were inspired by the excellent +* This application was adapted from a CS2103 sample application, [AddressBook-Level 4](https://github.com/nus-cs2103-AY1617S1/addressbook-level4) designed and built by *Assoc. Prof Damith C. Rajapakse* and his teaching team. +* This application was designed and built under the guidance and advice of our module tutor, *[Candiie](https://github.com/Candiie)*. +* This application used [Natty](http://natty.joestelmach.com/) by Joestelmach for natural date parsing. +* Some parts of this application were inspired by the excellent [Java FX tutorial](http://code.makery.ch/library/javafx-8-tutorial/) by *Marco Jakob*. diff --git a/build.gradle b/build.gradle index 46b06c1e42ec..5b607cfd48c1 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ * For more details take a look at the Java Quickstart chapter in the Gradle * user guide available at http://gradle.org/docs/2.2.1/userguide/tutorial_java_projects.html */ - + plugins { id "com.github.kt3k.coveralls" version "2.4.0" id "com.github.johnrengelman.shadow" version '1.2.3' @@ -52,6 +52,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:0.12" testCompile "junit:junit:$junitVersion" testCompile "org.testfx:testfx-core:$testFxVersion" @@ -61,7 +62,7 @@ allprojects { } testCompile "org.testfx:openjfx-monocle:$monocleVersion" } - + sourceSets { main { java { @@ -72,12 +73,12 @@ allprojects { } } } - + shadowJar { - archiveName = "addressbook.jar" + archiveName = "tars.jar" manifest { - attributes "Main-Class": "seedu.address.MainApp" + attributes "Main-Class": "tars.MainApp" } destinationDir = file("${buildDir}/jar/") @@ -113,8 +114,8 @@ tasks.coveralls { onlyIf { System.env.'CI' } } -class AddressBookTest extends Test { - public AddressBookTest() { +class TarsTest extends Test { + public TarsTest() { forkEvery = 1 systemProperty 'testfx.setup.timeout', '60000' } @@ -128,7 +129,7 @@ class AddressBookTest extends Test { } } -task guiTests(type: AddressBookTest) { +task guiTests(type: TarsTest) { include 'guitests/**' jacoco { @@ -137,8 +138,8 @@ task guiTests(type: AddressBookTest) { } -task nonGuiTests(type: AddressBookTest) { - include 'seedu/address/**' +task nonGuiTests(type: TarsTest) { + include 'tars/**' jacoco { destinationFile = new File("${buildDir}/jacoco/test.exec") @@ -146,7 +147,7 @@ task nonGuiTests(type: AddressBookTest) { } // Test mode depends on whether headless task has been run -task allTests(type: AddressBookTest) { +task allTests(type: TarsTest) { jacoco { destinationFile = new File("${buildDir}/jacoco/test.exec") } diff --git a/collate.bat b/collate.bat new file mode 100644 index 000000000000..2464b304f5a9 --- /dev/null +++ b/collate.bat @@ -0,0 +1,7 @@ +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 + +pause \ No newline at end of file diff --git a/collate.sh b/collate.sh new file mode 100644 index 000000000000..1a7bf8ca2667 --- /dev/null +++ b/collate.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +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 + +exit 0 \ No newline at end of file diff --git a/collated/docs/A0121533W.md b/collated/docs/A0121533W.md new file mode 100644 index 000000000000..6fca691890b4 --- /dev/null +++ b/collated/docs/A0121533W.md @@ -0,0 +1,147 @@ +# A0121533W +###### \AboutUs.md +``` md +#### [Foo En Teng Joel](http://github.com/jaeoheeail)
+
+ +* Components in charge of: [UI](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#ui-component) +* Aspects/tools in charge of: SceneBuilder +* Features implemented: + * [Editing Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#editing-a-task--edit) + * [Marking Tasks Done & Undone](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#marking-tasks--mark) + * [Deleting Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#deleting-a-task--del) +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0121533W.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0121533W.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0121533W.md)] +* Other Major Contributions: + * Designed App Logo + * Designed App UI + + ----- +``` +###### \DeveloperGuide.md +``` md +## Appendix D : Glossary + +##### Command +> Reserved keywords for you to execute a command e.g. add, edit, del, do, ud. + +##### Event +> Has a start time and end time. + +##### DateTime +> Variable that has information on a particular date and time. + +##### Deadline +> Tasks that have to be done before a specified deadline. + +##### Floating Tasks +> Tasks without specific dateTimes. + +##### Index +> Positive number corresponding to the order at which the item is listed. + +##### Mainstream OS +> Windows, Linux, Unix, OS-X. + +##### Prefix +> Reserved keywords for commands e.g. /p /dt /t + +##### Reserved Task +> Tasks that have multiple DateTimes at which one can be confirmed later. + +##### Storage Directory +> Your system's file path (e.g. /data/tars.xml) at which data are stored. + +##### Tag +> Categorization of a task. + +##### Task +> Something that needs to be done (see Floating Task, Deadline, Event). + + + +``` +###### \DeveloperGuide.md +``` md +## Appendix E : Product Survey + +Product | Strength | Weaknesses +-------- | :-------- | :-------- +[Wunderlist](https://www.wunderlist.com/)|
  1. Cloud-based
    • Ability to sync tasks
  2. Multiple-device Usage
  3. Data is stored on the device and syncs with cloud storage when there's internet access
    • Faster than internet based todo apps like Google Calendar
  4. Provides reminders
  5. Simple user interface not too cluttered
  6. Able to set a deadline (for dates only) for a task
|
  1. Requires a lot of clicks and fields to fill to save a task
  2. Unable to block multiple slots when the exact timing of a task is uncertain
  3. Unable to set a due time for tasks
+[Todo.txt](http://todotxt.com/)|
  1. Quick & easy unix-y access
  2. Solves Google calendar being too slow
  3. Manage tasks with as few keystrokes as possible
  4. Works without Internet connectivity
|
  1. No block feature
  2. Unable to look for suitable slot
+[Fantastical](https://flexibits.com/fantastical)|
  1. Flexible
    • Choose between dark and light theme
    • Works with Google, iCloud, Exchange and more
  2. Use natural language to quickly create events and reminders
|
  1. No block feature
  2. Need to click to create an event
  3. Only available for Mac
+[Todoist](https://en.todoist.com/)|
  1. Good parser
    • Extensive list of words to use that it is able to recognize (e.g. every day/week/month, every 27th, every Jan 27th)
  2. Able to reorganize task or sort by date, priority or name
  3. Ability to tag labels
  4. Able to see a week's overview of tasks or only today's task
  5. Able to import and export task in CSV format
  6. Able to search tasks easily (search bar at the top)
  7. Able to add task at any time and at any page (add task button next to search bar)
|
  1. No block feature
  2. Certain features can only be accessed by paying
+ + +``` +###### \UserGuide.md +``` md +#### Deleting a task : `del` +Deletes the task based on its index in the task list. +Formats: +* `del [INDEX ...]` +* `del ..` + +> Deletes the task at the specific ``. +> +> Start index of range must be before end index. + +Examples: +* `del 3 6` +* `del 1..3` + +``` +###### \UserGuide.md +``` md +#### Marking tasks as done : `do` +Marks the task based on its index in the task list as done. +Format: `do [INDEX ...]` +Format: `do ..` + +> Marks the task at the specific `` as `done`. +> +> Start index of range must be before end index. + +Examples: +* `do 2 4 6` +* `do 1..3` + +``` +###### \UserGuide.md +``` md +#### Editing a task : `edit` +Edits any component of a particular task. +Format: `edit [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] [/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]` + +> Edits the task at the specific ``. +> +> `/ta` adds a tag to the task. +> +> `/tr` removes a tag from the task. +> +> Parameters can be in any order. + +Examples: +* `edit 3 /n Meet John Tan /dt 08/10/2016 1000 to 1200 /p h /ta friend` + + +#### Exiting the program : `exit` +Exits the program. +Format: `exit` + +``` +###### \UserGuide.md +``` md +#### Marking tasks as undone : `ud` +Marks the task based on its index in the task list as undone. +Format: `ud [INDEX ...]` +Format: `ud ..` + +> Marks the task at the specific `` as `undone`. +> +> Start index of range must be before end index. + +Examples: +* `ud 2 4 6` +* `ud 1..3` + +``` diff --git a/collated/docs/A0124333U.md b/collated/docs/A0124333U.md new file mode 100644 index 000000000000..f18320b66c6d --- /dev/null +++ b/collated/docs/A0124333U.md @@ -0,0 +1,249 @@ +# A0124333U +###### \AboutUs.md +``` md +#### [Lee Wenwei Johnervan](http://github.com/johnervan)
+
+ +* Components in charge of: [Model](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#model-component) +* Aspects/tools in charge of: Testing, Windows Environment Tester, EclEmma, Documentation +* Features implemented: + * [Change File Storage Directory](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#changing-data-storage-location--cd) + * [Reserve Timeslots for Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#reserving-timeslots-for-a-task--rsv) + * [Delete Reserved Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#deleting-a-task-with-reserved-timeslots--rsv-d) + * [Confirm Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#confirming-a-reserved-timeslot--confirm) + * [Find Tasks [Quick Search & Filter Search]](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#finding-tasks--find) + * [List Free Timeslots in a Specified Day](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/src/main/java/tars/logic/commands/FreeCommand.java) +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0124333U.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0124333U.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0124333U.md)] +* Other Major Contributions: + * Did the initial refactoring from AddressBook-Level4 to TARS + * User Guide + +----- +``` +###### \DeveloperGuide.md +``` md +## Appendix A : User Stories + +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` + + +Priority | As a ... | I want to ... | So that I can... +-------- | :---------- | :--------- | :----------- +`* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App +`* * *` | user | add a new event (with start and end timings) | keep track of it and complete it in the future +`* * *` | user | add a new task (tasks that have to be done before a specific deadline) | keep track of the deadline +`* * *` | user | add a floating task (tasks without specific times) | have a task that can roll over to the next day if I did not get to it +`* * *` | user | delete a task | remove tasks that I no longer need to do +`* * *` | user | edit a task | change the details of the tasks +`* * *` | user | view tasks | decide on the follow-up action for each task +`* * *` | user | clear all the data | remove all my information +`* *` | user | prioritize my task | do the more important ones first +`* *` | user | search for a task by keywords | view the details of task and complete it +`* *` | user | undo a command | undo the last action that I just performed +`* *` | user | redo a command | redo the last action that I just performed +`* *` | user | add recurring tasks | save time entering the same task over multiple dates +`* *` | user | choose my data storage location | have the flexibility to use the program on multiple computers as they can read from the same file stored on the cloud e.g. Google Drive +`* *` | user | add a tag on tasks | categorize my task +`* *` | user | edit a tag | rename the tag without the need to delete and add it again +`* *` | user | mark my tasks as done | indicate that the task has been completed +`* *` | user | mark my tasks as undone | indicate that the task has not been completed +`* *` | user | view tasks by tags/priority/date | group my tasks based on a field of my choice +`* *` | user | reserve dates for a task/event | block out time slots and add them upon confirmation of the time and date details +`* *` |user| can view all tags and edit them | edit a specific tag of all tasks with that tag in one command +`*` | user | have flexibility in entering commands | type in commands without having to remember the exact format +`*` | user | have suggestions on free slots | decide when to add a new task or shift current tasks + +``` +###### \UserGuide.md +``` md +# User Guide + +* [Quick Start](#quick-start) +* [Features](#features) +* [FAQ](#faq) +* [Support Date Format](#supported-date-formats) +* [Command Summary](#command-summary) + +## Quick Start + +1. Ensure you have Java version `1.8.0_60` or later installed in your Computer. + > Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + + Click [here](http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html) to download the latest Java version. + +2. Download the latest `tars.jar` from the '[releases](https://github.com/CS2103AUG2016-F10-C1/main/releases)' tab. +3. Copy the file to the folder you want to use as the home folder for your TARS App. +4. Double-click the file to start the app. The GUI should appear in a few seconds. +5. Type the command in the command box and press Enter to execute it. + e.g. typing **`help`** and pressing Enter will open the help window. +6. Some example commands you can try: + * **`ls`** : lists all tasks + * **`add`**` Complete CS2103 Quiz 3 /dt 23/09/2016 /p h /t Quiz /t CS2103` : + adds a task `Complete CS2103 Quiz 3` to TARS. + * **`del`**` 3` : deletes the 3rd task shown in TARS. + * **`exit`** : exits the app +7. Refer to the [Features](#features) section below for details of each command. +8. Note + - All text in `< >` are required fields whereas those in `[ ]` are optional. + - `` refers to the index number of a task shown in the task list. + - The index **must be a positive integer** 1, 2, 3, ... + - Priority options are: `h` for High, `m` for Medium, `l` for Low. + + +## Features + +#### Adding a task : `add` +Adds a task to TARS +Format: ` [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]` + +> Support for events (i.e., has a start time and end time), deadlines (tasks that have to be done before a specific deadline), and floating tasks (tasks without specific times). +> +> Parameters can be in any order. + +Examples: +* `add Meet John Doe /dt 26/09/2016 0900 to 26/09/2016 1030 /t catchup` +* `add Complete CS2103 Quiz /dt 23/09/2016 /p h /t Quiz /t CS2103, /r 13 EVERY WEEK` +* `add Floating Task` + +``` +###### \UserGuide.md +``` md +#### Changing data storage location : `cd` +Changes the directory of the TARS storage file. +Format: `cd ` + +> Returns an error if the directory chosen is invalid. +> +> `` must end with the file type extension, `.xml` + +Examples: +* `cd C:\Users\John_Doe\Documents\tars.xml` + +#### Clearing the data storage file : `clear` +Clears the whole To-Do List storage file. +Format: `clear` + +``` +###### \UserGuide.md +``` md +#### Confirming a reserved timeslot : `confirm` +Confirms a dateTime for a reserved task and adds it to the task list. +Format: `confirm [/p PRIORITY] [/t TAG_NAME ...]` + +> Confirm the task of a specific `` at a dateTime of a specific ``. +> +> The `` refers to the index number shown in the reserved task list. +> +> The `` refers to the index number of the dateTime. + +Examples: +* `confirm 3 2 /p l /t Tag` + +``` +###### \UserGuide.md +``` md +#### Finding tasks : `find` +Finds all tasks containing a list of keywords (i.e. AND search). +Two modes: Quick Search & Filter Search. +Format: +* [Quick Search]: `find [KEYWORD ...]` +* [Filter Search]: `find [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]` + +> **Quick Search Mode**: Find tasks quickly by entering keywords that match what is displayed in the task list. +> +> **Filter Search Mode**: Find tasks using task filters (i.e. /n, /p, /dt, /do, /ud, /t). +> +> Use /n to filter tasks by task name. +> +> Use /p to filter tasks by priority level. +> +> Use /dt to filter tasks by date (in a date range). +> +> Use /do to filter all done tasks (Cannot be used together with /ud). +> +> Use /ud to filter all undone tasks (Cannot be used together with /do). +> +> Use /t to filter tasks by tags. +> +> `` are **case-insensitive**. +> +> Parameters can be in any order. + +Examples: +* `find meet John` uses Quick Search and returns all tasks containing BOTH the keywords "meet" and "John" (e.g. meet John Doe) +* `find /n meet /dt 17/10/2016 1300 to 18/10/2016 1400` uses Filter Search and returns all tasks whose name contains "meet" and whose task date falls within the range "17/10/2016 1300 to 18/10/2016 1400" (e.g. meet Tim for dinner, 17/10/2016 1800 to 17/10/2016 1900) + +``` +###### \UserGuide.md +``` md +#### Suggesting free timeslots : `free` +Suggests free timeslots in a specified day. +Format: `free ` + +> Does not check for tasks without dateTime nor tasks without a start dateTime. + +Examples: +* `free next tuesdsay` +* `free 26/10/2016` + +``` +###### \UserGuide.md +``` md +#### Reserving timeslots for a task : `rsv` +Reserves one or more timeslot for a task +Format: `rsv [/dt DATETIME ...]` + +> Multiple dateTimes can be added. + +Examples: +* `rsv Meet John Doe /dt 26/09/2016 0900 to 1030 /dt 28/09/2016 1000 to 1130` + +``` +###### \UserGuide.md +``` md +#### Deleting a task with reserved timeslots : `rsv /del` +Deletes a task with all its reserved time slots +Format: `rsv /del ` +Format: `rsv /del ..` + +> Deletes the task at the specific ``. +> +> Start index of range must be before end index. + +Examples: +* `rsv /del 5` +* `rsv /del 1..4` + +``` +###### \UserGuide.md +``` md +## Command Summary + +Command | Format +-------- | :-------- +[Add](#adding-a-task--add)| `add [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]` +[Change Storage Location](#changing-data-storage-location--cd) | `cd ` +[Clear](#clearing-the-data-storage-file--clear) | `clear` +[Confirm](#confirming-a-reserved-timeslot--confirm) | `confirm [/p PRIORITY] [/t TAG_NAME ...]` +[Delete](#deleting-a-task--del) | `del [INDEX ...]`
`del ..` +[Done](#marking-tasks-as-done--do) | `do [INDEX ...]`
`do ..` +[Edit](#editing-a-task--edit) | `edit [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] [/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]` +[Exit](#exiting-the-program--exit) | `exit` +[Find [Quick Search]](#finding-tasks--find) | `find [KEYWORD ...]` +[Find [Filter Search]](#finding-tasks--find) | `find [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]` +[Free](#suggesting-free-timeslots--free) | `free ` +[Help](#displaying-a-list-of-available-commands--help) | `help [COMMAND_WORD]` +[List](#listing-tasks--ls) | `ls` +[List [Date]](#listing-tasks--ls) | `ls /dt` +[List [Priority]](#listing-tasks--ls) | `ls /p` +[Redo](#redoing-a-command--redo) | `redo` +[Reserve](#reserving-timeslots-for-a-task--rsv) | `rsv [/dt DATETIME ...]` +[Reserve [Delete]](#deleting-a-task-with-reserved-timeslots--rsv-del) | `rsv /del `
`rsv /del ..` +[Tag [Delete]](#deleting-a-tag--tag-del) | `tag /del ` +[Tag [Edit]](#editing-a-tags-name--tag-e) | `tag /e ` +[Tag [List]](#listing-all-tags--tag-ls) | `tag /ls` +[Undone](#marking-tasks-as-undone--ud) | `ud [INDEX ...]`
`ud ..` +[Undo](#undoing-a-command--undo) | `undo` + +``` diff --git a/collated/docs/A0139924W.md b/collated/docs/A0139924W.md new file mode 100644 index 000000000000..ffacc3c13087 --- /dev/null +++ b/collated/docs/A0139924W.md @@ -0,0 +1,380 @@ +# A0139924W +###### \AboutUs.md +``` md +#### [Chia Wei Kang](http://github.com/weikangchia)
+
+ +* Components in charge of: [Logic](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#logic-component) +* Aspects/tools in charge of: Testing, Travis, Codacy, Coveralls +* Features implemented: + * [Undo Commands](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#undoing-a-command--undo) + * [Redo Commands](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#redoing-a-command--redo) + * [Edit Tags](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#editing-a-tags-name--tag-e) + * [Delete Tags from all Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#deleting-a-tag--tag-del) + * [List Tags](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#listing-all-tags--tag-ls) + * [Natural Date Input](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#supported-date-formats) + * Shortcut keys for undo and redo commands +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0139924W.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0139924W.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0139924W.md)] +* Other Major Contributions: + * ArgumentTokenizer (flexible commands) + * Did the refactoring of parser and logic command test + * Set up Travis, Codacy and Coveralls + +----- + +``` +###### \DeveloperGuide.md +``` md +### Logic component + +
+ +**API** : [`Logic.java`](../src/main/java/tars/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 task) and/or raise events. +4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui` + +``` +###### \DeveloperGuide.md +``` md +## Appendix B : Use Cases + +(For all use cases below, the **System** is the `TARS` and the **Actor** is the `user`, unless specified otherwise) + +#### Use case: UC01 - View help + +**MSS** + +1. User requests to view help +2. TARS shows a list of usage instructions
+Use case ends. + +#### Use case: UC02- Add task + +**MSS** + +1. User requests to submit a new task +2. TARS save the task and add the command to command history
+Use case ends. + +**Extensions** + +2a. The format is invalid + +> 2a1. TARS shows an error message
+ Use case resumes at step 1 + +2b. The end datetime is earlier than start datetime + +> 2b1. TARS shows an error message
+ Use case resumes at step 1 + +2c. The task already exists
+ +> 2c1. TARS shows an error message
+ Use case resume at step 1 + +#### Use case: UC03- Delete task + +**MSS** + +1. User requests to list tasks +2. TARS shows a list of tasks +3. User requests to delete a specific task in the list +4. TARS deletes the task
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC04 - Edit task + +**MSS** + +1. User requests to list tasks +2. TARS shows a list of tasks +3. User requests to edit a specific task in the list +4. TARS updates the task
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +3b. The format is invalid + +> 3b1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC05 - Edit tag name + +**MSS** + +1. User requests to list tags +2. TARS shows a list of tags +3. User requests to edit a specific tag in the list +4. TARS updates the tag
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +3b. The format is invalid + +> 3b1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC06 - Delete tag + +**MSS** + +1. User requests to list tags +2. TARS shows a list of tags +3. User requests to delete a specific tag in the list +4. TARS deletes the tag
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC07 - List tags + +**MSS** + +1. User requests to list tags +2. TARS shows a list of tags
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +#### Use case: UC08 - Undo a previous command + +**MSS** + +1. User requests to undo a previous command +2. TARS reinstates (undo) the last command in the undo history list and add the command to the redo history list
+Use case ends. + +**Extensions** + +2a. The undo history list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +#### Use case: UC09 - Redo a previous undo command + +**MSS** + +1. User requests to redo a previous command +2. TARS redo the last command in the redo history list and add the command to the undo history list
+Use case ends. + +**Extensions** + +2a. The redo history list is empty + +> 2a1. TARS shows an empty list 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. +5. Should have a command line interface as the primary mode of input. +6. Should not take more than 1 second to load the app. +7. Should not take more than 3 seconds to execute any commands. +8. Should not take more than 1 seconds to load the command result after a command execute. +9. Should not consume memory of more than 512 MB. +10. Should work without requiring an installer. +11. Should work on desktop without network/Internet connection. +12. Should be able to work stand-alone. It should not be a plug-in to another software. +13. Should not use relational databases. +14. Should store data locally and should be in human editable text file. +15. Should follow the Object-oriented paradigm. +16. Should not use third-party frameworks/libraries that + * are not free + * require installation + * violate other constraint + +``` +###### \UserGuide.md +``` md +#### Redoing a command : `redo` +Redo a previous command +Format: `redo` + +> Able to redo all `add`, `delete`, `edit`, `tag`, `rsv`, `confirm` and `del` commands from the time the app starts running. +> +> Keyboard shortcut: CTRL-Y + +``` +###### \UserGuide.md +``` md +#### Editing a tag's name : `tag /e` +Edits a tag's name +Format: `tag /e ` + +> Edits the name of the tag at the specific ``. + +Examples: +* `tag /e 5 Assignment` + +``` +###### \UserGuide.md +``` md +#### Deleting a tag : `tag /del` +Deletes a particular tag +Format: `tag /del ` + +> Deletes the tag at the specific ``. + +Examples: +* `tag /del 4` deletes the tag at Index 4 + +``` +###### \UserGuide.md +``` md +#### Listing all tags : `tag /ls` +Lists all tags in TARS +Format: `tag /ls` + +``` +###### \UserGuide.md +``` md +#### Undoing a command : `undo` +Undo a command executed by the user. +Format: `undo` + +> Able to undo all `add`, `delete`, `edit`, `tag`, `rsv`, `confirm` and `del` commands from the time the app starts running. +> +> Keyboard shortcut: CTRL-Z + + + +#### Saving the data +TARS data are saved in the hard disk automatically after any command that changes the data. +There is no need to save manually. + +## 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 TARS app. + +``` +###### \UserGuide.md +``` md +## Supported Date Formats +#### formal dates +Formal dates are those in which the day, month, and year are represented as integers separated by a common separator character. The year is optional and may precede the month or succeed the day of month. If a two-digit year is given, it must succeed the day of month. + +Examples: +* `28-01-2016` +* `28/01/2016` +* `1/02/2016` +* `2/2/16` + +#### relaxed dates +Relaxed dates are those in which the month, day of week, day of month, and year may be given in a loose, non-standard manner, with most parts being optional. + +Examples: +* `The 31st of April in the year 2008` +* `Fri, 21 Nov 1997` +* `Jan 21, '97` +* `Sun, Nov 21` +* `jan 1st` +* `february twenty-eighth` + +#### relative dates +Relative dates are those that are relative to the current date. + +Examples: +* `next thursday` +* `last wednesday` +* `today` +* `tomorrow` +* `yesterday` +* `next week` +* `next month` +* `next year` +* `3 days from now` +* `three weeks ago` + +#### prefixes +Most of the above date formats may be prefixed with a modifier. + +Examples: +`day after` +`the day before` +`the monday after` +`the monday before` +`2 fridays before` +`4 tuesdays after` + +#### time +The above date formats may be prefixed or suffixed with time information. + +Examples: +* `0600h` +* `06:00 hours` +* `6pm` +* `5:30 a.m.` +* `5` +* `12:59` +* `23:59` +* `8p` +* `noon` +* `afternoon` +* `midnight` + +#### relative times + +Examples: +* `10 seconds ago` +* `in 5 minutes` +* `4 minutes from now` + +``` diff --git a/collated/docs/A0140022H.md b/collated/docs/A0140022H.md new file mode 100644 index 000000000000..f5285b0dc3d5 --- /dev/null +++ b/collated/docs/A0140022H.md @@ -0,0 +1,58 @@ +# A0140022H +###### \AboutUs.md +``` md +#### [Calvin Yang Jiawei](http://github.com/origiri)
+
+ +* Components in charge of: [Storage](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#storage-component) +* Aspects/tools in charge of: Github +* Features implemented: + * [Add Recurring Task](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#adding-a-task--add) + * [List Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#listing-tasks--ls) + * [Help](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#displaying-a-list-of-available-commands--help) + * Result Summary +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0140022H.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0140022H.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0140022H.md)] +* Other Major Contributions: + * User Guide + +----- + +``` +###### \UserGuide.md +``` md +#### Displaying a list of available commands : `help` +Shows program usage instructions in help panel. +Format: `help [COMMAND_WORD]` + +> Help is also shown if you enter an incorrect command e.g. `abcd`. + +Examples: +* `help add` +* `help summary` + +``` +###### \UserGuide.md +``` md +#### Listing tasks : `ls` +Lists all tasks. +Format: +* `ls` +* `ls /dt [dsc]` +* `ls /p [dsc]` + +> All tasks listed by default. +> +> Use /dt to list all tasks by earliest end dateTime. +> +> Use /p to list all task by priority from low to high. +> +> Use dsc with previous two prefixes to reverse the order. + +Examples: +* `ls` +* `ls /dt` +* `ls /dt dsc` +* `ls /p` +* `ls /p dsc` + +``` diff --git a/collated/main/A0121533W.md b/collated/main/A0121533W.md new file mode 100644 index 000000000000..df869bb2a78d --- /dev/null +++ b/collated/main/A0121533W.md @@ -0,0 +1,1888 @@ +# A0121533W +###### \java\tars\commons\events\ui\RsvTaskAddedEvent.java +``` java +/** + * Indicates a task has been added + */ +public class RsvTaskAddedEvent extends BaseEvent { + + public final int targetIndex; + public final RsvTask task; + + public RsvTaskAddedEvent(int targetIndex, RsvTask task) { + this.targetIndex = targetIndex; + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\tars\commons\util\DateTimeUtil.java +``` java + /** + * Checks if given endDateTime is within today and the end of this week + */ + public static boolean isWithinWeek(LocalDateTime endDateTime) { + if (endDateTime == null) { + return false; + } else { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime endThisWeek = + now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) + .withHour(0).withMinute(0).withSecond(0); + return endDateTime.isAfter(now) + && endDateTime.isBefore(endThisWeek); + } + } + +``` +###### \java\tars\commons\util\DateTimeUtil.java +``` java + /** + * Checks if given endDateTime is before the end of today + */ + public static boolean isOverDue(LocalDateTime endDateTime) { + if (endDateTime == null) { + return false; + } else { + LocalDateTime now = LocalDateTime.now(); + return endDateTime.isBefore(now); + } + } + +``` +###### \java\tars\commons\util\StringUtil.java +``` java + /** + * Handles three different cases of strings and return them in the appropriate format + */ + public static String indexString(String s) + throws InvalidRangeException, IllegalValueException { + if (s.isEmpty()) { + return s; + } + if (isSingleNumber(s)) { + return getIndexForSingleNumber(s); + } else if (isListOfIndexes(s)) { + return getIndexesForList(s); + } else if (isRangeOfIndexes(s)) { + return getIndexesForRange(s); + } else { + throw new IllegalValueException( + UNEXPECTED_ERROR_IN_GETING_INDEX_FROM_STRING); + } + } + + private static boolean isSingleNumber(String s) { + return (s.indexOf(STRING_WHITESPACE) == INVALID_POSITION + && !s.contains(RANGE_SEPARATOR)); + } + + /** + * Returns a valid single index + */ + private static String getIndexForSingleNumber(String s) + throws IllegalValueException { + if (!isUnsignedInteger(s)) { + throw new IllegalValueException(INVALID_INDEX_ENTERED); + } + return s; + } + + private static boolean isListOfIndexes(String s) { + return (s.indexOf(STRING_WHITESPACE) != INVALID_POSITION + && !s.contains(RANGE_SEPARATOR)); + } + +``` +###### \java\tars\commons\util\StringUtil.java +``` java + /** + * Returns a valid list of indexes + */ + private static String getIndexesForList(String s) + throws IllegalValueException { + String indexString = EMPTY_STRING; + String[] indexArray = s.split(STRING_WHITESPACE); + for (int i = START_INDEX; i < indexArray.length; i++) { + String index = getIndexForSingleNumber(indexArray[i]); + indexString += index + STRING_WHITESPACE; + } + return indexString.trim(); + } + + private static boolean isRangeOfIndexes(String s) { + return s.contains(RANGE_SEPARATOR); + } + +``` +###### \java\tars\commons\util\StringUtil.java +``` java + /** + * Returns a list of indexes separated by white space from a range of indexes + */ + private static String getIndexesForRange(String s) + throws IllegalValueException, InvalidRangeException { + String rangeToReturn = EMPTY_STRING; + + int toIndex = s.indexOf(RANGE_SEPARATOR); + String start = s.substring(START_INDEX, toIndex); + String end = s.substring(toIndex + RANGE_SEPARATOR.length()); + + start = getIndexForSingleNumber(start); + end = getIndexForSingleNumber(end); + + int startInt = Integer.parseInt(start); + int endInt = Integer.parseInt(end); + + if (startInt > endInt) { + throw new InvalidRangeException(); + } + + for (int i = startInt; i <= endInt; i++) { + rangeToReturn += String.valueOf(i) + STRING_WHITESPACE; + } + + return rangeToReturn.trim(); + } + +} +``` +###### \java\tars\logic\commands\DeleteCommand.java +``` java +/** + * Deletes a task identified using it's last displayed index from tars. + */ +public class DeleteCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "del"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task based on its index in the task list.\n" + + "Parameters: [INDEX ...]\n" + "Example: " + + COMMAND_WORD + " 1\n" + COMMAND_WORD + " 1..3"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = + "Deleted Task:\n%1$s"; + public static final String MESSAGE_UNDO = "Added Task:\n%1$s"; + public static final String MESSAGE_REDO = "Deleted Task:\n%1$s"; + + private static final String MESSAGE_MISSING_TARGET_TASK = + "The target task cannot be missing"; + + private final String arguments; + private ArrayList deletedTasks = + new ArrayList(); + + public DeleteCommand(String args) { + this.arguments = args; + } + + @Override + public CommandResult execute() { + ArrayList tasksToDelete; + try { + tasksToDelete = getTasksFromIndexes( + this.arguments.split(StringUtil.STRING_WHITESPACE)); + } catch (InvalidTaskDisplayedException itde) { + return new CommandResult(itde.getMessage()); + } + for (ReadOnlyTask t : tasksToDelete) { + try { + model.deleteTask(t); + } catch (TaskNotFoundException tnfe) { + assert false : MESSAGE_MISSING_TARGET_TASK; + } + deletedTasks.add(t); + } + model.getUndoableCmdHist().push(this); + String formattedTaskList = new Formatter().formatTaskList(deletedTasks); + return new CommandResult( + String.format(MESSAGE_DELETE_TASK_SUCCESS, formattedTaskList)); + } + + /** + * Gets Tasks to delete from indexes + */ + private ArrayList getTasksFromIndexes(String[] indexes) + throws InvalidTaskDisplayedException { + UnmodifiableObservableList lastShownList = + model.getFilteredTaskList(); + ArrayList tasksList = new ArrayList(); + + for (int i = StringUtil.START_INDEX; i < indexes.length; i++) { + int targetIndex = Integer.parseInt(indexes[i]); + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new InvalidTaskDisplayedException( + Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + ReadOnlyTask task = + lastShownList.get(targetIndex - StringUtil.LAST_INDEX); + tasksList.add(task); + } + return tasksList; + } + +``` +###### \java\tars\logic\commands\DoCommand.java +``` java +/** + * Marks a task identified using it's last displayed index from tars as done. + */ +public class DoCommand extends Command { + + public static final String COMMAND_WORD = "do"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the task based on its index in the task list as done.\n" + + "Parameters: [INDEX ...]\n" + "Example: " + + COMMAND_WORD + " 3 5 7\n" + "OR " + COMMAND_WORD + " 1..3"; + + private String toDo; + + private MarkTaskUtil tracker; + + /** + * Convenience constructor using raw values. + * + * @throws InvalidRangeException + */ + public DoCommand(String toDo) { + this.toDo = toDo; + this.tracker = new MarkTaskUtil(); + } + + @Override + public CommandResult execute() { + assert model != null; + + try { + handleMarkDone(); + } catch (InvalidTaskDisplayedException e) { + return new CommandResult(e.getMessage()); + } catch (DuplicateTaskException dte) { + return new CommandResult(dte.getMessage()); + } + return new CommandResult(tracker.getResultFromTracker()); + } + + /** + * Marks status of task in model as done + */ + private void handleMarkDone() + throws InvalidTaskDisplayedException, DuplicateTaskException { + Status done = new Status(true); + ArrayList markDoneTasks = tracker.getTasksFromIndexes( + model, this.toDo.split(StringUtil.STRING_WHITESPACE), done); + model.mark(markDoneTasks, done); + } + +} +``` +###### \java\tars\logic\commands\EditCommand.java +``` java +/** + * Edits a task identified using it's last displayed index from tars. + */ +public class EditCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits any component of a particular task.\n" + + "Parameters: [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] " + + "[/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]\n" + "Example: " + COMMAND_WORD + + " 1 /n Lunch with John /dt 10/09/2016 1200 to 10/09/2016 1300 /p l /ta lunch /tr dinner"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited task: %1$s"; + public static final String MESSAGE_UNDO = "Edited to %1$s to %1$s"; + public static final String MESSAGE_REDO = "Edited to %1$s to %1$s"; + + private static final int DATETIME_INDEX_OF_ENDDATE = 1; + private static final int DATETIME_INDEX_OF_STARTDATE = 0; + private static final Prefix NAME_PREFIX = new Prefix("/n"); + private static final Prefix DATETIME_PREFIX = new Prefix("/dt"); + private static final Prefix PRIORITY_PREFIX = new Prefix("/p"); + private static final Prefix ADD_TAG_PREFIX = new Prefix("/ta"); + private static final Prefix REMOVE_TAG_PREFIX = new Prefix("/tr"); + + public final int targetIndex; + private ReadOnlyTask toBeReplacedTask; + private Task editedTask; + private ArgumentTokenizer argsTokenizer; + + /** + * Convenience constructor using raw values. + */ + public EditCommand(int targetIndex, ArgumentTokenizer argsTokenizer) { + this.targetIndex = targetIndex; + this.argsTokenizer = argsTokenizer; + } + + @Override + public CommandResult execute() { + assert model != null; + + UnmodifiableObservableList lastShownList = + model.getFilteredTaskList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult( + Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + toBeReplacedTask = lastShownList + .get(targetIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + editedTask = new Task(toBeReplacedTask); + + try { + updateTask(); + model.replaceTask(toBeReplacedTask, editedTask); + model.getUndoableCmdHist().push(this); + return new CommandResult( + String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + } catch (DateTimeException dte) { + return new CommandResult(Messages.MESSAGE_INVALID_DATE); + } catch (IllegalValueException | TagNotFoundException e) { + return new CommandResult(e.getMessage()); + } + } + +``` +###### \java\tars\logic\commands\MarkTaskUtil.java +``` java +/** + * Tracks changes made (if any) during do and ud command + */ +public class MarkTaskUtil { + + public static final String SUCCESS_DONE = + "Task: %1$s marked done successfully.\n"; + public static final String SUCCESS_UNDONE = + "Task: %1$s marked undone successfully.\n"; + public static final String ALREADY_DONE = + "Task: %1$s already marked done.\n"; + public static final String ALREADY_UNDONE = + "Task: %1$s already marked undone.\n"; + + private ArrayList markDoneTasks; + private ArrayList markUndoneTasks; + private ArrayList alreadyDoneTasks; + private ArrayList alreadyUndoneTasks; + + /** + * Constructor + */ + public MarkTaskUtil() { + this.markDoneTasks = new ArrayList(); + this.markUndoneTasks = new ArrayList(); + this.alreadyDoneTasks = new ArrayList(); + this.alreadyUndoneTasks = new ArrayList(); + } + + /** + * Adds target index of task to relevant "To Mark List" based on status + */ + public void addToMark(int targetIndex, Status status) { + if (status.status) { + addToMarkDoneTask(targetIndex); + } else { + addToMarkUndoneTask(targetIndex); + } + + } + + /** + * Adds target index of task to relevant "Already Marked List" based on status + */ + public void addAlreadyMarked(int targetIndex, Status status) { + if (status.status) { + addToAlreadyDoneTasks(targetIndex); + } else { + addToAlreadyUndoneTasks(targetIndex); + } + } + + /** + * Return string for each tasks index in the specific ArrayLists + */ + public String getResult() { + String markDoneTasksString = getIndexesString(markDoneTasks); + String markUndoneTasksString = getIndexesString(markUndoneTasks); + String alreadyDoneTasksString = getIndexesString(alreadyDoneTasks); + String alreadyUndoneTasksString = getIndexesString(alreadyUndoneTasks); + + String result = + formatResults(markDoneTasksString, markUndoneTasksString, + alreadyDoneTasksString, alreadyUndoneTasksString); + + return result; + } + + /** + * Main results formatter that will perform the formatting for all 4 cases i.e. Mark Done, Mark + * Undone, Already Done and Already Undone + */ + private String formatResults(String markDoneTasksString, + String markUndoneTasksString, String alreadyDoneTasksString, + String alreadyUndoneTasksString) { + + String markDoneResult = + getResultFromString(markDoneTasksString, SUCCESS_DONE); + String markUndoneResult = + getResultFromString(markUndoneTasksString, SUCCESS_UNDONE); + String alreadyDoneResult = + getResultFromString(alreadyDoneTasksString, ALREADY_DONE); + String aldreadyUndoneResult = + getResultFromString(alreadyUndoneTasksString, ALREADY_UNDONE); + + return markDoneResult + markUndoneResult + alreadyDoneResult + + aldreadyUndoneResult; + } + + /** + * Formats results of changes made + */ + private String getResultFromString(String tasksString, String format) { + String result = StringUtil.EMPTY_STRING; + if (!tasksString.isEmpty()) { + result = String.format(format, tasksString); + } + return result; + } + + /** + * Gets String of indexes separated by comma + */ + private String getIndexesString(ArrayList list) { + String toReturn = StringUtil.EMPTY_STRING; + if (!list.isEmpty()) { + for (int i = StringUtil.START_INDEX; i < list.size() - 1; i++) { + toReturn += + Integer.toString(list.get(i)) + StringUtil.STRING_COMMA; + } + // Add last index + toReturn += Integer.toString(list.get(list.size() - 1)); + } + return toReturn; + } + + private void addToMarkDoneTask(int index) { + this.markDoneTasks.add(index); + } + + private void addToMarkUndoneTask(int index) { + this.markUndoneTasks.add(index); + } + + private void addToAlreadyDoneTasks(int index) { + this.alreadyDoneTasks.add(index); + } + + private void addToAlreadyUndoneTasks(int index) { + this.alreadyUndoneTasks.add(index); + } + + /** + * Returns feedback message of mark command to user + */ + public String getResultFromTracker() { + String commandResult = getResult(); + return commandResult; + } + + /** + * Gets Tasks to mark from indexes + */ + public ArrayList getTasksFromIndexes(Model model, + String[] indexes, Status status) + throws InvalidTaskDisplayedException { + UnmodifiableObservableList lastShownList = + model.getFilteredTaskList(); + ArrayList tasksList = new ArrayList(); + + for (int i = 0; i < indexes.length; i++) { + int targetIndex = Integer.valueOf(indexes[i]); + if (lastShownList.size() < targetIndex) { + throw new InvalidTaskDisplayedException( + Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + ReadOnlyTask task = lastShownList.get(targetIndex - 1); + if (!task.getStatus().equals(status)) { + tasksList.add(task); + addToMark(targetIndex, status); + } else { + addAlreadyMarked(targetIndex, status); + } + } + return tasksList; + } + +} +``` +###### \java\tars\logic\commands\UdCommand.java +``` java +/** + * Marks a task identified using it's last displayed index from tars as undone. + */ +public class UdCommand extends Command { + + public static final String COMMAND_WORD = "ud"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the task based on its index in the task list as undone.\n" + + "Parameters: [INDEX ...]\n" + "Example: " + + COMMAND_WORD + " 3 5 7" + "OR " + COMMAND_WORD + " 1..3\n"; + + private String toUndo; + + private MarkTaskUtil tracker; + + /** + * Convenience constructor using raw values. + * + * @throws InvalidRangeException + */ + public UdCommand(String toUndo) { + this.toUndo = toUndo; + this.tracker = new MarkTaskUtil(); + } + + @Override + public CommandResult execute() { + assert model != null; + + try { + handleMarkUndone(); + } catch (InvalidTaskDisplayedException e) { + return new CommandResult(e.getMessage()); + } catch (DuplicateTaskException dte) { + return new CommandResult(dte.getMessage()); + } + return new CommandResult(tracker.getResultFromTracker()); + } + + /** + * Marks status of task in model as undone + */ + private void handleMarkUndone() + throws InvalidTaskDisplayedException, DuplicateTaskException { + Status undone = new Status(false); + ArrayList markUndoneTasks = tracker.getTasksFromIndexes( + model, this.toUndo.split(StringUtil.STRING_WHITESPACE), undone); + model.mark(markUndoneTasks, undone); + } + +} +``` +###### \java\tars\logic\parser\EditCommandParser.java +``` java +/** + * Edit command parser + */ +public class EditCommandParser extends CommandParser { + + private static final int START_INDEX = 0; + private static final int EMPTY_SIZE = 0; + + /** + * Parses arguments in the context of the edit task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + args = args.trim(); + int targetIndex = START_INDEX; + if (args.indexOf( + StringUtil.STRING_WHITESPACE) != StringUtil.INVALID_POSITION) { + targetIndex = args.indexOf(StringUtil.STRING_WHITESPACE); + } + + String index; + try { + index = StringUtil.indexString( + (args.substring(StringUtil.START_INDEX, targetIndex))); + } catch (Exception e) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + if (index.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + ArgumentTokenizer argsTokenizer = new ArgumentTokenizer(namePrefix, + priorityPrefix, dateTimePrefix, addTagPrefix, removeTagPrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.numPrefixFound() == EMPTY_SIZE) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + return new EditCommand(Integer.parseInt(index), argsTokenizer); + } + +} +``` +###### \java\tars\model\Model.java +``` java + /** + * Marks tasks as done or undone. + */ + void mark(ArrayList toMarkList, Status status) + throws DuplicateTaskException; +``` +###### \java\tars\model\ModelManager.java +``` java + @Override + public synchronized void mark(ArrayList toMarkList, + Status status) throws DuplicateTaskException { + tars.mark(toMarkList, status); + indicateTarsChanged(); + } + +``` +###### \java\tars\model\Tars.java +``` java + /** + * Replaces task in tars internal list + * + * @throws DuplicateTaskException if replacement task is the same as the task to replace + */ + public void replaceTask(ReadOnlyTask toReplace, Task replacement) + throws DuplicateTaskException { + if (toReplace.isSameStateAs(replacement)) { + throw new DuplicateTaskException(); + } + + ObservableList list = this.tasks.getInternalList(); + for (int i = StringUtil.START_INDEX; i < list.size(); i++) { + if (list.get(i).isSameStateAs(toReplace)) { + syncTagsWithMasterList(replacement); + list.set(i, replacement); + break; + } + } + } +``` +###### \java\tars\model\Tars.java +``` java + /** + * Marks every task in respective lists as done or undone + * + * @throws DuplicateTaskException + */ + public void mark(ArrayList toMarkList, Status status) + throws DuplicateTaskException { + for (ReadOnlyTask t : toMarkList) { + if (!t.getStatus().equals(status)) { + // prevent marking tasks which are already marked + Task toMark = new Task(t); + toMark.setStatus(status); + replaceTask(t, toMark); + } + } + } +``` +###### \java\tars\ui\formatter\Formatter.java +``` java + /** + * Formats a given RsvTask dateTime list to display on rsvTaskCard + */ + public static String formatDateTimeList(ArrayList dateTimeList) { + String formatted = StringUtil.EMPTY_STRING; + int count = 1; + for (DateTime dt : dateTimeList) { + formatted += String.format(DATETIME_FORMAT_STRING, count, + DateFormatter.formatDate(dt)); + count++; + } + return formatted; + } + + /** + * Formats a given tasks list to display on This Week Panel + */ + public static String formatThisWeekPanelTasksList( + List overduedTasks) { + String formatted = StringUtil.EMPTY_STRING; + int count = INITIAL_COUNT; + for (ReadOnlyTask t : overduedTasks) { + String taskName = t.getName().toString(); + formatted += String.format(OVERDUED_TASKS_STRING, count, taskName); + count++; + } + return formatted.trim(); + } +} +``` +###### \java\tars\ui\HelpPanel.java +``` java +/** + * UI Controller for a help page + */ +public class HelpPanel extends UiPart { + + private static final String FXML = "HelpPanel.fxml"; + private static final String USERGUIDE_URL = "/html/UserGuide.md.html"; + + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private WebView browser = new WebView(); + + public static HelpPanel load(Stage primaryStage, + AnchorPane helpPanelPlaceHolder) { + HelpPanel helpPanel = UiPartLoader.loadUiPart(primaryStage, + helpPanelPlaceHolder, new HelpPanel()); + helpPanel.configure(); + return helpPanel; + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void configure() { + addToPlaceholder(); + browser.getEngine().load(configureURL(UserGuide.DEFAULT)); + + } + +``` +###### \java\tars\ui\MainWindowEventsHandler.java +``` java +/** + * Handles all events that require main window. + */ +public class MainWindowEventsHandler { + + protected static Stage primaryStage; + + private static String LOG_MESSAGE_COMMAND_DETECTED = "%s command detected."; + + private static double xOffset = 0; + private static double yOffset = 0; + + private static VBox rootLayout; + private static TabPane tabPane; + + private static final Logger logger = LogsCenter.getLogger(MainWindow.class); + + public MainWindowEventsHandler(Stage primaryStage, VBox rootLayout, + TabPane tabPane) { + MainWindowEventsHandler.rootLayout = rootLayout; + MainWindowEventsHandler.primaryStage = primaryStage; + MainWindowEventsHandler.tabPane = tabPane; + EventsCenter.getInstance().registerHandler(this); + } + + public static void addMouseEventHandler() { + rootLayout.setOnMousePressed(new EventHandler() { + @Override + public void handle(MouseEvent event) { + xOffset = event.getSceneX(); + yOffset = event.getSceneY(); + } + }); + rootLayout.setOnMouseDragged(new EventHandler() { + @Override + public void handle(MouseEvent event) { + primaryStage.setX(event.getScreenX() - xOffset); + primaryStage.setY(event.getScreenY() - yOffset); + } + }); + } + + public static void addTabPaneHandler() { + rootLayout.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent event) { + if (event.getCode() == KeyCode.RIGHT) { + cycleTabPaneRight(); + event.consume(); + } else if (event.getCode() == KeyCode.LEFT) { + cycleTabPaneLeft(); + event.consume(); + } + } + }); + } + + @Subscribe + private void KeyCombinationPressedEventHandler( + KeyCombinationPressedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + event.getKeyCombination().getDisplayText())); + if (event + .getKeyCombination() == KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW) { + cycleTabPaneRight(); + } else if (event + .getKeyCombination() == KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW) { + cycleTabPaneLeft(); + } + } + + @Subscribe + private void CommandBoxTextFieldValueChangedEventHandler( + CommandBoxTextFieldValueChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, String.format( + LOG_MESSAGE_COMMAND_DETECTED, event.getTextFieldValue()))); + if (event.getTextFieldValue().equals(RsvCommand.COMMAND_WORD) || event + .getTextFieldValue().equals(ConfirmCommand.COMMAND_WORD)) { + tabPane.getSelectionModel() + .select(MainWindow.RSV_TASK_LIST_PANEL_TAB_PANE_INDEX); + } + } + + private static void cycleTabPaneRight() { + if (tabPane.getSelectionModel() + .isSelected((MainWindow.HELP_PANEL_TAB_PANE_INDEX))) { + tabPane.getSelectionModel().selectFirst(); + } else { + tabPane.getSelectionModel().selectNext(); + } + } + + private static void cycleTabPaneLeft() { + if (tabPane.getSelectionModel() + .isSelected((MainWindow.OVERVIEW_PANEL_TAB_PANE_INDEX))) { + tabPane.getSelectionModel().selectLast(); + } else { + tabPane.getSelectionModel().selectPrevious(); + } + } + +``` +###### \java\tars\ui\RsvTaskCard.java +``` java +/** + * UI Controller for Reserve Task Card + */ +public class RsvTaskCard extends UiPart { + + private static final int PREF_SIZE_HEIGHT = 75; + private static final int PREF_SIZE_WIDTH = 200; + private static final String DATE_TIME_LIST_AREA = "dateTimeListArea"; + private static final String FXML = "RsvTaskListCard.fxml"; + private static final String DATETIMELIST_ID = "dateTimeList"; + + private TextArea dateTimeListArea; + private AnchorPane dateTimeListPane; + + private StringProperty dateTimeListdisplayed = + new SimpleStringProperty(StringUtil.EMPTY_STRING); + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + + private RsvTask rsvTask; + private int displayedIndex; + + public RsvTaskCard() { + + } + + public static RsvTaskCard load(RsvTask rsvTask, int displayedIndex) { + RsvTaskCard card = new RsvTaskCard(); + card.cardPane = new HBox(); + card.dateTimeListPane = new AnchorPane(); + + card.rsvTask = rsvTask; + card.displayedIndex = displayedIndex; + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + name.setText(rsvTask.getName().taskName); + id.setText(displayedIndex + StringUtil.STRING_FULLSTOP); + setDateTimeList(); + configure(); + } + + public void configure() { + dateTimeListArea = new TextArea(); + dateTimeListArea.setEditable(false); + dateTimeListArea.setId(DATETIMELIST_ID); + dateTimeListArea.getStyleClass().removeAll(); + dateTimeListArea.getStyleClass().add(DATE_TIME_LIST_AREA); + dateTimeListArea.setWrapText(true); + dateTimeListArea.setPrefSize(PREF_SIZE_WIDTH, PREF_SIZE_HEIGHT); + dateTimeListArea.textProperty().bind(dateTimeListdisplayed); + dateTimeListArea.autosize(); + + dateTimeListPane.getChildren().add(dateTimeListArea); + cardPane.getChildren().add(dateTimeListPane); + } + + private void setDateTimeList() { + ArrayList dateTimeList = rsvTask.getDateTimeList(); + Collections.sort(dateTimeList); + String toSet = Formatter.formatDateTimeList(dateTimeList); + dateTimeListdisplayed.setValue(toSet); + } + + public HBox getLayout() { + return cardPane; + } + + @Override + public void setNode(Node node) { + cardPane = (HBox) node; + dateTimeListPane = (AnchorPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \java\tars\ui\RsvTaskListPanel.java +``` java +/** + * UI Controller for panel containing the list of reserved tasks. + */ +public class RsvTaskListPanel extends UiPart { + + private static String LOG_MESSAGE_LAYOUT_UPDATING = + "Updating layout for %s"; + private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final String FXML = "RsvTaskListPanel.fxml"; + private static final int START_INDEX = 1; + + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView rsvTaskListView; + + public RsvTaskListPanel() { + 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 RsvTaskListPanel load(Stage primaryStage, + AnchorPane rsvTaskListPlaceholder, + ObservableList rsvTaskList) { + RsvTaskListPanel rsvTaskListPanel = UiPartLoader.loadUiPart( + primaryStage, rsvTaskListPlaceholder, new RsvTaskListPanel()); + rsvTaskListPanel.configure(rsvTaskList); + return rsvTaskListPanel; + } + + private void configure(ObservableList rsvTaskList) { + setConnections(rsvTaskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList rsvTaskList) { + rsvTaskListView.setItems(rsvTaskList); + rsvTaskListView.setCellFactory(listView -> new RsvTaskListViewCell()); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + rsvTaskListView.scrollTo(index); + }); + } + + class RsvTaskListViewCell extends ListCell { + private RsvTask newlyAddedRsvTask; + + public RsvTaskListViewCell() { + registerAsAnEventHandler(this); + } + + @Override + protected void updateItem(RsvTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + RsvTaskCard card = + RsvTaskCard.load(task, getIndex() + START_INDEX); + HBox layout = card.getLayout(); + if (this.newlyAddedRsvTask != null + && this.newlyAddedRsvTask.isSameStateAs(task)) { + layout.setStyle(UiColor.TASK_CARD_NEWLY_ADDED_BORDER); + } else { + layout.setStyle(UiColor.TASK_CARD_DEFAULT_BORDER); + } + setGraphic(layout); + } + } + + @Subscribe + private void handleRsvTaskAddedEvent(RsvTaskAddedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + String.format(LOG_MESSAGE_LAYOUT_UPDATING, + event.task.toString()))); + this.newlyAddedRsvTask = event.task; + } + } + +} +``` +###### \java\tars\ui\TaskCard.java +``` java +/** + * UI Controller for Task Card + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + private static final String STATUS_UNDONE = "Undone"; + private static final String STATUS_DONE = "Done"; + private static final String PRIORITY_HIGH = "h"; + private static final String PRIORITY_MEDIUM = "m"; + private static final String PRIORITY_LOW = "l"; + private static final String LABEL_HIGH = "H"; + private static final String LABEL_MEDIUM = "M"; + private static final String LABEL_LOW = "L"; + private static final String LABEL_DONE = "✔"; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label date; + @FXML + private Label circleLabel; + @FXML + private Label tags; + @FXML + private Circle priorityCircle; + + private ReadOnlyTask task; + private int displayedIndex; + + public TaskCard() { + + } + + public static TaskCard load(ReadOnlyTask task, int displayedIndex) { + TaskCard card = new TaskCard(); + card.task = task; + card.displayedIndex = displayedIndex; + card.registerAsAnEventHandler(card); + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + setName(); + setIndex(); + setDate(); + setTags(); + setCircle(); + setCardTextColorByStatus(); + } + + private void setName() { + name.setText(task.getName().taskName); + } + + private void setIndex() { + id.setText(displayedIndex + StringUtil.STRING_FULLSTOP); + } + + private void setDate() { + date.setText(DateFormatter.formatDate(task.getDateTime())); + } + + /** + * Sets UI for Task Card Circle + */ + private void setCircle() { + String priority = task.getPriority().priorityLevel; + String status = task.getStatus().toString(); + + Color circleColor = getColorBasedOnPriorityAndStatus(priority, status); + String label = getLabelBasedOnPriorityAndStatus(priority, status); + + priorityCircle.setFill(circleColor); + + circleLabel.setText(label); + circleLabel.setStyle(UiColor.CIRCLE_LABEL_COLOR); + } + +``` +###### \java\tars\ui\TaskCard.java +``` java + /** + * Gets color for circle based on task's priority and status + */ + private Color getColorBasedOnPriorityAndStatus(String priority, + String status) { + if (status.equals(STATUS_DONE)) { + return UiColor.CircleColor.DONE.getCircleColor(); + } else { + switch (priority) { + case PRIORITY_HIGH: + return UiColor.CircleColor.HIGH.getCircleColor(); + case PRIORITY_MEDIUM: + return UiColor.CircleColor.MEDIUM.getCircleColor(); + case PRIORITY_LOW: + return UiColor.CircleColor.LOW.getCircleColor(); + default: + return UiColor.CircleColor.NONE.getCircleColor(); + } + } + } + +``` +###### \java\tars\ui\TaskCard.java +``` java + /** + * Gets label for circle based on task's priority and status + */ + private String getLabelBasedOnPriorityAndStatus(String priority, + String status) { + if (status.equals(STATUS_DONE)) { + return LABEL_DONE; + } else { + switch (priority) { + case PRIORITY_HIGH: + return LABEL_HIGH; + case PRIORITY_MEDIUM: + return LABEL_MEDIUM; + case PRIORITY_LOW: + return LABEL_LOW; + default: + return StringUtil.EMPTY_STRING; + } + } + } + + /** + * Sets text to different color based on the status of a task + */ + private void setCardTextColorByStatus() { + String taskStatus = task.getStatus().toString(); + String color = StringUtil.EMPTY_STRING; + switch (taskStatus) { + case STATUS_UNDONE: + color = UiColor.STATUS_UNDONE_TEXT_FILL_DARK; + break; + case STATUS_DONE: + color = UiColor.STATUS_DONE_TEXT_FILL; + break; + } + id.setStyle(color); + name.setStyle(color); + date.setStyle(color); + tags.setStyle(color); + + if (taskStatus.equals(STATUS_UNDONE)) { + date.setStyle(UiColor.STATUS_UNDONE_TEXT_FILL_LIGHT); + tags.setStyle(UiColor.STATUS_UNDONE_TEXT_FILL_LIGHT); + } + } + + @Subscribe + private void handleTarsChangeEvent(TarsChangedEvent event) { + setCircle(); + setCardTextColorByStatus(); + } + + + private void setTags() { + tags.setText(task.tagsString()); + } + + public HBox getLayout() { + return cardPane; + } + + @Override + public void setNode(Node node) { + cardPane = (HBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + +} +``` +###### \java\tars\ui\TaskListPanel.java +``` java +/** + * UI Controller for panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static String LOG_MESSAGE_LAYOUT_UPDATING = + "Updating layout for %s"; + + private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final String FXML = "TaskListPanel.fxml"; + private static final int START_INDEX = 1; + + @FXML + private ListView taskListView; + @FXML + private VBox panel; + + private AnchorPane placeHolderPane; + + public static TaskListPanel load(Stage primaryStage, + AnchorPane taskListPlaceholder, + ObservableList taskList) { + TaskListPanel taskListPanel = UiPartLoader.loadUiPart(primaryStage, + taskListPlaceholder, new TaskListPanel()); + taskListPanel.configure(taskList); + return taskListPanel; + } + + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + }); + } + + class TaskListViewCell extends ListCell { + private ReadOnlyTask newlyAddedTask; + + public TaskListViewCell() { + registerAsAnEventHandler(this); + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + TaskCard card = TaskCard.load(task, getIndex() + START_INDEX); + HBox layout = card.getLayout(); + if (this.newlyAddedTask != null + && this.newlyAddedTask.isSameStateAs(task)) { + layout.setStyle(UiColor.TASK_CARD_NEWLY_ADDED_BORDER); + } else { + layout.setStyle(UiColor.TASK_CARD_DEFAULT_BORDER); + } + setGraphic(layout); + } + } + + @Subscribe + private void handleTaskAddedEvent(TaskAddedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + String.format(LOG_MESSAGE_LAYOUT_UPDATING, + event.task.toString()))); + this.newlyAddedTask = event.task; + } + } + +} +``` +###### \java\tars\ui\ThisWeekPanel.java +``` java +/** + * UI Controller for this week panel + */ +public class ThisWeekPanel extends UiPart { + + private static List list; + private static List upcomingTasks = + new ArrayList(); + private static List overduedTasks = + new ArrayList(); + + private static final String LOG_MESSAGE_UPDATE_THIS_WEEK_PANEL = + "Update this week panel"; + private static final Logger logger = + LogsCenter.getLogger(ThisWeekPanel.class); + private static final String FXML = "ThisWeekPanel.fxml"; + private static final String THISWEEK_PANEL_STYLE_SHEET = "thisWeek-panel"; + private static final String STATUS_UNDONE = "Undone"; + private static final String TASK_LIST_ELLIPSIS = "\n...\n"; + private static final DateFormat df = new SimpleDateFormat("E, MMM dd"); + private static final int MIN_SIZE = 5; + + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private Label date; + @FXML + private Label numUpcoming; + @FXML + private Label numOverdue; + @FXML + private Label overduedTasksList; + @FXML + private Label upcomingTasksList; + + public static ThisWeekPanel load(Stage primaryStage, + AnchorPane thisWeekPanelPlaceHolder, List taskList) { + ThisWeekPanel thisWeekPanel = UiPartLoader.loadUiPart(primaryStage, + thisWeekPanelPlaceHolder, new ThisWeekPanel()); + list = taskList; + thisWeekPanel.configure(); + return thisWeekPanel; + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void configure() { + panel.getStyleClass().add(THISWEEK_PANEL_STYLE_SHEET); + setDate(); + handleUpcomingTasks(); + handleOverdueTasks(); + addToPlaceholder(); + registerAsAnEventHandler(this); + } + + private void setDate() { + Date today = new Date(); + date.setText(df.format(today)); + } + + /** + * Updates number of upcoming tasks and lists them + */ + private void handleUpcomingTasks() { + int count = 0; + for (ReadOnlyTask t : list) { + if (DateTimeUtil.isWithinWeek(t.getDateTime().getEndDate()) + && t.getStatus().toString().equals(STATUS_UNDONE)) { + count++; + upcomingTasks.add(t); + } + } + numUpcoming.setText(String.valueOf(count)); + if (count == 0) { + upcomingTasksList.setText(StringUtil.EMPTY_STRING); + } else { + setThisWeekPanelTaskList(count, upcomingTasks, upcomingTasksList); + } + } + + /** + * Updates number of overdued tasks and lists them + */ + private void handleOverdueTasks() { + int count = 0; + for (ReadOnlyTask t : list) { + if (DateTimeUtil.isOverDue(t.getDateTime().getEndDate()) + && t.getStatus().toString().equals(STATUS_UNDONE)) { + count++; + overduedTasks.add(t); + } + } + numOverdue.setText(String.valueOf(count)); + if (count == 0) { + overduedTasksList.setText(StringUtil.EMPTY_STRING); + } else { + setThisWeekPanelTaskList(count, overduedTasks, overduedTasksList); + } + } + + /** + * Set text for tasksLists to display top five tasks + */ + private void setThisWeekPanelTaskList(int count, + List tasksList, Label taskListLabel) { + List topFiveTasks = tasksList.subList( + StringUtil.START_INDEX, Math.min(tasksList.size(), MIN_SIZE)); + String list = Formatter.formatThisWeekPanelTasksList(topFiveTasks); + if (tasksList.size() > MIN_SIZE) { + list = list + TASK_LIST_ELLIPSIS; + } + taskListLabel.setText(list); + } + + /** + * Updates panel with latest data + */ + private void updateThisWeekPanel() { + upcomingTasks.clear(); + handleUpcomingTasks(); + overduedTasks.clear(); + handleOverdueTasks(); + } + + @Subscribe + private void handleTarsChangedEvent(TarsChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_UPDATE_THIS_WEEK_PANEL)); + updateThisWeekPanel(); + } + + @Subscribe + private void handleTarsStorageChangeDirectoryEvent( + TarsStorageDirectoryChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_UPDATE_THIS_WEEK_PANEL)); + updateThisWeekPanel(); + } +} +``` +###### \java\tars\ui\UiColor.java +``` java + * + */ +public class UiColor { + + public static final String STATUS_UNDONE_TEXT_FILL_DARK = + "-fx-text-fill: #212121"; + public static final String STATUS_UNDONE_TEXT_FILL_LIGHT = + "-fx-text-fill: #757575"; + public static final String STATUS_DONE_TEXT_FILL = + "-fx-text-fill: lightgrey"; + public static final String CIRCLE_LABEL_COLOR = "-fx-text-fill: white;"; + public static final String TASK_CARD_NEWLY_ADDED_BORDER = + "-fx-border-color: #2E8AF7"; + public static final String TASK_CARD_DEFAULT_BORDER = + "-fx-border-color: lightgrey"; + + public enum CircleColor { + HIGH(Color.RED), MEDIUM(Color.ORANGE), LOW(Color.GREEN), DONE( + Color.LIGHTGREY), NONE(Color.TRANSPARENT); + private Color circleColor; + + CircleColor(Color circleColor) { + this.circleColor = circleColor; + } + + Color getCircleColor() { + return circleColor; + } + } +} +``` +###### \resources\view\TarsTheme.css +``` css + +/* + * ==================================================================== + * ============================Fonts=================================== + * ==================================================================== + */ + +@font-face { + font-family: Roboto; + src: url("../fonts/roboto/Roboto-Regular.ttf"); +} + +@font-face { + font-family: "Roboto Medium"; + src: url("../fonts/roboto/Roboto-Medium.ttf"); +} + +@font-face { + font-family: "Roboto Light"; + src: url("../fonts/roboto/Roboto-Light.ttf"); +} + +@font-face { + font-family: "Roboto Black"; + src: url("../fonts/roboto/Roboto-Black.ttf"); +} + +@font-face { + font-family: "Roboto Bold"; + src: url("../fonts/roboto/Roboto-Bold.ttf"); +} + +.root { + -fx-focus-color: transparent; +} + +.background { + -fx-background-color: #455A64; +} + +.list-label { + -fx-font-size: 18px; + -fx-text-fill: #212121; + -fx-background-color: white; + -fx-font-family: "Roboto Black"; +} + +.error { + -fx-background-color: #F44336; +} + +/* + * ==================================================================== + * ========================SplitPane=================================== + * ==================================================================== + */ + +.split-pane:horizontal .split-pane-divider { + -fx-border-color: transparent; + -fx-background-color: lightgrey; + -fx-padding: 0 0.1 0 0.1; +} + +.split-pane { + -fx-border-radius: 0; + -fx-border-width: 0; + -fx-background-color: white; +} + +/* + * ==================================================================== + * ==========================TabPane=================================== + * ==================================================================== + */ + +.tab-pane { + -fx-padding: 0 10 0 5; +} + +.tab-pane .tab { + -fx-background-color: white; + -fx-border-width: 0 0 2 0; +} + +.tab .tab-label { + -fx-font-size: 18px; + -fx-text-fill: #212121; + -fx-font-family: "Roboto Black"; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.tab:selected { + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-background-color: white; + -fx-border-width: 0 0 2 0; + -fx-border-color: transparent transparent #2E8AF7 transparent; +} + +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; +} + +.tab-pane *.tab-header-background { + -fx-background-color: white; + -fx-border-width: 1 0 1 0; +} + + +.thisWeek-panel { + -fx-background-color: white; +} + +.thisWeek-panel-label { + -fx-font-size: 24px; + -fx-text-fill: #212121; +} + +.date-label { + -fx-font-size: 32px; + -fx-text-fill: #212121; +} + +/* + * ==================================================================== + * =========================CommandBox================================= + * ==================================================================== + */ + +.text-field { + -fx-font-size: 15px; + -fx-border-color: transparent; + -fx-font-family: "Roboto"; + -fx-background-insets: 0; +} + +.text-field:focused { + -fx-focus-color: transparent; +} + +/* + * ==================================================================== + * ==============Results Display & RsvTask DateTimeList================ + * ==================================================================== + */ + + .anchor-pane { + -fx-background-color: #F6F6F6; +} + +.result-display { + -fx-background-color: #F6F6F6; + -fx-border-color: transparent; + -fx-focus-color: transparent; + -fx-background-insets: 0; +} + +.result-display .label { + -fx-text-fill: #212121; +} + +.text-area { + -fx-background-insets: 0; + -fx-background-color: transparent; +} + +.text-area .content { + -fx-background-color: #F6F6F6; + -fx-border-color: transparent; + -fx-background-insets: 0; +} + +.dateTimeListArea .content { + -fx-background-color: white; +} + +/* + * ==================================================================== + * ========ListView and ListCell for TaskList/RsvTaskList============== + * ==================================================================== + */ + +.taskList { + -fx-border-width: 1 0 0 0; + -fx-border-color: lightgrey; +} + +.list-view { + -fx-background-color: white; + -fx-background-insets: 0; + -fx-padding: 0px; +} + +#cardPane { + -fx-background-color: white; + -fx-border-color: lightgrey; + -fx-border-width: 0 0 2 0; +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:empty { + -fx-background-color: white; +} + +.cell_index_label { + -fx-font-size: 16px; + -fx-font-family: "Roboto Bold"; +} + +.cell_big_label { + -fx-font-size: 16px; + -fx-font-family: "Roboto"; + -fx-text-fill: #212121; +} + +.cell_medium_label { + -fx-font-size: 14px; + -fx-font-family: "Roboto"; + -fx-text-fill: #757575; +} + +.cell_small_label { + -fx-font-size: 14px; + -fx-text-fill: #757575; + -fx-font-family: "Roboto"; +} + +.circle_label { + -fx-font-weight: bold; + -fx-font-size: 16px; +} + + +/* + * ==================================================================== + * ===========================Status Bar=============================== + * ==================================================================== + */ + + .status-bar { + -fx-background-color: #2e8af7; + -fx-text-fill: white; +} + +.status-bar .label { + -fx-text-fill: white; + -fx-font-size: 12px; +} + +/* + * ==================================================================== + * ============================Dialog================================== + * ==================================================================== + */ + +.dialog-pane { + -fx-background-color: white; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: lightgrey; +} + +.dialog-pane > *.label.content { + -fx-font-size: 12px; + -fx-font-weight: bold; + -fx-text-fill: #212121; + -fx-font-family: "Roboto"; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: white; +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 12px; + -fx-font-style: italic; + -fx-fill: lightgrey; + -fx-text-fill: #212121; + -fx-font-family: "Roboto"; +} + +/* + * ==================================================================== + * =========================Scroll-Bar================================= + * ==================================================================== + */ + +.scroll-bar .thumb { + -fx-background-color: darkgrey; + -fx-background-insets: 2; +} + +.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 5 1 5; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 5 1 5 1; +} + +``` diff --git a/collated/main/A0124333U.md b/collated/main/A0124333U.md new file mode 100644 index 000000000000..8f829f0b4da6 --- /dev/null +++ b/collated/main/A0124333U.md @@ -0,0 +1,1673 @@ +# A0124333U +###### \java\tars\commons\core\KeyCombinations.java +``` java +/** + * Container for all key combinations used by program + */ +public class KeyCombinations { + + public static final KeyCombination KEY_COMB_CTRL_RIGHT_ARROW = + new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.CONTROL_DOWN); + public static final KeyCombination KEY_COMB_CTRL_LEFT_ARROW = + new KeyCodeCombination(KeyCode.LEFT, KeyCombination.CONTROL_DOWN); + + public static final KeyCombination KEY_COMB_CTRL_Z = + new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN); + public static final KeyCombination KEY_COMB_CTRL_Y = + new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN); + +} +``` +###### \java\tars\commons\events\storage\TarsStorageDirectoryChangedEvent.java +``` java +/** + * An event where the user changes the Tars Storage Directory/File Path + */ +public class TarsStorageDirectoryChangedEvent extends BaseEvent { + private static String MESSAGE_FILE_PATH_CHANGED = "File Path changed to %s"; + + private final String newFilePath; + private final Config newConfig; + + public TarsStorageDirectoryChangedEvent(String newFilePath, + Config newConfig) { + this.newFilePath = newFilePath; + this.newConfig = newConfig; + } + + public String getNewFilePath() { + return this.newFilePath; + } + + public Config getNewConfig() { + return this.newConfig; + } + + @Override + public String toString() { + return String.format(MESSAGE_FILE_PATH_CHANGED, this.newFilePath); + } + +} +``` +###### \java\tars\commons\events\ui\KeyCombinationPressedEvent.java +``` java +/** + * Indicates that the user has pressed a key combination + */ +public class KeyCombinationPressedEvent extends BaseEvent { + + private KeyCombination keyComb; + + public KeyCombinationPressedEvent(KeyCombination keyComb) { + this.keyComb = keyComb; + } + + public KeyCombination getKeyCombination() { + return keyComb; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\tars\commons\util\DateTimeUtil.java +``` java + /** + * Checks whether the dateTimeQuery falls within the range of the dateTimeSource i.e. + * dateTimeQuery startDateTime is equals to or before the dateTimeSource endDateTime && + * dateTimeQuery endDateTime is equals to or after the dateTimeSource startDateTime + * + * @param dateTimeSource + * @param dateTimeQuery + */ + public static boolean isDateTimeWithinRange(DateTime dateTimeSource, + DateTime dateTimeQuery) { + + // Return false if task is a floating task (i.e. no start or end + // dateTime + if (dateTimeSource.getEndDate() == null) { + return false; + } + + DateTime dateTime1 = fillDateTime(dateTimeSource); + DateTime dateTime2 = fillDateTime(dateTimeQuery); + + return !dateTime1.getEndDate().isBefore(dateTime2.getStartDate()) + && !dateTime1.getStartDate().isAfter(dateTime2.getEndDate()); + } + + /** + * Checks whether the dateTimeQuery conflicts with the dateTimeSource i.e. dateTimeQuery + * endDateTime occurs after the dateTimeSource startDateTime && dateTimeQuery startDateTime + * occurs before the dateTimeSource endDateTime + */ + public static boolean isDateTimeConflicting(DateTime dateTimeSource, + DateTime dateTimeQuery) { + + // Return false if task is a floating task (i.e. no start or end + // dateTime + if (dateTimeSource.getEndDate() == null) { + return false; + } + + DateTime dateTime1 = fillDateTime(dateTimeSource); + DateTime dateTime2 = fillDateTime(dateTimeQuery); + + return dateTime1.getEndDate().isAfter(dateTime2.getStartDate()) + && dateTime1.getStartDate().isBefore(dateTime2.getEndDate()); + } + + private static DateTime fillDateTime(DateTime filledDateTime) { + DateTime dateTimeToFill = new DateTime(); + + dateTimeToFill.setEndDateTime(filledDateTime.getEndDate()); + + if (filledDateTime.getStartDate() != null) { + dateTimeToFill.setStartDateTime(filledDateTime.getStartDate()); + } else { + dateTimeToFill.setStartDateTime(filledDateTime.getEndDate()); + } + return dateTimeToFill; + } + + + /** + * Returns an arraylist of free datetime slots in a specified date + */ + public static ArrayList getListOfFreeTimeSlotsInDate( + DateTime dateToCheck, + ArrayList listOfFilledTimeSlotsInDate) { + ArrayList listOfFreeTimeSlots = new ArrayList(); + LocalDateTime startDateTime = dateToCheck.getStartDate(); + LocalDateTime endDateTime; + + for (DateTime dt : listOfFilledTimeSlotsInDate) { + if (dt.getStartDate() == null) { + continue; + } else { + endDateTime = dt.getStartDate(); + } + + if (startDateTime.isBefore(endDateTime)) { + listOfFreeTimeSlots + .add(new DateTime(startDateTime, endDateTime)); + } + + if (startDateTime.isBefore(dt.getEndDate())) { + startDateTime = dt.getEndDate(); + } + } + + if (startDateTime.isBefore(dateToCheck.getEndDate())) { + listOfFreeTimeSlots + .add(new DateTime(startDateTime, dateToCheck.getEndDate())); + } + + return listOfFreeTimeSlots; + } + + + public static String getDayAndDateString(DateTime dateTime) { + StringBuilder sb = new StringBuilder(); + + sb.append(dateTime.getEndDate().getDayOfWeek() + .getDisplayName(TextStyle.FULL, Locale.ENGLISH)) + .append(StringUtil.STRING_COMMA).append(dateTime.getEndDate() + .format(stringFormatterWithoutTime)); + + return sb.toString(); + } + + + public static String getStringOfFreeDateTimeInDate(DateTime dateToCheck, + ArrayList listOfFreeTimeSlotsInDate) { + StringBuilder sb = new StringBuilder(); + + sb.append(getDayAndDateString(dateToCheck)) + .append(StringUtil.STRING_COLON); + + int counter = 1; + + for (DateTime dt : listOfFreeTimeSlotsInDate) { + sb.append(String.format(MESSAGE_FREE_TIME_SLOT, counter, + dt.getStartDate().format(stringFormatterWithoutDate), + dt.getEndDate().format(stringFormatterWithoutDate), + getDurationBetweenTwoLocalDateTime(dt.getStartDate(), + dt.getEndDate()))); + counter++; + } + + return sb.toString(); + } + + public static String getDurationBetweenTwoLocalDateTime( + LocalDateTime startDateTime, LocalDateTime endDateTime) { + Duration duration = Duration.between(startDateTime, endDateTime); + long hours = duration.toHours(); + long minutes = duration.toMinutes() % 60; + + return String.format(MESSAGE_DURATION, hours, minutes); + } + +``` +###### \java\tars\logic\commands\CdCommand.java +``` java +/** + * Changes the directory of the Tars storage file, tars.xml + */ +public class CdCommand extends Command { + + public static final String COMMAND_WORD = "cd"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Changes the directory of the " + + "TARS storage file.\n" + "Parameters: \n" + + "Example: " + COMMAND_WORD + " data/tars.xml"; + + public static final String MESSAGE_INVALID_FILEPATH = + "Invalid file path. File paths should end with the file type .xml \n" + + "Example: " + COMMAND_WORD + " data/tars.xml"; + + public static final String MESSAGE_SUCCESS_NEW_FILE = + "Change Directory Success! New file created! Directory of TARS storage file" + + " changed to: '%1$s'."; + + public static final String MESSAGE_SUCCESS_EXISTING_FILE = + "Change Directory Success! File read successfully! Directory of TARS storage " + + "file changed to : '%1$s'."; + + public static final String MESSAGE_FAILURE_WRITE_FILE = + "Unable to write to location, please choose another directory"; + + public static final String MESSAGE_FAILURE_READ_FILE = + "Unable to read from location, please choose another directory"; + + private static final String xmlFileExt = "xml"; + + private final String newFilePath; + private Storage storageUpdater = new StorageManager(); + private Config newConfig = new Config(); + + public CdCommand(String filepath) { + this.newFilePath = filepath; + } + + public final static String getXmlFileExt() { + return xmlFileExt; + } + + @Override + public String toString() { + return this.newFilePath; + } + + @Override + public CommandResult execute() { + + newConfig.setTarsFilePath(newFilePath); + File file = new File(newFilePath); + + EventsCenter.getInstance().post(new ScrollToTopEvent()); + return FileUtil.isFileExists(file) ? readTarsFromNewFilePath() + : saveTarsToNewFilePath(); + } + + private CommandResult saveTarsToNewFilePath() { + try { + // try to save TARS data into new file + storageUpdater.saveTarsInNewFilePath(model.getTars(), newFilePath); + if (storageUpdater.isFileSavedSuccessfully(newFilePath)) { + updateTarsSystemWithNewFilePath(); + return new CommandResult( + String.format(MESSAGE_SUCCESS_NEW_FILE, newFilePath)); + } else { + return new CommandResult(MESSAGE_FAILURE_WRITE_FILE); + } + } catch (IOException ioe) { + return new CommandResult(MESSAGE_FAILURE_WRITE_FILE); + } + + } + + private CommandResult readTarsFromNewFilePath() { + Optional tarsOptional; + ReadOnlyTars tarsDataToOverwrite; + try { + tarsOptional = storageUpdater.readTarsFromNewFilePath(newFilePath); + + tarsDataToOverwrite = tarsOptional.orElse(null); + + if (tarsDataToOverwrite != null) { + model.overwriteDataFromNewFilePath(tarsDataToOverwrite); + updateTarsSystemWithNewFilePath(); + return new CommandResult(String + .format(MESSAGE_SUCCESS_EXISTING_FILE, newFilePath)); + } else { + return new CommandResult(MESSAGE_FAILURE_READ_FILE); + } + } catch (DataConversionException | IOException e) { + return new CommandResult(MESSAGE_FAILURE_READ_FILE); + } + + } + + private void updateTarsSystemWithNewFilePath() throws IOException { + storageUpdater.updateTarsStorageDirectory(newFilePath, newConfig); + ConfigUtil.updateConfig(newConfig); + } + +} +``` +###### \java\tars\logic\commands\ConfirmCommand.java +``` java +/** + * Confirms a specified datetime for a reserved task and add it into the task list + */ +public class ConfirmCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "confirm"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Confirms a datetime for a reserved task" + + " and adds it to the task list.\n" + + "Parameters: [/p PRIORITY] [/t TAG_NAME ...]\n" + + "Example: " + COMMAND_WORD + " 1 3 /p h /t tag"; + + public static final String MESSAGE_CONFIRM_SUCCESS = + "Task Confirmation Success! New task added: %1$s"; + + private final int taskIndex; + private final int dateTimeIndex; + private final String priority; + private final Set tagSet = new HashSet<>(); + + private String conflictingTaskList = StringUtil.EMPTY_STRING; + private Task toConfirm; + private RsvTask rsvTask; + + public ConfirmCommand(int taskIndex, int dateTimeIndex, String priority, + Set tags) throws IllegalValueException { + this.taskIndex = taskIndex; + this.dateTimeIndex = dateTimeIndex; + this.priority = priority; + + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + } + + @Override + public CommandResult execute() { + assert model != null; + UnmodifiableObservableList lastShownList = + model.getFilteredRsvTaskList(); + + if (lastShownList.size() < taskIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult( + Messages.MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX); + } + + rsvTask = lastShownList + .get(taskIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + + if (rsvTask.getDateTimeList().size() < dateTimeIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult( + Messages.MESSAGE_INVALID_DATETIME_DISPLAYED_INDEX); + } + + try { + toConfirm = new Task(rsvTask.getName(), rsvTask.getDateTimeList() + .get((dateTimeIndex - StringUtil.DISPLAYED_INDEX_OFFSET)), + new Priority(priority), new Status(), + new UniqueTagList(tagSet)); + } catch (IllegalValueException ive) { + return new CommandResult(String + .format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + try { + model.deleteRsvTask(rsvTask); + } catch (RsvTaskNotFoundException rtnfe) { + return new CommandResult(Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND); + } + + try { + conflictingTaskList += + model.getTaskConflictingDateTimeWarningMessage( + toConfirm.getDateTime()); + model.addTask(toConfirm); + } catch (DuplicateTaskException e) { + return new CommandResult(Messages.MESSAGE_DUPLICATE_TASK); + } + + model.getUndoableCmdHist().push(this); + return new CommandResult(getSuccessMessageSummary()); + } + + private String getSuccessMessageSummary() { + String summary = + String.format(MESSAGE_CONFIRM_SUCCESS, toConfirm.toString()); + + if (!conflictingTaskList.isEmpty()) { + summary += StringUtil.STRING_NEWLINE + + Messages.MESSAGE_CONFLICTING_TASKS_WARNING + + conflictingTaskList; + } + + return summary; + } + +``` +###### \java\tars\logic\commands\FindCommand.java +``` java +/** + * Finds and lists all tasks 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 tasks containing a list of keywords (i.e. AND search)." + + "keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters (Quick Search Mode): [KEYWORD ...]\n" + + "Parameters (Filter Search Mode): [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]\n" + + "Examples (Quick Serach Mode): " + COMMAND_WORD + + " CS2103 projects" + "Examples (Filter Search Mode): " + + COMMAND_WORD + " /n CS2103 projects /dt 10/09/2016 1000 to " + + "20/09/2016 0100 /t school projects /do"; + + private static String MESSAGE_QUICK_SEARCH_KEYWORDS = + "\nQuick Search Keywords: %s"; + + private TaskQuery taskQuery = null; + private ArrayList quickSearchKeywords = null; + private String searchKeywords = ""; + + public FindCommand(TaskQuery taskQuery) { + this.taskQuery = taskQuery; + } + + public FindCommand(ArrayList quickSearchKeywords) { + this.quickSearchKeywords = quickSearchKeywords; + } + + @Override + public CommandResult execute() { + if (isFilterSearchModeUsed()) { + model.updateFilteredTaskListUsingFlags(taskQuery); + searchKeywords = StringUtil.STRING_NEWLINE + taskQuery.toString(); + } + + if (isQuickSearchModeUsed()) { + model.updateFilteredTaskListUsingQuickSearch(quickSearchKeywords); + searchKeywords = String.format(MESSAGE_QUICK_SEARCH_KEYWORDS, + quickSearchKeywords.toString()); + } + EventsCenter.getInstance().post(new ScrollToTopEvent()); + return new CommandResult(getMessageForTaskListShownSummary( + model.getFilteredTaskList().size()) + searchKeywords); + } + + private Boolean isFilterSearchModeUsed() { + return taskQuery != null; + } + + private Boolean isQuickSearchModeUsed() { + return quickSearchKeywords != null; + } + +} +``` +###### \java\tars\logic\commands\FreeCommand.java +``` java + * + * Suggests free time slots on a specified date + */ +public class FreeCommand extends Command { + + public static final String COMMAND_WORD = "free"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Suggests free timeslots in a specified day.\n" + + "Parameters: \n" + "Example: free 29/10/2016"; + + public static final String MESSAGE_DATE_RANGE_DETECTED = + "Range of datetime detected. Please only input a single datetime"; + public static final String MESSAGE_SUCCESS = "Free timeslots on %1$s"; + public static final String MESSAGE_FREE_DAY = + "You have no event tasks or reserved event tasks on %1$s"; + public static final String MESSAGE_NO_FREE_TIMESLOTS = + "You have no free time slots on %1$s"; + + private DateTime dateToCheck; + + public FreeCommand(DateTime dateToCheck) { + this.dateToCheck = dateToCheck; + + // Ensure that dateToCheck covers the whole day + this.dateToCheck.setStartDateTime(dateToCheck.getEndDate() + .withHour(DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY) + .withMinute(DateTimeUtil.DATETIME_FIRST_MINUTE_OF_DAY) + .withSecond(DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY)); + + this.dateToCheck.setEndDateTime(dateToCheck.getEndDate() + .withHour(DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY) + .withMinute(DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY) + .withSecond(DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY)); + } + + @Override + public CommandResult execute() { + ArrayList listOfFilledTimeSlots = + model.getListOfFilledTimeSlotsInDate(dateToCheck); + ArrayList listOfFreeTimeSlots = + DateTimeUtil.getListOfFreeTimeSlotsInDate(dateToCheck, + listOfFilledTimeSlots); + + model.updateFilteredTaskListUsingDate(dateToCheck); + + if (listOfFilledTimeSlots.isEmpty()) { + return new CommandResult(String.format(MESSAGE_FREE_DAY, + DateTimeUtil.getDayAndDateString(dateToCheck))); + } else if (listOfFreeTimeSlots.isEmpty()) { + return new CommandResult(String.format(MESSAGE_NO_FREE_TIMESLOTS, + DateTimeUtil.getDayAndDateString(dateToCheck))); + } else { + return new CommandResult(String.format(MESSAGE_SUCCESS, + DateTimeUtil.getStringOfFreeDateTimeInDate(dateToCheck, + listOfFreeTimeSlots))); + } + } + +} +``` +###### \java\tars\logic\commands\RsvCommand.java +``` java +/** + * Adds a reserved task which has a list of reserved date times that can confirmed later on. + */ +public class RsvCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "rsv"; + public static final String COMMAND_WORD_DEL = "rsv /del"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reserves one or more timeslot for a task.\n" + + "Parameters: [/dt DATETIME ...]\n" + + "Example: " + COMMAND_WORD + + " Meet John Doe /dt 26/09/2016 0900 to 1030, 28/09/2016 1000 to 1130"; + + public static final String MESSAGE_USAGE_DEL = COMMAND_WORD_DEL + + ": Deletes a reserved task in the last reserved task listing \n" + + "Parameters: INDEX (must be a positive integer)\n " + "Example: " + + COMMAND_WORD_DEL + " 1\n" + "OR " + COMMAND_WORD_DEL + " 1..3"; + + public static final String MESSAGE_DATETIME_NOT_FOUND = + "At least one DateTime is required!\n" + MESSAGE_USAGE; + + public static final String MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX = + "The Reserved Task Index is invalid!"; + + public static final String MESSAGE_SUCCESS = "New task reserved: %1$s"; + public static final String MESSAGE_SUCCESS_DEL = + "Deleted reserved tasks:\n%1$s"; + public static final String MESSAGE_UNDO_DELETE = "Deleted %1$s"; + public static final String MESSAGE_UNDO_ADD = "Added:\n%1$s"; + public static final String MESSAGE_REDO_DELETE = "Deleted:%1$s"; + public static final String MESSAGE_REDO_ADD = "Added %1$s"; + + private static final String MESSAGE_CONFLICT_FOR = "\nConflicts for "; + private static final int INDEX_OF_ENDDATE = 1; + private static final int INDEX_OF_STARTDATE = 0; + + private RsvTask toReserve = null; + private String rangeIndexString = ""; + private String conflictingTaskList = ""; + private ArrayList rsvTasksToDelete; + + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + * @throws DateTimeException if given dateTime string is invalid. + */ + public RsvCommand(String name, Set dateTimeStringSet) + throws IllegalValueException { + + Set dateTimeSet = new HashSet<>(); + for (String[] dateTimeStringArray : dateTimeStringSet) { + dateTimeSet + .add(new DateTime(dateTimeStringArray[INDEX_OF_STARTDATE], + dateTimeStringArray[INDEX_OF_ENDDATE])); + } + + this.toReserve = new RsvTask(new Name(name), + new ArrayList(dateTimeSet)); + } + + public RsvCommand(String rangeIndexString) { + this.rangeIndexString = rangeIndexString; + } + + @Override + public CommandResult execute() { + assert model != null; + + if (toReserve != null) { + return addRsvTask(); + } else { + return delRsvTask(); + } + + } + + private CommandResult addRsvTask() { + try { + for (DateTime dt : toReserve.getDateTimeList()) { + if (!model.getTaskConflictingDateTimeWarningMessage(dt) + .isEmpty()) { + conflictingTaskList += MESSAGE_CONFLICT_FOR + dt.toString() + + StringUtil.STRING_COLON; + conflictingTaskList += + model.getTaskConflictingDateTimeWarningMessage(dt); + } + } + model.addRsvTask(toReserve); + model.getUndoableCmdHist().push(this); + return new CommandResult(getSuccessMessageSummary()); + } catch (DuplicateTaskException e) { + return new CommandResult(Messages.MESSAGE_DUPLICATE_TASK); + } + } + + private CommandResult delRsvTask() { + rsvTasksToDelete = new ArrayList(); + + try { + rsvTasksToDelete = getRsvTasksFromIndexes( + this.rangeIndexString.split(StringUtil.STRING_WHITESPACE)); + } catch (InvalidTaskDisplayedException itde) { + return new CommandResult(itde.getMessage()); + } + + for (RsvTask t : rsvTasksToDelete) { + try { + model.deleteRsvTask(t); + } catch (RsvTaskNotFoundException rtnfe) { + return new CommandResult( + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND); + } + } + + model.getUndoableCmdHist().push(this); + String deletedRsvTasksList = + new Formatter().formatRsvTaskList(rsvTasksToDelete); + return new CommandResult( + String.format(MESSAGE_SUCCESS_DEL, deletedRsvTasksList)); + } + + /** + * Gets Tasks to delete + * + * @param indexes + * @throws InvalidTaskDisplayedException + */ + private ArrayList getRsvTasksFromIndexes(String[] indexes) + throws InvalidTaskDisplayedException { + UnmodifiableObservableList lastShownList = + model.getFilteredRsvTaskList(); + ArrayList rsvTasksList = new ArrayList(); + + for (int i = StringUtil.START_INDEX; i < indexes.length; i++) { + int targetIndex = Integer.parseInt(indexes[i]); + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new InvalidTaskDisplayedException( + Messages.MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX); + } + RsvTask rsvTask = + lastShownList.get(targetIndex - StringUtil.LAST_INDEX); + rsvTasksList.add(rsvTask); + } + return rsvTasksList; + } + + private String getSuccessMessageSummary() { + String summary = String.format(MESSAGE_SUCCESS, toReserve.toString()); + + if (!conflictingTaskList.isEmpty()) { + summary += StringUtil.STRING_NEWLINE + + Messages.MESSAGE_CONFLICTING_TASKS_WARNING + + conflictingTaskList; + } + + return summary; + } + +``` +###### \java\tars\logic\parser\ConfirmCommandParser.java +``` java +/** + * Confirm command parser + */ +public class ConfirmCommandParser extends CommandParser { + + private static final int EXPECTED_INDEX_STRING_ARRAY_LENGTH = 2; + private static final int INDEX_OF_TASK = 0; + private static final int INDEX_OF_DATETIME = 1; + + @Override + public Command prepareCommand(String args) { + // there is no arguments + if (args.trim().isEmpty()) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } + + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(priorityPrefix, tagPrefix); + argsTokenizer.tokenize(args); + + int taskIndex; + int dateTimeIndex; + + try { + String indexArgs = argsTokenizer.getPreamble().get(); + String[] indexStringArray = StringUtil.indexString(indexArgs) + .split(StringUtil.STRING_WHITESPACE); + if (indexStringArray.length != EXPECTED_INDEX_STRING_ARRAY_LENGTH) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } else { + taskIndex = Integer.parseInt(indexStringArray[INDEX_OF_TASK]); + dateTimeIndex = + Integer.parseInt(indexStringArray[INDEX_OF_DATETIME]); + } + } catch (IllegalValueException | NoSuchElementException e) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } catch (InvalidRangeException ire) { + return new IncorrectCommand(ire.getMessage()); + } + + try { + return new ConfirmCommand(taskIndex, dateTimeIndex, + argsTokenizer.getValue(priorityPrefix) + .orElse(StringUtil.EMPTY_STRING), + argsTokenizer.getMultipleValues(tagPrefix) + .orElse(new HashSet<>())); + } catch (IllegalValueException ive) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\tars\logic\parser\FreeCommandParser.java +``` java +/** + * Free command parser + */ +public class FreeCommandParser extends CommandParser { + + public static final int FIRST_DATETIME_INDEX = 0; + public static final int SECOND_DATETIME_INDEX = 1; + + @Override + public Command prepareCommand(String args) { + args = args.trim(); + + if (args.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + } + + String[] dateTimeStringArray = {StringUtil.EMPTY_STRING}; + + try { + dateTimeStringArray = DateTimeUtil.parseStringToDateTime(args); + } catch (DateTimeException dte) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + } + + if (!dateTimeStringArray[FIRST_DATETIME_INDEX].isEmpty()) { + return new IncorrectCommand( + FreeCommand.MESSAGE_DATE_RANGE_DETECTED); + } else { + try { + return new FreeCommand(new DateTime(dateTimeStringArray[FIRST_DATETIME_INDEX], + dateTimeStringArray[SECOND_DATETIME_INDEX])); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } catch (IllegalDateException ide) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } + } + } + +} +``` +###### \java\tars\logic\parser\RsvCommandParser.java +``` java +/** + * Reserve command parser + */ +public class RsvCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + // there is no arguments + if (args.trim().isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, RsvCommand.MESSAGE_USAGE)); + } + + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(dateTimePrefix, deletePrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.getValue(deletePrefix).isPresent()) { + return prepareRsvDel(argsTokenizer); + } else { + return prepareRsvAdd(argsTokenizer); + } + } + + // Parses arguments for adding a reserved task + private Command prepareRsvAdd(ArgumentTokenizer argsTokenizer) { + if (!argsTokenizer.getValue(dateTimePrefix).isPresent()) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_DATETIME_NOT_FOUND)); + } + + Set dateTimeStringSet = new HashSet<>(); + + try { + for (String dateTimeString : argsTokenizer + .getMultipleValues(dateTimePrefix).get()) { + dateTimeStringSet.add( + DateTimeUtil.parseStringToDateTime(dateTimeString)); + } + + return new RsvCommand(argsTokenizer.getPreamble().get(), + dateTimeStringSet); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } catch (NoSuchElementException nse) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, RsvCommand.MESSAGE_USAGE)); + } + } + + // Parses arguments for deleting one or more reserved tasks + private Command prepareRsvDel(ArgumentTokenizer argsTokenizer) { + try { + if (argsTokenizer.getPreamble().isPresent()) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE_DEL)); + } + + String rangeIndex = StringUtil + .indexString(argsTokenizer.getValue(deletePrefix).get()); + return new RsvCommand(rangeIndex); + } catch (InvalidRangeException | IllegalValueException ie) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE_DEL)); + } + } + +} +``` +###### \java\tars\model\Model.java +``` java + /** + * Overwrites current data with data from a new file path. + */ + public void overwriteDataFromNewFilePath(ReadOnlyTars newData); + +``` +###### \java\tars\model\Model.java +``` java + /** + * Deletes the reserved task. + */ + void deleteRsvTask(RsvTask target) throws RsvTaskNotFoundException; + + /** Adds the given reserved task */ + void addRsvTask(RsvTask rsvTask) throws DuplicateTaskException; + + /** Checks for tasks with conflicting datetime and returns a string of all conflicting tasks */ + String getTaskConflictingDateTimeWarningMessage(DateTime dateTimeToCheck); + +``` +###### \java\tars\model\Model.java +``` java + /** + * Updates the filter of the filtered task list to filter by the given keywords of each given + * task attribute + */ + void updateFilteredTaskListUsingFlags(TaskQuery taskQuery); + + /** + * Updates the filter of the filtered task list to filter by the given keywords of a string + * consisting of all the attributes of each task + */ + void updateFilteredTaskListUsingQuickSearch( + ArrayList lazySearchKeywords); + +``` +###### \java\tars\model\Model.java +``` java + /** + * Returns an ArrayList of DateTime in a specified date + */ + public ArrayList getListOfFilledTimeSlotsInDate( + DateTime dateToCheck); + +``` +###### \java\tars\model\ModelManager.java +``` java + /** + * Returns a string of tasks and rsv tasks whose datetime conflicts with a specified datetime + */ + public String getTaskConflictingDateTimeWarningMessage( + DateTime dateTimeToCheck) { + StringBuilder conflictingTasksStringBuilder = + new StringBuilder(StringUtil.EMPTY_STRING); + + if (dateTimeToCheck.getEndDate() == null) { + return StringUtil.EMPTY_STRING; + } + + appendConflictingTasks(conflictingTasksStringBuilder, dateTimeToCheck); + appendConflictingRsvTasks(conflictingTasksStringBuilder, + dateTimeToCheck); + + return conflictingTasksStringBuilder.toString(); + } + + private void appendConflictingTasks( + StringBuilder conflictingTasksStringBuilder, + DateTime dateTimeToCheck) { + + int taskCount = 1; + for (ReadOnlyTask t : tars.getTaskList()) { + + if (t.getStatus().status == Status.UNDONE && DateTimeUtil + .isDateTimeConflicting(t.getDateTime(), dateTimeToCheck)) { + conflictingTasksStringBuilder.append(String + .format(CONFLICTING_TASK, taskCount, t.getAsText())); + taskCount++; + } + } + } + + private void appendConflictingRsvTasks( + StringBuilder conflictingTasksStringBuilder, + DateTime dateTimeToCheck) { + + int rsvCount = 1; + + for (RsvTask rt : tars.getRsvTaskList()) { + if (rt.getDateTimeList().stream() + .filter(dateTimeSource -> DateTimeUtil + .isDateTimeConflicting(dateTimeSource, + dateTimeToCheck)) + .count() > 0) { + conflictingTasksStringBuilder.append(String + .format(CONFLICTING_RSV_TASK, rsvCount, rt.toString())); + rsvCount++; + + } + } + + } + + /** + * Returns a sorted arraylist of filled datetime slots in a specified date Datetimes with no + * startdate are not added into the list + */ + public ArrayList getListOfFilledTimeSlotsInDate( + DateTime dateToCheck) { + ArrayList listOfDateTime = new ArrayList(); + + addTimeSlotsFromTasks(listOfDateTime, dateToCheck); + addTimeSlotsFromRsvTasks(listOfDateTime, dateToCheck); + + Collections.sort(listOfDateTime); + + return listOfDateTime; + } + + private void addTimeSlotsFromTasks(ArrayList listOfDateTime, + DateTime dateToCheck) { + for (ReadOnlyTask t : tars.getTaskList()) { + if (t.getStatus().status == Status.UNDONE + && t.getDateTime().getStartDate() != null + && DateTimeUtil.isDateTimeWithinRange(t.getDateTime(), + dateToCheck)) { + listOfDateTime.add(t.getDateTime()); + } + } + } + + private void addTimeSlotsFromRsvTasks(ArrayList listOfDateTime, + DateTime dateToCheck) { + + for (RsvTask rt : tars.getRsvTaskList()) { + for (DateTime dt : rt.getDateTimeList()) { + if (dt.getStartDate() != null && DateTimeUtil + .isDateTimeWithinRange(dt, dateToCheck)) { + listOfDateTime.add(dt); + } + } + } + } + + +``` +###### \java\tars\model\qualifiers\FlagSearchQualifier.java +``` java + +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; +import tars.model.task.TaskQuery; + +public class FlagSearchQualifier implements Qualifier { + + private TaskQuery taskQuery; + + public FlagSearchQualifier(TaskQuery taskQuery) { + this.taskQuery = taskQuery; + } + + @Override + public boolean run(ReadOnlyTask task) { + + return isNameFound(task) && isDateTimeFound(task) + && isPriorityFound(task) && isStatusFound(task) + && isTagFound(task); + } + + private Boolean isNameFound(ReadOnlyTask task) { + if (taskQuery.getNameKeywordsAsList().get(StringUtil.START_INDEX) + .isEmpty()) { + return true; + } else { + return taskQuery.getNameKeywordsAsList().stream() + .filter(keyword -> StringUtil.containsIgnoreCase( + task.getName().taskName, keyword)) + .count() == taskQuery.getNameKeywordsAsList().size(); + } + } + + private Boolean isDateTimeFound(ReadOnlyTask task) { + if (taskQuery.getDateTimeQueryRange() == null) { + return true; + } else { + return DateTimeUtil.isDateTimeWithinRange(task.getDateTime(), + taskQuery.getDateTimeQueryRange()); + } + } + + private Boolean isPriorityFound(ReadOnlyTask task) { + if (taskQuery.getPriorityKeywordsAsList().get(StringUtil.START_INDEX) + .isEmpty()) { + return true; + } else { + return taskQuery.getPriorityKeywordsAsList().stream() + .filter(keyword -> StringUtil + .containsIgnoreCase(task.priorityString(), keyword)) + .count() == taskQuery.getPriorityKeywordsAsList().size(); + } + } + + private Boolean isStatusFound(ReadOnlyTask task) { + if (taskQuery.getStatusQuery().isEmpty()) { + return true; + } else { + return taskQuery.getStatusQuery() == task.getStatus().toString(); + } + } + + private Boolean isTagFound(ReadOnlyTask task) { + if (taskQuery.getTagKeywordsAsList().get(StringUtil.START_INDEX) + .isEmpty()) { + return true; + } else { + String stringOfTags = task.tagsString() + .replace(StringUtil.STRING_COMMA.trim(), + StringUtil.EMPTY_STRING) + .replace(StringUtil.STRING_SQUARE_BRACKET_OPEN, + StringUtil.EMPTY_STRING) + .replace(StringUtil.STRING_SQUARE_BRACKET_CLOSE, + StringUtil.EMPTY_STRING); + return taskQuery.getTagKeywordsAsList().stream() + .filter(keyword -> StringUtil + .containsIgnoreCase(stringOfTags, keyword)) + .count() == taskQuery.getTagKeywordsAsList().size(); + } + } + +} +``` +###### \java\tars\model\qualifiers\QuickSearchQualifier.java +``` java + +import java.util.ArrayList; + +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; + +public class QuickSearchQualifier implements Qualifier { + + private static final String LABEL_TAGS = "Tags: "; + private static final String LABEL_STATUS = "Status: "; + private static final String LABEL_PRIORITY = "Priority: "; + private static final String LABEL_DATETIME = "DateTime: "; + private final ArrayList quickSearchKeywords; + + public QuickSearchQualifier(ArrayList quickSearchKeywords) { + this.quickSearchKeywords = quickSearchKeywords; + } + + private String removeLabels(String taskAsString) { + String editedString = taskAsString + .replace(StringUtil.STRING_SQUARE_BRACKET_OPEN, + StringUtil.EMPTY_STRING) + .replace(StringUtil.STRING_SQUARE_BRACKET_CLOSE, + StringUtil.STRING_WHITESPACE) + .replace(LABEL_DATETIME, StringUtil.EMPTY_STRING) + .replace(LABEL_PRIORITY, StringUtil.EMPTY_STRING) + .replace(LABEL_STATUS, StringUtil.EMPTY_STRING) + .replace(LABEL_TAGS, StringUtil.EMPTY_STRING); + return editedString; + } + + @Override + public boolean run(ReadOnlyTask task) { + String taskAsString = removeLabels(task.getAsText()); + return quickSearchKeywords.stream().filter( + keyword -> StringUtil.containsIgnoreCase(taskAsString, keyword)) + .count() == quickSearchKeywords.size(); + } + +} +``` +###### \java\tars\model\Tars.java +``` java + /** + * Adds a reserved task to tars. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent reserved task already exists. + */ + public void addRsvTask(RsvTask rt) throws DuplicateTaskException { + rsvTasks.add(rt); + } + +``` +###### \java\tars\model\task\rsv\RsvTask.java +``` java +/** + * A task that has unconfirmed, reserved dates. + */ +public class RsvTask { + + private static String RSV_TASK_STRING = "%1$s DateTime: %2$s"; + + protected Name name; + protected ArrayList dateTimeList = new ArrayList(); + + public RsvTask() { + + } + + public RsvTask(Name name, ArrayList dateTimeList) { + assert !CollectionUtil.isAnyNull(name, dateTimeList); + + this.name = name; + this.dateTimeList = dateTimeList; + + } + + /** + * Copy constructor. + */ + public RsvTask(RsvTask source) { + this(source.getName(), source.getDateTimeList()); + } + + /* + * Accessors + */ + + public Name getName() { + return name; + } + + public ArrayList getDateTimeList() { + return dateTimeList; + } + + /* + * Mutators + */ + public void setName(Name name) { + this.name = name; + } + + public void setDateTimeList(ArrayList dateTimeList) { + this.dateTimeList = dateTimeList; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RsvTask // instanceof handles nulls + && this.isSameStateAs((RsvTask) other)); + } + + public boolean isSameStateAs(RsvTask other) { + return other == this // short circuit if same object + || (other != null // this is first to avoid NPE below + && other.getName().equals(this.getName()) + && other.getDateTimeList() + .equals(this.getDateTimeList())); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing + // your own + return Objects.hash(name, dateTimeList); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(String.format(RSV_TASK_STRING, getName(), + getDateTimeList().toString())); + + return builder.toString(); + } + +} +``` +###### \java\tars\model\task\rsv\UniqueRsvTaskList.java +``` java +/** + * A list of reserved tasks that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see RsvTask#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueRsvTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * 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 RsvTaskNotFoundException extends Exception {} + + /** + * Constructs empty RsvTaskList. + */ + public UniqueRsvTaskList() { + } + + /** + * Returns true if the list contains an equivalent reserved task as the given argument. + */ + public boolean contains(RsvTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Adds a reserved task to the list. + * + * @throws DuplicateTaskException if the reserved task to add is a duplicate of an existing + * reserved task in the list. + */ + public void add(RsvTask toAdd) throws DuplicateTaskException { + assert toAdd != null; + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent reserved task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public boolean remove(RsvTask toRemove) throws RsvTaskNotFoundException { + assert toRemove != null; + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new RsvTaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + 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 UniqueRsvTaskList // instanceof handles nulls + && this.internalList.equals( + ((UniqueRsvTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} +``` +###### \java\tars\model\task\TaskQuery.java +``` java +public class TaskQuery extends Task { + + public final static String MESSAGE_BOTH_STATUS_SEARCHED_ERROR = + "Both '-do (Done)' and '-ud (Undone)' flags " + + "have been detected.\n" + + "Please search for either '-do (Done)' or '-ud (Undone)' status"; + + private String tagQuery = ""; + private String statusString = ""; + + public TaskQuery() {} + + public TaskQuery(Name name, DateTime dateTime, Priority priority, + Status status, UniqueTagList tags) { + super(name, dateTime, priority, status, tags); + } + + /* --------------- SETTER METHODS -------------------- */ + + public void createNameQuery(String nameQueryString) + throws IllegalValueException { + name = new Name(nameQueryString); + } + + public void createDateTimeQuery(String[] dateTimeQueryString) + throws DateTimeException, IllegalDateException { + dateTime = new DateTime(dateTimeQueryString[0], dateTimeQueryString[1]); + } + + public void createPriorityQuery(String priorityString) + throws IllegalValueException { + + /* + * To convert long versions of priority strings (i.e. high, medium, low) into characters + * (i.e. h, m, l) + */ + switch (priorityString) { + case PRIORITY_HIGH: + priorityString = PRIORITY_H; + break; + + case PRIORITY_MEDIUM: + priorityString = PRIORITY_M; + break; + + case PRIORITY_LOW: + priorityString = PRIORITY_L; + break; + + default: + break; + } + + priority = new Priority(priorityString); + } + + public void createStatusQuery(Boolean statusQuery) { + status = new Status(); + status.status = statusQuery; + } + + public void createTagsQuery(String tagQueryString) { + tagQuery = tagQueryString; + } + + /* --------------- GETTER METHODS -------------------- */ + + public ArrayList getNameKeywordsAsList() { + return new ArrayList(Arrays.asList( + getName().taskName.split(StringUtil.STRING_WHITESPACE))); + } + + public DateTime getDateTimeQueryRange() { + if (getDateTime().getEndDate() != null) { + return getDateTime(); + } else { + return null; + } + } + + public ArrayList getPriorityKeywordsAsList() { + return new ArrayList(Arrays.asList(priorityString())); + } + + public String getStatusQuery() { + if (status != null) { + statusString = status.toString(); + } + return statusString; + } + + public ArrayList getTagKeywordsAsList() { + return new ArrayList( + Arrays.asList(tagQuery.split(StringUtil.STRING_WHITESPACE))); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Filter Search Keywords: "); + + if (!getName().toString().isEmpty()) { + builder.append("[Task Name: ").append(getName()).append("] "); + } + if (getDateTime().getEndDate() != null) { + builder.append("[DateTime: ").append(getDateTime()).append("] "); + } + if (!priorityString().isEmpty()) { + builder.append("[Priority: ").append(priorityString()).append("] "); + } + if (!statusString.isEmpty()) { + builder.append("[Status: ").append(statusString).append("] "); + } + if (!tagQuery.isEmpty()) { + builder.append("[Tags: ").append(tagQuery).append("]"); + } + + return builder.toString(); + } + +} +``` +###### \java\tars\storage\Storage.java +``` java + /** + * Updates Tars Storage Directory + * + * @param newFilePath + * @param newConfig + */ + void updateTarsStorageDirectory(String newFilePath, Config newConfig); + + void saveTarsInNewFilePath(ReadOnlyTars tars, String newFilePath) throws IOException; + + boolean isFileSavedSuccessfully(String filePath); + + Optional readTarsFromNewFilePath(String newFilePath) + throws DataConversionException, FileNotFoundException; +} +``` +###### \java\tars\storage\StorageManager.java +``` java + @Override + public void saveTarsInNewFilePath(ReadOnlyTars tars, String newFilePath) + throws IOException { + tarsStorage = new XmlTarsStorage(newFilePath); + tarsStorage.saveTars(tars, newFilePath); + } + + public boolean isFileSavedSuccessfully(String filePath) { + Path path = Paths.get(filePath); + return Files.exists(path); + } + + public void updateTarsStorageDirectory(String newFilePath, + Config newConfig) { + tarsStorage = new XmlTarsStorage(newFilePath); + indicateTarsStorageDirectoryChanged(newFilePath, newConfig); + } + + // Raise an event that the tars storage directory has changed + private void indicateTarsStorageDirectoryChanged(String newFilePath, + Config newConfig) { + raise(new TarsStorageDirectoryChangedEvent(newFilePath, newConfig)); + } + +``` +###### \java\tars\ui\CommandBox.java +``` java + private void setTextFieldKeyPressedHandler() { + commandTextField.setOnKeyPressed(new EventHandler() { + public void handle(KeyEvent ke) { + if (ke.getCode().equals(KeyCode.UP)) { + setTextToShowPrevCmdText(ke); + } else if (ke.getCode().equals(KeyCode.DOWN)) { + setTextToShowNextCmdText(ke); + } else if (KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW + .match(ke)) { + raise(new KeyCombinationPressedEvent( + KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW)); + ke.consume(); + } else if (KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW.match(ke)) { + raise(new KeyCombinationPressedEvent( + KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW)); + ke.consume(); + } else if (KeyCombinations.KEY_COMB_CTRL_Z.match(ke)) { + handleUndoAndRedoKeyRequest(UndoCommand.COMMAND_WORD); + } else if (KeyCombinations.KEY_COMB_CTRL_Y.match(ke)) { + handleUndoAndRedoKeyRequest(RedoCommand.COMMAND_WORD); + } + } + }); + } + + private void setTextFieldValueHandler() { + commandTextField.textProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue.equals(RsvCommand.COMMAND_WORD) + || newValue.equals(ConfirmCommand.COMMAND_WORD)) { + raise(new CommandBoxTextFieldValueChangedEvent( + newValue)); + } + }); + + } + +``` +###### \java\tars\ui\CommandBox.java +``` java + /** + * Adds the user input command text into the "prev" stack + */ + private void addCmdTextToPrevStack(String cmdText) { + if (!prevCmdTextHistStack.contains(cmdText)) { + prevCmdTextHistStack.push(cmdText); + } + } + + /** + * Adds the user input command text into the "next" stack + */ + private void addCmdTextToNextStack(String cmdText) { + if (!nextCmdTextHistStack.contains(cmdText)) { + nextCmdTextHistStack.push(cmdText); + } + } + + /** + * Shows the prev cmdtext in the CommandBox. Does nothing if "prev" stack is empty + */ + private void setTextToShowPrevCmdText(KeyEvent ke) { + if (!prevCmdTextHistStack.isEmpty()) { + if (nextCmdTextHistStack.isEmpty()) { + nextCmdTextHistStack.push(commandTextField.getText()); + } + String cmdTextToShow = prevCmdTextHistStack.pop(); + addCmdTextToNextStack(cmdTextToShow); + if (commandTextField.getText().equals(cmdTextToShow) + && !prevCmdTextHistStack.isEmpty()) { + cmdTextToShow = prevCmdTextHistStack.pop(); + addCmdTextToNextStack(cmdTextToShow); + } + ke.consume(); + commandTextField.setText(cmdTextToShow); + } + } + + /** + * Shows the next cmdtext in the CommandBox. Does nothing if "next" stack is empty + */ + private void setTextToShowNextCmdText(KeyEvent ke) { + if (!nextCmdTextHistStack.isEmpty()) { + String cmdTextToShow = nextCmdTextHistStack.pop(); + addCmdTextToPrevStack(cmdTextToShow); + if (commandTextField.getText().equals(cmdTextToShow) + && !nextCmdTextHistStack.isEmpty()) { + cmdTextToShow = nextCmdTextHistStack.pop(); + if (!nextCmdTextHistStack.isEmpty()) { + addCmdTextToNextStack(cmdTextToShow); + } + } + ke.consume(); + commandTextField.setText(cmdTextToShow); + } + } + + /** + * Sets the command box style to indicate a correct command. + */ + private void setStyleToIndicateCorrectCommand() { + commandTextField.getStyleClass().remove(COMMAND_TEXT_FIELD_ERROR); + commandTextField.setText(StringUtil.EMPTY_STRING); + } + + @Subscribe + private void handleIncorrectCommandAttempted( + IncorrectCommandAttemptedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, String + .format(LOG_MESSAGE_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(COMMAND_TEXT_FIELD_ERROR); + } + +} +``` +###### \java\tars\ui\StatusBarFooter.java +``` java + @Subscribe + private void handleTarsStorageChangeDirectoryEvent( + TarsStorageDirectoryChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setSaveLocation(String.format(LOG_MESSAGE_STORAGE_LOCATION_CHANGED, + event.getNewFilePath())); + } +} +``` diff --git a/collated/main/A0139924W.md b/collated/main/A0139924W.md new file mode 100644 index 000000000000..2a10c5006cc6 --- /dev/null +++ b/collated/main/A0139924W.md @@ -0,0 +1,1639 @@ +# A0139924W +###### \java\tars\commons\util\DateTimeUtil.java +``` java + /** + * Extracts the new task's dateTime from the string arguments. + * + * @return String[] with first index being the startDate time and second index being the end + * date time + */ + public static String[] parseStringToDateTime(String dateTimeArg) { + return NattyDateTimeUtil.parseStringToDateTime(dateTimeArg); + } +``` +###### \java\tars\commons\util\DateTimeUtil.java +``` java + /** + * Modify the date based on the new hour, min and sec + */ + public static Date setDateTime(Date toBeEdit, int hour, int min, int sec) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(toBeEdit); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, min); + calendar.set(Calendar.SECOND, sec); + toBeEdit = calendar.getTime(); + + return toBeEdit; + } + +``` +###### \java\tars\commons\util\NattyDateTimeUtil.java +``` java +/** + * Natty date time utility + */ +public class NattyDateTimeUtil { + private static final SimpleDateFormat CONVERT_NATTY_TIME_FORMAT = + new SimpleDateFormat("dd/MM/yyyy HHmm"); + private static final String DASH_DATE_FORMAT = "(\\b\\d{1,2})-(\\d{1,2})"; + private static final String SLASH_DATE_FORMAT = "(\\b\\d{1,2})/(\\d{1,2})"; + private static final String DASH_DATE_REPLACEMENT = "$2-$1"; + private static final String SLASH_DATE_REPLACEMENT = "$2/$1"; + + private static final int EMPTY_GROUP_SIZE = 0; + private static final int START_DATE_SIZE = 1; + private static final int START_END_DATE_SIZE = 2; + private static final int FIRST_GROUP = 0; + private static final int SECOND_GROUP = 1; + private static final int FIRST_CHILD = 0; + private static final int SECOND_CHILD = 1; + private static final String NATTY_TIME_PREFIX = "EXPLICIT_TIME"; + + /** + * Extracts the new task's dateTime from the string arguments using natty. + * + * @return String[] with first index being the startDate time and second index being the end + * date time + */ + public static String[] parseStringToDateTime(String dateTimeArg) { + String endDateTime = StringUtil.EMPTY_STRING; + String startDateTime = StringUtil.EMPTY_STRING; + String formattedDateTimeArg = convertToUsDateFormat(dateTimeArg); + + Parser parser = new Parser(TimeZone.getDefault()); + List groups = parser.parse(formattedDateTimeArg); + + if (isInvalidDateTimeArg(dateTimeArg, groups)) { + throw new DateTimeException(Messages.MESSAGE_INVALID_DATE); + } + + if (groups.size() > EMPTY_GROUP_SIZE) { + DateGroup group = groups.get(FIRST_GROUP); + if (group.getDates().size() == START_DATE_SIZE) { + return extractStartDate(group); + } + + if (group.getDates().size() == START_END_DATE_SIZE) { + return extractStartAndEndDate(group); + } + } + + return new String[] {startDateTime, endDateTime}; + } + + /** + * Change the date format to US date format. + * + * @return formatted datetime in US format + */ + private static String convertToUsDateFormat(String rawDateTime) { + String formattedDateTime = rawDateTime.trim() + .replaceAll(DASH_DATE_FORMAT, DASH_DATE_REPLACEMENT) + .replaceAll(SLASH_DATE_FORMAT, SLASH_DATE_REPLACEMENT); + return formattedDateTime; + } + + /** + * Change the date format to Asia date format. + * + * @return formatted datetime in Asia format + */ + private static String convertToAsiaDateFormat(Date toBeFormattedDateTime) { + return CONVERT_NATTY_TIME_FORMAT.format(toBeFormattedDateTime); + } + + /** + * Checks if the datetime is a invalid format. + * + * @return true if the given datetime is invalid + */ + private static boolean isInvalidDateTimeArg(String dateTimeArg, + List groups) { + return (dateTimeArg.trim().length() > StringUtil.EMPTY_STRING_LENGTH + && groups.size() == EMPTY_GROUP_SIZE); + } + + /** + * Extracts start date time from natty group + */ + private static String[] extractStartDate(DateGroup group) { + String treeString = StringUtil.EMPTY_STRING; + String endDateTime = StringUtil.EMPTY_STRING; + Date date; + + treeString = group.getSyntaxTree().getChild(FIRST_CHILD).toStringTree(); + date = group.getDates().get(FIRST_GROUP); + if (!isTimePresent(treeString)) { + date = DateTimeUtil.setDateTime(date, + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + } + + endDateTime = convertToAsiaDateFormat(date); + + return new String[] {StringUtil.EMPTY_STRING, endDateTime}; + } + + /** + * Extracts start and end date time from natty group + */ + private static String[] extractStartAndEndDate(DateGroup group) { + String firstTreeString = StringUtil.EMPTY_STRING; + String secondTreeString = StringUtil.EMPTY_STRING; + String startDateTime = StringUtil.EMPTY_STRING; + String endDateTime = StringUtil.EMPTY_STRING; + Date firstDate; + Date secondDate; + + firstTreeString = + group.getSyntaxTree().getChild(FIRST_CHILD).toStringTree(); + secondTreeString = + group.getSyntaxTree().getChild(SECOND_CHILD).toStringTree(); + firstDate = group.getDates().get(FIRST_GROUP); + secondDate = group.getDates().get(SECOND_GROUP); + + if (!isTimePresent(firstTreeString)) { + firstDate = DateTimeUtil.setDateTime(firstDate, + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + } + + if (!isTimePresent(secondTreeString)) { + secondDate = DateTimeUtil.setDateTime(secondDate, + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + } + + startDateTime = CONVERT_NATTY_TIME_FORMAT.format(firstDate); + endDateTime = CONVERT_NATTY_TIME_FORMAT.format(secondDate); + + return new String[] {startDateTime, endDateTime}; + } + + /** + * Checks if time is present + */ + private static boolean isTimePresent(String treeString) { + return treeString.contains(NATTY_TIME_PREFIX); + } +} +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + @Override + public CommandResult undo() { + assert model != null; + try { + for (Task toAdd : taskArray) { + model.deleteTask(toAdd); + } + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO, toAdd))); + } catch (TaskNotFoundException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_TASK_CANNOT_BE_FOUND)); + } + } + +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + @Override + public CommandResult redo() { + assert model != null; + try { + for (Task toAdd : taskArray) { + model.addTask(toAdd); + } + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + messageSummary())); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + +``` +###### \java\tars\logic\commands\ClearCommand.java +``` java +/** + * Clears tars. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "TARS has been cleared!"; + + @Override + public CommandResult execute() { + assert model != null; + model.resetData(Tars.getEmptyTars()); + + model.getUndoableCmdHist().clear(); + model.getRedoableCmdHist().clear(); + + return new CommandResult(MESSAGE_SUCCESS); + } +} +``` +###### \java\tars\logic\commands\ConfirmCommand.java +``` java + @Override + public CommandResult undo() { + try { + model.addRsvTask(rsvTask); + model.deleteTask(toConfirm); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } catch (TaskNotFoundException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_TASK_CANNOT_BE_FOUND)); + } + + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + +``` +###### \java\tars\logic\commands\ConfirmCommand.java +``` java + @Override + public CommandResult redo() { + try { + model.deleteRsvTask(rsvTask); + model.addTask(toConfirm); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } catch (RsvTaskNotFoundException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND)); + } + + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + +} +``` +###### \java\tars\logic\commands\DeleteCommand.java +``` java + @Override + public CommandResult undo() { + try { + for (ReadOnlyTask t : deletedTasks) { + Task taskToAdd = new Task(t); + model.addTask(taskToAdd); + } + String formattedTaskList = + new Formatter().formatTaskList(deletedTasks); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO, formattedTaskList))); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + +``` +###### \java\tars\logic\commands\DeleteCommand.java +``` java + @Override + public CommandResult redo() { + try { + for (ReadOnlyTask t : deletedTasks) { + Task taskToAdd = new Task(t); + model.deleteTask(taskToAdd); + } + String formattedTaskList = + new Formatter().formatTaskList(deletedTasks); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO, formattedTaskList))); + } catch (TaskNotFoundException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_TASK_CANNOT_BE_FOUND)); + } + } + +} +``` +###### \java\tars\logic\commands\EditCommand.java +``` java + /** + * Update task if there is a change + * + * @throws IllegalValueException + * @throws TagNotFoundException + */ + private void updateTask() throws IllegalValueException, TagNotFoundException { + updateNameIfChanged(); + updatePriorityIfChanged(); + updateDateTimeIfChanged(); + addTagsIfFound(); + deleteTagsIfFound(); + } + + /** + * Update the name field if there is a change + * + * @throws IllegalValueException + */ + private void updateNameIfChanged() throws IllegalValueException { + if (isFieldChanged(NAME_PREFIX)) { + Name editedName = + new Name(argsTokenizer.getValue(NAME_PREFIX).get()); + editedTask.setName(editedName); + } + } + + /** + * Update the priority if there is a change + * + * @throws IllegalValueException + */ + private void updatePriorityIfChanged() throws IllegalValueException { + if (isFieldChanged(PRIORITY_PREFIX)) { + Priority editedPriority = + new Priority(argsTokenizer.getValue(PRIORITY_PREFIX).get()); + editedTask.setPriority(editedPriority); + } + } + + /** + * Update the date time if there is a change + * + * @throws IllegalDateException + */ + private void updateDateTimeIfChanged() throws IllegalDateException { + if (isFieldChanged(DATETIME_PREFIX)) { + String[] dateTimeArray = DateTimeUtil.parseStringToDateTime( + argsTokenizer.getValue(DATETIME_PREFIX).get()); + DateTime editedDateTime = + new DateTime(dateTimeArray[DATETIME_INDEX_OF_STARTDATE], + dateTimeArray[DATETIME_INDEX_OF_ENDDATE]); + editedTask.setDateTime(editedDateTime); + } + } + + /** + * Add tag if there is a change + * + * @throws IllegalValueException + * @throws DuplicateTagException + * @throws TagNotFoundException + */ + private void addTagsIfFound() throws IllegalValueException, DuplicateTagException, + TagNotFoundException { + Set tagsToAdd = argsTokenizer.getMultipleValues(ADD_TAG_PREFIX) + .orElse(new HashSet<>()); + updateTagList(ADD_TAG_PREFIX, tagsToAdd); + } + + /** + * Remove tag if there is a change + * + * @throws IllegalValueException + * @throws DuplicateTagException + * @throws TagNotFoundException + */ + private void deleteTagsIfFound() throws IllegalValueException, DuplicateTagException, + TagNotFoundException { + Set tagsToAdd = argsTokenizer.getMultipleValues(REMOVE_TAG_PREFIX) + .orElse(new HashSet<>()); + updateTagList(REMOVE_TAG_PREFIX, tagsToAdd); + } + + /** + * Update tag list + * + * @throws IllegalValueException + * @throws TagNotFoundException + */ + private void updateTagList(Prefix mutatorPrefix, Set mutateTagNames) + throws IllegalValueException, TagNotFoundException { + UniqueTagList replacement = editedTask.getTags(); + + for (String mutateTagName : mutateTagNames) { + Tag mutateTag = new Tag(mutateTagName); + + if (ADD_TAG_PREFIX.equals(mutatorPrefix)) { + replacement.add(mutateTag); + } + + if (REMOVE_TAG_PREFIX.equals(mutatorPrefix)) { + replacement.remove(mutateTag); + } + } + + editedTask.setTags(replacement); + } + + /** + * Checks if the field need to be updated + * + * @return true if the field need update + */ + private boolean isFieldChanged(Prefix prefix) { + return !argsTokenizer.getValue(prefix).orElse(StringUtil.EMPTY_STRING) + .equals(StringUtil.EMPTY_STRING); + } + + @Override + public CommandResult undo() { + assert model != null; + try { + model.replaceTask(editedTask, new Task(toBeReplacedTask)); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO, toBeReplacedTask))); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + + @Override + public CommandResult redo() { + assert model != null; + try { + model.replaceTask(toBeReplacedTask, editedTask); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO, toBeReplacedTask))); + } catch (DuplicateTaskException e) { + return new CommandResult(String + .format(RedoCommand.MESSAGE_UNSUCCESS, e.getMessage())); + } + } +} +``` +###### \java\tars\logic\commands\RedoCommand.java +``` java +/** + * Redo an undoable command. + */ +public class RedoCommand extends Command { + + public static final String COMMAND_WORD = "redo"; + public static final String MESSAGE_SUCCESS = "Redo successfully.\n%1$s"; + public static final String MESSAGE_UNSUCCESS = "Redo unsuccessfully.\n%1$s"; + + public static final String MESSAGE_EMPTY_REDO_CMD_HIST = + "No more actions that can be redo."; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Redo a previous command.\n" + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute() { + assert model != null; + + if (model.getRedoableCmdHist().isEmpty()) { + return new CommandResult(MESSAGE_EMPTY_REDO_CMD_HIST); + } + + UndoableCommand command = + (UndoableCommand) model.getRedoableCmdHist().pop(); + model.getUndoableCmdHist().push(command); + return command.redo(); + } +} +``` +###### \java\tars\logic\commands\RsvCommand.java +``` java + @Override + public CommandResult undo() { + if (toReserve != null) { + try { + return undoRsvAdd(); + } catch (RsvTaskNotFoundException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND)); + } + } else { + try { + return undoRsvDelete(); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + } + +``` +###### \java\tars\logic\commands\RsvCommand.java +``` java + @Override + public CommandResult redo() { + if (toReserve != null) { + try { + return redoRsvAdd(); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } else { + try { + return redoRsvDelete(); + } catch (RsvTaskNotFoundException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND)); + } + } + } + + private CommandResult undoRsvAdd() throws RsvTaskNotFoundException { + model.deleteRsvTask(toReserve); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO_DELETE, toReserve))); + } + + private CommandResult undoRsvDelete() throws DuplicateTaskException { + for (RsvTask rsvTask : rsvTasksToDelete) { + model.addRsvTask(rsvTask); + } + + String addedRsvTasksList = + new Formatter().formatRsvTaskList(rsvTasksToDelete); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO_ADD, addedRsvTasksList))); + } + + private CommandResult redoRsvAdd() throws DuplicateTaskException { + model.addRsvTask(toReserve); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO_ADD, toReserve))); + } + + private CommandResult redoRsvDelete() throws RsvTaskNotFoundException { + for (RsvTask rsvTask : rsvTasksToDelete) { + model.deleteRsvTask(rsvTask); + } + + String deletedRsvTasksList = + new Formatter().formatRsvTaskList(rsvTasksToDelete); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO_DELETE, deletedRsvTasksList))); + } +} +``` +###### \java\tars\logic\commands\TagCommand.java +``` java +/** + * Rename and delete tag from a list of tags in TARS + */ +public class TagCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "tag"; + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": [/ls] [/e ] [/del ]"; + public static final String MESSAGE_RENAME_TAG_SUCCESS = + "%1$s renamed to [%2$s]"; + public static final String MESSAGE_DELETE_TAG_SUCCESS = "Deleted Tag: %1$s"; + + private static final int TAG_SECOND_INDEX = 1; + private static final int TAG_FIRST_INDEX = 0; + private static final Prefix listPrefix = new Prefix("/ls"); + private static final Prefix editPrefix = new Prefix("/e"); + private static final Prefix deletePrefix = new Prefix("/del"); + + private final Prefix prefix; + private final String[] args; + + private ReadOnlyTag toBeRenamed; + private ReadOnlyTag toBeDeleted; + private Tag newTag; + private ArrayList editedTaskList; + + public TagCommand(Prefix prefix, String... args) { + this.prefix = prefix; + this.args = args; + } + + @Override + public CommandResult execute() { + CommandResult result = null; + + try { + if (listPrefix.equals(prefix)) { + result = executeListTag(); + } else if (editPrefix.equals(prefix)) { + result = executeEditTag(); + } else if (deletePrefix.equals(prefix)) { + result = executeDeleteTag(); + } + } catch (DuplicateTagException e) { + return new CommandResult(e.getMessage()); + } catch (TagNotFoundException e) { + return new CommandResult(e.getMessage()); + } catch (IllegalValueException e) { + return new CommandResult(Tag.MESSAGE_TAG_CONSTRAINTS); + } catch (NumberFormatException e) { + return new CommandResult( + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + TagCommand.MESSAGE_USAGE)); + } + + return result; + } + + private CommandResult executeListTag() { + ObservableList allTags = + model.getUniqueTagList(); + return new CommandResult(new Formatter().formatTags(allTags)); + } + + private CommandResult executeEditTag() throws DuplicateTagException, + IllegalValueException, TagNotFoundException { + int targetedIndex = Integer.parseInt(args[TAG_FIRST_INDEX]); + String newTagName = args[TAG_SECOND_INDEX]; + + if (isInValidIndex(targetedIndex)) { + return new CommandResult( + Messages.MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + } + + toBeRenamed = model.getUniqueTagList() + .get(targetedIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + newTag = new Tag(newTagName); + model.renameTasksWithNewTag(toBeRenamed, newTag); + + model.getUndoableCmdHist().push(this); + return new CommandResult( + String.format(String.format(MESSAGE_RENAME_TAG_SUCCESS, + toBeRenamed.getAsText(), newTagName))); + } + + private CommandResult executeDeleteTag() throws DuplicateTagException, + IllegalValueException, TagNotFoundException { + int targetedIndex = Integer.parseInt(args[TAG_FIRST_INDEX]); + + if (isInValidIndex(targetedIndex)) { + return new CommandResult( + Messages.MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + } + + toBeDeleted = model.getUniqueTagList() + .get(targetedIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + editedTaskList = model.removeTagFromAllTasks(toBeDeleted); + + model.getUndoableCmdHist().push(this); + return new CommandResult( + String.format(MESSAGE_DELETE_TAG_SUCCESS, toBeDeleted)); + } + + /** + * Checks if the targetedIndex is a valid index + * + * @param targetedIndex + * @return true if targetedIndex is an invalid index + */ + private boolean isInValidIndex(int targetedIndex) { + return targetedIndex < 1 + || model.getUniqueTagList().size() < targetedIndex; + } + + @Override + public CommandResult undo() { + try { + if (editPrefix.equals(prefix)) { + model.renameTasksWithNewTag(newTag, new Tag(toBeRenamed)); + + } else if (deletePrefix.equals(prefix)) { + model.addTagToAllTasks(toBeDeleted, editedTaskList); + } + } catch (Exception e) { + return new CommandResult(UndoCommand.MESSAGE_UNSUCCESS); + } + + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + + @Override + public CommandResult redo() { + try { + if (editPrefix.equals(prefix)) { + model.renameTasksWithNewTag(toBeRenamed, newTag); + } else if (deletePrefix.equals(prefix)) { + editedTaskList = model.removeTagFromAllTasks(toBeDeleted); + } + } catch (Exception e) { + return new CommandResult(RedoCommand.MESSAGE_UNSUCCESS); + } + + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + +} +``` +###### \java\tars\logic\commands\UndoableCommand.java +``` java +/** + * Represents a undoable command with hidden internal logic and the ability to be executed. + */ +public abstract class UndoableCommand extends Command { + + public abstract CommandResult undo(); + + public abstract CommandResult redo(); +} +``` +###### \java\tars\logic\commands\UndoCommand.java +``` java +/** + * Undo an undoable command. + */ +public class UndoCommand extends Command { + + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_SUCCESS = "Undo successfully.\n%1$s"; + public static final String MESSAGE_UNSUCCESS = "Undo unsuccessfully.\n%1$s"; + public static final String MESSAGE_EMPTY_UNDO_CMD_HIST = + "No more actions that can be undo."; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Undo a previous command\n" + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute() { + assert model != null; + + if (model.getUndoableCmdHist().isEmpty()) { + return new CommandResult(MESSAGE_EMPTY_UNDO_CMD_HIST); + } + + UndoableCommand command = + (UndoableCommand) model.getUndoableCmdHist().pop(); + model.getRedoableCmdHist().push(command); + + return command.undo(); + } + +} +``` +###### \java\tars\logic\parser\AddCommandParser.java +``` java +/** + * Add command parser + */ +public class AddCommandParser extends CommandParser { + + /** + * Parses arguments in the context of the add task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(tagPrefix, priorityPrefix, dateTimePrefix, recurringPrefix); + argsTokenizer.tokenize(args); + + try { + return new AddCommand(argsTokenizer.getPreamble().get(), + DateTimeUtil.parseStringToDateTime( + argsTokenizer.getValue(dateTimePrefix).orElse(StringUtil.EMPTY_STRING)), + argsTokenizer.getValue(priorityPrefix).orElse(StringUtil.EMPTY_STRING), + argsTokenizer.getMultipleValues(tagPrefix).orElse(new HashSet()), + ExtractorUtil.getRecurringFromArgs( + argsTokenizer.getValue(recurringPrefix).orElse(StringUtil.EMPTY_STRING), + recurringPrefix)); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } catch (NoSuchElementException nse) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\tars\logic\parser\ArgumentTokenizer.java +``` java +/** + * Tokenizes arguments string of the form: {@code preamble value value ...}
+ * e.g. {@code some preamble text /dt today at 3pm /t tag1 /t tag2 /ls} where prefixes are + * {@code /dt /t}.
+ * 1. An argument's value can be an empty string e.g. the value of {@code /ls} in the above + * example.
+ * 2. Leading and trailing whitespaces of an argument value will be discarded.
+ * 3. A prefix need to have leading and trailing spaces e.g. the {@code /d today at 3pm /t tag1} in + * the above example
+ * 4. An argument may be repeated and all its values will be accumulated e.g. the value of + * {@code /t} in the above example.
+ */ +public class ArgumentTokenizer { + private static final int EMPTY_SIZE = 0; + private static final int INVALID_POS = -1; + private static final int START_INDEX_POS = -1; + + private final Prefix[] prefixes; + private HashMap prefixValueMap; + private TreeMap prefixPosMap; + private String args; + + public ArgumentTokenizer(Prefix... prefixes) { + this.prefixes = prefixes; + init(); + } + + public void tokenize(String args) { + resetExtractorState(); + this.args = args; + this.prefixPosMap = getPrefixPositon(); + extractArguments(); + } + + private void init() { + this.args = StringUtil.EMPTY_STRING; + this.prefixValueMap = new HashMap(); + this.prefixPosMap = new TreeMap(); + } + + private void resetExtractorState() { + this.prefixValueMap.clear(); + } + + /** + * Gets all prefix positions from arguments string + */ + private TreeMap getPrefixPositon() { + prefixPosMap = new TreeMap(); + + for (int i = StringUtil.START_INDEX; i < prefixes.length; i++) { + int curIndexPos = START_INDEX_POS; + + do { + curIndexPos = args.indexOf( + StringUtil.STRING_WHITESPACE + prefixes[i].value, + curIndexPos + StringUtil.LAST_INDEX); + + if (curIndexPos >= StringUtil.START_INDEX) { + prefixPosMap.put(curIndexPos, prefixes[i]); + } + } while (curIndexPos >= StringUtil.START_INDEX); + } + + return prefixPosMap; + } + + /** + * Extracts the option's prefix and arg from arguments string. + */ + private HashMap extractArguments() { + prefixValueMap = new HashMap(); + + int endPos = args.length(); + + for (Map.Entry entry : prefixPosMap.descendingMap() + .entrySet()) { + Prefix prefix = entry.getValue(); + Integer pos = entry.getKey(); + + if (pos == INVALID_POS) { + continue; + } + + String arg = args.substring(pos, endPos).trim(); + endPos = pos; + + if (prefixValueMap.containsKey(prefix)) { + prefixValueMap.put(prefix, prefixValueMap.get(prefix) + .concat(StringUtil.STRING_WHITESPACE).concat(arg)); + } else { + prefixValueMap.put(prefix, arg); + } + + } + + return prefixValueMap; + } + + public Optional getValue(Prefix prefix) { + if (!prefixValueMap.containsKey(prefix)) { + return Optional.empty(); + } + + return Optional + .of(getMultipleValues(prefix).get().iterator().next().trim()); + } + + public Optional> getMultipleValues(Prefix prefix) { + if (!prefixValueMap.containsKey(prefix)) { + return Optional.empty(); + } + return Optional + .of(getMultipleFromArgs(prefixValueMap.get(prefix), prefix)); + } + + public Optional getMultipleRawValues(Prefix prefix) { + if (!prefixValueMap.containsKey(prefix)) { + return Optional.empty(); + } + + return Optional.of(prefixValueMap.get(prefix).replaceAll( + prefix.value + StringUtil.STRING_WHITESPACE, + StringUtil.EMPTY_STRING)); + } + + public int numPrefixFound() { + return prefixPosMap.size(); + } + + public Optional getPreamble() { + if (args.trim().length() == StringUtil.EMPTY_STRING_LENGTH) { + return Optional.empty(); + } + + if (prefixPosMap.size() == EMPTY_SIZE) { + return Optional.of(args.trim()); + } else if (prefixPosMap.firstKey() == StringUtil.START_INDEX) { + return Optional.empty(); + } + + return Optional.of( + args.substring(StringUtil.START_INDEX, prefixPosMap.firstKey()) + .trim()); + } + + private Set getMultipleFromArgs(String multipleArguments, + Prefix prefix) { + if (multipleArguments.isEmpty()) { + return Collections.emptySet(); + } + + multipleArguments = multipleArguments.trim(); + + // replace first delimiter prefix, then split + List multipleArgList = Arrays.asList(multipleArguments + .replaceFirst(prefix.value + StringUtil.STRING_WHITESPACE, + StringUtil.EMPTY_STRING) + .split(StringUtil.STRING_WHITESPACE + prefix.value + + StringUtil.STRING_WHITESPACE)); + + for (int i = StringUtil.START_INDEX; i < multipleArgList.size(); i++) { + multipleArgList.set(i, multipleArgList.get(i).trim()); + } + + return new HashSet<>(multipleArgList); + } + +} +``` +###### \java\tars\logic\parser\CommandParser.java +``` java +/** + * Represents a parser command with hidden internal logic and the ability to be executed. + */ +public abstract class CommandParser { + protected static final Prefix namePrefix = new Prefix("/n"); + protected static final Prefix tagPrefix = new Prefix("/t"); + protected static final Prefix priorityPrefix = new Prefix("/p"); + protected static final Prefix dateTimePrefix = new Prefix("/dt"); + protected static final Prefix recurringPrefix = new Prefix("/r"); + protected static final Prefix deletePrefix = new Prefix("/del"); + protected static final Prefix addTagPrefix = new Prefix("/ta"); + protected static final Prefix removeTagPrefix = new Prefix("/tr"); + protected static final Prefix donePrefix = new Prefix("/do"); + protected static final Prefix undonePrefix = new Prefix("/ud"); + protected static final Prefix listPrefix = new Prefix("/ls"); + protected static final Prefix editPrefix = new Prefix("/e"); + + public abstract Command prepareCommand(String args); +} +``` +###### \java\tars\logic\parser\Parser.java +``` java +/** + * Parses user input. + */ +public class Parser { + + private static final String PARSER_MATCHER_ARGUMENTS = "arguments"; + + private static final String PARSER_MATCHER_COMMANDWORD = "commandWord"; + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = + Pattern.compile("(?\\S+)(?.*)"); + + /** + * Used for mapping a list of known command + */ + private static Map> commandParserMap = + new HashMap>(); + + static { + fillCommandMap(); + } + + private static void fillCommandMap() { + commandParserMap.put(AddCommand.COMMAND_WORD, AddCommandParser.class); + commandParserMap.put(RsvCommand.COMMAND_WORD, RsvCommandParser.class); + commandParserMap.put(EditCommand.COMMAND_WORD, EditCommandParser.class); + commandParserMap.put(DeleteCommand.COMMAND_WORD, + DeleteCommandParser.class); + commandParserMap.put(ConfirmCommand.COMMAND_WORD, + ConfirmCommandParser.class); + commandParserMap.put(ClearCommand.COMMAND_WORD, + ClearCommandParser.class); + commandParserMap.put(FindCommand.COMMAND_WORD, FindCommandParser.class); + commandParserMap.put(ListCommand.COMMAND_WORD, ListCommandParser.class); + commandParserMap.put(UndoCommand.COMMAND_WORD, UndoCommandParser.class); + commandParserMap.put(RedoCommand.COMMAND_WORD, RedoCommandParser.class); + commandParserMap.put(DoCommand.COMMAND_WORD, DoCommandParser.class); + commandParserMap.put(UdCommand.COMMAND_WORD, UdCommandParser.class); + commandParserMap.put(CdCommand.COMMAND_WORD, CdCommandParser.class); + commandParserMap.put(TagCommand.COMMAND_WORD, TagCommandParser.class); + commandParserMap.put(FreeCommand.COMMAND_WORD, FreeCommandParser.class); + commandParserMap.put(ExitCommand.COMMAND_WORD, ExitCommandParser.class); + commandParserMap.put(HelpCommand.COMMAND_WORD, HelpCommandParser.class); + } + + /** + * 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(PARSER_MATCHER_COMMANDWORD); + final String arguments = matcher.group(PARSER_MATCHER_ARGUMENTS); + + if (!commandParserMap.containsKey(commandWord)) { + return new IncorrectCommandParser().prepareCommand(arguments); + } + + try { + return commandParserMap.get(commandWord).newInstance() + .prepareCommand(arguments); + } catch (Exception ex) { + return new IncorrectCommandParser().prepareCommand(arguments); + } + } + +} +``` +###### \java\tars\logic\parser\Prefix.java +``` java +/** + * A prefix that marks the beginning of an argument e.g. '/t' in 'add CS2103 Project Meeting /t + * meeting' + */ +public class Prefix { + private static final int HASHCODE_NULL_VALUE = 0; + public final String value; + + public Prefix(String value) { + this.value = value; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Prefix)) { + return false; + } + + if (other == this) { + return true; + } + + Prefix otherPrefix = (Prefix) other; + return otherPrefix.value.equals(this.value); + } + + @Override + public int hashCode() { + return this.value == null ? HASHCODE_NULL_VALUE : this.value.hashCode(); + } + +} +``` +###### \java\tars\logic\parser\RedoCommandParser.java +``` java +/** + * Redo command parser + */ +public class RedoCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + if (!args.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, RedoCommand.MESSAGE_USAGE)); + } + return new RedoCommand(); + } + +} +``` +###### \java\tars\logic\parser\TagCommandParser.java +``` java +/** + * Tag command parser + */ +public class TagCommandParser extends CommandParser { + private static final Pattern TAG_EDIT_COMMAND_FORMAT = + Pattern.compile("\\d+ \\w+$"); + + /** + * Parses arguments in the context of the tag command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(listPrefix, editPrefix, deletePrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.getValue(listPrefix).isPresent()) { + return new TagCommand(listPrefix); + } + + if (argsTokenizer.getValue(editPrefix).isPresent()) { + String editArgs = argsTokenizer.getValue(editPrefix).get(); + final Matcher matcher = TAG_EDIT_COMMAND_FORMAT.matcher(editArgs); + if (matcher.matches()) { + return new TagCommand(editPrefix, + editArgs.split(StringUtil.STRING_WHITESPACE)); + } + } + + if (argsTokenizer.getValue(deletePrefix).isPresent()) { + String index = argsTokenizer.getValue(deletePrefix).get(); + return new TagCommand(deletePrefix, index); + } + + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + +} +``` +###### \java\tars\logic\parser\UndoCommandParser.java +``` java +/** + * Undo command parser + */ +public class UndoCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + if (!args.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, UndoCommand.MESSAGE_USAGE)); + } + return new UndoCommand(); + } + +} +``` +###### \java\tars\model\Model.java +``` java + /** + * Rename all task with the old tag with new tag name + */ + void renameTasksWithNewTag(ReadOnlyTag toBeRenamed, Tag newTag) + throws IllegalValueException, TagNotFoundException, + DuplicateTagException; + + /** Remove the tag from all task */ + ArrayList removeTagFromAllTasks(ReadOnlyTag toBeDeleted) + throws DuplicateTagException, IllegalValueException, + TagNotFoundException; + + /** Add tag to all task */ + void addTagToAllTasks(ReadOnlyTag toBeAdded, + ArrayList toBeEdited) throws DuplicateTagException, + IllegalValueException, TagNotFoundException; + +``` +###### \java\tars\model\Model.java +``` java + /** + * Returns the undoable command history stack + */ + Stack getUndoableCmdHist(); + + /** Returns the redoable command history stack */ + Stack getRedoableCmdHist(); + +``` +###### \java\tars\model\ModelManager.java +``` java + @Override + public synchronized void renameTasksWithNewTag(ReadOnlyTag toBeRenamed, + Tag newTag) throws IllegalValueException, TagNotFoundException, + DuplicateTagException { + + tars.getUniqueTagList().update(toBeRenamed, newTag); + tars.renameTasksWithNewTag(toBeRenamed, newTag); + + indicateTarsChanged(); + } + +``` +###### \java\tars\model\ModelManager.java +``` java + @Override + public synchronized ArrayList removeTagFromAllTasks( + ReadOnlyTag toBeDeleted) + throws TagNotFoundException, IllegalValueException { + + ArrayList editedTasks = + tars.removeTagFromAllTasks(toBeDeleted); + tars.getUniqueTagList().remove(new Tag(toBeDeleted)); + + indicateTarsChanged(); + return editedTasks; + } + +``` +###### \java\tars\model\ModelManager.java +``` java + @Override + public synchronized void addTagToAllTasks(ReadOnlyTag toBeAdded, + ArrayList allTasks) throws DuplicateTagException, + IllegalValueException, TagNotFoundException { + tars.addTagToAllTasks(toBeAdded, allTasks); + tars.getUniqueTagList().add(new Tag(toBeAdded)); + + indicateTarsChanged(); + } + + @Override + public synchronized void deleteTask(ReadOnlyTask target) + throws TaskNotFoundException { + tars.removeTask(target); + indicateTarsChanged(); + } + + @Override + public synchronized void addTask(Task task) throws DuplicateTaskException { + tars.addTask(task); + raise(new TaskAddedEvent(tars.getTaskList().size(), task)); + updateFilteredListToShowAll(); + indicateTarsChanged(); + } + + @Override + public synchronized void deleteRsvTask(RsvTask target) + throws RsvTaskNotFoundException { + tars.removeRsvTask(target); + indicateTarsChanged(); + } + + @Override + public synchronized void addRsvTask(RsvTask rsvTask) + throws DuplicateTaskException { + tars.addRsvTask(rsvTask); + raise(new RsvTaskAddedEvent(tars.getRsvTaskList().size(), rsvTask)); + raise(new RsvTaskAddedEvent(tars.getRsvTaskList().size(), rsvTask)); + indicateTarsChanged(); + } + +``` +###### \java\tars\model\ModelManager.java +``` java + @Override + public synchronized void replaceTask(ReadOnlyTask toUndo, Task replacement) + throws DuplicateTaskException { + tars.replaceTask(toUndo, replacement); + indicateTarsChanged(); + } + +``` +###### \java\tars\model\tag\ReadOnlyTag.java +``` java +/** + * Unmodifiable view of tars + */ +public interface ReadOnlyTag { + public String getAsText(); +} +``` +###### \java\tars\model\Tars.java +``` java + public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { + tags.add(t); + } + +``` +###### \java\tars\model\Tars.java +``` java + public void removeTag(Tag t) throws UniqueTagList.TagNotFoundException { + tags.remove(t); + } + +``` +###### \java\tars\model\Tars.java +``` java + /** + * Rename all task with the new tag + * + * @param toBeRenamed tag to be replaced with new the new tag + * @param newTag new tag + * @throws IllegalValueException if the given tag name string is invalid. + * @throws TagNotFoundException if there is no matching tags. + */ + public void renameTasksWithNewTag(ReadOnlyTag toBeRenamed, Tag newTag) + throws IllegalValueException, TagNotFoundException { + + for (int i = StringUtil.START_INDEX; i < tasks.getInternalList() + .size(); i++) { + Task toEdit = new Task(tasks.getInternalList().get(i)); + UniqueTagList tags = toEdit.getTags(); + if (tags.contains(new Tag(toBeRenamed))) { + tags.update(toBeRenamed, newTag); + toEdit.setTags(tags); + tasks.getInternalList().set(i, toEdit); + } + } + } + + /** + * Remove the tag from all tasks + * + * @param toBeDeleted + * @throws IllegalValueException if the given tag name string is invalid. + * @throws TagNotFoundException if there is no matching tags. + */ + public ArrayList removeTagFromAllTasks( + ReadOnlyTag toBeDeleted) throws IllegalValueException, + TagNotFoundException, DuplicateTagException { + ArrayList editedTasks = new ArrayList(); + + for (int i = StringUtil.START_INDEX; i < tasks.getInternalList() + .size(); i++) { + Task toEdit = new Task(tasks.getInternalList().get(i)); + UniqueTagList tags = toEdit.getTags(); + if (tags.contains(new Tag(toBeDeleted))) { + tags.remove(new Tag(toBeDeleted)); + toEdit.setTags(tags); + tasks.getInternalList().set(i, toEdit); + editedTasks.add(toEdit); + } + } + + return editedTasks; + } + + /** + * Remove the tag from all tasks + * + * @param toBeDeleted + * @throws IllegalValueException if the given tag name string is invalid. + * @throws TagNotFoundException if there is no matching tags. + */ + public void addTagToAllTasks(ReadOnlyTag toBeAdded, + ArrayList allTasks) throws IllegalValueException, + TagNotFoundException, DuplicateTagException { + + for (int i = StringUtil.START_INDEX; i < allTasks.size(); i++) { + for (int j = StringUtil.START_INDEX; j < tasks.getInternalList() + .size(); j++) { + Task toEdit = new Task(tasks.getInternalList().get(j)); + if (toEdit.equals(allTasks.get(i))) { + UniqueTagList tags = toEdit.getTags(); + tags.add(new Tag(toBeAdded)); + toEdit.setTags(tags); + tasks.getInternalList().set(i, toEdit); + } + } + } + } + +``` +###### \java\tars\ui\CommandBox.java +``` java + /** + * Handle any undo and redo request + */ + private void handleUndoAndRedoKeyRequest(String commandWord) { + if (UndoCommand.COMMAND_WORD.equals(commandWord)) { + mostRecentResult = logic.execute(UndoCommand.COMMAND_WORD); + } else if (RedoCommand.COMMAND_WORD.equals(commandWord)) { + mostRecentResult = logic.execute(RedoCommand.COMMAND_WORD); + } + resultDisplay.postMessage(mostRecentResult.feedbackToUser); + logger.info(String.format(LOG_MESSAGE_RESULT, + mostRecentResult.feedbackToUser)); + } + +``` +###### \java\tars\ui\formatter\DateFormatter.java +``` java +/** + * Container for formatting dates + */ +public class DateFormatter { + private static final String DATE_FORMAT_DASH = " - "; + private static final DateTimeFormatter DATE_FORMAT = + DateTimeFormatter.ofPattern("E, MMM dd yyyy"); + private static final DateTimeFormatter TIME_FORMAT = + DateTimeFormatter.ofPattern("hh:mm a"); + private static final DateTimeFormatter NORMAL_DATETIME_FORMAT = + DateTimeFormatter.ofPattern("E, MMM dd yyyy hh:mm a"); + private static final DateTimeFormatter SAME_DAY_DATE_FORMAT = + DateTimeFormatter.ofPattern("ddMMyyyy"); + + private static final String TODAY_PREFIX_TEXT = "Today at "; + private static final String TOMORROW_PREFIX_TEXT = "Tomorrow at "; + + public static String formatDate(DateTime dateTime) { + LocalDateTime startDateTime = dateTime.getStartDate(); + LocalDateTime endDateTime = dateTime.getEndDate(); + + if (startDateTime != null && endDateTime == null) { + return DateFormatter.generateSingleDateFormat(startDateTime); + } else if (startDateTime == null && endDateTime != null) { + return DateFormatter.generateSingleDateFormat(endDateTime); + } else if (startDateTime != null && endDateTime != null) { + return DateFormatter.generateDateRangeFormat(startDateTime, + endDateTime); + } else { + return StringUtil.EMPTY_STRING; + } + } + + public static String generateSingleDateFormat(LocalDateTime firstDate) { + if (isToday(firstDate)) { + return TODAY_PREFIX_TEXT + TIME_FORMAT.format(firstDate); + } else if (isTomorrow(firstDate)) { + return TOMORROW_PREFIX_TEXT + TIME_FORMAT.format(firstDate); + } else { + return NORMAL_DATETIME_FORMAT.format(firstDate); + } + } + + public static String generateDateRangeFormat(LocalDateTime firstDate, + LocalDateTime secondDate) { + if (isSameDay(firstDate, secondDate)) { + return DATE_FORMAT.format(firstDate) + StringUtil.STRING_WHITESPACE + + TIME_FORMAT.format(firstDate) + DATE_FORMAT_DASH + + TIME_FORMAT.format(secondDate); + } else { + return NORMAL_DATETIME_FORMAT.format(firstDate) + DATE_FORMAT_DASH + + NORMAL_DATETIME_FORMAT.format(secondDate); + } + } + + private static boolean isToday(LocalDateTime firstDate) { + return isSameDay(firstDate, LocalDateTime.now()); + } + + private static boolean isTomorrow(LocalDateTime firstDate) { + return isSameDay(firstDate, LocalDateTime.now().plusDays(1)); + } + + private static boolean isSameDay(LocalDateTime firstDate, + LocalDateTime secondDate) { + return SAME_DAY_DATE_FORMAT.format(firstDate) + .equals(SAME_DAY_DATE_FORMAT.format(secondDate)); + } + +} +``` +###### \java\tars\ui\formatter\Formatter.java +``` java +/** + * Container for formatting + */ +public class Formatter { + private static final int INITIAL_COUNT = 1; + private static final String STRING_TASKS = "tasks"; + private static final String STRING_TAGS = "tags"; + + /** Format of indexed list item */ + private static final String MESSAGE_INDEXED_LIST_ITEM = "%1$d.\t%2$s"; + + public static final String EMPTY_LIST_MESSAGE = "0 %1$s listed."; + public static final String DATETIME_FORMAT_STRING = "[%1$s] %2$s\n\n"; + private static String OVERDUED_TASKS_STRING = "[%1$s] %2$s\n"; + + public String formatTags(List tags) { + final List formattedTags = new ArrayList<>(); + + if (tags.size() == 0) { + return String.format(EMPTY_LIST_MESSAGE, STRING_TAGS); + } + + for (ReadOnlyTag tag : tags) { + formattedTags.add(tag.getAsText()); + } + return asIndexedList(formattedTags); + } + + public String formatTaskList(List taskList) { + final List formattedTasks = new ArrayList<>(); + + if (taskList.size() == 0) { + return String.format(EMPTY_LIST_MESSAGE, STRING_TASKS); + } + + for (ReadOnlyTask task : taskList) { + formattedTasks.add(task.getAsText()); + } + return asIndexedList(formattedTasks); + } + + public String formatRsvTaskList(List rsvTaskList) { + final List formattedTasks = new ArrayList<>(); + + if (rsvTaskList.size() == 0) { + return String.format(EMPTY_LIST_MESSAGE, STRING_TASKS); + } + + for (RsvTask task : rsvTaskList) { + formattedTasks.add(task.toString()); + } + return asIndexedList(formattedTasks); + } + + /** + * Formats a list of strings as an indexed list. + */ + private static String asIndexedList(List listItems) { + final StringBuilder formatted = new StringBuilder(); + int displayIndex = StringUtil.DISPLAYED_INDEX_OFFSET; + for (String listItem : listItems) { + formatted.append(getIndexedListItem(displayIndex++, listItem)) + .append(StringUtil.STRING_NEWLINE); + } + return formatted.toString(); + } + + /** + * Formats a string as an indexed list item. + * + * @param visibleIndex index for this listing + */ + private static String getIndexedListItem(int visibleIndex, + String listItem) { + return String.format(MESSAGE_INDEXED_LIST_ITEM, visibleIndex, listItem); + } + +``` +###### \java\tars\ui\StatusBarFooter.java +``` java + private void setSaveLocation(String location) { + this.saveLocationLabel.setText(location); + } + + private void addSaveLocation() { + this.saveLocationStatus = new StatusBar(); + this.saveLocationLabel = new Label(); + this.saveLocationStatus.setText(StringUtil.EMPTY_STRING); + this.saveLocationStatus.getRightItems().add(saveLocationLabel); + FxViewUtil.applyAnchorBoundaryParameters(saveLocationStatus, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); + saveLocStatusBarPane.getChildren().add(saveLocationStatus); + } + + private void setSyncStatus(String status) { + this.syncStatusLabel.setText(status); + } + + private void addSyncStatus() { + this.syncStatus = new StatusBar(); + this.syncStatusLabel = new Label(); + this.syncStatus.setText(StringUtil.EMPTY_STRING); + this.syncStatus.getLeftItems().add(syncStatusLabel); + FxViewUtil.applyAnchorBoundaryParameters(syncStatus, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); + syncStatusBarPane.getChildren().add(syncStatus); + } + +``` diff --git a/collated/main/A0140022H.md b/collated/main/A0140022H.md new file mode 100644 index 000000000000..4c9e1107ec1c --- /dev/null +++ b/collated/main/A0140022H.md @@ -0,0 +1,755 @@ +# A0140022H +###### \java\tars\commons\events\ui\ShowHelpRequestEvent.java +``` java +/** + * An event requesting to view the help page. + */ +public class ShowHelpRequestEvent extends BaseEvent { + + private String args; + + public ShowHelpRequestEvent(String args) { + this.args = args; + } + + public String getHelpRequestEventArgs() { + return args; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\tars\commons\events\ui\TaskAddedEvent.java +``` java +/** + * Indicates a task has been added + */ +public class TaskAddedEvent extends BaseEvent { + + public final int targetIndex; + public final ReadOnlyTask task; + + public TaskAddedEvent(int targetIndex, ReadOnlyTask task) { + this.targetIndex = targetIndex; + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\tars\commons\util\DateTimeUtil.java +``` java + /** + * Modifies the date based on the frequency for recurring tasks. + */ + public static String modifyDate(String dateToModify, String frequency) { + LocalDateTime date = LocalDateTime.parse(dateToModify, formatter); + + switch (frequency.toLowerCase()) { + case DATETIME_DAY: + date = date.plusDays(DATETIME_INCREMENT); + break; + case DATETIME_WEEK: + date = date.plusWeeks(DATETIME_INCREMENT); + break; + case DATETIME_MONTH: + date = date.plusMonths(DATETIME_INCREMENT); + break; + case DATETIME_YEAR: + date = date.plusYears(DATETIME_INCREMENT); + break; + } + + dateToModify = date.format(stringFormatter); + return dateToModify; + } + + public static LocalDateTime setLocalTime(LocalDateTime dateTime, int hour, + int min, int sec) { + return LocalDateTime.of(dateTime.getYear(), dateTime.getMonth(), + dateTime.getDayOfMonth(), hour, min, sec); + } +} +``` +###### \java\tars\commons\util\ExtractorUtil.java +``` java +/** + * Container for methods which extract data from string + */ +public class ExtractorUtil { + + /** + * Extracts the new task's recurring args from add command. + */ + public static String[] getRecurringFromArgs(String recurringArguments, + Prefix prefix) throws IllegalValueException { + recurringArguments = recurringArguments + .replaceFirst(prefix.value, StringUtil.EMPTY_STRING).trim(); + String[] recurringString = + recurringArguments.split(StringUtil.STRING_WHITESPACE); + + return recurringString; + } + +} +``` +###### \java\tars\logic\commands\AddCommand.java +``` java +/** + * Adds a task to tars. + */ +public class AddCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a task to tars.\n" + + "Parameters: [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]\n " + + "Example: " + COMMAND_WORD + + " cs2103 project meeting /dt 05/09/2016 1400 to 06/09/2016 2200 /p h /t project /r 2 every week"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_UNDO = "Removed %1$s"; + public static final String MESSAGE_REDO = "Added %1$s"; + + private static final int DATETIME_INDEX_OF_ENDDATE = 1; + private static final int DATETIME_INDEX_OF_STARTDATE = 0; + private static final int DATETIME_EMPTY_DATE = 0; + + private static final int ADDTASK_FIRST_ITERATION = 0; + private static final int ADDTASK_DEFAULT_NUMTASK = 1; + private static final String ADDTASK_STRING_EMPTY = ""; + private static final String ADDTASK_STRING_NEWLINE = "\n"; + + private static final int RECURRINGSTRING_NOT_EMPTY = 1; + private static final int RECURRINGSTRING_INDEX_OF_NUMTASK = 0; + private static final int RECURRINGSTRING_INDEX_OF_FREQUENCY = 2; + + private Task toAdd; + private ArrayList taskArray; + + private String conflictingTaskList = ""; + +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + * @throws DateTimeException if given dateTime string is invalid. + */ + public AddCommand(String name, String[] dateTime, String priority, + Set tags, String[] recurringString) + throws IllegalValueException, DateTimeException { + + taskArray = new ArrayList(); + + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + + addToTaskArray(name, dateTime, priority, recurringString, tagSet); + + } +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + private void addToTaskArray(String name, String[] dateTime, String priority, + String[] recurringString, final Set tagSet) + throws IllegalValueException, IllegalDateException { + int numTask = ADDTASK_DEFAULT_NUMTASK; + if (recurringString != null + && recurringString.length > RECURRINGSTRING_NOT_EMPTY) { + numTask = Integer.parseInt( + recurringString[RECURRINGSTRING_INDEX_OF_NUMTASK]); + } + + for (int i = ADDTASK_FIRST_ITERATION; i < numTask; i++) { + if (i != ADDTASK_FIRST_ITERATION) { + if (recurringString != null + && recurringString.length > RECURRINGSTRING_NOT_EMPTY) { + modifyDateTime(dateTime, recurringString, + DATETIME_INDEX_OF_STARTDATE); + modifyDateTime(dateTime, recurringString, + DATETIME_INDEX_OF_ENDDATE); + } + } + this.toAdd = new Task(new Name(name), + new DateTime(dateTime[DATETIME_INDEX_OF_STARTDATE], + dateTime[DATETIME_INDEX_OF_ENDDATE]), + new Priority(priority), new Status(), + new UniqueTagList(tagSet)); + taskArray.add(toAdd); + } + } +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + private void modifyDateTime(String[] dateTime, String[] recurringString, + int dateTimeIndex) { + if (dateTime[dateTimeIndex] != null + && dateTime[dateTimeIndex].length() > DATETIME_EMPTY_DATE) { + dateTime[dateTimeIndex] = DateTimeUtil.modifyDate( + dateTime[dateTimeIndex], + recurringString[RECURRINGSTRING_INDEX_OF_FREQUENCY]); + } + } +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + @Override + public CommandResult execute() { + assert model != null; + try { + addTasks(); + model.getUndoableCmdHist().push(this); + return new CommandResult(messageSummary()); + } catch (DuplicateTaskException e) { + return new CommandResult(Messages.MESSAGE_DUPLICATE_TASK); + } + } +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + private void addTasks() throws DuplicateTaskException { + for (Task toAdd : taskArray) { + conflictingTaskList += + model.getTaskConflictingDateTimeWarningMessage( + toAdd.getDateTime()); + model.addTask(toAdd); + + if (taskArray.size() == ADDTASK_DEFAULT_NUMTASK && ((toAdd + .getDateTime().getStartDate() == null + && toAdd.getDateTime().getEndDate() != null) + || (toAdd.getDateTime().getStartDate() != null + && toAdd.getDateTime().getEndDate() != null))) { + model.updateFilteredTaskListUsingDate(toAdd.getDateTime()); + } + } + } + +``` +###### \java\tars\logic\commands\AddCommand.java +``` java + private String messageSummary() { + String summary = ADDTASK_STRING_EMPTY; + + for (Task toAdd : taskArray) { + summary += String.format(MESSAGE_SUCCESS, + toAdd + ADDTASK_STRING_NEWLINE); + } + + if (!conflictingTaskList.isEmpty()) { + summary += StringUtil.STRING_NEWLINE + + Messages.MESSAGE_CONFLICTING_TASKS_WARNING + + conflictingTaskList; + } + return summary; + } +``` +###### \java\tars\logic\commands\HelpCommand.java +``` java +/** + * 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 in help panel.\n" + + "Parameters: [COMMAND WORD]\n" + "Example: " + + COMMAND_WORD + " add"; + + public static final String SHOWING_HELP_MESSAGE = + "Switched to Help tab pane."; + + private String args; + + public HelpCommand(String args) { + this.args = args; + } + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ShowHelpRequestEvent(args)); + return new CommandResult(SHOWING_HELP_MESSAGE); + } +} +``` +###### \java\tars\logic\commands\ListCommand.java +``` java +/** + * Lists all tasks in tars to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "ls"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + public static final String MESSAGE_SUCCESS_DATETIME = + "Listed all tasks by earliest datetime first"; + public static final String MESSAGE_SUCCESS_DATETIME_DESCENDING = + "Listed all tasks by latest datetime first"; + public static final String MESSAGE_SUCCESS_PRIORITY = + "Listed all tasks by priority from low to high"; + public static final String MESSAGE_SUCCESS_PRIORITY_DESCENDING = + "Listed all tasks by priority from high to low"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Lists all tasks.\n" + "Parameters: [KEYWORD] " + + "Example: " + COMMAND_WORD + " /dt"; + + private static final String LIST_ARG_DATETIME = "/dt"; + private static final String LIST_ARG_PRIORITY = "/p"; + private static final String LIST_KEYWORD_DESCENDING = "dsc"; + + private Set keywords; + + public ListCommand() {} + + public ListCommand(Set arguments) { + this.keywords = arguments; + } + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ScrollToTopEvent()); + if (keywords != null && !keywords.isEmpty()) { + return listByKeyword(); + } else { + model.updateFilteredListToShowAll(); + return new CommandResult(MESSAGE_SUCCESS); + } + } + + private CommandResult listByKeyword() { + if (keywords.contains(LIST_ARG_DATETIME) + || keywords.contains(LIST_ARG_PRIORITY) + || keywords.contains(LIST_KEYWORD_DESCENDING)) { + + model.sortFilteredTaskList(keywords); + + if (keywords.contains(LIST_KEYWORD_DESCENDING)) { + if (keywords.contains(LIST_ARG_DATETIME)) + return new CommandResult( + MESSAGE_SUCCESS_DATETIME_DESCENDING); + else + return new CommandResult( + MESSAGE_SUCCESS_PRIORITY_DESCENDING); + } else { + if (keywords.contains(LIST_ARG_DATETIME)) + return new CommandResult(MESSAGE_SUCCESS_DATETIME); + else + return new CommandResult(MESSAGE_SUCCESS_PRIORITY); + } + } else { + model.updateFilteredListToShowAll(); + return new CommandResult(String + .format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + } +} +``` +###### \java\tars\logic\parser\HelpCommandParser.java +``` java +/** + * Help command parser + */ +public class HelpCommandParser extends CommandParser { + + private static final int EMPTY_ARGS = 0; + + @Override + public Command prepareCommand(String args) { + + args = args.trim().toLowerCase(); + + if (args.length() > EMPTY_ARGS) { + ArrayList keywordArray = fillKeywordArray(); + + if (!keywordArray.contains(args)) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + HelpCommand.MESSAGE_USAGE)); + } + } + + return new HelpCommand(args); + } + + private ArrayList fillKeywordArray() { + ArrayList keywordArray = new ArrayList(); + keywordArray.add(UserGuide.ADD); + keywordArray.add(UserGuide.CD); + keywordArray.add(UserGuide.CLEAR); + keywordArray.add(UserGuide.CONFIRM); + keywordArray.add(UserGuide.DELETE); + keywordArray.add(UserGuide.DONE); + keywordArray.add(UserGuide.EDIT); + keywordArray.add(UserGuide.EXIT); + keywordArray.add(UserGuide.FIND); + keywordArray.add(UserGuide.FREE); + keywordArray.add(UserGuide.HELP); + keywordArray.add(UserGuide.LIST); + keywordArray.add(UserGuide.REDO); + keywordArray.add(UserGuide.RSV); + keywordArray.add(UserGuide.RSV_DELETE); + keywordArray.add(UserGuide.TAG_EDIT); + keywordArray.add(UserGuide.TAG_DELETE); + keywordArray.add(UserGuide.TAG_LIST); + keywordArray.add(UserGuide.UNDONE); + keywordArray.add(UserGuide.UNDO); + keywordArray.add(UserGuide.SUMMARY); + return keywordArray; + } + +} +``` +###### \java\tars\logic\parser\ListCommandParser.java +``` java +/** + * List command parser + */ +public class ListCommandParser extends CommandParser { + private static final Pattern KEYWORDS_ARGS_FORMAT = + Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); // one or more whitespace + + /** + * Parses arguments in the context of the list task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + + if (args.isEmpty()) { + return new ListCommand(); + } + + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = + matcher.group("keywords").split(StringUtil.REGEX_WHITESPACE); + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + return new ListCommand(keywordSet); + } + +} +``` +###### \java\tars\model\Model.java +``` java + /** + * Sorts the filtered task list by the given keywords + */ + void sortFilteredTaskList(Set keywords); + + /** + * Updates the filtered task list by the given dateTime + */ + void updateFilteredTaskListUsingDate(DateTime dateTime); + +} +``` +###### \java\tars\model\ModelManager.java +``` java + public void updateFilteredTaskListUsingDate(DateTime dateTime) { + updateFilteredTaskList( + new PredicateExpression(new DateQualifier(dateTime))); + } + + /** + * Sorts filtered list based on keywords + */ + public void sortFilteredTaskList(Set keywords) { + if (keywords.contains(LIST_ARG_PRIORITY)) { + if (keywords.contains(LIST_KEYWORD_DESCENDING)) { + tars.sortByPriorityDescending(); + } else { + tars.sortByPriority(); + } + } else if (keywords.contains(LIST_ARG_DATETIME)) { + if (keywords.contains(LIST_KEYWORD_DESCENDING)) { + tars.sortByDatetimeDescending(); + } else { + tars.sortByDatetime(); + } + } + } + +``` +###### \java\tars\model\qualifiers\DateQualifier.java +``` java +import java.time.LocalDateTime; + +import tars.commons.util.DateTimeUtil; +import tars.model.task.DateTime; +import tars.model.task.ReadOnlyTask; + +public class DateQualifier implements Qualifier { + + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + private final DateTime dateTimeQuery; + + public DateQualifier(DateTime dateTime) { + if (dateTime.getStartDate() != null) { + startDateTime = DateTimeUtil.setLocalTime(dateTime.getStartDate(), + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + endDateTime = DateTimeUtil.setLocalTime(dateTime.getEndDate(), + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_LAST_SECOND_OF_DAY); + } else { + startDateTime = DateTimeUtil.setLocalTime(dateTime.getEndDate(), + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY); + endDateTime = DateTimeUtil.setLocalTime(dateTime.getEndDate(), + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_LAST_SECOND_OF_DAY); + } + + dateTimeQuery = new DateTime(); + dateTimeQuery.setStartDateTime(startDateTime); + dateTimeQuery.setEndDateTime(endDateTime); + } + + @Override + public boolean run(ReadOnlyTask task) { + return DateTimeUtil.isDateTimeWithinRange(task.getDateTime(), + dateTimeQuery); + } +} +``` +###### \java\tars\model\Tars.java +``` java + /** + * Sorts internal list by priority from low to high + */ + public void sortByPriority() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o1.getPriority().compareTo(o2.getPriority()); + } + }); + } + + /** + * Sorts internal list by priority from high to low + */ + public void sortByPriorityDescending() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o2.getPriority().compareTo(o1.getPriority()); + } + }); + } + + /** + * Sorts internal list by earliest end dateTime first + */ + public void sortByDatetime() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o1.getDateTime().compareTo(o2.getDateTime()); + } + }); + } + + /** + * Sorts internal list by latest end dateTime first + */ + public void sortByDatetimeDescending() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o2.getDateTime().compareTo(o1.getDateTime()); + } + }); + } +``` +###### \java\tars\ui\HelpPanel.java +``` java + private String configureURL(String args) { + String url = + HelpPanel.class.getResource(USERGUIDE_URL).toExternalForm(); + + switch (args) { + case UserGuide.ADD: + url = url.concat(UserGuide.ADD_ID); + break; + case UserGuide.CD: + url = url.concat(UserGuide.CD_ID); + break; + case UserGuide.CLEAR: + url = url.concat(UserGuide.CLEAR_ID); + break; + case UserGuide.CONFIRM: + url = url.concat(UserGuide.CONFIRM_ID); + break; + case UserGuide.DELETE: + url = url.concat(UserGuide.DELETE_ID); + break; + case UserGuide.DONE: + url = url.concat(UserGuide.DONE_ID); + break; + case UserGuide.EDIT: + url = url.concat(UserGuide.EDIT_ID); + break; + case UserGuide.EXIT: + url = url.concat(UserGuide.EXIT_ID); + break; + case UserGuide.FIND: + url = url.concat(UserGuide.FIND_ID); + break; + case UserGuide.FREE: + url = url.concat(UserGuide.FREE_ID); + break; + case UserGuide.HELP: + url = url.concat(UserGuide.HELP_ID); + break; + case UserGuide.LIST: + url = url.concat(UserGuide.LIST_ID); + break; + case UserGuide.REDO: + url = url.concat(UserGuide.REDO_ID); + break; + case UserGuide.RSV: + url = url.concat(UserGuide.RSV_ID); + break; + case UserGuide.RSV_DELETE: + url = url.concat(UserGuide.RSV_DELETE_ID); + break; + case UserGuide.TAG_EDIT: + url = url.concat(UserGuide.TAG_EDIT_ID); + break; + case UserGuide.TAG_DELETE: + url = url.concat(UserGuide.TAG_DELETE_ID); + break; + case UserGuide.TAG_LIST: + url = url.concat(UserGuide.TAG_LIST_ID); + break; + case UserGuide.UNDONE: + url = url.concat(UserGuide.UNDONE_ID); + break; + case UserGuide.UNDO: + url = url.concat(UserGuide.UNDO_ID); + break; + case UserGuide.SUMMARY: + url = url.concat(UserGuide.SUMMARY_ID); + break; + default: + break; + } + + return url; + } + + public void loadUserGuide(String args) { + browser.getEngine().load(configureURL(args)); + } + +} +``` +###### \java\tars\ui\MainWindowEventsHandler.java +``` java + public static void handleHelp(HelpPanel helpPanel, String args) { + helpPanel.loadUserGuide(args); + tabPane.getSelectionModel() + .select(MainWindow.HELP_PANEL_TAB_PANE_INDEX); + } + +} +``` +###### \java\tars\ui\UiManager.java +``` java + @Subscribe + private void handleShowHelpEvent(ShowHelpRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getEventsHandler(); + MainWindowEventsHandler.handleHelp(mainWindow.getHelpPanel(), + event.getHelpRequestEventArgs()); + } +``` +###### \java\tars\ui\UserGuide.java +``` java +/** + * Container for help command and user guide + */ +public class UserGuide { + + public static final String DEFAULT = ""; + + public static final String ADD = "add"; + public static final String CD = "cd"; + public static final String CLEAR = "clear"; + public static final String CONFIRM = "confirm"; + public static final String DELETE = "del"; + public static final String DONE = "do"; + public static final String EDIT = "edit"; + public static final String EXIT = "exit"; + public static final String FIND = "find"; + public static final String FREE = "free"; + public static final String HELP = "help"; + public static final String LIST = "ls"; + public static final String REDO = "redo"; + public static final String RSV = "rsv"; + public static final String RSV_DELETE = "rsv /del"; + public static final String TAG_EDIT = "tag /e"; + public static final String TAG_DELETE = "tag /del"; + public static final String TAG_LIST = "tag /ls"; + public static final String UNDONE = "ud"; + public static final String UNDO = "undo"; + public static final String SUMMARY = "summary"; + + public static final String ADD_ID = "#Adding_a_task__add_38"; + public static final String CD_ID = "#Changing_data_storage_location__cd_52"; + public static final String CLEAR_ID = + "#Clearing_the_data_storage_file__clear_63"; + public static final String CONFIRM_ID = + "#Confirming_a_reserved_timeslot__confirm_68"; + public static final String DELETE_ID = "#Deleting_a_task__del_82"; + public static final String DONE_ID = "#Marking_tasks_as_done__do_97"; + public static final String EDIT_ID = "#Editing_a_task__edit_111"; + public static final String EXIT_ID = "#Exiting_the_program__exit_127"; + public static final String FIND_ID = "#Finding_tasks__find_132"; + public static final String FREE_ID = "#Suggesting_free_timeslots__free_164"; + public static final String HELP_ID = + "#Displaying_a_list_of_available_commands__help_175"; + public static final String LIST_ID = "#Listing_tasks__ls_182"; + public static final String REDO_ID = "#Redoing_a_command__redo_205"; + public static final String RSV_ID = + "#Reserving_timeslots_for_a_task__rsv_212"; + public static final String RSV_DELETE_ID = + "#Deleting_a_task_with_reserved_timeslots__rsv_del_222"; + public static final String TAG_EDIT_ID = "#Editing_a_tags_name__tag_e_236"; + public static final String TAG_DELETE_ID = "#Deleting_a_tag__tag_del_246"; + public static final String TAG_LIST_ID = "#Listing_all_tags__tag_ls_256"; + public static final String UNDONE_ID = "#Marking_tasks_as_undone__ud_261"; + public static final String UNDO_ID = "#Undoing_a_command__undo_275"; + public static final String SUMMARY_ID = "#Command_Summary_365"; +} +``` diff --git a/collated/test/A0121533W.md b/collated/test/A0121533W.md new file mode 100644 index 000000000000..68610ad991f4 --- /dev/null +++ b/collated/test/A0121533W.md @@ -0,0 +1,381 @@ +# A0121533W +###### \java\guitests\DeleteCommandTest.java +``` java +/** + * GUI test for delete command + */ +public class DeleteCommandTest extends TarsGuiTest { + + @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("del " + currentList.length + 1); + assertResultMessage("The task index provided is invalid"); + + } + + /** + * Runs the delete command to delete the task at specified index and confirms the result is + * correct. + * + * @param targetIndexOneIndexed e.g. to delete the first task in the list, 1 should be given as + * the target index. + * @param currentList A copy of the current list of tasks (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("del " + targetIndexOneIndexed); + + // confirm the list now contains all previous tasks except the deleted task + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + + // confirm the result message is correct + assertResultMessage(String.format(MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + taskToDelete + "\n")); + + } + +} +``` +###### \java\guitests\DoUdCommandTest.java +``` java +/** + * GUI test for done and undone command + */ +public class DoUdCommandTest extends TarsGuiTest { + + @Test + public void doTasks() throws IllegalValueException { + // Initialize Tars list + TestTask[] currentList = td.getTypicalTasks(); + + // Mark tasks as done by indexes + commandBox.runCommand("do 1 2 3"); + + // Confirm the list now contains the specified tasks to be mark as undone + Status done = new Status(true); + int[] indexesToMarkDoneIndexes = {1, 2, 3}; + TestTask[] expectedDoneListIndexes = + TestUtil.markTasks(currentList, indexesToMarkDoneIndexes, done); + assertTrue(taskListPanel.isListMatching(expectedDoneListIndexes)); + + // Mark tasks as done by range + commandBox.runCommand("do 4..7"); + + int[] indexesToMarkDoneRange = {1, 2, 3, 4, 5, 6, 7}; + TestTask[] expectedDoneListRange = + TestUtil.markTasks(currentList, indexesToMarkDoneRange, done); + assertTrue(taskListPanel.isListMatching(expectedDoneListRange)); + + // Mark tasks as undone by indexes + commandBox.runCommand("ud 1 2 3"); + + // Confirm the list now contains the specified tasks to be mark as undone + Status undone = new Status(false); + int[] indexesToMarkUndoneIndexes = {1, 2, 3}; + TestTask[] expectedUndoneListIndexes = TestUtil.markTasks(currentList, + indexesToMarkUndoneIndexes, undone); + assertTrue(taskListPanel.isListMatching(expectedUndoneListIndexes)); + + // Mark tasks as undone by range + commandBox.runCommand("ud 4..7"); + + int[] indexesToMarkUndoneRange = {1, 2, 3, 4, 5, 6, 7}; + TestTask[] expectedUndoneListRange = TestUtil.markTasks(currentList, + indexesToMarkUndoneRange, undone); + assertTrue(taskListPanel.isListMatching(expectedUndoneListRange)); + + // invalid do command + commandBox.runCommand("do abc"); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DoCommand.MESSAGE_USAGE)); + + // invalid do index + commandBox.runCommand("do 8"); + assertResultMessage("The task index provided is invalid"); + + // invalid do range + commandBox.runCommand("do 3..2"); + assertResultMessage( + String.format("Start index should be before end index." + "\n" + + DoCommand.MESSAGE_USAGE)); + + // invalid ud command + commandBox.runCommand("ud abc"); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UdCommand.MESSAGE_USAGE)); + + // invalid ud index + commandBox.runCommand("ud 8"); + assertResultMessage("The task index provided is invalid"); + + // invalid do range + commandBox.runCommand("ud 3..2"); + assertResultMessage( + String.format("Start index should be before end index." + "\n" + + UdCommand.MESSAGE_USAGE)); + } + +} +``` +###### \java\tars\commons\util\DateTimeUtilTest.java +``` java + @Test + public void isWithinWeek_dateTimeNullValue_returnFalse() { + LocalDateTime nullDateTime = null; + assertFalse(DateTimeUtil.isWithinWeek(nullDateTime)); + } + + @Test + public void isWithinWeek_dateTimeNotWithinWeek_returnFalse() { + LocalDateTime nextMonth = + LocalDateTime.now().plus(1, ChronoUnit.MONTHS); + LocalDateTime lastMonth = + LocalDateTime.now().minus(1, ChronoUnit.MONTHS); + assertFalse(DateTimeUtil.isWithinWeek(nextMonth)); + assertFalse(DateTimeUtil.isWithinWeek(lastMonth)); + } + + @Test + public void isOverDue_dateTimeNullValue_returnFalse() { + LocalDateTime nullDateTime = null; + assertFalse(DateTimeUtil.isOverDue(nullDateTime)); + } + + @Test + public void isOverDue_dateTimeOverDue_returnTrue() { + LocalDateTime yesterday = LocalDateTime.now().minus(1, ChronoUnit.DAYS); + assertTrue(DateTimeUtil.isOverDue(yesterday)); + } + + @Test + public void isOverDue_dateTimeNotOverDue_returnFalse() { + LocalDateTime tomorrow = LocalDateTime.now().plus(1, ChronoUnit.DAYS); + assertFalse(DateTimeUtil.isOverDue(tomorrow)); + } + +``` +###### \java\tars\logic\DeleteLogicCommandTest.java +``` java +/** + * Logic command test for delete + */ +public class DeleteLogicCommandTest extends LogicCommandTest { + private static final int FIRST_TASK_IN_LIST = 0; + private static final int SECOND_TASK_IN_LIST = 1; + private static final int THIRD_TASK_IN_LIST = 2; + private static final int NUM_TASK_TO_DELETE = 3; + + @Test + public void execute_delete_invalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE); + assertIncorrectIndexFormatBehaviorForCommand("del ", expectedMessage); + } + + @Test + public void execute_delete_indexNotFoundErrorMessageShown() + throws Exception { + assertIndexNotFoundBehaviorForCommand("del"); + } + +``` +###### \java\tars\logic\DoLogicCommandTest.java +``` java +/** + * Logic command test for do + */ +public class DoLogicCommandTest extends LogicCommandTest { + private static final int NUM_TASKS_IN_LIST = 2; + private static final int NUM_TASKS_IN_RANGE = 5; + + @Test + public void execute_mark_allTaskAsDone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, false); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars, true); + + assertCommandBehavior("do 1 2", + "Task: 1, 2 marked done successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_mark_alreadyDone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, true); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars, true); + + assertCommandBehavior("do 1 2", "Task: 1, 2 already marked done.\n", + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_mark_rangeDone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_RANGE, helper, false); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_RANGE, helper, expectedTars, true); + + assertCommandBehavior("do 1..5", + "Task: 1, 2, 3, 4, 5 marked done successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + private void generateTestTars(int numTasks, TypicalTestDataHelper helper, boolean status) + throws Exception { + Status s = new Status(status); + + Task[] taskArray = new Task[numTasks]; + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + taskI.setStatus(s); + taskArray[i-1] = taskI; + } + + List taskList = helper.generateTaskList(taskArray); + + helper.addToModel(model, taskList); + } + + private void generateExpectedTars(int numTasks, TypicalTestDataHelper helper, + Tars expectedTars, boolean status) throws Exception, DuplicateTaskException { + + Status s = new Status(status); + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + taskI.setStatus(s); + expectedTars.addTask(taskI); + } + } +} +``` +###### \java\tars\logic\UdLogicCommandTest.java +``` java +/** + * Logic command test for ud + */ +public class UdLogicCommandTest extends LogicCommandTest { + + private static final int NUM_TASKS_IN_LIST = 2; + private static final int NUM_TASKS_IN_RANGE = 5; + + @Test + public void execute_mark_allTaskAsUndone() throws Exception { + + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, true); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars); + + assertCommandBehavior("ud 1 2", + "Task: 1, 2 marked undone successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_mark_alreadyUndone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, false); + + Tars expectedTars = new Tars(); + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars); + + assertCommandBehavior("ud 1 2", "Task: 1, 2 already marked undone.\n", + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_mark_rangeUndone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_RANGE, helper, true); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_RANGE, helper, expectedTars); + + assertCommandBehavior("ud 1..5", + "Task: 1, 2, 3, 4, 5 marked undone successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + private void generateTestTars(int numTasks, TypicalTestDataHelper helper, boolean status) + throws Exception { + Status s = new Status(status); + + Task[] taskArray = new Task[numTasks]; + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + taskI.setStatus(s); + taskArray[i-1] = taskI; + } + + List taskList = helper.generateTaskList(taskArray); + + helper.addToModel(model, taskList); + } + + private void generateExpectedTars(int numTasks, TypicalTestDataHelper helper, + Tars expectedTars) throws Exception, DuplicateTaskException { + + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + expectedTars.addTask(taskI); + } + } +} +``` +###### \java\tars\testutil\TestUtil.java +``` java + /** + * Marks the task as done with index 1 in the list of tasks. + * + * @param tasks An array of tasks. + * @param indexes An array of indexes to mark + * @return The modified array of marked tasks + */ + public static TestTask[] markTasks(final TestTask[] tasks, int[] indexesToMark, Status status) { + List listOfTasks = asList(tasks); + for (int i = 0; i < indexesToMark.length; i++) { + listOfTasks.get(i).setStatus(status); + } + + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); + } +``` diff --git a/collated/test/A0124333U.md b/collated/test/A0124333U.md new file mode 100644 index 000000000000..14003e980819 --- /dev/null +++ b/collated/test/A0124333U.md @@ -0,0 +1,1177 @@ +# A0124333U +###### \java\guitests\CommandBoxTest.java +``` java + @Test + public void commandBox_cycleThroughCommandTextHist() { + commandBox.runCommand("User Input Command"); + commandBox.runCommand("User Input Command 2"); + commandBox.runCommand("User Input Command 3"); + commandBox.enterCommand("Temp User Input Command"); + + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 3"); + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 2"); + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command"); + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command"); + commandBox.pressDownKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 2"); + commandBox.pressDownKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 3"); + commandBox.pressDownKey(); + assertEquals(commandBox.getCommandInput(), "Temp User Input Command"); + + } + + @Test + public void commandBox_cycleThroughTabPane() { + commandBox.pressCtrlRightArrowKeys(); + assertTrue(commandBox.getRsvTaskListPanelHandle().isTabSelected()); + commandBox.pressCtrlRightArrowKeys(); + assertTrue(commandBox.getHelpPanelHandle().isTabSelected()); + commandBox.pressCtrlRightArrowKeys(); + assertTrue(commandBox.getOverviewPanelHandle().isTabSelected()); + commandBox.pressCtrlLeftArrowKeys(); + assertTrue(commandBox.getHelpPanelHandle().isTabSelected()); + commandBox.pressCtrlLeftArrowKeys(); + assertTrue(commandBox.getRsvTaskListPanelHandle().isTabSelected()); + } + + @Test + public void commandBox_textFieldValueChangesEvents_success() { + commandBox.enterCommand("rsv"); + assertTrue(commandBox.getRsvTaskListPanelHandle().isTabSelected()); + commandBox.pressCtrlLeftArrowKeys(); + commandBox.enterCommand("confirm"); + } + + @Test + public void commandBox_undoShortcut() { + commandBox.pressCtrlZArrowKeys(); + assertEquals(resultDisplay.getText(), UndoCommand.MESSAGE_EMPTY_UNDO_CMD_HIST); + } + + @Test + public void commandBox_redoShortcut() { + commandBox.pressCtrlYArrowKeys(); + assertEquals(resultDisplay.getText(), RedoCommand.MESSAGE_EMPTY_REDO_CMD_HIST); + } + +} +``` +###### \java\guitests\ConfirmCommandTest.java +``` java +/** + * GUI test for confirm command + */ +public class ConfirmCommandTest extends TarsGuiTest { + + @Test + public void confirm() { + TestTask[] currentTaskList = td.getTypicalTasks(); + TestRsvTask[] currentRsvTaskList = td.getTypicalRsvTasks(); + TestRsvTask rsvTaskToConfirm = td.rsvTaskA; + TestTask confirmedTask = td.cfmTaskA; + commandBox.runCommand("confirm 1 2 /p h"); + TestTask[] expectedTaskList = + TestUtil.addTasksToList(currentTaskList, confirmedTask); + TestRsvTask[] expectedRsvTaskList = TestUtil + .delRsvTaskFromList(currentRsvTaskList, rsvTaskToConfirm); + + // confirm the new card contains the right data + TaskCardHandle addedCard = + taskListPanel.navigateToTask(confirmedTask.getName().taskName); + assertMatching(confirmedTask, addedCard); + + // confirm that the rsv task list does not contain the confirmed task, + // and that the task list contains the confirmed task + assertTrue(taskListPanel.isListMatching(expectedTaskList)); + assertTrue(rsvTaskListPanel.isListMatching(expectedRsvTaskList)); + + } + +} +``` +###### \java\guitests\EditCommandTest.java +``` java +/** + * GUI test for edit command + */ +public class EditCommandTest extends TarsGuiTest { + + @Test + public void edit() throws IllegalValueException { + // Initialize Tars list + TestTask[] currentList = td.getTypicalTasks(); + + // Edit one task + Name nameToEdit = new Name("Edited Task A"); + Priority priorityToEdit = new Priority("l"); + commandBox.runCommand("edit 1 /n Edited Task A /p l"); + int indexToEdit = 1; + + // confirm the list now contains the edited task + TestTask[] expectedList = TestUtil.editTask(currentList, + indexToEdit - 1, nameToEdit, priorityToEdit); + assertTrue(taskListPanel.isListMatching(expectedList)); + + // invalid command + commandBox.runCommand("edit 1 Johnny"); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditCommand.MESSAGE_USAGE)); + + // invalid index + commandBox.runCommand( + "edit " + (currentList.length + 1) + " /n invalidIndex"); + assertResultMessage("The task index provided is invalid"); + } + +} +``` +###### \java\guitests\FindCommandTest.java +``` java +/** + * GUI test for find command + */ +public class FindCommandTest extends TarsGuiTest { + + @Test + public void find_quickSearch_nonEmptyList() { + assertFindResultForQuickSearch("find Meeting"); // no results + assertFindResultForQuickSearch("find Task B", td.taskB); // single result + assertFindResultForQuickSearch("find Task", td.taskA, td.taskB, + td.taskC, td.taskD, td.taskE, td.taskF, td.taskG); // multiple results + + // find after deleting one result + commandBox.runCommand("del 1"); + assertFindResultForQuickSearch("find A"); + } + + @Test + public void find_filterSearch_nonEmptyList() { + assertFindResultForFilterSearch("find /n Task B", td.taskB); // single result + + // find after deleting one result + commandBox.runCommand("del 1"); + assertFindResultForFilterSearch("find /n Task B"); // no results + } + + @Test + public void find_quickSearch_emptyList() { + commandBox.runCommand("clear"); + assertFindResultForQuickSearch("find No Such Task"); // no results + } + + @Test + public void find_invalidCommand_fail() { + commandBox.runCommand("findmeeting"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + } + + private void assertFindResultForQuickSearch(String command, + TestTask... expectedHits) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + + String[] keywordsArray = command.split("\\s+"); + ArrayList keywordsList = + new ArrayList(Arrays.asList(keywordsArray)); + keywordsList.remove(0); + + assertResultMessage(expectedHits.length + " tasks listed!\n" + + "Quick Search Keywords: " + keywordsList.toString()); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } + + private void assertFindResultForFilterSearch(String command, + TestTask... expectedHits) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + + String keywordString = "[Task Name: Task B] "; + + assertResultMessage(expectedHits.length + " tasks listed!\n" + + "Filter Search Keywords: " + keywordString); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } +} +``` +###### \java\guitests\guihandles\ThisWeekPanelHandle.java +``` java +/** + * GUI test for this week panel + */ +public class ThisWeekPanelHandle extends GuiHandle { + + private static final String TAB_PANEL_ROOT_FIELD_ID = "#tabPane"; + + public ThisWeekPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public boolean isTabSelected() { + return ((TabPane) getNode(TAB_PANEL_ROOT_FIELD_ID)).getSelectionModel() + .isSelected(MainWindow.OVERVIEW_PANEL_TAB_PANE_INDEX); + } + +} +``` +###### \java\guitests\HelpPanelTest.java +``` java +/** + * GUI test for help command + */ +public class HelpPanelTest extends TarsGuiTest { + + @Test + public void openHelpPanel() { + assertHelpPanelSelected(commandBox.runHelpCommand()); + } + + private void assertHelpPanelSelected(HelpPanelHandle helpPanelHandle) { + assertTrue(helpPanelHandle.isTabSelected()); + } +} +``` +###### \java\guitests\RsvCommandTest.java +``` java +/** + * GUI test for rsv command + */ +public class RsvCommandTest extends TarsGuiTest { + + @Test + public void rsv() { + // reserve one task + TestRsvTask[] currentList = td.getTypicalRsvTasks(); + TestRsvTask taskToRsv = td.rsvTaskC; + assertRsvSuccess(taskToRsv, currentList); + currentList = TestUtil.addRsvTasksToList(currentList, taskToRsv); + + // reserve another task + taskToRsv = td.rsvTaskD; + assertRsvSuccess(taskToRsv, currentList); + currentList = TestUtil.addRsvTasksToList(currentList, taskToRsv); + + // add duplicate task + commandBox.runCommand(td.rsvTaskD.getRsvCommand()); + assertResultMessage(Messages.MESSAGE_DUPLICATE_TASK); + assertTrue(rsvTaskListPanel.isListMatching(currentList)); + + // delete a reserved task + TestRsvTask rsvTaskToDel = td.rsvTaskC; + commandBox.runCommand("rsv /del 3"); + TestRsvTask[] expectedList = TestUtil.delRsvTaskFromList(currentList, rsvTaskToDel); + assertTrue(rsvTaskListPanel.isListMatching(expectedList)); + currentList = TestUtil.delRsvTaskFromList(currentList, taskToRsv); + + // add to empty list + commandBox.runCommand("clear"); + assertRsvSuccess(td.rsvTaskA); + + // invalid command + + commandBox.runCommand("reserves Meeting"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + + } + + private void assertRsvSuccess(TestRsvTask taskToRsv, TestRsvTask... currentList) { + commandBox.runCommand(taskToRsv.getRsvCommand()); + + // confirm the new card contains the right data + RsvTaskCardHandle addedCard = rsvTaskListPanel.navigateToRsvTask(taskToRsv.getName().taskName); + assertMatching(taskToRsv, addedCard); + + // confirm the list now contains all previous tasks plus the new task + TestRsvTask[] expectedList = TestUtil.addRsvTasksToList(currentList, taskToRsv); + assertTrue(rsvTaskListPanel.isListMatching(expectedList)); + } + +} +``` +###### \java\tars\commons\util\DateTimeUtilTest.java +``` java + @Test + public void isDateTimeWithinRange_emptyDateTimeSource() throws Exception { + DateTime dateTimeSource = + new DateTime(StringUtil.EMPTY_STRING, StringUtil.EMPTY_STRING); + DateTime dateTimeQuery = + new DateTime("17/01/2016 1200", "18/01/2016 1200"); + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQuery)); + } + + @Test + public void isDateTimeWithinRange_dateTimeOutOfRange() throws Exception { + DateTime dateTimeSource = + new DateTime("15/01/2016 1200", "16/01/2016 1200"); + DateTime dateTimeSource2 = + new DateTime("19/01/2016 1200", "20/01/2016 1200"); + DateTime dateTimeQuery = + new DateTime("17/01/2016 1200", "18/01/2016 1200"); + + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQuery)); + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource2, + dateTimeQuery)); + } + + @Test + public void isDateTimeWithinRange_dateTimeWithinRange() throws Exception { + DateTime dateTimeSource = + new DateTime("14/01/2016 1200", "16/01/2016 1200"); + DateTime dateTimeQueryFullyInRange = + new DateTime("14/01/2016 2000", "15/01/2016 1200"); + DateTime dateTimeQueryPartiallyInRange = + new DateTime("13/01/2016 1000", "15/01/2016 1200"); + + assertTrue(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQueryFullyInRange)); + assertTrue(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQueryPartiallyInRange)); + } + + @Test + public void isDateTimeWithinRange_dateTimeWithoutStartDate() + throws Exception { + DateTime dateTimeSource = + new DateTime("15/01/2016 1200", "17/01/2016 1100"); + DateTime dateTimeSourceWithoutStartDate = + new DateTime("", "16/01/2016 1200"); + DateTime dateTimeQuery = + new DateTime("14/01/2016 2000", "17/01/2016 1200"); + DateTime dateTimeQueryWithoutStartDate = + new DateTime("", "16/01/2016 1200"); + DateTime dateTimeQueryWithoutStartDate2 = + new DateTime("", "18/01/2016 1200"); + + assertTrue(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQuery)); + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQueryWithoutStartDate2)); + assertTrue(DateTimeUtil.isDateTimeWithinRange( + dateTimeSourceWithoutStartDate, dateTimeQuery)); + assertTrue(DateTimeUtil.isDateTimeWithinRange( + dateTimeSourceWithoutStartDate, dateTimeQueryWithoutStartDate)); + assertFalse(DateTimeUtil.isDateTimeWithinRange( + dateTimeSourceWithoutStartDate, + dateTimeQueryWithoutStartDate2)); + } + + @Test + public void isDateTimeConflicting_dateTimeConflicts() throws Exception { + DateTime dateTimeSource = + new DateTime("14/01/2016 1200", "16/01/2016 1200"); + DateTime conflictingDateTimeQuery = + new DateTime("14/01/2016 2000", "15/01/2016 1200"); + DateTime conflictingDateTimeQuery2 = + new DateTime("13/01/2016 1000", "15/01/2016 1200"); + + assertTrue(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + conflictingDateTimeQuery)); + assertTrue(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + conflictingDateTimeQuery2)); + } + + @Test + public void isDateTimeConflicting_dateTimeNotConflicting() + throws Exception { + DateTime dateTimeSource = + new DateTime("14/01/2016 1200", "16/01/2016 1200"); + DateTime dateTimeQueryOutOfRange = + new DateTime("18/01/2016 2000", "19/01/2016 1200"); + DateTime dateTimeAdjacent = + new DateTime("13/01/2016 1000", "14/01/2016 1200"); + + assertFalse(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + dateTimeQueryOutOfRange)); + assertFalse(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + dateTimeAdjacent)); + } + + @Test + public void getListOfFreeTimeSlotsInDate_success() + throws DateTimeException, IllegalDateException { + ArrayList listOfFilledTimeSlots = new ArrayList(); + DateTime dateToCheck = + new DateTime("29/10/2016 0000", "29/10/2016 2359"); + ArrayList currentList = new ArrayList(); + ArrayList expectedList = new ArrayList(); + + // Initialize listOfFilledTimeSlots + listOfFilledTimeSlots + .add(new DateTime("27/10/2016 1200", "29/10/2016 0830")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 0500", "29/10/2016 0630")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 0730", "29/10/2016 0900")); + listOfFilledTimeSlots.add(new DateTime("", "29/10/2016 1300")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 1400", "29/10/2016 1500")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 2330", "30/10/2016 0100")); + + // Initialize expectedList + expectedList.add(new DateTime("29/10/2016 0900", "29/10/2016 1400")); + expectedList.add(new DateTime("29/10/2016 1500", "29/10/2016 2330")); + + currentList = DateTimeUtil.getListOfFreeTimeSlotsInDate(dateToCheck, + listOfFilledTimeSlots); + + assertEquals(expectedList, currentList); + } + + @Test + public void getDurationInMinutesBetweenTwoLocalDateTime_success() { + LocalDateTime ldt1 = LocalDateTime.of(2016, 10, 29, 9, 36); + LocalDateTime ldt2 = LocalDateTime.of(2016, 10, 29, 14, 28); + + assertEquals( + DateTimeUtil.getDurationBetweenTwoLocalDateTime(ldt1, ldt2), + "4 hr 52 min"); + } + +} +``` +###### \java\tars\logic\CdLogicCommandTest.java +``` java +/** + * Logic command test for cd + */ +public class CdLogicCommandTest extends LogicCommandTest { + @Test + public void execute_cd_incorrectArgsFormatErrorMessageShown() + throws Exception { + assertCommandBehavior("cd ", CdCommand.MESSAGE_INVALID_FILEPATH); + } + + @Test + public void execute_cd_invalidFileTypeErrorMessageShown() throws Exception { + assertCommandBehavior("cd invalidFileType", + CdCommand.MESSAGE_INVALID_FILEPATH); + } + + @Test + public void execute_cd_new_file_success() throws Exception { + String tempTestTarsFilePath = + saveFolder.getRoot().getPath() + "TempTestTars.xml"; + assertCommandBehavior("cd " + tempTestTarsFilePath, String.format( + CdCommand.MESSAGE_SUCCESS_NEW_FILE, tempTestTarsFilePath)); + } + + @Test + public void execute_cd_existing_file_failureToReadExistingFile() + throws Exception { + String existingFilePath = + saveFolder.getRoot().getPath() + "TempTars.xml"; + File existingFile = new File(existingFilePath); + FileUtil.createIfMissing(existingFile); + assertCommandBehavior("cd " + existingFilePath, + CdCommand.MESSAGE_FAILURE_READ_FILE); + } + + @Test + public void execute_cd_existing_file_success() throws Exception { + String existingFilePath = + saveFolder.getRoot().getPath() + "TempTars.xml"; + TarsStorage testStorage = new XmlTarsStorage(existingFilePath); + testStorage.saveTars(Tars.getEmptyTars()); + assertCommandBehavior("cd " + existingFilePath, String.format( + CdCommand.MESSAGE_SUCCESS_EXISTING_FILE, existingFilePath)); + } +} +``` +###### \java\tars\logic\ConfirmLogicCommandTest.java +``` java +/** + * Logic command test for confirm + */ +public class ConfirmLogicCommandTest extends LogicCommandTest { + @Test + public void execute_confirm_invalidArgsFormatErrorMessageShown() + throws Exception { + + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE); + assertCommandBehavior("confirm ", expectedMessage); + assertCommandBehavior("confirm /p h 1 2", expectedMessage); + assertCommandBehavior("confirm 1 1 -dt invalidFlag", expectedMessage); + assertCommandBehavior("confirm 1 1 3", expectedMessage); + } + + @Test + public void execute_confirm_invalidRsvTaskIndex_ErrorMessageShown() + throws Exception { + assertCommandBehavior("confirm 2 3", + MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX); + } + + @Test + public void execute_confirm_invalidRsvTaskDateTimeIndex_ErrorMessageShown() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = new Tars(); + RsvTask rsvTask = + helper.generateReservedTaskWithOneDateTimeOnly("Test Task"); + expectedTars.addRsvTask(rsvTask); + model.addRsvTask(rsvTask); + + assertCommandBehavior("confirm 1 3", + Messages.MESSAGE_INVALID_DATETIME_DISPLAYED_INDEX, expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_confirm_success() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + + // Create added task + Task addedTask = helper.generateTaskWithName("Test Task"); + + // Create end state taskList with one confirmed task + List taskList = new ArrayList(); + taskList.add(addedTask); + + // Create Empty end state rsvTaskList + List rsvTaskList = new ArrayList(); + + RsvTask rsvTask = + helper.generateReservedTaskWithOneDateTimeOnly("Test Task"); + + Tars expectedTars = new Tars(); + expectedTars.addTask(addedTask); + + // Set Tars start state to 1 reserved task, and 0 tasks. + model.resetData(new Tars()); + model.addRsvTask(rsvTask); + + String expectedMessage = String + .format(ConfirmCommand.MESSAGE_CONFIRM_SUCCESS, addedTask); + assertCommandBehaviorWithRsvTaskList("confirm 1 1 /p h /t tag", + expectedMessage, expectedTars, taskList, rsvTaskList); + + } + +``` +###### \java\tars\logic\EditLogicCommandTest.java +``` java +/** + * Logic command test for edit + */ +public class EditLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_edit_invalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditCommand.MESSAGE_USAGE); + + assertInvalidInputBehaviorForEditCommand("edit ", expectedMessage); + assertInvalidInputBehaviorForEditCommand( + "edit 1 -invalidFlag invalidArg", expectedMessage); + } + + @Test + public void execute_edit_indexNotFoundErrorMessageShown() throws Exception { + assertIndexNotFoundBehaviorForCommand("edit"); + } + + @Test + public void execute_edit_invalidTaskData() throws Exception { + assertInvalidInputBehaviorForEditCommand("edit 1 /n []\\[;]", + Name.MESSAGE_NAME_CONSTRAINTS); + assertInvalidInputBehaviorForEditCommand( + "edit 1 /dt @@@notAValidDate@@@", MESSAGE_INVALID_DATE); + assertInvalidInputBehaviorForEditCommand("edit 1 /p medium", + Priority.MESSAGE_PRIORITY_CONSTRAINTS); + assertInvalidInputBehaviorForEditCommand( + "edit 1 /n validName /dt invalidDate", MESSAGE_INVALID_DATE); + assertInvalidInputBehaviorForEditCommand("edit 1 /tr $#$", + Tag.MESSAGE_TAG_CONSTRAINTS); + } + + @Test + public void execute_edit_editedCorrectTask() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskToAdd = helper.meetAdam(); + model.addTask(taskToAdd); + Tars expectedTars = prepareExpectedTars(); + + String inputCommand = "edit 1 /n Meet Betty Green /dt 20/09/2016 1800 " + + "to 21/09/2016 1800 /p h /tr tag2 /ta tag3"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(EditCommand.MESSAGE_EDIT_TASK_SUCCESS, + expectedTars.getTaskList().get(0)), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_edit_editedDuplicateTask() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskToAdd = helper.meetAdam(); + model.addTask(taskToAdd); + Tars expectedTars = new Tars(); + expectedTars.addTask(taskToAdd); + + String inputCommand = "edit 1 /n Meet Adam Brown"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + new DuplicateTaskException().getMessage().toString(), + expectedTars, expectedTars.getTaskList()); + } + + private void assertInvalidInputBehaviorForEditCommand(String inputCommand, + String expectedMessage) throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + List taskList = helper.generateTaskList(2); + + // set Tars state to 2 tasks + model.resetData(new Tars()); + for (Task p : taskList) { + model.addTask(p); + } + + assertCommandBehavior(inputCommand, expectedMessage, model.getTars(), + taskList); + } + +``` +###### \java\tars\logic\FindLogicCommandTest.java +``` java +/** + * Logic command test for find + */ +public class FindLogicCommandTest extends LogicCommandTest { + + @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_quickSearchOnlyMatchesFullWordsInNames() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.generateTaskWithName("bla bla KEY bla"); + Task pTarget2 = helper.generateTaskWithName("bla KEY bla bceofeia"); + Task p1 = helper.generateTaskWithName("KE Y"); + Task p2 = helper.generateTaskWithName("KEYKEYKEY sduauo"); + + List fourTasks = + helper.generateTaskList(p1, pTarget1, p2, pTarget2); + Tars expectedTars = helper.generateTars(fourTasks); + List expectedList = helper.generateTaskList(pTarget1, pTarget2); + helper.addToModel(model, fourTasks); + + String searchKeywords = "\nQuick Search Keywords: [KEY]"; + + assertCommandBehavior("find KEY", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_quickSearchIsNotCaseSensitive() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task p1 = helper.generateTaskWithName("bla bla KEY bla"); + Task p2 = helper.generateTaskWithName("bla KEY bla bceofeia"); + Task p3 = helper.generateTaskWithName("key key"); + Task p4 = helper.generateTaskWithName("KEy sduauo"); + + List fourTasks = helper.generateTaskList(p3, p1, p4, p2); + Tars expectedTars = helper.generateTars(fourTasks); + List expectedList = fourTasks; + helper.addToModel(model, fourTasks); + + String searchKeywords = "\nQuick Search Keywords: [KEY]"; + + assertCommandBehavior("find KEY", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_quickSearchMatchesIfAllKeywordPresent() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task p1 = helper.generateTaskWithName("bla bla KEY bla"); + Task p2 = helper.generateTaskWithName("bla rAnDoM bla bceofeia"); + Task p3 = helper.generateTaskWithName("sduauo"); + Task pTarget1 = helper.generateTaskWithName("key key rAnDoM"); + + List fourTasks = helper.generateTaskList(p1, p2, p3, pTarget1); + Tars expectedTars = helper.generateTars(fourTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, fourTasks); + + String searchKeywords = "\nQuick Search Keywords: [key, rAnDoM]"; + + assertCommandBehavior("find key rAnDoM", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchMatchesIfAllKeywordPresent() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = "\nFilter Search Keywords: [Task Name: adam] " + + "[DateTime: 01/09/2016 1400 to 01/09/2016 1500] [Priority: medium] " + + "[Status: Undone] [Tags: tag1]"; + + assertCommandBehavior( + "find /n adam /dt 01/09/2016 1400 to 01/09/2016 1500 /p medium /ud /t tag1", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchWithoutDateTimeQuery() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = "\nFilter Search Keywords: [Task Name: adam] " + + "[Priority: medium] " + "[Status: Undone] [Tags: tag1]"; + + assertCommandBehavior("find /n adam /p medium /ud /t tag1", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchSingleDateTimeQuery() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = + "\nFilter Search Keywords: [DateTime: 01/09/2016 1400] "; + + assertCommandBehavior("find /dt 01/09/2016 1400", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchTaskNotFound() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(); + helper.addToModel(model, threeTasks); + + String searchKeywords = + "\nFilter Search Keywords: [DateTime: 01/09/2010 1400] "; + + assertCommandBehavior("find /dt 01/09/2010 1400", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchBothDoneAndUndoneSearched() + throws Exception { + + assertCommandBehavior("find /do /ud", + TaskQuery.MESSAGE_BOTH_STATUS_SEARCHED_ERROR); + } + + @Test + public void execute_find_filterSearchMultipleFlagsUsed() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = + "\nFilter Search Keywords: [Task Name: meet adam] " + + "[Priority: medium] " + + "[Status: Undone] [Tags: tag2 tag1]"; + + assertCommandBehavior("find /n meet adam /p medium /ud /t tag1 /t tag2", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } +} +``` +###### \java\tars\logic\FreeLogicCommandTest.java +``` java +/** + * Logic command test for free + */ +public class FreeLogicCommandTest extends LogicCommandTest { + @Test + public void execute_free_incorrectArgsFormat_errorMessageShown() + throws Exception { + assertCommandBehavior("free ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + assertCommandBehavior("free invalidargs", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + assertCommandBehavior("free 29/10/2016 to 30/10/2016", + FreeCommand.MESSAGE_DATE_RANGE_DETECTED); + } + + @Test + public void execute_free_noFreeTimeSlotResult() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.fillModelAndTarsForFreeCommand(model); + + Task task4 = helper.generateTaskWithNameAndDate("Task 4", + new DateTime("10/10/2016 1500", "12/10/2016 1400")); + List expectedShownTaskList = helper.generateTaskList(task4); + + assertCommandBehavior("free 11/10/2016", + String.format(FreeCommand.MESSAGE_NO_FREE_TIMESLOTS, + "Tuesday, 11/10/2016"), + expectedTars, expectedShownTaskList); + + // Case where the user types in a time should still be allowed to pass. Programme will + // extract the date + assertCommandBehavior("free 11/10/2016 0900", + String.format(FreeCommand.MESSAGE_NO_FREE_TIMESLOTS, + "Tuesday, 11/10/2016"), + expectedTars, expectedShownTaskList); + } + + @Test + public void execute_free_freeDayResult() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.fillModelAndTarsForFreeCommand(model); + + // Create expected empty list + List expectedShownTaskList = helper.generateTaskList(); + + assertCommandBehavior("free 01/11/2016", + String.format(FreeCommand.MESSAGE_FREE_DAY, + "Tuesday, 01/11/2016"), + expectedTars, expectedShownTaskList); + } + + @Test + public void execute_free_freeTimeSlotsFound() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.fillModelAndTarsForFreeCommand(model); + + + // Fill up expected shown task list + Task taskWithoutStartDate = helper.generateTaskWithNameAndDate( + "Task without startdate", new DateTime("", "29/10/2016 1500")); + Task task1 = helper.generateTaskWithNameAndDate("Task 1", + new DateTime("28/10/2016 2200", "29/10/2016 0100")); + Task task2 = helper.generateTaskWithNameAndDate("Task 2", + new DateTime("29/10/2016 1430", "29/10/2016 1800")); + List expectedShownTaskList = + helper.generateTaskList(taskWithoutStartDate, task1, task2); + + StringBuilder sb = new StringBuilder(); + + sb.append("Saturday, 29/10/2016").append(": \n") + .append("1. 0100hrs to 1400hrs (13 hr 0 min)\n") + .append("2. 1800hrs to 2359hrs (5 hr 59 min)"); + + assertCommandBehavior("free 29/10/2016", + String.format(FreeCommand.MESSAGE_SUCCESS, sb.toString()), + expectedTars, expectedShownTaskList); + } +} +``` +###### \java\tars\logic\RsvLogicCommandTest.java +``` java +/** + * Logic command test for rsv + */ +public class RsvLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_rsvInvalidArgsFormatErrorMessageShown() + throws Exception { + + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE); + assertCommandBehavior("rsv ", expectedMessage); + } + + @Test + public void execute_rsvAddInvalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessageForNullDate = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_DATETIME_NOT_FOUND); + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE); + assertCommandBehavior("rsv Rsv Task Without Date", + expectedMessageForNullDate); + assertCommandBehavior("rsv Rsv Task with flags other than date -p h", + expectedMessageForNullDate); + assertCommandBehavior("rsv /dt tomorrow", expectedMessage); + assertCommandBehavior("rsv Rsv Task with invalid Date /dt invalidDate", + MESSAGE_INVALID_DATE); + } + + @Test + public void execute_rsvDelInvalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE_DEL); + assertCommandBehavior("rsv invalidArgs /del 1", expectedMessage); + assertCommandBehavior("rsv /del invalidValue", expectedMessage); + } + + @Test + public void execute_rsv_del_successful() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + + // Create a reserved task + RsvTask rsvTask = + helper.generateReservedTaskWithOneDateTimeOnly("Test Task"); + + // Create empty taskList + List taskList = new ArrayList(); + + // Create empty end state rsvTaskList + List rsvTaskList = new ArrayList(); + + // Create empty end state Tars + Tars expectedTars = new Tars(); + + // Set Tars start state to 1 reserved task, and 0 tasks. + model.resetData(new Tars()); + model.addRsvTask(rsvTask); + + String expectedMessage = String.format(RsvCommand.MESSAGE_SUCCESS_DEL, + "1.\t" + rsvTask + "\n"); + assertCommandBehaviorWithRsvTaskList("rsv /del 1", expectedMessage, + expectedTars, taskList, rsvTaskList); + } + + @Test + public void execute_rsv_conflictingTaskShowWarning() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask rsvTaskA = + helper.generateReservedTaskWithNameAndDate("Rsv Task A", + new DateTime("14/10/2016 0900", "16/10/2016 0900")); + RsvTask taskToRsv = + helper.generateReservedTaskWithNameAndDate("Task To Rsv", + new DateTime("13/10/2016 1000", "15/10/2016 1000")); + + // Create empty taskList + List taskList = new ArrayList(); + + List rsvTaskList = new ArrayList(); + rsvTaskList.add(rsvTaskA); + rsvTaskList.add(taskToRsv); + + Tars expectedTars = new Tars(); + String expectedMessage = + String.format(RsvCommand.MESSAGE_SUCCESS, taskToRsv.toString()) + + "\n" + MESSAGE_CONFLICTING_TASKS_WARNING + + "\nConflicts for " + + taskToRsv.getDateTimeList().get(0).toString() + ": " + + "\nRsvTask 1: " + rsvTaskA.toString(); + + expectedTars.addRsvTask(rsvTaskA); + expectedTars.addRsvTask(taskToRsv); + + model.resetData(new Tars()); + model.addRsvTask(rsvTaskA); + + assertCommandBehaviorWithRsvTaskList( + "rsv Task To Rsv /dt 13/10/2016 1000 to 15/10/2016 1000", + expectedMessage, expectedTars, taskList, rsvTaskList); + + } + +``` +###### \java\tars\logic\TypicalTestDataHelper.java +``` java + */ + protected Task generateTaskWithNameAndDate(String name, DateTime dateTime) throws Exception { + assert (dateTime != null && name != null); + return new Task(new Name(name), dateTime, new Priority("h"), new Status(false), + new UniqueTagList(new Tag("tag"))); + } + + /** + * Generates a RsvTask object with given name and datetime(s) + */ + protected RsvTask generateReservedTaskWithNameAndDate(String name, DateTime... dateTimes) throws Exception { + ArrayList dateTimeList = new ArrayList(); + for (DateTime dt : dateTimes) { + dateTimeList.add(dt); + } + return new RsvTask(new Name(name), dateTimeList); + } + + /** + * Generates a RsvTask object with given name and a dummy dateTime + */ + protected RsvTask generateReservedTaskWithOneDateTimeOnly(String name) throws Exception { + ArrayList dateTimeList = new ArrayList(); + dateTimeList.add(new DateTime("05/09/2016 1400", "06/09/2016 2200")); + return new RsvTask(new Name(name), dateTimeList); + } + + protected Tars fillModelAndTarsForFreeCommand(Model model) throws Exception { + RsvTask rsvTask1 = generateReservedTaskWithNameAndDate("rsvTask1", + new DateTime("29/10/2016 1400", "29/10/2016 1500"), + new DateTime("30/10/2016 1400", "30/10/2016 1500")); + RsvTask rsvTask2 = generateReservedTaskWithNameAndDate("rsvTask2", + new DateTime("28/10/2016 0900", "28/10/2016 1400")); + Task floatingTask = generateTaskWithNameAndDate("Floating Task", new DateTime("", "")); + Task taskWithoutStartDate = generateTaskWithNameAndDate("Task without startdate", + new DateTime("", "29/10/2016 1500")); + Task task1 = generateTaskWithNameAndDate("Task 1", new DateTime("28/10/2016 2200", "29/10/2016 0100")); + Task task2 = generateTaskWithNameAndDate("Task 2", new DateTime("29/10/2016 1430", "29/10/2016 1800")); + Task task3 = generateTaskWithNameAndDate("Task 3", new DateTime("01/10/2016 1400", "01/10/2016 1500")); + Task task4 = generateTaskWithNameAndDate("Task 4", new DateTime("10/10/2016 1500", "12/10/2016 1400")); + + Tars tars = new Tars(); + tars.addRsvTask(rsvTask1); + tars.addRsvTask(rsvTask2); + tars.addTask(floatingTask); + tars.addTask(taskWithoutStartDate); + tars.addTask(task1); + tars.addTask(task2); + tars.addTask(task3); + tars.addTask(task4); + + model.addRsvTask(rsvTask1); + model.addRsvTask(rsvTask2); + model.addTask(floatingTask); + model.addTask(taskWithoutStartDate); + model.addTask(task1); + model.addTask(task2); + model.addTask(task3); + model.addTask(task4); + + return tars; + } +} +``` +###### \java\tars\testutil\RsvTaskBuilder.java +``` java +/** + * A utility class to help with building reserve task objects. + */ +public class RsvTaskBuilder { + + private TestRsvTask rsvTask; + + public RsvTaskBuilder() { + this.rsvTask = new TestRsvTask(); + } + + public RsvTaskBuilder withName(String name) throws IllegalValueException { + this.rsvTask.setName(new Name(name)); + return this; + } + + public RsvTaskBuilder withDateTime(String dateTime1, String dateTime2) + throws IllegalValueException { + this.rsvTask.setDateTimeList(new DateTime(dateTime1, dateTime2)); + return this; + } + + public TestRsvTask build() { + return this.rsvTask; + } + +} +``` +###### \java\tars\testutil\TestRsvTask.java +``` java +/** + * A mutable reserve task object. For testing only + */ +public class TestRsvTask extends RsvTask { + + public void setDateTimeList(DateTime...dateTimes) { + for (DateTime dt : dateTimes) { + dateTimeList.add(dt); + } + } + + public String getRsvCommand() { + StringBuilder sb = new StringBuilder(); + sb.append("rsv " + this.getName().taskName + " "); + for (DateTime dt : dateTimeList) { + sb.append("/dt " + dt.toString() + " "); + } + + return sb.toString(); + } + +} +``` +###### \java\tars\testutil\TestUtil.java +``` java + /** + * Edits the task with index 1 on the list of tasks. + * + * @param tasks An array of tasks. + * @param indexToEdit Index of the task to edit. + * @param nameToEdit Name of the task to edit. + * @param priorityToEdit Priority of the task to edit. + * @return The modified array of tasks. + */ + public static TestTask[] editTask(final TestTask[] tasks, int indexToEdit, Name nameToEdit, + Priority priorityToEdit) { + List listOfTasks = asList(tasks); + listOfTasks.get(indexToEdit).setName(nameToEdit); + listOfTasks.get(indexToEdit).setPriority(priorityToEdit); + + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); + } + +``` diff --git a/collated/test/A0139924W.md b/collated/test/A0139924W.md new file mode 100644 index 000000000000..ab7611fdba96 --- /dev/null +++ b/collated/test/A0139924W.md @@ -0,0 +1,1237 @@ +# A0139924W +###### \java\guitests\UndoCommandTest.java +``` java +/** + * GUI test for undo command + */ +public class UndoCommandTest extends TarsGuiTest { + + @Test + public void undo_add_successful() { + // setup + TestTask taskToUndo = td.taskH; + commandBox.runCommand(taskToUndo.getAddCommand()); + + commandBox.runCommand("undo"); + TestTask[] expectedList = {td.taskG}; + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(AddCommand.MESSAGE_UNDO, taskToUndo))); + } + + @Test + public void undo_delete_successful() { + // setup + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToUndo = currentList[currentList.length - 1]; + commandBox.runCommand("del " + currentList.length); + + commandBox.runCommand("undo"); + currentList = td.getTypicalTasks(); + assertTrue(taskListPanel.isListMatching(currentList)); + assertResultMessage(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(DeleteCommand.MESSAGE_UNDO, + "1.\t" + taskToUndo + "\n"))); + + } +} +``` +###### \java\tars\commons\util\DateTimeUtilTest.java +``` java + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void parseStringToDateTime_invalidDate() { + thrown.expect(DateTimeException.class); + thrown.expectMessage(Messages.MESSAGE_INVALID_DATE); + + DateTimeUtil.parseStringToDateTime("abc"); + DateTimeUtil.parseStringToDateTime("hello world"); + + DateTimeUtil.parseStringToDateTime("+1"); + DateTimeUtil.parseStringToDateTime("-1"); + } + + @Test + public void parseStringToDateTime_emptyArgs() { + String[] expected = + new String[] {StringUtil.EMPTY_STRING, StringUtil.EMPTY_STRING}; + String[] actual = DateTimeUtil + .parseStringToDateTime(StringUtil.STRING_WHITESPACE); + + assertEquals(expected[0], actual[0]); + assertEquals(expected[1], actual[1]); + + actual = DateTimeUtil.parseStringToDateTime(StringUtil.EMPTY_STRING); + assertEquals(expected[0], actual[0]); + assertEquals(expected[1], actual[1]); + } + + @Test + public void parseStringToDateTime_singleDateSuccessful() { + String[] expectedDateTime = + {StringUtil.EMPTY_STRING, "01/01/2016 1500"}; + String[] actualDateTime = + DateTimeUtil.parseStringToDateTime("1/1/2016 1500"); + + assertArrayEquals(expectedDateTime, actualDateTime); + } + + @Test + public void parseStringToDateTime_dateRangeSuccessful() { + String[] expectedDateTime = {"01/01/2016 1500", "02/01/2016 1600"}; + String[] actualDateTime = DateTimeUtil + .parseStringToDateTime("1/1/2016 1500 to 2/1/2016 1600"); + + assertArrayEquals(expectedDateTime, actualDateTime); + } + +``` +###### \java\tars\logic\AddLogicCommandTest.java +``` java +/** + * Logic command test for add + */ +public class AddLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_add_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCommand.MESSAGE_USAGE); + assertCommandBehavior( + "add /dt 22/04/2016 1400 to 23/04/2016 2200 /p h Valid Task Name", + expectedMessage); + assertCommandBehavior("add", expectedMessage); + } + + @Test + public void execute_add_invalidTaskData() throws Exception { + assertCommandBehavior( + "add []\\[;] /dt 05/09/2016 1400 to 06/09/2016 2200 /p m", + Name.MESSAGE_NAME_CONSTRAINTS); + assertCommandBehavior( + "add name - hello world /dt 05/09/2016 1400 to 06/09/2016 2200 /p m", + Name.MESSAGE_NAME_CONSTRAINTS); + assertCommandBehavior( + "add Valid Task Name /dt @@@notAValidDate@@@ -p m", + MESSAGE_INVALID_DATE); + assertCommandBehavior( + "add Valid Task Name /dt 05/09/2016 1400 to 01/09/2016 2200 /p m", + MESSAGE_INVALID_END_DATE); + assertCommandBehavior( + "add Valid Task Name /dt 05/09/2016 1400 to 06/09/2016 2200 /p medium", + Priority.MESSAGE_PRIORITY_CONSTRAINTS); + assertCommandBehavior( + "add Valid Task Name /dt 05/09/2016 1400 to 06/09/2016 2200 /p m /t invalid_-[.tag", + Tag.MESSAGE_TAG_CONSTRAINTS); + + } + + @Test + public void execute_add_successful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_add_endDateSuccessful() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.generateTaskWithEndDateOnly("Jane"); + Tars expectedTars = new Tars(); + expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_add_floatingTaskSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.floatingTask(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_add_emptyTaskNameInvalidFormat() throws Exception { + assertCommandBehavior("add ", + String.format(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCommand.MESSAGE_USAGE))); + } + +``` +###### \java\tars\logic\AddLogicCommandTest.java +``` java + @Test + public void execute_undoAndRedo_addSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute add command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeAdded); + + // execute undo command and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(AddCommand.MESSAGE_UNDO, toBeAdded)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addTask(toBeAdded); + + // execute redo command and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(AddCommand.MESSAGE_SUCCESS, + toBeAdded + "\n")), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_addUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute add command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeAdded); + model.deleteTask(toBeAdded); + + // execute undo command and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + + model.addTask(toBeAdded); + expectedTars.addTask(toBeAdded); + + // execute redo command and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + } +} +``` +###### \java\tars\logic\ConfirmLogicCommandTest.java +``` java + @Test + public void execute_undoAndRedo_confirmSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Task taskToConfirm = helper.generateTaskWithName("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addTask(taskToConfirm); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "confirm 1 1 /p h /t tag"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(ConfirmCommand.MESSAGE_CONFIRM_SUCCESS, + taskToConfirm), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(taskToConfirm); + expectedTars.addRsvTask(taskToRsv); + + // execute undo command and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + expectedTars.addTask(taskToConfirm); + expectedTars.removeRsvTask(taskToRsv); + + // execute redo command and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_confirmUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Task taskToConfirm = helper.generateTaskWithName("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addTask(taskToConfirm); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "confirm 1 1 /p h /t tag"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(ConfirmCommand.MESSAGE_CONFIRM_SUCCESS, + taskToConfirm), + expectedTars, expectedTars.getTaskList()); + + model.addRsvTask(taskToRsv); + expectedTars.addRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + model.deleteRsvTask(taskToRsv); + expectedTars.removeRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_RSV_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + } + +} +``` +###### \java\tars\logic\DeleteLogicCommandTest.java +``` java + @Test + public void execute_delete_invalidRange() throws Exception { + String expectedMessage = InvalidRangeException.MESSAGE_INVALID_RANGE; + assertCommandBehavior("del 2..1", expectedMessage); + } +``` +###### \java\tars\logic\DeleteLogicCommandTest.java +``` java + @Test + public void execute_undoAndRedo_delSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeRemoved = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeRemoved); + + model.addTask(toBeRemoved); + expectedTars.removeTask(toBeRemoved); + + // execute command and verify result + assertCommandBehavior("del 1", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + toBeRemoved + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addTask(toBeRemoved); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(DeleteCommand.MESSAGE_UNDO, + "1.\t" + toBeRemoved + "\n")), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeRemoved); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(DeleteCommand.MESSAGE_REDO, + "1.\t" + toBeRemoved + "\n")), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_delUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeRemoved = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeRemoved); + + model.addTask(toBeRemoved); + expectedTars.removeTask(toBeRemoved); + + // execute command and verify result + assertCommandBehavior("del 1", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + toBeRemoved + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addTask(toBeRemoved); + model.addTask(toBeRemoved); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeRemoved); + model.deleteTask(toBeRemoved); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + } + +} +``` +###### \java\tars\logic\EditLogicCommandTest.java +``` java + @Test + public void execute_undoAndRedo_editSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskToAdd = helper.meetAdam(); + Tars expectedTars = prepareExpectedTars(); + Task editedTask = + expectedTars.getUniqueTaskList().getInternalList().get(0); + + model.addTask(taskToAdd); + + String inputCommand = "edit 1 /n Meet Betty Green /dt 20/09/2016 1800 " + + "to 21/09/2016 1800 /p h /tr tag2 /ta tag3"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(EditCommand.MESSAGE_EDIT_TASK_SUCCESS, + expectedTars.getTaskList().get(0)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.replaceTask(editedTask, taskToAdd); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(EditCommand.MESSAGE_UNDO, taskToAdd)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.replaceTask(taskToAdd, editedTask); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(EditCommand.MESSAGE_REDO, taskToAdd)), + expectedTars, expectedTars.getTaskList()); + } + + private static Tars prepareExpectedTars() throws IllegalValueException { + Tars expectedTars = new Tars(); + Name editedName = new Name("Meet Betty Green"); + DateTime editedDateTime = + new DateTime("20/09/2016 1800", "21/09/2016 1800"); + Priority editedPriority = new Priority("h"); + Status editedStatus = new Status(false); + Tag tag1 = new Tag("tag1"); + Tag tag2 = new Tag("tag3"); + UniqueTagList editedTags = new UniqueTagList(tag1, tag2); + Task editedTask = new Task(editedName, editedDateTime, editedPriority, + editedStatus, editedTags); + expectedTars.addTask(editedTask); + expectedTars.getUniqueTagList().add(new Tag("tag2")); + + return expectedTars; + } +} +``` +###### \java\tars\logic\HelpLogicCommandTest.java +``` java +/** + * Logic command test for help + */ +public class HelpLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_help_successful() throws Exception { + assertCommandBehavior("help", HelpCommand.SHOWING_HELP_MESSAGE); + assertTrue(helpShown); + } + + @Test + public void execute_help_invalidArgs() throws Exception { + assertCommandBehavior("help hello world", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_help_showAddUsageSuccessful() throws Exception { + assertCommandBehavior("help add", HelpCommand.SHOWING_HELP_MESSAGE); + assertTrue(helpShown); + } +} +``` +###### \java\tars\logic\parser\ArgumentTokenizerTest.java +``` java +/** + * Argument tokenizer test. + * + * Credit: Test adapted from nus-cs2103-AY1617S1/addressbook-level4 + */ +public class ArgumentTokenizerTest { + private static final Prefix unknownPrefix = new Prefix("/uuuuuu"); + private static final Prefix tagPrefix = new Prefix("/t"); + private static final Prefix dateTimePrefix = new Prefix("/dt"); + private static final Prefix namePrefix = new Prefix("/n"); + + @Test + public void accessors_notTokenizedYet() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix); + + assertPreambleAbsent(tokenizer); + assertArgumentAbsent(tokenizer, tagPrefix); + } + + @Test + public void tokenize_emptyArgsString_noValues() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix); + String argsString = " "; + tokenizer.tokenize(argsString); + + assertPreambleAbsent(tokenizer); + assertArgumentAbsent(tokenizer, tagPrefix); + } + + @Test + public void tokenize_noPrefixes_allTakenAsPreamble() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(); + String argsString = " some random string /t tag with leading and trailing spaces "; + tokenizer.tokenize(argsString); + + // Same string expected as preamble, but leading/trailing spaces should be trimmed + assertPreamblePresent(tokenizer, argsString.trim()); + + } + + @Test + public void tokenize_oneArgument() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix); + + // Preamble present + tokenizer.tokenize(" Some preamble string /t Argument value "); + assertPreamblePresent(tokenizer, "Some preamble string"); + assertArgumentPresent(tokenizer, tagPrefix, "Argument value"); + + // No preamble + tokenizer.tokenize(" /t Argument value "); + assertPreambleAbsent(tokenizer); + assertArgumentPresent(tokenizer, tagPrefix, "Argument value"); + + } + + @Test + public void tokenize_multipleArguments() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix, dateTimePrefix, namePrefix); + + // Only two arguments are present + tokenizer.tokenize("SomePreambleString /t dashT-Value /n slashP value"); + assertPreamblePresent(tokenizer, "SomePreambleString"); + assertArgumentPresent(tokenizer, namePrefix, "slashP value"); + assertArgumentPresent(tokenizer, tagPrefix, "dashT-Value"); + assertArgumentAbsent(tokenizer, dateTimePrefix); + + /* Also covers: Reusing of the tokenizer multiple times */ + + // Reuse tokenizer on an empty string to ensure state is correctly reset + // (i.e. no stale values from the previous tokenizing remain in the state) + tokenizer.tokenize(""); + assertPreambleAbsent(tokenizer); + assertArgumentAbsent(tokenizer, tagPrefix); + + /** Also covers: testing for prefixes not specified as a prefix **/ + + // Prefixes not previously given to the tokenizer should not return any values + String stringWithUnknownPrefix = unknownPrefix.value + "some value"; + tokenizer.tokenize(stringWithUnknownPrefix); + assertArgumentAbsent(tokenizer, unknownPrefix); + assertPreamblePresent(tokenizer, stringWithUnknownPrefix); // Unknown prefix is taken as + // part of preamble + } + + @Test + public void tokenize_multipleArgumentsWithRepeats() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix, namePrefix); + + tokenizer.tokenize("SomePreambleString /n /t dashT-Value /t another"); + assertPreamblePresent(tokenizer, "SomePreambleString"); + assertArgumentPresent(tokenizer, tagPrefix, "dashT-Value", "another"); + assertArgumentPresent(tokenizer, namePrefix, "/n"); + } + + private void assertPreamblePresent(ArgumentTokenizer argsTokenizer, String expectedPreamble) { + assertEquals(expectedPreamble, argsTokenizer.getPreamble().get()); + } + + private void assertPreambleAbsent(ArgumentTokenizer argsTokenizer) { + assertFalse(argsTokenizer.getPreamble().isPresent()); + } + + private void assertArgumentAbsent(ArgumentTokenizer argsTokenizer, Prefix prefix) { + assertFalse(argsTokenizer.getValue(prefix).isPresent()); + } + + private void assertArgumentPresent(ArgumentTokenizer argsTokenizer, + Prefix prefix, String... expectedValues) { + + // Verify the first value is returned + assertEquals(expectedValues[0], argsTokenizer.getValue(prefix).get()); + + // Verify the number of values returned is as expected + assertEquals(expectedValues.length, + argsTokenizer.getMultipleValues(prefix).get().size()); + + // Verify all values returned are as expected and in order + final AtomicInteger count = new AtomicInteger(); + argsTokenizer.getMultipleValues(prefix).get().forEach((v) -> { + assertEquals(expectedValues[count.getAndIncrement()], v); + }); + + } +} +``` +###### \java\tars\logic\parser\PrefixTest.java +``` java +/** + * Prefix test + */ +public class PrefixTest { + + @Test + public void equalsMethod() { + Prefix tagPrefix = new Prefix("/tag"); + + assertEquals(tagPrefix, tagPrefix); + assertEquals(tagPrefix, new Prefix("/tag")); + + assertNotEquals(tagPrefix, "/tag"); + assertNotEquals(tagPrefix, new Prefix("/nTag")); + } +} +``` +###### \java\tars\logic\RedoLogicCommandTest.java +``` java +/** + * Logic command test for redo + */ +public class RedoLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_redo_emptyCmdHistStack() throws Exception { + assertCommandBehavior(RedoCommand.COMMAND_WORD, + RedoCommand.MESSAGE_EMPTY_REDO_CMD_HIST); + } + + @Test + public void execute_redo_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RedoCommand.MESSAGE_USAGE); + assertCommandBehavior("redo EXTRA ARGUMENTS", expectedMessage); + assertCommandBehavior("redo 123", expectedMessage); + } +} +``` +###### \java\tars\logic\RsvLogicCommandTest.java +``` java + @Test + public void execute_undoAndRedo_rsvSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addRsvTask(taskToRsv); + + String inputCommand = + "rsv Reserved Task /dt 05/09/2016 1400 to 06/09/2016 2200"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS, taskToRsv), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_UNDO_DELETE, + taskToRsv)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_REDO_ADD, taskToRsv)), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_rsvUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addRsvTask(taskToRsv); + + String inputCommand = + "rsv Reserved Task /dt 05/09/2016 1400 to 06/09/2016 2200"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS, taskToRsv), + expectedTars, expectedTars.getTaskList()); + + model.deleteRsvTask(taskToRsv); + expectedTars.removeRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_RSV_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + + model.addRsvTask(taskToRsv); + expectedTars.addRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_rsvDelSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "rsv /del 1"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS_DEL, + "1.\t" + taskToRsv + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_UNDO_ADD, + "1.\t" + taskToRsv + "\n")), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_REDO_DELETE, + "1.\t" + taskToRsv + "\n")), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_rsvDelUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "rsv /del 1"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS_DEL, + "1.\t" + taskToRsv + "\n"), + expectedTars, expectedTars.getTaskList()); + + model.addRsvTask(taskToRsv); + expectedTars.addRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + model.deleteRsvTask(taskToRsv); + expectedTars.removeRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_RSV_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + + } +} +``` +###### \java\tars\logic\TagLogicCommandTest.java +``` java +/** + * Logic command test for tag + */ +public class TagLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_tag_invalidPrefix() throws Exception { + assertCommandBehavior("tag /gg", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tag_invalidFormat() throws Exception { + assertCommandBehavior("tag ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag RANDOM_TEXT", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tag_invalidIndex() throws Exception { + // EP: negative number + assertCommandBehavior("tag /e -1 VALIDTASKNAME", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /del -1", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + + // EP: zero + assertCommandBehavior("tag /e 0 VALIDTASKNAME", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + assertCommandBehavior("tag /del 0", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + + // EP: signed number + assertCommandBehavior("tag /e +1 VALIDTASKNAME", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /e -2 VALIDTASKNAME", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /del +1", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + assertCommandBehavior("tag /del -1", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + + // EP: invalid number + assertCommandBehavior("tag /del aaa", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /del bbb", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tag_emptyParameters() throws Exception { + assertCommandBehavior("tag", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -e", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -e ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -del", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -del ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tagList_emptyListSuccessful() throws Exception { + // execute command and verify result + assertCommandBehavior("tag /ls", + new Formatter().formatTags(model.getUniqueTagList())); + } + + @Test + public void execute_tagList_filledListSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /ls", + new Formatter().formatTags(model.getUniqueTagList()), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_successful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeRenamed = + expectedTars.getUniqueTagList().getInternalList().get(0); + Tag newTag = new Tag("tag3"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + // execute command and verify result + assertCommandBehavior("tag /e 1 tag3", + String.format(String.format( + TagCommand.MESSAGE_RENAME_TAG_SUCCESS, "tag1", "tag3")), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_duplicate() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /e 1 tag2", MESSAGE_DUPLICATE_TAG, + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_invalidIndex() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /e 3 VALIDTAGNAME", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX, expectedTars, + expectedTars.getTaskList()); + + assertCommandBehavior("tag /e 4 VALIDTAGNAME", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX, expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_invalidTagName() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /e 1 INVALID_TAG_NAME", + Tag.MESSAGE_TAG_CONSTRAINTS, expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_tagDel_successful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeDeleted = + expectedTars.getUniqueTagList().getInternalList().get(0); + + expectedTars.getUniqueTagList().remove(new Tag(toBeDeleted)); + expectedTars.removeTagFromAllTasks(toBeDeleted); + + // execute command and verify result + assertCommandBehavior("tag /del 1", String + .format(TagCommand.MESSAGE_DELETE_TAG_SUCCESS, toBeDeleted), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagDel_invalidIndex() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /del 3", MESSAGE_INVALID_TAG_DISPLAYED_INDEX, + expectedTars, expectedTars.getTaskList()); + + assertCommandBehavior("tag /del 4", MESSAGE_INVALID_TAG_DISPLAYED_INDEX, + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_tagEditSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeRenamed = + expectedTars.getUniqueTagList().getInternalList().get(0); + Tag newTag = new Tag("tag3"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + assertCommandBehavior("tag /e 1 tag3", + String.format(String.format( + TagCommand.MESSAGE_RENAME_TAG_SUCCESS, "tag1", "tag3")), + expectedTars, expectedTars.getTaskList()); + + toBeRenamed = expectedTars.getUniqueTagList().getInternalList().get(0); + newTag = new Tag("tag1"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + toBeRenamed = expectedTars.getUniqueTagList().getInternalList().get(0); + newTag = new Tag("tag3"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_tagDelSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeDeleted = + expectedTars.getUniqueTagList().getInternalList().get(0); + + expectedTars.getUniqueTagList().remove(new Tag(toBeDeleted)); + ArrayList editedTaskList = + expectedTars.removeTagFromAllTasks(toBeDeleted); + + // execute command and verify result + assertCommandBehavior("tag /del 1", String + .format(TagCommand.MESSAGE_DELETE_TAG_SUCCESS, toBeDeleted), + expectedTars, expectedTars.getTaskList()); + + expectedTars.getUniqueTagList().add(new Tag(toBeDeleted)); + expectedTars.addTagToAllTasks(toBeDeleted, editedTaskList); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + expectedTars.getUniqueTagList().remove(new Tag(toBeDeleted)); + expectedTars.removeTagFromAllTasks(toBeDeleted); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + } +} +``` +###### \java\tars\logic\UndoLogicCommandTest.java +``` java +/** + * Logic command test for undo + */ +public class UndoLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_undo_emptyCmdHistStack() throws Exception { + assertCommandBehavior(UndoCommand.COMMAND_WORD, + UndoCommand.MESSAGE_EMPTY_UNDO_CMD_HIST); + } + + @Test + public void execute_undo_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UndoCommand.MESSAGE_USAGE); + assertCommandBehavior("undo EXTRA ARGUMENTS", expectedMessage); + assertCommandBehavior("undo 123", expectedMessage); + } + +} +``` +###### \java\tars\ui\formatter\DateFormatterTest.java +``` java +/** + * Date formatter test + */ +public class DateFormatterTest { + + @Test + public void generateSingleDateFormat_todayCorrectFormat() { + LocalDate testDate = LocalDate.now(); + LocalTime testTime = LocalTime.of(0, 0, 0); + LocalDateTime testDateTime = LocalDateTime.of(testDate, testTime); + + assertEquals("Today at 12:00 AM", + DateFormatter.generateSingleDateFormat(testDateTime)); + } + + @Test + public void generateSingleDateFormat_tomorrowCorrectFormat() { + LocalDate testDate = LocalDate.now().plusDays(1); + LocalTime testTime = LocalTime.of(0, 0, 0); + LocalDateTime testDateTime = LocalDateTime.of(testDate, testTime); + + assertEquals("Tomorrow at 12:00 AM", + DateFormatter.generateSingleDateFormat(testDateTime)); + } + + @Test + public void generateSingleDateFormat_otherDayCorrectFormat() { + LocalDate testDate = LocalDate.of(2010, 10, 10); + LocalTime testTime = LocalTime.of(0, 0, 0); + LocalDateTime testDateTime = LocalDateTime.of(testDate, testTime); + + assertEquals("Sun, Oct 10 2010 12:00 AM", + DateFormatter.generateSingleDateFormat(testDateTime)); + } + + @Test + public void generateDateRangeFormat_sameDayCorrectFormat() { + LocalDate testDateA = LocalDate.of(2010, 10, 10); + LocalTime testTimeA = LocalTime.of(0, 0, 0); + LocalDate testDateB = LocalDate.of(2010, 10, 10); + LocalTime testTimeB = LocalTime.of(1, 0, 0); + LocalDateTime testDateTimeA = LocalDateTime.of(testDateA, testTimeA); + LocalDateTime testDateTimeB = LocalDateTime.of(testDateB, testTimeB); + + assertEquals("Sun, Oct 10 2010 12:00 AM - 01:00 AM", DateFormatter + .generateDateRangeFormat(testDateTimeA, testDateTimeB)); + } + + @Test + public void generateDateRangeFormat_diffDayCorrectFormat() { + LocalDate testDateA = LocalDate.of(2010, 10, 10); + LocalTime testTimeA = LocalTime.of(0, 0, 0); + LocalDate testDateB = LocalDate.of(2010, 11, 10); + LocalTime testTimeB = LocalTime.of(1, 0, 0); + LocalDateTime testDateTimeA = LocalDateTime.of(testDateA, testTimeA); + LocalDateTime testDateTimeB = LocalDateTime.of(testDateB, testTimeB); + + assertEquals("Sun, Oct 10 2010 12:00 AM - Wed, Nov 10 2010 01:00 AM", + DateFormatter.generateDateRangeFormat(testDateTimeA, + testDateTimeB)); + } + + @Test + public void formatDate_singleStartDateCorrectFormat() + throws DateTimeException, IllegalDateException { + DateTime dateTime = new DateTime("", "10/10/2010 1200"); + + assertEquals("Sun, Oct 10 2010 12:00 PM", + DateFormatter.formatDate(dateTime)); + } +} +``` +###### \java\tars\ui\formatter\FormatterTest.java +``` java +/** + * Formatter test + */ +public class FormatterTest { + + @Test + public void formatTaskList_emptyList() { + String actualFormattedText = + new Formatter().formatTaskList(new ArrayList()); + String expectedFormmatedText = + String.format(Formatter.EMPTY_LIST_MESSAGE, "tasks"); + + assertEquals(expectedFormmatedText, actualFormattedText); + } + + @Test + public void formatRsvTaskList_emptyList() { + String actualFormattedText = + new Formatter().formatRsvTaskList(new ArrayList()); + String expectedFormmatedText = + String.format(Formatter.EMPTY_LIST_MESSAGE, "tasks"); + + assertEquals(expectedFormmatedText, actualFormattedText); + } +} +``` diff --git a/collated/test/A0140022H.md b/collated/test/A0140022H.md new file mode 100644 index 000000000000..44f13b6b3a83 --- /dev/null +++ b/collated/test/A0140022H.md @@ -0,0 +1,327 @@ +# A0140022H +###### \java\guitests\AddCommandTest.java +``` java +/** + * GUI test for add command + */ +public class AddCommandTest extends TarsGuiTest { + + @Test + public void add() { + // add one task + TestTask[] currentList = td.getTypicalTasks(); + TestTask[] expectedList1 = {td.taskG}; + TestTask taskToAdd = td.taskH; + assertAddSuccess(taskToAdd, expectedList1); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + // add another task + taskToAdd = td.taskI; + TestTask[] expectedList2 = {td.taskH}; + assertAddSuccess(taskToAdd, expectedList2); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + expectedList2 = TestUtil.addTasksToList(expectedList2, taskToAdd); + + // add duplicate task + commandBox.runCommand(td.taskH.getAddCommand()); + assertResultMessage(Messages.MESSAGE_DUPLICATE_TASK); + assertTrue(taskListPanel.isListMatching(expectedList2)); + + // add to empty list + commandBox.runCommand("clear"); + assertAddSuccess(td.taskA); + + // invalid command + commandBox.runCommand("adds Johnny"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + + assertAddSuccess(td.taskA); + } + + private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { + commandBox.runCommand(taskToAdd.getAddCommand()); + + // confirm the new card contains the right data + TaskCardHandle addedCard = + taskListPanel.navigateToTask(taskToAdd.getName().taskName); + 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)); + } + + @Test + public void addRecurring() { + TestTask[] recurringList = new TestTask[0]; + recurringList = + TestUtil.addTasksToList(recurringList, td.taskC, td.taskD); + try { + recurringList[1].setName(new Name("Task C")); + recurringList[1].setPriority(new Priority("l")); + } catch (IllegalValueException e) { + e.printStackTrace(); + } + + commandBox.runCommand("clear"); + commandBox.runCommand( + "add Task C /dt 03/09/2016 1400 to 04/09/2016 1400 /p l /r 2 every day"); + assertTrue(taskListPanel.isListMatching(recurringList)); + } +} +``` +###### \java\guitests\ListCommandTest.java +``` java +/** + * GUI test for list commands + */ +public class ListCommandTest extends TarsGuiTest { + + private TestTask[] currentList = td.getTypicalTasks();; + + @Test + public void listAllTask() { + TestTask[] expectedList = currentList; + commandBox.runCommand("ls"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS); + } + + @Test + public void listAllTaskByDateTime() { + TestTask[] expectedList = currentList; + commandBox.runCommand("ls /dt"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_DATETIME); + } + + @Test + public void listAllTaskByDateTimeDescending() { + TestTask[] expectedList = {td.taskG, td.taskF, td.taskE, td.taskD, + td.taskC, td.taskB, td.taskA}; + commandBox.runCommand("ls /dt dsc"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_DATETIME_DESCENDING); + } + + @Test + public void listAllTaskByPriority() { + TestTask[] expectedList = {td.taskC, td.taskF, td.taskB, td.taskE, + td.taskA, td.taskD, td.taskG}; + commandBox.runCommand("ls /p"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_PRIORITY); + } + + @Test + public void listAllTaskByPriorityDescending() { + TestTask[] expectedList = {td.taskA, td.taskD, td.taskG, td.taskB, + td.taskE, td.taskC, td.taskF}; + commandBox.runCommand("ls /p dsc"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_PRIORITY_DESCENDING); + } + + @Test + public void listInvalidCommand() { + TestTask[] expectedList = currentList; + commandBox.runCommand("ls /r"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } +} +``` +###### \java\tars\commons\util\DateTimeUtilTest.java +``` java + @Test + public void modifyDate() { + String dateToModify = "06/09/2016 2200"; + + String frequencyDay = "day"; + String frequencyWeek = "week"; + String frequencyMonth = "month"; + String frequencyYear = "year"; + + String expectedDay = "07/09/2016 2200"; + String expectedWeek = "13/09/2016 2200"; + String expectedMonth = "06/10/2016 2200"; + String expectedYear = "06/09/2017 2200"; + + String modifiedDay = + DateTimeUtil.modifyDate(dateToModify, frequencyDay); + String modifiedWeek = + DateTimeUtil.modifyDate(dateToModify, frequencyWeek); + String modifiedMonth = + DateTimeUtil.modifyDate(dateToModify, frequencyMonth); + String modifiedYear = + DateTimeUtil.modifyDate(dateToModify, frequencyYear); + + assertEquals(expectedDay, modifiedDay); + assertEquals(expectedWeek, modifiedWeek); + assertEquals(expectedMonth, modifiedMonth); + assertEquals(expectedYear, modifiedYear); + } + +``` +###### \java\tars\logic\AddLogicCommandTest.java +``` java + @Test + public void execute_add_recurring() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Task toBeAdded2 = helper.meetAdam(); + toBeAdded2.setDateTime( + new DateTime("08/09/2016 1400", "08/09/2016 1500")); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + expectedTars.addTask(toBeAdded2); + + // execute command and verify result + String expectedMessage = + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"); + expectedMessage += + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded2 + "\n"); + assertCommandBehavior( + helper.generateAddCommand(toBeAdded).concat(" /r 2 every week"), + expectedMessage, expectedTars, expectedTars.getTaskList()); + } +``` +###### \java\tars\logic\ListLogicCommandTest.java +``` java +/** + * Logic command test for list + */ +public class ListLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_list_invalidFlagsErrorMessageShown() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ListCommand.MESSAGE_USAGE); + assertIncorrectIndexFormatBehaviorForCommand("ls -", expectedMessage); + } + + @Test + public void execute_list_showsAllTasks() throws Exception { + // prepare expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.generateTars(2); + List expectedList = expectedTars.getTaskList(); + + // prepare tars state + helper.addToModel(model, 2); + + assertCommandBehavior("ls", ListCommand.MESSAGE_SUCCESS, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByPriority() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setPriority(new Priority("l")); + task2.setPriority(new Priority("m")); + task3.setPriority(new Priority("h")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task3); + expectedTars.addTask(task2); + expectedTars.addTask(task1); + List listToSort = helper.generateTaskList(task3, task2, task1); + List expectedList = helper.generateTaskList(task1, task2, task3); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /p", + ListCommand.MESSAGE_SUCCESS_PRIORITY, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByPriorityDescending() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setPriority(new Priority("l")); + task2.setPriority(new Priority("m")); + task3.setPriority(new Priority("h")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task1); + expectedTars.addTask(task2); + expectedTars.addTask(task3); + List listToSort = helper.generateTaskList(task1, task2, task3); + List expectedList = helper.generateTaskList(task3, task2, task1); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /p dsc", + ListCommand.MESSAGE_SUCCESS_PRIORITY_DESCENDING, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByDatetime() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setDateTime(new DateTime("", "01/02/2016 1600")); + task2.setDateTime(new DateTime("", "02/02/2016 1600")); + task3.setDateTime(new DateTime("", "03/02/2016 1600")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task3); + expectedTars.addTask(task2); + expectedTars.addTask(task1); + List listToSort = helper.generateTaskList(task3, task2, task1); + List expectedList = helper.generateTaskList(task1, task2, task3); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /dt", + ListCommand.MESSAGE_SUCCESS_DATETIME, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByDatetimeDescending() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setDateTime(new DateTime("", "01/02/2016 1600")); + task2.setDateTime(new DateTime("", "02/02/2016 1600")); + task3.setDateTime(new DateTime("", "03/02/2016 1600")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task1); + expectedTars.addTask(task2); + expectedTars.addTask(task3); + List listToSort = helper.generateTaskList(task1, task2, task3); + List expectedList = helper.generateTaskList(task3, task2, task1); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /dt dsc", + ListCommand.MESSAGE_SUCCESS_DATETIME_DESCENDING, expectedTars, + expectedList); + } +} +``` +###### \java\tars\logic\LogicCommandTest.java +``` java + protected void assertCommandBehaviorForList(String inputCommand, + String expectedMessage, ReadOnlyTars expectedTars, + 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.getFilteredTaskList()); + + // Confirm the state of data (saved and in-memory) is as expected + assertEquals(expectedTars, latestSavedTars); + } +``` diff --git a/copyright.txt b/copyright.txt index 93aa2a39ce25..0d254fe5a4d9 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,8 +1,4 @@ -Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob - -Copyright by Susumu Yoshida - http://www.mcdodesign.com/ -- address_book_32.png -- AddressApp.ico +Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob Copyright by Jan Jan Kovařík - http://glyphicons.com/ - calendar.png diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 33df65bea583..7ee75507e243 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -4,49 +4,83 @@ We are a team based in the [School of Computing, National University of Singapor ## Project Team -#### [Damith C. Rajapakse](http://www.comp.nus.edu.sg/~damithch)
-
+#### [Chuan Wei Candiie](https://github.com/Candiie)
+
**Role**: Project Advisor ------ +----- -#### [Joshua Lee](http://github.com/lejolly) -
-Role: Developer
-Responsibilities: UI +[comment]: # (@@author A0124333U) +#### [Lee Wenwei Johnervan](http://github.com/johnervan)
+
+* Components in charge of: [Model](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#model-component) +* Aspects/tools in charge of: Testing, Windows Environment Tester, EclEmma, Documentation +* Features implemented: + * [Change File Storage Directory](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#changing-data-storage-location--cd) + * [Reserve Timeslots for Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#reserving-timeslots-for-a-task--rsv) + * [Delete Reserved Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#deleting-a-task-with-reserved-timeslots--rsv-d) + * [Confirm Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#confirming-a-reserved-timeslot--confirm) + * [Find Tasks [Quick Search & Filter Search]](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#finding-tasks--find) + * [List Free Timeslots in a Specified Day](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/src/main/java/tars/logic/commands/FreeCommand.java) +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0124333U.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0124333U.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0124333U.md)] +* Other Major Contributions: + * Did the initial refactoring from AddressBook-Level4 to TARS + * User Guide + ----- +[comment]: # (@@author A0140022H) +#### [Calvin Yang Jiawei](http://github.com/origiri)
+
-#### [Leow Yijin](http://github.com/yijinl) -
-Role: Developer
-Responsibilities: Data +* Components in charge of: [Storage](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#storage-component) +* Aspects/tools in charge of: Github +* Features implemented: + * [Add Recurring Task](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#adding-a-task--add) + * [List Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#listing-tasks--ls) + * [Help](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#displaying-a-list-of-available-commands--help) + * Result Summary +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0140022H.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0140022H.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0140022H.md)] +* Other Major Contributions: + * User Guide ----- -#### [Martin Choo](http://github.com/m133225) -
-Role: Developer
-Responsibilities: Dev Ops - ------ +[comment]: # (@@author A0139924W) +#### [Chia Wei Kang](http://github.com/weikangchia)
+
-#### [Thien Nguyen](https://github.com/ndt93) - Role: Developer
- Responsibilities: Threading - - ----- +* Components in charge of: [Logic](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#logic-component) +* Aspects/tools in charge of: Testing, Travis, Codacy, Coveralls +* Features implemented: + * [Undo Commands](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#undoing-a-command--undo) + * [Redo Commands](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#redoing-a-command--redo) + * [Edit Tags](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#editing-a-tags-name--tag-e) + * [Delete Tags from all Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#deleting-a-tag--tag-del) + * [List Tags](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#listing-all-tags--tag-ls) + * [Natural Date Input](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#supported-date-formats) + * Shortcut keys for undo and redo commands +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0139924W.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0139924W.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0139924W.md)] +* Other Major Contributions: + * ArgumentTokenizer (flexible commands) + * Did the refactoring of parser and logic command test + * Set up Travis, Codacy and Coveralls -#### [You Liang](http://github.com/yl-coder) -
- Role: Developer
- Responsibilities: UI - - ----- +----- -# Contributors +[comment]: # (@@author A0121533W) +#### [Foo En Teng Joel](http://github.com/jaeoheeail)
+
-We welcome contributions. See [Contact Us](ContactUs.md) page for more info. +* Components in charge of: [UI](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/DeveloperGuide.md#ui-component) +* Aspects/tools in charge of: SceneBuilder +* Features implemented: + * [Editing Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#editing-a-task--edit) + * [Marking Tasks Done & Undone](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#marking-tasks--mark) + * [Deleting Tasks](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/docs/UserGuide.md#deleting-a-task--del) +* Code Written: [[functional code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/main/A0121533W.md)][[test code](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/test/A0121533W.md)][[docs](https://github.com/CS2103AUG2016-F10-C1/main/blob/master/collated/docs/A0121533W.md)] +* Other Major Contributions: + * Designed App Logo + * Designed App UI -* [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..41bc591b4537 100644 --- a/docs/ContactUs.md +++ b/docs/ContactUs.md @@ -1,8 +1,12 @@ # Contact Us -* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/se-edu/addressbook-level4/issues) - if you noticed bugs or have suggestions on how to improve. +* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/CS2103AUG2016-F10-C1/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: + +1. [Chia Wei Kang](mailto:weikangchia@u.nus.edu) +2. [Calvin Yang](mailto:e0003907@u.nus.edu) +3. [Johnervan Lee](mailto:johnervan@u.nus.edu) +4. [Joel Foo](mailto:joel.foo@u.nus.edu) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index acc94d2e1367..a28aee993a00 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -11,7 +11,7 @@ * [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 E: Product Survey](#appendix-e--product-survey) ## Setting up @@ -50,18 +50,18 @@ 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/tars/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 clean up method where necessary. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. Two of those classes play an important role at the architecture level. * `EventsCentre` : This class (written using [Google's Event Bus library](https://github.com/google/guava/wiki/EventBusExplained)) - is used to by componnents to communicate with other components using events (i.e. a form of _Event Driven_ design) + is used to by components to communicate with other components using events (i.e. a form of _Event Driven_ design) * `LogsCenter` : Used by many classes to write log messages to the App's log files. The rest of the App consists four components. -* [**`UI`**](#ui-component) : The UI of tha App. +* [**`UI`**](#ui-component) : The UI of the App. * [**`Logic`**](#logic-component) : The command executor. * [**`Model`**](#model-component) : Holds the data of the App in-memory. * [**`Storage`**](#storage-component) : Reads data from, and writes data to, the hard disk. @@ -71,16 +71,16 @@ Each of the four components * Exposes its functionality using a `{Component Name}Manager` class e.g. `LogicManager.java` The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the -command `delete 3`. +command `del 1`. - + >Note how the `Model` simply raises a `ModelChangedEvent` when the model is 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 @@ -92,15 +92,15 @@ The sections below give more details of each component.
-**API** : [`Ui.java`](../src/main/java/seedu/address/ui/Ui.java) +**API** : [`Ui.java`](../src/main/java/tars/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` inherits from the abstract `UiPart` class -and they can be loaded using the `UiPartLoader`. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TaskListPanel`, +`TaskCard`, `StatusBarFooter`, `TabPane` etc. All these, including the `MainWindow` inherits 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/tars/ui/MainWindow.java) is specified in [`MainWindow.fxml`](../src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -108,27 +108,30 @@ 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 raises from various parts of the App and updates the UI accordingly. + ### Logic component
-**API** : [`Logic.java`](../src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](../src/main/java/tars/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. +3. The command execution can affect the `Model` (e.g. adding a task) and/or raise events. 4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui` + + ### Model component
-**API** : [`Model.java`](../src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](../src/main/java/tars/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 so that the UI automatically updates when the data in the list change. * Does not depend on any of the other three components. @@ -136,15 +139,15 @@ The `Model`,
-**API** : [`Storage.java`](../src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](../src/main/java/tars/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 TARS data in xml format and read it back. ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commans` package. +Classes used by multiple components are in the `tars.commons` package. ## Implementation @@ -205,13 +208,13 @@ Tests can be found in the `./src/test/java` folder. 2. **Non-GUI Tests** - These are tests not involving the GUI. They include, 1. _Unit tests_ targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.UrlUtilTest` + e.g. `tars.commons.UrlUtilTest` 2. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest` + e.g. `tars.storage.StorageManagerTest` 3. Hybrids of unit and integration tests. These test are checking multiple code units as well as - how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest` + how they are connected together.
+ e.g. `tars.logic.LogicManagerTest` **Headless GUI Testing** : Thanks to the ([TestFX](https://github.com/TestFX/TestFX)) library we use, @@ -236,76 +239,295 @@ 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, TARS 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.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)
+[comment]: # (@@author A0124333U) ## Appendix A : User Stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` Priority | As a ... | I want to ... | So that I can... --------- | :-------- | :--------- | :----------- +-------- | :---------- | :--------- | :----------- `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App -`* * *` | user | add a new person | -`* * *` | user | delete a person | remove entries that I no longer need -`* * *` | user | find a person by name | locate details of persons without having to go through the entire list -`* *` | user | hide [private contact details](#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 +`* * *` | user | add a new event (with start and end timings) | keep track of it and complete it in the future +`* * *` | user | add a new task (tasks that have to be done before a specific deadline) | keep track of the deadline +`* * *` | user | add a floating task (tasks without specific times) | have a task that can roll over to the next day if I did not get to it +`* * *` | user | delete a task | remove tasks that I no longer need to do +`* * *` | user | edit a task | change the details of the tasks +`* * *` | user | view tasks | decide on the follow-up action for each task +`* * *` | user | clear all the data | remove all my information +`* *` | user | prioritize my task | do the more important ones first +`* *` | user | search for a task by keywords | view the details of task and complete it +`* *` | user | undo a command | undo the last action that I just performed +`* *` | user | redo a command | redo the last action that I just performed +`* *` | user | add recurring tasks | save time entering the same task over multiple dates +`* *` | user | choose my data storage location | have the flexibility to use the program on multiple computers as they can read from the same file stored on the cloud e.g. Google Drive +`* *` | user | add a tag on tasks | categorize my task +`* *` | user | edit a tag | rename the tag without the need to delete and add it again +`* *` | user | mark my tasks as done | indicate that the task has been completed +`* *` | user | mark my tasks as undone | indicate that the task has not been completed +`* *` | user | view tasks by tags/priority/date | group my tasks based on a field of my choice +`* *` | user | reserve dates for a task/event | block out time slots and add them upon confirmation of the time and date details +`* *` |user| can view all tags and edit them | edit a specific tag of all tasks with that tag in one command +`*` | user | have flexibility in entering commands | type in commands without having to remember the exact format +`*` | user | have suggestions on free slots | decide when to add a new task or shift current tasks + +[comment]: # (@@author A0139924W) +## Appendix B : Use Cases -{More to be added} +(For all use cases below, the **System** is the `TARS` and the **Actor** is the `user`, unless specified otherwise) -## Appendix B : Use Cases +#### Use case: UC01 - View help -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +**MSS** -#### Use case: Delete person +1. User requests to view help +2. TARS shows a list of usage instructions
+Use case ends. + +#### Use case: UC02- 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. User requests to submit a new task +2. TARS save the task and add the command to command history
+Use case ends. + +**Extensions** + +2a. The format is invalid + +> 2a1. TARS shows an error message
+ Use case resumes at step 1 + +2b. The end datetime is earlier than start datetime + +> 2b1. TARS shows an error message
+ Use case resumes at step 1 + +2c. The task already exists
+ +> 2c1. TARS shows an error message
+ Use case resume at step 1 + +#### Use case: UC03- Delete task + +**MSS** + +1. User requests to list tasks +2. TARS shows a list of tasks +3. User requests to delete a specific task in the list +4. TARS deletes the task
Use case ends. **Extensions** 2a. The list is empty -> Use case ends +> 2a1. Use case ends 3a. The given index is invalid -> 3a1. AddressBook shows an error message
+> 3a1. TARS shows an error message
Use case resumes at step 2 + +#### Use case: UC04 - Edit task -{More to be added} +**MSS** + +1. User requests to list tasks +2. TARS shows a list of tasks +3. User requests to edit a specific task in the list +4. TARS updates the task
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +3b. The format is invalid + +> 3b1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC05 - Edit tag name + +**MSS** + +1. User requests to list tags +2. TARS shows a list of tags +3. User requests to edit a specific tag in the list +4. TARS updates the tag
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +3b. The format is invalid + +> 3b1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC06 - Delete tag + +**MSS** + +1. User requests to list tags +2. TARS shows a list of tags +3. User requests to delete a specific tag in the list +4. TARS deletes the tag
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +3a. The given index is invalid + +> 3a1. TARS shows an error message
+ Use case resumes at step 2 + +#### Use case: UC07 - List tags + +**MSS** + +1. User requests to list tags +2. TARS shows a list of tags
+Use case ends. + +**Extensions** + +2a. The list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +#### Use case: UC08 - Undo a previous command + +**MSS** + +1. User requests to undo a previous command +2. TARS reinstates (undo) the last command in the undo history list and add the command to the redo history list
+Use case ends. + +**Extensions** + +2a. The undo history list is empty + +> 2a1. TARS shows an empty list message
+ Use case ends + +#### Use case: UC09 - Redo a previous undo command + +**MSS** + +1. User requests to redo a previous command +2. TARS redo the last command in the redo history list and add the command to the undo history list
+Use case ends. + +**Extensions** + +2a. The redo history list is empty + +> 2a1. TARS shows an empty list 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. +5. Should have a command line interface as the primary mode of input. +6. Should not take more than 1 second to load the app. +7. Should not take more than 3 seconds to execute any commands. +8. Should not take more than 1 seconds to load the command result after a command execute. +9. Should not consume memory of more than 512 MB. +10. Should work without requiring an installer. +11. Should work on desktop without network/Internet connection. +12. Should be able to work stand-alone. It should not be a plug-in to another software. +13. Should not use relational databases. +14. Should store data locally and should be in human editable text file. +15. Should follow the Object-oriented paradigm. +16. Should not use third-party frameworks/libraries that + * are not free + * require installation + * violate other constraint + +[comment]: # (@@author A0121533W) +## Appendix D : Glossary -{More to be added} +##### Command +> Reserved keywords for you to execute a command e.g. add, edit, del, do, ud. -## Appendix D : Glossary +##### Event +> Has a start time and end time. + +##### DateTime +> Variable that has information on a particular date and time. + +##### Deadline +> Tasks that have to be done before a specified deadline. + +##### Floating Tasks +> Tasks without specific dateTimes. + +##### Index +> Positive number corresponding to the order at which the item is listed. ##### Mainstream OS +> Windows, Linux, Unix, OS-X. + +##### Prefix +> Reserved keywords for commands e.g. /p /dt /t + +##### Reserved Task +> Tasks that have multiple DateTimes at which one can be confirmed later. -> Windows, Linux, Unix, OS-X +##### Storage Directory +> Your system's file path (e.g. /data/tars.xml) at which data are stored. -##### Private contact detail +##### Tag +> Categorization of a task. -> A contact detail that is not meant to be shared with others +##### Task +> Something that needs to be done (see Floating Task, Deadline, Event). + + +[comment]: # (@@author A0121533W) ## Appendix E : Product Survey -{TODO: Add a summary of competing products} +Product | Strength | Weaknesses +-------- | :-------- | :-------- +[Wunderlist](https://www.wunderlist.com/)|
  1. Cloud-based
    • Ability to sync tasks
  2. Multiple-device Usage
  3. Data is stored on the device and syncs with cloud storage when there's internet access
    • Faster than internet based todo apps like Google Calendar
  4. Provides reminders
  5. Simple user interface not too cluttered
  6. Able to set a deadline (for dates only) for a task
|
  1. Requires a lot of clicks and fields to fill to save a task
  2. Unable to block multiple slots when the exact timing of a task is uncertain
  3. Unable to set a due time for tasks
+[Todo.txt](http://todotxt.com/)|
  1. Quick & easy unix-y access
  2. Solves Google calendar being too slow
  3. Manage tasks with as few keystrokes as possible
  4. Works without Internet connectivity
|
  1. No block feature
  2. Unable to look for suitable slot
+[Fantastical](https://flexibits.com/fantastical)|
  1. Flexible
    • Choose between dark and light theme
    • Works with Google, iCloud, Exchange and more
  2. Use natural language to quickly create events and reminders
|
  1. No block feature
  2. Need to click to create an event
  3. Only available for Mac
+[Todoist](https://en.todoist.com/)|
  1. Good parser
    • Extensive list of words to use that it is able to recognize (e.g. every day/week/month, every 27th, every Jan 27th)
  2. Able to reorganize task or sort by date, priority or name
  3. Ability to tag labels
  4. Able to see a week's overview of tasks or only today's task
  5. Able to import and export task in CSV format
  6. Able to search tasks easily (search bar at the top)
  7. Able to add task at any time and at any page (add task button next to search bar)
|
  1. No block feature
  2. Certain features can only be accessed by paying
+ diff --git a/docs/LearningOutcomes.md b/docs/LearningOutcomes.md deleted file mode 100644 index 31f26a37b480..000000000000 --- a/docs/LearningOutcomes.md +++ /dev/null @@ -1,20 +0,0 @@ -# Learning Outcomes -After studying this code and completing the corresponding exercises, you should be able to, - -1. [Use High-Level Designs `[LO-HighLevelDesign]`](#use-high-level-designs-lo-highleveldesign) - - ------------------------------------------------------------------------------------------------------- - -## Use High-Level Designs `[LO-HighLevelDesign]` -Note how the [Developer Guide](DeveloperGuide.md#design) describes the high-level design using an -_Architecture Diagrams_ and high-level sequence diagrams. - -#### Exercise: Add more user stories - -* ... - ------------------------------------------------------------------------------------------------------- - -{More to be added} - diff --git a/docs/UserGuide.md b/docs/UserGuide.md index c18832432d1f..8a50f594fd2e 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,127 +1,401 @@ +[comment]: # (@@author A0124333U) # User Guide * [Quick Start](#quick-start) * [Features](#features) * [FAQ](#faq) +* [Support Date Format](#supported-date-formats) * [Command Summary](#command-summary) ## Quick Start -0. Ensure you have Java version `1.8.0_60` or later installed in your Computer.
- > Having any Java 8 version is not enough.
+1. Ensure you have Java version `1.8.0_60` or later installed in your Computer. + > 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' tab. -2. Copy the file to the folder you want to use as the home folder for your Address Book. -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.
+ Click [here](http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html) to download the latest Java version. + +2. Download the latest `tars.jar` from the '[releases](https://github.com/CS2103AUG2016-F10-C1/main/releases)' tab. +3. Copy the file to the folder you want to use as the home folder for your TARS App. +4. Double-click the file to start the app. The GUI should appear in a few seconds. +5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window. -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 +6. Some example commands you can try: + * **`ls`** : lists all tasks + * **`add`**` Complete CS2103 Quiz 3 /dt 23/09/2016 /p h /t Quiz /t CS2103` : + adds a task `Complete CS2103 Quiz 3` to TARS. + * **`del`**` 3` : deletes the 3rd task shown in TARS. * **`exit`** : exits the app -6. Refer to the [Features](#features) section below for details of each command.
+7. Refer to the [Features](#features) section below for details of each command. +8. Note + - All text in `< >` are required fields whereas those in `[ ]` are optional. + - `` refers to the index number of a task shown in the task list. + - The index **must be a positive integer** 1, 2, 3, ... + - Priority options are: `h` for High, `m` for Medium, `l` for Low. ## Features - -#### 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]...` +#### Adding a task : `add` +Adds a task to TARS +Format: ` [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]` -> Words in `UPPER_CASE` are the parameters, items in `SQUARE_BRACKETS` are optional, -> items with `...` after them can have multiple instances. Order of parameters are fixed. +> Support for events (i.e., has a start time and end time), deadlines (tasks that have to be done before a specific deadline), and floating tasks (tasks without specific times). > -> Persons can have any number of tags (including 0) +> Parameters can be in any order. 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 Meet John Doe /dt 26/09/2016 0900 to 26/09/2016 1030 /t catchup` +* `add Complete CS2103 Quiz /dt 23/09/2016 /p h /t Quiz /t CS2103, /r 13 EVERY WEEK` +* `add Floating Task` -#### Listing all persons : `list` -Shows a list of all persons in the address book.
-Format: `list` +[comment]: # (@@author A0124333U) +#### Changing data storage location : `cd` +Changes the directory of the TARS storage file. +Format: `cd ` -#### 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]` +> Returns an error if the directory chosen is invalid. +> +> `` must end with the file type extension, `.xml` -> The search is case sensitive, the order of the keywords does not matter, only the name is searched, -and persons matching at least one keyword will be returned (i.e. `OR` search). +Examples: +* `cd C:\Users\John_Doe\Documents\tars.xml` -Examples: -* `find John`
- Returns `John Doe` but not `john` -* `find Betsy Tim John`
- Returns Any person having names `Betsy`, `Tim`, or `John` +#### Clearing the data storage file : `clear` +Clears the whole To-Do List storage file. +Format: `clear` -#### Deleting a person : `delete` -Deletes the specified person from the address book. Irreversible.
-Format: `delete INDEX` +[comment]: # (@@author A0124333U) +#### Confirming a reserved timeslot : `confirm` +Confirms a dateTime for a reserved task and adds it to the task list. +Format: `confirm [/p PRIORITY] [/t TAG_NAME ...]` -> Deletes the person 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, ... +> Confirm the task of a specific `` at a dateTime of a specific ``. +> +> The `` refers to the index number shown in the reserved task list. +> +> The `` refers to the index number of the dateTime. -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. +Examples: +* `confirm 3 2 /p l /t Tag` -#### Select a person : `select` -Selects the person identified by the index number used in the last person listing.
-Format: `select INDEX` +[comment]: # (@@author A0121533W) +#### Deleting a task : `del` +Deletes the task based on its index in the task list. +Formats: +* `del [INDEX ...]` +* `del ..` -> Selects the person and loads the Google search page the person 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, ... +> Deletes the task at the specific ``. +> +> Start index of range must be before end index. -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. +Examples: +* `del 3 6` +* `del 1..3` + +[comment]: # (@@author A0121533W) +#### Marking tasks as done : `do` +Marks the task based on its index in the task list as done. +Format: `do [INDEX ...]` +Format: `do ..` + +> Marks the task at the specific `` as `done`. +> +> Start index of range must be before end index. + +Examples: +* `do 2 4 6` +* `do 1..3` + +[comment]: # (@@author A0121533W) +#### Editing a task : `edit` +Edits any component of a particular task. +Format: `edit [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] [/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]` + +> Edits the task at the specific ``. +> +> `/ta` adds a tag to the task. +> +> `/tr` removes a tag from the task. +> +> Parameters can be in any order. + +Examples: +* `edit 3 /n Meet John Tan /dt 08/10/2016 1000 to 1200 /p h /ta friend` -#### Clearing all entries : `clear` -Clears all entries from the address book.
-Format: `clear` #### Exiting the program : `exit` -Exits the program.
-Format: `exit` +Exits the program. +Format: `exit` + +[comment]: # (@@author A0124333U) +#### Finding tasks : `find` +Finds all tasks containing a list of keywords (i.e. AND search). +Two modes: Quick Search & Filter Search. +Format: +* [Quick Search]: `find [KEYWORD ...]` +* [Filter Search]: `find [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]` + +> **Quick Search Mode**: Find tasks quickly by entering keywords that match what is displayed in the task list. +> +> **Filter Search Mode**: Find tasks using task filters (i.e. /n, /p, /dt, /do, /ud, /t). +> +> Use /n to filter tasks by task name. +> +> Use /p to filter tasks by priority level. +> +> Use /dt to filter tasks by date (in a date range). +> +> Use /do to filter all done tasks (Cannot be used together with /ud). +> +> Use /ud to filter all undone tasks (Cannot be used together with /do). +> +> Use /t to filter tasks by tags. +> +> `` are **case-insensitive**. +> +> Parameters can be in any order. + +Examples: +* `find meet John` uses Quick Search and returns all tasks containing BOTH the keywords "meet" and "John" (e.g. meet John Doe) +* `find /n meet /dt 17/10/2016 1300 to 18/10/2016 1400` uses Filter Search and returns all tasks whose name contains "meet" and whose task date falls within the range "17/10/2016 1300 to 18/10/2016 1400" (e.g. meet Tim for dinner, 17/10/2016 1800 to 17/10/2016 1900) + +[comment]: # (@@author A0124333U) +#### Suggesting free timeslots : `free` +Suggests free timeslots in a specified day. +Format: `free ` + +> Does not check for tasks without dateTime nor tasks without a start dateTime. + +Examples: +* `free next tuesdsay` +* `free 26/10/2016` + +[comment]: # (@@author A0140022H) +#### Displaying a list of available commands : `help` +Shows program usage instructions in help panel. +Format: `help [COMMAND_WORD]` + +> Help is also shown if you enter an incorrect command e.g. `abcd`. + +Examples: +* `help add` +* `help summary` + +[comment]: # (@@author A0140022H) +#### Listing tasks : `ls` +Lists all tasks. +Format: +* `ls` +* `ls /dt [dsc]` +* `ls /p [dsc]` + +> All tasks listed by default. +> +> Use /dt to list all tasks by earliest end dateTime. +> +> Use /p to list all task by priority from low to high. +> +> Use dsc with previous two prefixes to reverse the order. + +Examples: +* `ls` +* `ls /dt` +* `ls /dt dsc` +* `ls /p` +* `ls /p dsc` + +[comment]: # (@@author A0139924W) +#### Redoing a command : `redo` +Redo a previous command +Format: `redo` + +> Able to redo all `add`, `delete`, `edit`, `tag`, `rsv`, `confirm` and `del` commands from the time the app starts running. +> +> Keyboard shortcut: CTRL-Y + +[comment]: # (@@author A0124333U) +#### Reserving timeslots for a task : `rsv` +Reserves one or more timeslot for a task +Format: `rsv [/dt DATETIME ...]` + +> Multiple dateTimes can be added. + +Examples: +* `rsv Meet John Doe /dt 26/09/2016 0900 to 1030 /dt 28/09/2016 1000 to 1130` + +[comment]: # (@@author A0124333U) +#### Deleting a task with reserved timeslots : `rsv /del` +Deletes a task with all its reserved time slots +Format: `rsv /del ` +Format: `rsv /del ..` + +> Deletes the task at the specific ``. +> +> Start index of range must be before end index. + +Examples: +* `rsv /del 5` +* `rsv /del 1..4` + +[comment]: # (@@author A0139924W) +#### Editing a tag's name : `tag /e` +Edits a tag's name +Format: `tag /e ` + +> Edits the name of the tag at the specific ``. + +Examples: +* `tag /e 5 Assignment` + +[comment]: # (@@author A0139924W) +#### Deleting a tag : `tag /del` +Deletes a particular tag +Format: `tag /del ` + +> Deletes the tag at the specific ``. + +Examples: +* `tag /del 4` deletes the tag at Index 4 + +[comment]: # (@@author A0139924W) +#### Listing all tags : `tag /ls` +Lists all tags in TARS +Format: `tag /ls` + +[comment]: # (@@author A0121533W) +#### Marking tasks as undone : `ud` +Marks the task based on its index in the task list as undone. +Format: `ud [INDEX ...]` +Format: `ud ..` + +> Marks the task at the specific `` as `undone`. +> +> Start index of range must be before end index. + +Examples: +* `ud 2 4 6` +* `ud 1..3` + +[comment]: # (@@author A0139924W) +#### Undoing a command : `undo` +Undo a command executed by the user. +Format: `undo` + +> Able to undo all `add`, `delete`, `edit`, `tag`, `rsv`, `confirm` and `del` commands from the time the app starts running. +> +> Keyboard shortcut: CTRL-Z + + #### Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data.
+TARS data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. ## FAQ -**Q**: How do I transfer my data to another Computer?
+**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 preious Address Book. - + the file that contains the data of your previous TARS app. + +[comment]: # (@@author A0139924W) +## Supported Date Formats +#### formal dates +Formal dates are those in which the day, month, and year are represented as integers separated by a common separator character. The year is optional and may precede the month or succeed the day of month. If a two-digit year is given, it must succeed the day of month. + +Examples: +* `28-01-2016` +* `28/01/2016` +* `1/02/2016` +* `2/2/16` + +#### relaxed dates +Relaxed dates are those in which the month, day of week, day of month, and year may be given in a loose, non-standard manner, with most parts being optional. + +Examples: +* `The 31st of April in the year 2008` +* `Fri, 21 Nov 1997` +* `Jan 21, '97` +* `Sun, Nov 21` +* `jan 1st` +* `february twenty-eighth` + +#### relative dates +Relative dates are those that are relative to the current date. + +Examples: +* `next thursday` +* `last wednesday` +* `today` +* `tomorrow` +* `yesterday` +* `next week` +* `next month` +* `next year` +* `3 days from now` +* `three weeks ago` + +#### prefixes +Most of the above date formats may be prefixed with a modifier. + +Examples: +`day after` +`the day before` +`the monday after` +`the monday before` +`2 fridays before` +`4 tuesdays after` + +#### time +The above date formats may be prefixed or suffixed with time information. + +Examples: +* `0600h` +* `06:00 hours` +* `6pm` +* `5:30 a.m.` +* `5` +* `12:59` +* `23:59` +* `8p` +* `noon` +* `afternoon` +* `midnight` + +#### relative times + +Examples: +* `10 seconds ago` +* `in 5 minutes` +* `4 minutes from now` + +[comment]: # (@@author A0124333U) ## 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 [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]` +[Change Storage Location](#changing-data-storage-location--cd) | `cd ` +[Clear](#clearing-the-data-storage-file--clear) | `clear` +[Confirm](#confirming-a-reserved-timeslot--confirm) | `confirm [/p PRIORITY] [/t TAG_NAME ...]` +[Delete](#deleting-a-task--del) | `del [INDEX ...]`
`del ..` +[Done](#marking-tasks-as-done--do) | `do [INDEX ...]`
`do ..` +[Edit](#editing-a-task--edit) | `edit [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] [/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]` +[Exit](#exiting-the-program--exit) | `exit` +[Find [Quick Search]](#finding-tasks--find) | `find [KEYWORD ...]` +[Find [Filter Search]](#finding-tasks--find) | `find [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]` +[Free](#suggesting-free-timeslots--free) | `free ` +[Help](#displaying-a-list-of-available-commands--help) | `help [COMMAND_WORD]` +[List](#listing-tasks--ls) | `ls` +[List [Date]](#listing-tasks--ls) | `ls /dt` +[List [Priority]](#listing-tasks--ls) | `ls /p` +[Redo](#redoing-a-command--redo) | `redo` +[Reserve](#reserving-timeslots-for-a-task--rsv) | `rsv [/dt DATETIME ...]` +[Reserve [Delete]](#deleting-a-task-with-reserved-timeslots--rsv-del) | `rsv /del `
`rsv /del ..` +[Tag [Delete]](#deleting-a-tag--tag-del) | `tag /del ` +[Tag [Edit]](#editing-a-tags-name--tag-e) | `tag /e ` +[Tag [List]](#listing-all-tags--tag-ls) | `tag /ls` +[Undone](#marking-tasks-as-undone--ud) | `ud [INDEX ...]`
`ud ..` +[Undo](#undoing-a-command--undo) | `undo` + diff --git a/docs/UsingGradle.md b/docs/UsingGradle.md index 09c5fc6a142f..a25bb5841181 100644 --- a/docs/UsingGradle.md +++ b/docs/UsingGradle.md @@ -34,7 +34,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 `tars.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.
@@ -44,7 +44,7 @@ Gradle commands look like this: If we package only our own class files into the JAR file, it will not work properly unless the user has all the other JAR files (i.e. third party libraries) our classes depend on, which is rather inconvenient. Therefore, we package all dependencies into a single JAR files, creating what is also known as a _fat_ JAR file. - To create a fat JAR fil, we are using the Gradle plugin [shadow jar](https://github.com/johnrengelman/shadow). + To create a fat JAR file, we are using the Gradle plugin [shadow jar](https://github.com/johnrengelman/shadow). ## Running Tests diff --git a/docs/diagrams/Diagrams.pptx b/docs/diagrams/Diagrams.pptx index 6a03bc10627d..577837180e68 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..66a1731667ee 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/CalvinYang.png b/docs/images/CalvinYang.png new file mode 100644 index 000000000000..fa303a07785f Binary files /dev/null and b/docs/images/CalvinYang.png differ diff --git a/docs/images/Candiie.png b/docs/images/Candiie.png new file mode 100644 index 000000000000..d5fff691d8b7 Binary files /dev/null and b/docs/images/Candiie.png differ diff --git a/docs/images/ChiaWeiKang.png b/docs/images/ChiaWeiKang.png new file mode 100644 index 000000000000..957886dfba6d Binary files /dev/null and b/docs/images/ChiaWeiKang.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/JoelFoo.png b/docs/images/JoelFoo.png new file mode 100644 index 000000000000..db73996ba840 Binary files /dev/null and b/docs/images/JoelFoo.png differ diff --git a/docs/images/JohnervanLee.png b/docs/images/JohnervanLee.png new file mode 100644 index 000000000000..a5d1bc09aef4 Binary files /dev/null and b/docs/images/JohnervanLee.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 12849cdbb136..f2c231153bef 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..1d37904536d6 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png deleted file mode 100644 index 1e836f10dcd8..000000000000 Binary files a/docs/images/SDforDeletePerson.png and /dev/null differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png deleted file mode 100644 index ecec0805d32c..000000000000 Binary files a/docs/images/SDforDeletePersonEventHandling.png and /dev/null differ diff --git a/docs/images/SDforDeleteTask.png b/docs/images/SDforDeleteTask.png new file mode 100644 index 000000000000..120045147290 Binary files /dev/null and b/docs/images/SDforDeleteTask.png differ diff --git a/docs/images/SDforDeleteTaskEventHandling.png b/docs/images/SDforDeleteTaskEventHandling.png new file mode 100644 index 000000000000..30387d2a4f19 Binary files /dev/null and b/docs/images/SDforDeleteTaskEventHandling.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..9eb310ec3244 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 deleted file mode 100644 index 7121a50a442a..000000000000 Binary files a/docs/images/Ui.png and /dev/null differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 459245e267af..d015a9772c3f 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/tars_ui.png b/docs/images/tars_ui.png new file mode 100644 index 000000000000..ac8e549627f8 Binary files /dev/null and b/docs/images/tars_ui.png differ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java deleted file mode 100644 index 4b48efe60824..000000000000 --- a/src/main/java/seedu/address/MainApp.java +++ /dev/null @@ -1,188 +0,0 @@ -package seedu.address; - -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; - -/** - * 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); - - protected Ui ui; - protected Logic logic; - protected Storage storage; - protected Model model; - protected Config config; - protected UserPrefs userPrefs; - - public MainApp() {} - - @Override - public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); - super.init(); - - config = initConfig(getApplicationParameter("config")); - storage = new StorageManager(config.getAddressBookFilePath(), config.getUserPrefsFilePath()); - - userPrefs = initPrefs(config); - - initLogging(config); - - model = initModelManager(storage, userPrefs); - - logic = new LogicManager(model, storage); - - 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; - try { - addressBookOptional = storage.readAddressBook(); - if(!addressBookOptional.isPresent()){ - logger.info("Data file not found. Will be starting with an empty AddressBook"); - } - initialData = addressBookOptional.orElse(new AddressBook()); - } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); - } catch (FileNotFoundException e) { - logger.warning("Problem while reading from the file. . Will be starting with an empty AddressBook"); - initialData = new AddressBook(); - } - - return new ModelManager(initialData, userPrefs); - } - - private void initLogging(Config config) { - LogsCenter.init(config); - } - - protected Config initConfig(String configFilePath) { - Config initializedConfig; - String configFilePathUsed; - - configFilePathUsed = Config.DEFAULT_CONFIG_FILE; - - if(configFilePath != null) { - logger.info("Custom Config file specified " + configFilePath); - configFilePathUsed = configFilePath; - } - - logger.info("Using config file : " + configFilePathUsed); - - try { - Optional configOptional = ConfigUtil.readConfig(configFilePathUsed); - initializedConfig = configOptional.orElse(new Config()); - } catch (DataConversionException e) { - logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. " + - "Using default config properties"); - initializedConfig = new Config(); - } - - //Update config file in case it was missing to begin with or there are new/unused fields - try { - ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); - } catch (IOException e) { - logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); - } - return initializedConfig; - } - - protected UserPrefs initPrefs(Config config) { - assert config != null; - - String prefsFilePath = config.getUserPrefsFilePath(); - logger.info("Using prefs file : " + prefsFilePath); - - UserPrefs initializedPrefs; - try { - Optional prefsOptional = storage.readUserPrefs(); - initializedPrefs = prefsOptional.orElse(new UserPrefs()); - } catch (DataConversionException e) { - logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. " + - "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"); - initializedPrefs = new UserPrefs(); - } - - //Update prefs file in case it was missing to begin with or there are new/unused fields - try { - storage.saveUserPrefs(initializedPrefs); - } catch (IOException e) { - logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); - } - - return initializedPrefs; - } - - private void initEventsCenter() { - EventsCenter.getInstance().registerHandler(this); - } - - @Override - public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); - ui.start(primaryStage); - } - - @Override - public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); - ui.stop(); - try { - storage.saveUserPrefs(userPrefs); - } catch (IOException e) { - logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); - } - Platform.exit(); - System.exit(0); - } - - @Subscribe - public void handleExitAppRequestEvent(ExitAppRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - this.stop(); - } - - public static void main(String[] args) { - launch(args); - } -} 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/JumpToListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java deleted file mode 100644 index 0580d27aecf5..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; - -/** - * Indicates a request to jump to the list of persons - */ -public class JumpToListRequestEvent extends BaseEvent { - - public final int targetIndex; - - public JumpToListRequestEvent(int targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - -} 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/events/ui/ShowHelpRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java deleted file mode 100644 index a7e40940b2c7..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; - -/** - * An event requesting to view the help page. - */ -public class ShowHelpRequestEvent extends BaseEvent { - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - -} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java deleted file mode 100644 index 0e7f12c80042..000000000000 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package seedu.address.commons.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Arrays; -import java.util.List; - -/** - * Helper functions for handling strings. - */ -public class StringUtil { - public static boolean containsIgnoreCase(String source, String query) { - String[] split = source.toLowerCase().split("\\s+"); - List strings = Arrays.asList(split); - return strings.stream().filter(s -> s.equals(query.toLowerCase())).count() > 0; - } - - /** - * Returns a detailed message of the t, including the stack trace. - */ - public static String getDetails(Throwable t){ - StringWriter sw = new StringWriter(); - t.printStackTrace(new PrintWriter(sw)); - return t.getMessage() + "\n" + sw.toString(); - } - - /** - * Returns true if s represents an unsigned integer e.g. 1, 2, 3, ...
- * Will return false for null, empty string, "-1", "0", "+1", and " 2 " (untrimmed) "3 0" (contains whitespace). - * @param s Should be trimmed. - */ - public static boolean isUnsignedInteger(String s){ - return s != null && s.matches("^0*[1-9]\\d*$"); - } -} diff --git a/src/main/java/seedu/address/commons/util/UrlUtil.java b/src/main/java/seedu/address/commons/util/UrlUtil.java deleted file mode 100644 index c701fea753d5..000000000000 --- a/src/main/java/seedu/address/commons/util/UrlUtil.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.commons.util; - -import java.net.URL; - -/** - * An utility class for URL - */ -public class UrlUtil { - - /** - * Returns true if both URLs have the same base URL - */ - public static boolean compareBaseUrls(URL url1, URL url2) { - - if (url1 == null || url2 == null) { - return false; - } - return url1.getHost().toLowerCase().replaceFirst("www.", "") - .equals(url2.getHost().replaceFirst("www.", "").toLowerCase()) - && url1.getPath().replaceAll("/", "").toLowerCase() - .equals(url2.getPath().replaceAll("/", "").toLowerCase()); - } - -} 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/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/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/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/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/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index ffe589ac3c4c..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,34 +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; - - /** - * 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; - -} 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 767490dbf8e2..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, FileNotFoundException; - - @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 a6f2eddcc802..000000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +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 XmlAddressBookStorage addressBookStorage; - private JsonUserPrefStorage userPrefStorage; - - - public StorageManager(String addressBookFilePath, String userPrefsFilePath) { - super(); - this.addressBookStorage = new XmlAddressBookStorage(addressBookFilePath); - this.userPrefStorage = new JsonUserPrefStorage(userPrefsFilePath); - } - - // ================ UserPrefs methods ============================== - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(UserPrefs userPrefs) throws IOException { - userPrefStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public String getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, FileNotFoundException { - logger.fine("Attempting to read data from file: " + addressBookStorage.getAddressBookFilePath()); - - return addressBookStorage.readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - addressBookStorage.saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - - @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/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/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java deleted file mode 100644 index 27a5210cadaf..000000000000 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ /dev/null @@ -1,38 +0,0 @@ -package seedu.address.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; - -/** - * Stores addressbook data in an XML file - */ -public class XmlFileStorage { - /** - * Saves the given addressbook data to the specified file. - */ - public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) - throws FileNotFoundException { - try { - XmlUtil.saveDataToFile(file, addressBook); - } catch (JAXBException e) { - assert false : "Unexpected exception " + e.getMessage(); - } - } - - /** - * Returns address book in the file or an empty address book - */ - public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, - FileNotFoundException { - try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); - } catch (JAXBException e) { - throw new DataConversionException(e); - } - } - -} 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 2a496e1494f8..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,86 +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) { - e.printStackTrace(); - return null; - } - } - - @Override - public UniquePersonList getUniquePersonList() { - UniquePersonList lists = new UniquePersonList(); - for (XmlAdaptedPerson p : persons) { - try { - lists.add(p.toModelType()); - } catch (IllegalValueException e) { - - } - } - return lists; - } - - @Override - public List getPersonList() { - return persons.stream().map(p -> { - try { - return p.toModelType(); - } catch (IllegalValueException e) { - e.printStackTrace(); - return null; - } - }).collect(Collectors.toCollection(ArrayList::new)); - } - - @Override - public List getTagList() { - return Collections.unmodifiableList(tags); - } - -} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index 54b88318019b..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,68 +0,0 @@ -package seedu.address.ui; - -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; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart{ - - private static Logger logger = LogsCenter.getLogger(BrowserPanel.class); - private WebView browser; - - /** - * Constructor is kept private as {@link #load(AnchorPane)} is the only way to create a BrowserPanel. - */ - private BrowserPanel() { - - } - - @Override - public void setNode(Node node) { - //not applicable - } - - @Override - public String getFxmlPath() { - return null; //not applicable - } - - /** - * Factory method for creating a Browser Panel. - * This method should be called after the FX runtime is initialized and in FX application thread. - * @param placeholder The AnchorPane where the BrowserPanel must be inserted - */ - public static BrowserPanel load(AnchorPane placeholder){ - logger.info("Initializing browser"); - BrowserPanel browserPanel = new BrowserPanel(); - browserPanel.browser = new WebView(); - placeholder.setOnKeyPressed(Event::consume); // To prevent triggering events for typing inside the loaded Web page. - FxViewUtil.applyAnchorBoundaryParameters(browserPanel.browser, 0.0, 0.0, 0.0, 0.0); - placeholder.getChildren().add(browserPanel.browser); - return browserPanel; - } - - public void loadPersonPage(ReadOnlyPerson person) { - loadPage("https://www.google.com.sg/#safe=off&q=" + person.getName().fullName.replaceAll(" ", "+")); - } - - public void loadPage(String url){ - browser.getEngine().load(url); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - -} 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/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java deleted file mode 100644 index 45b765ab6a0c..000000000000 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.ui; - -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; - -/** - * Controller for a help page - */ -public class HelpWindow extends UiPart { - - private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); - private static final String ICON = "/images/help_icon.png"; - 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"; - - private AnchorPane mainPane; - - private Stage dialogStage; - - public static HelpWindow load(Stage primaryStage) { - logger.fine("Showing help page about the application."); - HelpWindow helpWindow = UiPartLoader.loadUiPart(primaryStage, new HelpWindow()); - helpWindow.configure(); - return helpWindow; - } - - @Override - public void setNode(Node node) { - mainPane = (AnchorPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - private void configure(){ - Scene scene = new Scene(mainPane); - //Null passed as the parent stage to make it non-modal. - dialogStage = createDialogStage(TITLE, null, scene); - dialogStage.setMaximized(true); //TODO: set a more appropriate initial size - setIcon(dialogStage, ICON); - - WebView browser = new WebView(); - browser.getEngine().load(USERGUIDE_URL); - FxViewUtil.applyAnchorBoundaryParameters(browser, 0.0, 0.0, 0.0, 0.0); - mainPane.getChildren().add(browser); - } - - public void show() { - dialogStage.showAndWait(); - } -} 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/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java deleted file mode 100644 index f74f66be6fc9..000000000000 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ /dev/null @@ -1,98 +0,0 @@ -package seedu.address.ui; - -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; - -/** - * A ui for the status bar that is displayed at the footer of the application. - */ -public class StatusBarFooter extends UiPart { - private static final Logger logger = LogsCenter.getLogger(StatusBarFooter.class); - private StatusBar syncStatus; - private StatusBar saveLocationStatus; - - private GridPane mainPane; - - @FXML - private AnchorPane saveLocStatusBarPane; - - @FXML - private AnchorPane syncStatusBarPane; - - private AnchorPane placeHolder; - - private static final String FXML = "StatusBarFooter.fxml"; - - public static StatusBarFooter load(Stage stage, AnchorPane placeHolder, String saveLocation) { - StatusBarFooter statusBarFooter = UiPartLoader.loadUiPart(stage, placeHolder, new StatusBarFooter()); - statusBarFooter.configure(saveLocation); - return statusBarFooter; - } - - public void configure(String saveLocation) { - addMainPane(); - addSyncStatus(); - setSyncStatus("Not updated yet in this session"); - addSaveLocation(); - setSaveLocation("./" + saveLocation); - registerAsAnEventHandler(this); - } - - private void addMainPane() { - FxViewUtil.applyAnchorBoundaryParameters(mainPane, 0.0, 0.0, 0.0, 0.0); - placeHolder.getChildren().add(mainPane); - } - - private void setSaveLocation(String location) { - this.saveLocationStatus.setText(location); - } - - private void addSaveLocation() { - this.saveLocationStatus = new StatusBar(); - FxViewUtil.applyAnchorBoundaryParameters(saveLocationStatus, 0.0, 0.0, 0.0, 0.0); - saveLocStatusBarPane.getChildren().add(saveLocationStatus); - } - - private void setSyncStatus(String status) { - this.syncStatus.setText(status); - } - - private void addSyncStatus() { - this.syncStatus = new StatusBar(); - FxViewUtil.applyAnchorBoundaryParameters(syncStatus, 0.0, 0.0, 0.0, 0.0); - syncStatusBarPane.getChildren().add(syncStatus); - } - - @Override - public void setNode(Node node) { - mainPane = (GridPane) node; - } - - @Override - public void setPlaceholder(AnchorPane placeholder) { - this.placeHolder = placeholder; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { - String lastUpdated = (new Date()).toString(); - logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); - setSyncStatus("Last Updated: " + lastUpdated); - } -} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java deleted file mode 100644 index 4a4dba3a2f6e..000000000000 --- a/src/main/java/seedu/address/ui/UiManager.java +++ /dev/null @@ -1,126 +0,0 @@ -package seedu.address.ui; - -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; - -/** - * The manager of the UI component. - */ -public class UiManager extends ComponentManager implements Ui { - private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; - - private Logic logic; - private Config config; - private UserPrefs prefs; - private MainWindow mainWindow; - - public UiManager(Logic logic, Config config, UserPrefs prefs) { - super(); - this.logic = logic; - this.config = config; - this.prefs = prefs; - } - - @Override - public void start(Stage primaryStage) { - logger.info("Starting UI..."); - primaryStage.setTitle(config.getAppTitle()); - - //Set the application icon. - primaryStage.getIcons().add(getImage(ICON_APPLICATION)); - - try { - mainWindow = MainWindow.load(primaryStage, config, prefs, logic); - mainWindow.show(); //This should be called before creating other UI parts - mainWindow.fillInnerParts(); - - } catch (Throwable e) { - logger.severe(StringUtil.getDetails(e)); - showFatalErrorDialogAndShutdown("Fatal error during initializing", e); - } - } - - @Override - public void stop() { - prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); - mainWindow.hide(); - mainWindow.releaseResources(); - } - - private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { - final String content = details + ":\n" + cause.toString(); - showAlertDialogAndWait(AlertType.ERROR, "File Op Error", description, content); - } - - private Image getImage(String imagePath) { - return new Image(MainApp.class.getResourceAsStream(imagePath)); - } - - void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { - showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); - } - - private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, - String contentText) { - final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); - alert.initOwner(owner); - alert.setTitle(title); - alert.setHeaderText(headerText); - alert.setContentText(contentText); - - alert.showAndWait(); - } - - private void showFatalErrorDialogAndShutdown(String title, Throwable e) { - logger.severe(title + " " + e.getMessage() + StringUtil.getDetails(e)); - showAlertDialogAndWait(Alert.AlertType.ERROR, title, e.getMessage(), e.toString()); - Platform.exit(); - System.exit(1); - } - - //==================== Event Handling Code ================================================================= - - @Subscribe - private void handleDataSavingExceptionEvent(DataSavingExceptionEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - showFileOperationAlertAndWait("Could not save data", "Could not save data to file", event.exception); - } - - @Subscribe - private void handleShowHelpEvent(ShowHelpRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.handleHelp(); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getPersonListPanel().scrollTo(event.targetIndex); - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event){ - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.loadPersonPage(event.getNewSelection()); - } - -} diff --git a/src/main/java/tars/MainApp.java b/src/main/java/tars/MainApp.java new file mode 100644 index 000000000000..bd8c533dac6f --- /dev/null +++ b/src/main/java/tars/MainApp.java @@ -0,0 +1,227 @@ +package tars; + +import com.google.common.eventbus.Subscribe; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.stage.Stage; +import tars.model.*; +import tars.commons.core.Config; +import tars.commons.core.EventsCenter; +import tars.commons.core.LogsCenter; +import tars.commons.core.Version; +import tars.commons.events.ui.ExitAppRequestEvent; +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.ConfigUtil; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.logic.*; +import tars.storage.Storage; +import tars.storage.StorageManager; +import tars.ui.Ui; +import tars.ui.UiManager; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * The main entry point to the application. + */ +public class MainApp extends Application { + public static final Version VERSION = new Version(1, 0, 0, true); + + private static final String INITIALIZING_HEADER = + "=============================[ Initializing Tars ]==========================="; + private static final String PARAMETER_CONFIG = "config"; + private static final String LOG_MESSAGE_PREFS_SAVE_FAILURE = + "Failed to save preferences "; + private static final String LOG_MESSAGE_DATA_FILE_NOT_FOUND = + "Data file not found. Will be starting with an empty Tars"; + private static final String LOG_MESSAGE_DATA_FILE_INCORRECT_FORMAT = + "Data file not in the correct format. Will be starting with an empty Tars"; + private static final String LOG_MESSAGE_DATA_FILE_CORRUPTED = + "Problem while reading from the file. Will be starting with an empty Tars"; + private static final String LOG_MESSAGE_CUSTOM_CONFIG_FILE = + "Custom Config file specified "; + private static final String LOG_MESSAGE_USING_CONFIG_FILE = + "Using config file : "; + private static final String LOG_MESSAGE_CONFIG_FILE_SAVE_FAILURE = + "Failed to save config file : "; + private static final String LOG_MESSAGE_USING_PREFS_FILE = + "Using prefs file : "; + private static final String LOG_MESSAGE_PROBLEM_READING_FROM_FILE = + "Problem while reading from the file. . Will be starting with an empty Tars"; + private static final String LOG_MESSAGE_STARTING_TARS = "Starting Tars "; + private static final String DAY_STRING_TODAY = "today"; + private static final String STOPPING_TARS_HEADER = + "============================ [ Stopping TARS ] ============================="; + private static final int SYSTEM_EXIT_NO_ERROR = 0; + + private static final Logger logger = LogsCenter.getLogger(MainApp.class); + + private static String LOG_MESSAGE_PREFS_FILE_INCORRECT_FORMAT = + "UserPrefs file at %s is not in the correct format. Using default user prefs"; + private static String LOG_MESSAGE_CONFIG_FILE_INCORRECT_FORMAT = + "Config file at %s is not in the correct format. Using default config properties"; + + protected Ui ui; + protected Logic logic; + protected Storage storage; + protected Model model; + protected Config config; + protected UserPrefs userPrefs; + + @Override + public void init() throws Exception { + logger.info(INITIALIZING_HEADER); + super.init(); + + config = initConfig(getApplicationParameter(PARAMETER_CONFIG)); + storage = new StorageManager(config.getTarsFilePath(), + config.getUserPrefsFilePath()); + + userPrefs = initPrefs(config); + + initLogging(config); + + model = initModelManager(storage); + + logic = new LogicManager(model, storage); + + ui = new UiManager(logic, config, userPrefs); + + // to improve the natty parser runtime for the first query + new Thread(() -> DateTimeUtil.parseStringToDateTime(DAY_STRING_TODAY)) + .start(); + + initEventsCenter(); + } + + private String getApplicationParameter(String parameterName) { + Map applicationParameters = getParameters().getNamed(); + return applicationParameters.get(parameterName); + } + + private Model initModelManager(Storage storage) { + Optional tarsOptional; + ReadOnlyTars initialData; + try { + tarsOptional = storage.readTars(); + if (!tarsOptional.isPresent()) { + logger.info(LOG_MESSAGE_DATA_FILE_NOT_FOUND); + } + initialData = tarsOptional.orElse(new Tars()); + } catch (DataConversionException e) { + logger.warning(LOG_MESSAGE_DATA_FILE_INCORRECT_FORMAT); + initialData = new Tars(); + } catch (FileNotFoundException e) { + logger.warning(LOG_MESSAGE_DATA_FILE_CORRUPTED); + initialData = new Tars(); + } + + return new ModelManager(initialData); + } + + private void initLogging(Config config) { + LogsCenter.init(config); + } + + protected Config initConfig(String configFilePath) { + Config initializedConfig; + String configFilePathUsed; + + configFilePathUsed = Config.DEFAULT_CONFIG_FILE; + + if (configFilePath != null) { + logger.info(LOG_MESSAGE_CUSTOM_CONFIG_FILE + configFilePath); + configFilePathUsed = configFilePath; + } + + logger.info(LOG_MESSAGE_USING_CONFIG_FILE + configFilePathUsed); + + try { + Optional configOptional = + ConfigUtil.readConfig(configFilePathUsed); + initializedConfig = configOptional.orElse(new Config()); + } catch (DataConversionException e) { + logger.warning(String.format( + LOG_MESSAGE_CONFIG_FILE_INCORRECT_FORMAT, configFilePath)); + initializedConfig = new Config(); + } + + // Update config file in case it was missing to begin with or there are new/unused fields + try { + ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); + } catch (IOException e) { + logger.warning(LOG_MESSAGE_CONFIG_FILE_SAVE_FAILURE + + StringUtil.getDetails(e)); + } + return initializedConfig; + } + + protected UserPrefs initPrefs(Config config) { + assert config != null; + + String prefsFilePath = config.getUserPrefsFilePath(); + logger.info(LOG_MESSAGE_USING_PREFS_FILE + prefsFilePath); + + UserPrefs initializedPrefs; + try { + Optional prefsOptional = storage.readUserPrefs(); + initializedPrefs = prefsOptional.orElse(new UserPrefs()); + } catch (DataConversionException e) { + logger.warning(String.format( + LOG_MESSAGE_PREFS_FILE_INCORRECT_FORMAT, prefsFilePath)); + initializedPrefs = new UserPrefs(); + } catch (IOException e) { + logger.warning(LOG_MESSAGE_PROBLEM_READING_FROM_FILE); + initializedPrefs = new UserPrefs(); + } + + // Update prefs file in case it was missing to begin with or there are new/unused fields + try { + storage.saveUserPrefs(initializedPrefs); + } catch (IOException e) { + logger.warning(LOG_MESSAGE_CONFIG_FILE_SAVE_FAILURE + + StringUtil.getDetails(e)); + } + + return initializedPrefs; + } + + private void initEventsCenter() { + EventsCenter.getInstance().registerHandler(this); + } + + @Override + public void start(Stage primaryStage) { + logger.info(LOG_MESSAGE_STARTING_TARS + MainApp.VERSION); + ui.start(primaryStage); + } + + @Override + public void stop() { + logger.info(STOPPING_TARS_HEADER); + ui.stop(); + try { + storage.saveUserPrefs(userPrefs); + } catch (IOException e) { + logger.severe( + LOG_MESSAGE_PREFS_SAVE_FAILURE + StringUtil.getDetails(e)); + } + Platform.exit(); + System.exit(SYSTEM_EXIT_NO_ERROR); + } + + @Subscribe + public void handleExitAppRequestEvent(ExitAppRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + this.stop(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/tars/commons/core/ComponentManager.java similarity index 75% rename from src/main/java/seedu/address/commons/core/ComponentManager.java rename to src/main/java/tars/commons/core/ComponentManager.java index 4bc8564e5824..240e0c591166 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/tars/commons/core/ComponentManager.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package tars.commons.core; -import seedu.address.commons.events.BaseEvent; +import tars.commons.events.BaseEvent; /** * Base class for *Manager classes @@ -13,7 +13,7 @@ public abstract class ComponentManager { /** * Uses default {@link EventsCenter} */ - public ComponentManager(){ + public ComponentManager() { this(EventsCenter.getInstance()); } @@ -22,7 +22,7 @@ public ComponentManager(EventsCenter eventsCenter) { eventsCenter.registerHandler(this); } - protected void raise(BaseEvent event){ + protected void raise(BaseEvent event) { eventsCenter.post(event); } } diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/tars/commons/core/Config.java similarity index 61% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/tars/commons/core/Config.java index 6441c9ef20f4..5581d6f46848 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/tars/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tars.commons.core; import java.util.Objects; import java.util.logging.Level; @@ -11,15 +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 = "T.A.R.S. - Task and Remember Stuff"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; - private String addressBookFilePath = "data/addressbook.xml"; - private String addressBookName = "MyAddressBook"; - - - public Config() { - } + private String tarsFilePath = "data/tars.xml"; + private String tarsName = "MyTars"; public String getAppTitle() { return appTitle; @@ -45,29 +41,29 @@ public void setUserPrefsFilePath(String userPrefsFilePath) { this.userPrefsFilePath = userPrefsFilePath; } - public String getAddressBookFilePath() { - return addressBookFilePath; + public String getTarsFilePath() { + return tarsFilePath; } - public void setAddressBookFilePath(String addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setTarsFilePath(String tarsFilePath) { + this.tarsFilePath = tarsFilePath; } - public String getAddressBookName() { - return addressBookName; + public String getTarsName() { + return tarsName; } - public void setAddressBookName(String addressBookName) { - this.addressBookName = addressBookName; + public void setTarsName(String tarsName) { + this.tarsName = tarsName; } @Override public boolean equals(Object other) { - if (other == this){ + if (other == this) { return true; } - if (!(other instanceof Config)){ //this handles null as well. + if (!(other instanceof Config)) { // this handles null as well. return false; } @@ -76,13 +72,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(tarsFilePath, o.tarsFilePath) + && Objects.equals(tarsName, o.tarsName); } @Override public int hashCode() { - return Objects.hash(appTitle, logLevel, userPrefsFilePath, addressBookFilePath, addressBookName); + return Objects.hash(appTitle, logLevel, userPrefsFilePath, tarsFilePath, tarsName); } @Override @@ -91,8 +87,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 : " + tarsFilePath); + sb.append("\nTars name : " + tarsName); return sb.toString(); } diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/tars/commons/core/EventsCenter.java similarity index 76% rename from src/main/java/seedu/address/commons/core/EventsCenter.java rename to src/main/java/tars/commons/core/EventsCenter.java index 9652cd5c227b..74dfedffb143 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/tars/commons/core/EventsCenter.java @@ -1,7 +1,8 @@ -package seedu.address.commons.core; +package tars.commons.core; import com.google.common.eventbus.EventBus; -import seedu.address.commons.events.BaseEvent; + +import tars.commons.events.BaseEvent; import java.util.logging.Logger; @@ -10,8 +11,11 @@ */ public class EventsCenter { private static final Logger logger = LogsCenter.getLogger(EventsCenter.class); - private final EventBus eventBus; + private static final String LOG_EVENT_POSTED = + "------[Event Posted] %1$s: %2$s"; + private static EventsCenter instance; + private final EventBus eventBus; public static EventsCenter getInstance() { if (instance == null) { @@ -37,9 +41,11 @@ public EventsCenter registerHandler(Object handler) { * Posts an event to the event bus. */ public EventsCenter post(E event) { - logger.info("------[Event Posted] " + event.getClass().getCanonicalName() + ": " + event.toString()); + logger.info(String.format(LOG_EVENT_POSTED, + event.getClass().getCanonicalName(), event.toString())); eventBus.post(event); return this; } + } diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/tars/commons/core/GuiSettings.java similarity index 79% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/tars/commons/core/GuiSettings.java index e157ac8b8679..e7eb8a5bf512 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/tars/commons/core/GuiSettings.java @@ -1,9 +1,11 @@ -package seedu.address.commons.core; +package tars.commons.core; import java.awt.*; import java.io.Serializable; import java.util.Objects; +import tars.commons.util.StringUtil; + /** * A Serializable class that contains the GUI settings. */ @@ -22,7 +24,8 @@ public GuiSettings() { this.windowCoordinates = null; // null represent no coordinates } - public GuiSettings(Double windowWidth, Double windowHeight, int xPosition, int yPosition) { + public GuiSettings(Double windowWidth, Double windowHeight, int xPosition, + int yPosition) { this.windowWidth = windowWidth; this.windowHeight = windowHeight; this.windowCoordinates = new Point(xPosition, yPosition); @@ -42,14 +45,14 @@ public Point getWindowCoordinates() { @Override public boolean equals(Object other) { - if (other == this){ + if (other == this) { return true; } - if (!(other instanceof GuiSettings)){ //this handles null as well. + if (!(other instanceof GuiSettings)) { // this handles null as well. return false; } - GuiSettings o = (GuiSettings)other; + GuiSettings o = (GuiSettings) other; return Objects.equals(windowWidth, o.windowWidth) && Objects.equals(windowHeight, o.windowHeight) @@ -63,10 +66,10 @@ public int hashCode() { } @Override - public String toString(){ + public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Width : " + windowWidth + "\n"); - sb.append("Height : " + windowHeight + "\n"); + sb.append("Width : " + windowWidth + StringUtil.STRING_NEWLINE); + sb.append("Height : " + windowHeight + StringUtil.STRING_NEWLINE); sb.append("Position : " + windowCoordinates); return sb.toString(); } diff --git a/src/main/java/tars/commons/core/KeyCombinations.java b/src/main/java/tars/commons/core/KeyCombinations.java new file mode 100644 index 000000000000..eac97f29e927 --- /dev/null +++ b/src/main/java/tars/commons/core/KeyCombinations.java @@ -0,0 +1,23 @@ +package tars.commons.core; + +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; + +// @@author A0124333U +/** + * Container for all key combinations used by program + */ +public class KeyCombinations { + + public static final KeyCombination KEY_COMB_CTRL_RIGHT_ARROW = + new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.CONTROL_DOWN); + public static final KeyCombination KEY_COMB_CTRL_LEFT_ARROW = + new KeyCodeCombination(KeyCode.LEFT, KeyCombination.CONTROL_DOWN); + + public static final KeyCombination KEY_COMB_CTRL_Z = + new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN); + public static final KeyCombination KEY_COMB_CTRL_Y = + new KeyCodeCombination(KeyCode.Y, KeyCombination.CONTROL_DOWN); + +} diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/tars/commons/core/LogsCenter.java similarity index 61% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/tars/commons/core/LogsCenter.java index 17939bab4975..bd2fb8dbf19b 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/tars/commons/core/LogsCenter.java @@ -1,31 +1,38 @@ -package seedu.address.commons.core; - -import seedu.address.commons.events.BaseEvent; +package tars.commons.core; import java.io.IOException; import java.util.logging.*; +import tars.commons.events.BaseEvent; +import tars.commons.util.StringUtil; + /** - * Configures and manages loggers and handlers, including their logging level - * Named {@link Logger}s can be obtained from this class
- * These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.
+ * Configures and manages loggers and handlers, including their logging level Named {@link Logger}s + * can be obtained from this class
+ * These loggers have been configured to output messages to the console and a {@code .log} file by + * default, at the {@code INFO} level. A new {@code .log} file with a new numbering will be created + * after the log file reaches 5MB big, up to a maximum of 5 files.
*/ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; - private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final int MAX_FILE_SIZE_IN_BYTES = + (int) (Math.pow(2, 20) * 5); // 5MB + private static final String LOG_FILE = "tars.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; private static ConsoleHandler consoleHandler; + + private static final String LOG_MESSAGE_ERROR_ADDING_FILE = + "Error adding file handler for logger."; + private static final String LOG_EVENT_HANDLED = + "---[Event handled][%1$s]%2$s"; /** - * Initializes with a custom log level (specified in the {@code config} object) - * Loggers obtained *AFTER* this initialization will have their logging level changed
- * Logging levels for existing loggers will only be updated if the logger with the same name is requested again - * from the LogsCenter. + * Initializes with a custom log level (specified in the {@code config} object) Loggers obtained + * *AFTER* this initialization will have their logging level changed
+ * Logging levels for existing loggers will only be updated if the logger with the same name is + * requested again from the LogsCenter. */ public static void init(Config config) { currentLogLevel = config.getLogLevel(); @@ -47,7 +54,8 @@ public static Logger getLogger(String name) { } private static void addConsoleHandler(Logger logger) { - if (consoleHandler == null) consoleHandler = createConsoleHandler(); + if (consoleHandler == null) + consoleHandler = createConsoleHandler(); logger.addHandler(consoleHandler); } @@ -60,15 +68,17 @@ private static void removeHandlers(Logger logger) { private static void addFileHandler(Logger logger) { try { - if (fileHandler == null) fileHandler = createFileHandler(); + if (fileHandler == null) + fileHandler = createFileHandler(); logger.addHandler(fileHandler); } catch (IOException e) { - logger.warning("Error adding file handler for logger."); + logger.warning(LOG_MESSAGE_ERROR_ADDING_FILE); } } private static FileHandler createFileHandler() throws IOException { - FileHandler fileHandler = new FileHandler(LOG_FILE, MAX_FILE_SIZE_IN_BYTES, MAX_FILE_COUNT, true); + FileHandler fileHandler = new FileHandler(LOG_FILE, + MAX_FILE_SIZE_IN_BYTES, MAX_FILE_COUNT, true); fileHandler.setFormatter(new SimpleFormatter()); fileHandler.setLevel(currentLogLevel); return fileHandler; @@ -84,21 +94,24 @@ private static ConsoleHandler createConsoleHandler() { * Creates a Logger for the given class name. */ public static Logger getLogger(Class clazz) { - if (clazz == null) return Logger.getLogger(""); + if (clazz == null) + return Logger.getLogger(StringUtil.EMPTY_STRING); return getLogger(clazz.getSimpleName()); } /** - * Decorates the given string to create a log message suitable for logging event handling methods. + * Decorates the given string to create a log message suitable for logging event handling + * methods. */ - public static String getEventHandlingLogMessage(BaseEvent e, String message) { - return "---[Event handled][" + e + "]" + message; + public static String getEventHandlingLogMessage(BaseEvent e, + String message) { + return String.format(LOG_EVENT_HANDLED, e, message); } /** * @see #getEventHandlingLogMessage(BaseEvent, String) */ public static String getEventHandlingLogMessage(BaseEvent e) { - return getEventHandlingLogMessage(e,""); + return getEventHandlingLogMessage(e, StringUtil.EMPTY_STRING); } } diff --git a/src/main/java/tars/commons/core/Messages.java b/src/main/java/tars/commons/core/Messages.java new file mode 100644 index 000000000000..6882defd7018 --- /dev/null +++ b/src/main/java/tars/commons/core/Messages.java @@ -0,0 +1,23 @@ +package tars.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_DATE = "Invalid date time entered!"; + public static final String MESSAGE_INVALID_END_DATE = "End dateTime should be after start dateTime."; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX = "The reserved task index provided is invalid"; + public static final String MESSAGE_INVALID_TAG_DISPLAYED_INDEX = "The tag index provided is invalid"; + public static final String MESSAGE_INVALID_DATETIME_DISPLAYED_INDEX = "The datetime index provided is invalid"; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_TASK_CANNOT_BE_FOUND = "Task cannot be found!"; + public static final String MESSAGE_RSV_TASK_CANNOT_BE_FOUND = "Reserved Task cannot be found!"; + public static final String MESSAGE_TAG_CANNOT_BE_FOUND = "Tag cannot be found!"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in tars"; + public static final String MESSAGE_DUPLICATE_TAG = "Operation would result in duplicate tags"; + public static final String MESSAGE_CONFLICTING_TASKS_WARNING = "Warning! DateTime conflict with: "; +} diff --git a/src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java b/src/main/java/tars/commons/core/UnmodifiableObservableList.java similarity index 98% rename from src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java rename to src/main/java/tars/commons/core/UnmodifiableObservableList.java index 5c25d8647a8d..16b32465d0d5 100644 --- a/src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java +++ b/src/main/java/tars/commons/core/UnmodifiableObservableList.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tars.commons.core; import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; @@ -18,7 +18,8 @@ */ public class UnmodifiableObservableList implements ObservableList { - public static final String MUTATION_OP_EXCEPTION_MESSAGE = "Attempted to modify an unmodifiable view"; + public static final String MUTATION_OP_EXCEPTION_MESSAGE = + "Attempted to modify an unmodifiable view"; private final ObservableList backingList; diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/tars/commons/core/Version.java similarity index 95% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/tars/commons/core/Version.java index 7ecb85b18f82..9dcab248b3f5 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/tars/commons/core/Version.java @@ -1,8 +1,10 @@ -package seedu.address.commons.core; +package tars.commons.core; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import tars.commons.util.StringUtil; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -66,7 +68,8 @@ public static Version fromString(String versionString) throws IllegalArgumentExc @JsonValue public String toString() { - return String.format("V%d.%d.%d%s", major, minor, patch, isEarlyAccess ? "ea" : ""); + return String.format("V%d.%d.%d%s", major, minor, patch, + isEarlyAccess ? "ea" : StringUtil.EMPTY_STRING); } @Override diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/tars/commons/events/BaseEvent.java similarity index 57% rename from src/main/java/seedu/address/commons/events/BaseEvent.java rename to src/main/java/tars/commons/events/BaseEvent.java index 723a9c69fbd5..96b5f920e486 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/tars/commons/events/BaseEvent.java @@ -1,12 +1,13 @@ -package seedu.address.commons.events; +package tars.commons.events; public abstract class BaseEvent { /** - * All Events should have a clear unambiguous custom toString message so that feedback message creation - * stays consistent and reusable. + * All Events should have a clear unambiguous custom toString message so that feedback message + * creation stays consistent and reusable. * - * For example the event manager post method will call any posted event's toString and print it in the console. + * For example the event manager post method will call any posted event's toString and print it + * in the console. */ public abstract String toString(); diff --git a/src/main/java/tars/commons/events/model/TarsChangedEvent.java b/src/main/java/tars/commons/events/model/TarsChangedEvent.java new file mode 100644 index 000000000000..ec8370ff500d --- /dev/null +++ b/src/main/java/tars/commons/events/model/TarsChangedEvent.java @@ -0,0 +1,23 @@ +package tars.commons.events.model; + +import tars.commons.events.BaseEvent; +import tars.model.ReadOnlyTars; + +/** Indicates the Tars in the model has changed */ +public class TarsChangedEvent extends BaseEvent { + + private static String TARS_SUMMARY = + "number of tasks %1$s, number of reserved tasks %2$s, number of tags %3$s"; + + public final ReadOnlyTars data; + + public TarsChangedEvent(ReadOnlyTars data) { + this.data = data; + } + + @Override + public String toString() { + return String.format(TARS_SUMMARY, data.getTaskList().size(), + data.getRsvTaskList().size(), data.getTagList().size()); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java b/src/main/java/tars/commons/events/storage/DataSavingExceptionEvent.java similarity index 71% rename from src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java rename to src/main/java/tars/commons/events/storage/DataSavingExceptionEvent.java index f0a0640ee523..633e93359245 100644 --- a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java +++ b/src/main/java/tars/commons/events/storage/DataSavingExceptionEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.storage; +package tars.commons.events.storage; -import seedu.address.commons.events.BaseEvent; +import tars.commons.events.BaseEvent; /** * Indicates an exception during a file saving @@ -14,7 +14,7 @@ public DataSavingExceptionEvent(Exception exception) { } @Override - public String toString(){ + public String toString() { return exception.toString(); } diff --git a/src/main/java/tars/commons/events/storage/TarsStorageDirectoryChangedEvent.java b/src/main/java/tars/commons/events/storage/TarsStorageDirectoryChangedEvent.java new file mode 100644 index 000000000000..bdc44ceb1c78 --- /dev/null +++ b/src/main/java/tars/commons/events/storage/TarsStorageDirectoryChangedEvent.java @@ -0,0 +1,35 @@ +package tars.commons.events.storage; + +import tars.commons.core.Config; +import tars.commons.events.BaseEvent; + +// @@author A0124333U +/** + * An event where the user changes the Tars Storage Directory/File Path + */ +public class TarsStorageDirectoryChangedEvent extends BaseEvent { + private static String MESSAGE_FILE_PATH_CHANGED = "File Path changed to %s"; + + private final String newFilePath; + private final Config newConfig; + + public TarsStorageDirectoryChangedEvent(String newFilePath, + Config newConfig) { + this.newFilePath = newFilePath; + this.newConfig = newConfig; + } + + public String getNewFilePath() { + return this.newFilePath; + } + + public Config getNewConfig() { + return this.newConfig; + } + + @Override + public String toString() { + return String.format(MESSAGE_FILE_PATH_CHANGED, this.newFilePath); + } + +} diff --git a/src/main/java/tars/commons/events/ui/CommandBoxTextFieldValueChangedEvent.java b/src/main/java/tars/commons/events/ui/CommandBoxTextFieldValueChangedEvent.java new file mode 100644 index 000000000000..67d73030571b --- /dev/null +++ b/src/main/java/tars/commons/events/ui/CommandBoxTextFieldValueChangedEvent.java @@ -0,0 +1,25 @@ +package tars.commons.events.ui; + +import tars.commons.events.BaseEvent; + +/** + * Indicates a change of value in the command box + */ +public class CommandBoxTextFieldValueChangedEvent extends BaseEvent { + + private String value; + + public CommandBoxTextFieldValueChangedEvent(String value) { + this.value = value; + } + + public String getTextFieldValue() { + return value; + } + + @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/tars/commons/events/ui/ExitAppRequestEvent.java similarity index 70% rename from src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java rename to src/main/java/tars/commons/events/ui/ExitAppRequestEvent.java index 9af6194543a3..de095618e497 100644 --- a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java +++ b/src/main/java/tars/commons/events/ui/ExitAppRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package tars.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import tars.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/tars/commons/events/ui/IncorrectCommandAttemptedEvent.java similarity index 68% rename from src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java rename to src/main/java/tars/commons/events/ui/IncorrectCommandAttemptedEvent.java index 991f7ae9fa25..2cb95787c184 100644 --- a/src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java +++ b/src/main/java/tars/commons/events/ui/IncorrectCommandAttemptedEvent.java @@ -1,7 +1,7 @@ -package seedu.address.commons.events.ui; +package tars.commons.events.ui; -import seedu.address.commons.events.BaseEvent; -import seedu.address.logic.commands.Command; +import tars.commons.events.BaseEvent; +import tars.logic.commands.Command; /** * Indicates an attempt to execute an incorrect command diff --git a/src/main/java/tars/commons/events/ui/KeyCombinationPressedEvent.java b/src/main/java/tars/commons/events/ui/KeyCombinationPressedEvent.java new file mode 100644 index 000000000000..f10550d388ac --- /dev/null +++ b/src/main/java/tars/commons/events/ui/KeyCombinationPressedEvent.java @@ -0,0 +1,27 @@ +package tars.commons.events.ui; + +import javafx.scene.input.KeyCombination; +import tars.commons.events.BaseEvent; + +// @@author A0124333U +/** + * Indicates that the user has pressed a key combination + */ +public class KeyCombinationPressedEvent extends BaseEvent { + + private KeyCombination keyComb; + + public KeyCombinationPressedEvent(KeyCombination keyComb) { + this.keyComb = keyComb; + } + + public KeyCombination getKeyCombination() { + return keyComb; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/tars/commons/events/ui/RsvTaskAddedEvent.java b/src/main/java/tars/commons/events/ui/RsvTaskAddedEvent.java new file mode 100644 index 000000000000..85389e7fdb77 --- /dev/null +++ b/src/main/java/tars/commons/events/ui/RsvTaskAddedEvent.java @@ -0,0 +1,25 @@ +package tars.commons.events.ui; + +import tars.commons.events.BaseEvent; +import tars.model.task.rsv.RsvTask; + +// @@author A0121533W +/** + * Indicates a task has been added + */ +public class RsvTaskAddedEvent extends BaseEvent { + + public final int targetIndex; + public final RsvTask task; + + public RsvTaskAddedEvent(int targetIndex, RsvTask task) { + this.targetIndex = targetIndex; + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/tars/commons/events/ui/ScrollToTopEvent.java b/src/main/java/tars/commons/events/ui/ScrollToTopEvent.java new file mode 100644 index 000000000000..52c225d8dbed --- /dev/null +++ b/src/main/java/tars/commons/events/ui/ScrollToTopEvent.java @@ -0,0 +1,17 @@ +package tars.commons.events.ui; + +import tars.commons.events.BaseEvent; + +/** + * Indicate that the task list should scroll to the top + */ +public class ScrollToTopEvent extends BaseEvent { + + public ScrollToTopEvent() {} + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/tars/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/tars/commons/events/ui/ShowHelpRequestEvent.java new file mode 100644 index 000000000000..72c13b78fe1b --- /dev/null +++ b/src/main/java/tars/commons/events/ui/ShowHelpRequestEvent.java @@ -0,0 +1,26 @@ +package tars.commons.events.ui; + +import tars.commons.events.BaseEvent; + +// @@author A0140022H +/** + * An event requesting to view the help page. + */ +public class ShowHelpRequestEvent extends BaseEvent { + + private String args; + + public ShowHelpRequestEvent(String args) { + this.args = args; + } + + public String getHelpRequestEventArgs() { + return args; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/tars/commons/events/ui/TaskAddedEvent.java b/src/main/java/tars/commons/events/ui/TaskAddedEvent.java new file mode 100644 index 000000000000..bf36b562f89e --- /dev/null +++ b/src/main/java/tars/commons/events/ui/TaskAddedEvent.java @@ -0,0 +1,25 @@ +package tars.commons.events.ui; + +import tars.commons.events.BaseEvent; +import tars.model.task.ReadOnlyTask; + +// @@author A0140022H +/** + * Indicates a task has been added + */ +public class TaskAddedEvent extends BaseEvent { + + public final int targetIndex; + public final ReadOnlyTask task; + + public TaskAddedEvent(int targetIndex, ReadOnlyTask task) { + this.targetIndex = targetIndex; + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/tars/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/tars/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..bf9d5a7a2c0e 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/tars/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package tars.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java b/src/main/java/tars/commons/exceptions/DuplicateDataException.java similarity index 58% rename from src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java rename to src/main/java/tars/commons/exceptions/DuplicateDataException.java index 17aa63d5020c..e1e935dab76c 100644 --- a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java +++ b/src/main/java/tars/commons/exceptions/DuplicateDataException.java @@ -1,9 +1,9 @@ -package seedu.address.commons.exceptions; +package tars.commons.exceptions; /** * Signals an error caused by duplicate data where there should be none. */ -public abstract class DuplicateDataException extends IllegalValueException { +public class DuplicateDataException extends IllegalValueException { public DuplicateDataException(String message) { super(message); } diff --git a/src/main/java/tars/commons/exceptions/DuplicateTaskException.java b/src/main/java/tars/commons/exceptions/DuplicateTaskException.java new file mode 100644 index 000000000000..a96232d8143a --- /dev/null +++ b/src/main/java/tars/commons/exceptions/DuplicateTaskException.java @@ -0,0 +1,13 @@ +package tars.commons.exceptions; + +/** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ +public class DuplicateTaskException extends DuplicateDataException { + private static final String MESSAGE_DUPLICATE_TASK_ERROR = + "Operation would result in duplicate tasks"; + + public DuplicateTaskException() { + super(MESSAGE_DUPLICATE_TASK_ERROR); + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/tars/commons/exceptions/IllegalValueException.java similarity index 88% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/tars/commons/exceptions/IllegalValueException.java index a473b43bd86f..eac1be782aa0 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/tars/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package tars.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/tars/commons/exceptions/InvalidRangeException.java b/src/main/java/tars/commons/exceptions/InvalidRangeException.java new file mode 100644 index 000000000000..4617bb52e320 --- /dev/null +++ b/src/main/java/tars/commons/exceptions/InvalidRangeException.java @@ -0,0 +1,13 @@ +package tars.commons.exceptions; + +/** + * Signals invalid range entered. + */ +public class InvalidRangeException extends Exception { + public static final String MESSAGE_INVALID_RANGE = + "Start index must be before end index."; + + public InvalidRangeException() { + super(MESSAGE_INVALID_RANGE); + } +} diff --git a/src/main/java/tars/commons/exceptions/InvalidTaskDisplayedException.java b/src/main/java/tars/commons/exceptions/InvalidTaskDisplayedException.java new file mode 100644 index 000000000000..413dc9d81f03 --- /dev/null +++ b/src/main/java/tars/commons/exceptions/InvalidTaskDisplayedException.java @@ -0,0 +1,10 @@ +package tars.commons.exceptions; + +/** + * Signals an index exceed the size of the internal list. + */ +public class InvalidTaskDisplayedException extends Exception { + public InvalidTaskDisplayedException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/tars/commons/util/AppUtil.java similarity index 79% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/tars/commons/util/AppUtil.java index 09a528f1188e..53b345c35bee 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/tars/commons/util/AppUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package tars.commons.util; import javafx.scene.image.Image; -import seedu.address.MainApp; +import tars.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/tars/commons/util/CollectionUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/tars/commons/util/CollectionUtil.java index fde8394f31e5..0a044afb0b8c 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/tars/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tars.commons.util; import java.util.Collection; import java.util.HashSet; @@ -21,8 +21,6 @@ public static boolean isAnyNull(Object... items) { return false; } - - /** * Throws an assertion error if the collection or any item in it is null. */ @@ -37,7 +35,7 @@ public static void assertNoNullElements(Collection items) { public static boolean elementsAreUnique(Collection items) { final Set testSet = new HashSet<>(); for (Object item : items) { - final boolean itemAlreadyExists = !testSet.add(item); // see Set documentation + final boolean itemAlreadyExists = !testSet.add(item); if (itemAlreadyExists) { return false; } diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/tars/commons/util/ConfigUtil.java similarity index 51% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/tars/commons/util/ConfigUtil.java index af42e03df06c..cbf509506972 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/tars/commons/util/ConfigUtil.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package tars.commons.util; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import tars.commons.core.Config; +import tars.commons.core.LogsCenter; +import tars.commons.exceptions.DataConversionException; import java.io.File; import java.io.IOException; @@ -15,30 +15,41 @@ public class ConfigUtil { private static final Logger logger = LogsCenter.getLogger(ConfigUtil.class); + private static final String configFilePath = "config.json"; + private static String LOG_MESSAGE_CONFIG_FILE_NOT_FOUND = + "Config file %s not found"; + private static String LOG_MESSAGE_CONFIG_FILE_READING_ERROR = + "Error reading from config file %1$s: %2$s"; /** - * Returns the Config object from the given file or {@code Optional.empty()} object if the file is not found. - * If any values are missing from the file, default values will be used, as long as the file is a valid json file. + * Returns the Config object from the given file or {@code Optional.empty()} object if the file + * is not found. If any values are missing from the file, default values will be used, as long + * as the file is a valid json file. + * * @param configFilePath cannot be null. * @throws DataConversionException if the file format is not as expected. */ - public static Optional readConfig(String configFilePath) throws DataConversionException { + public static Optional readConfig(String configFilePath) + throws DataConversionException { assert configFilePath != null; File configFile = new File(configFilePath); if (!configFile.exists()) { - logger.info("Config file " + configFile + " not found"); + logger.info(String.format(LOG_MESSAGE_CONFIG_FILE_NOT_FOUND, + configFile)); return Optional.empty(); } Config config; try { - config = FileUtil.deserializeObjectFromJsonFile(configFile, Config.class); + config = FileUtil.deserializeObjectFromJsonFile(configFile, + Config.class); } catch (IOException e) { - logger.warning("Error reading from config file " + configFile + ": " + e); + logger.warning(String.format(LOG_MESSAGE_CONFIG_FILE_READING_ERROR, + configFile, e)); throw new DataConversionException(e); } @@ -46,17 +57,23 @@ public static Optional readConfig(String configFilePath) throws DataConv } /** - * Saves the Config object to the specified file. - * Overwrites existing file if it exists, creates a new file if it doesn't. + * Saves the Config object to the specified file. Overwrites existing file if it exists, creates + * a new file if it doesn't. + * * @param config cannot be null * @param configFilePath cannot be null * @throws IOException if there was an error during writing to the file */ - public static void saveConfig(Config config, String configFilePath) throws IOException { + public static void saveConfig(Config config, String configFilePath) + throws IOException { assert config != null; assert configFilePath != null; FileUtil.serializeObjectToJsonFile(new File(configFilePath), config); } + public static void updateConfig(Config newConfig) throws IOException { + saveConfig(newConfig, configFilePath); + } + } diff --git a/src/main/java/tars/commons/util/DateTimeUtil.java b/src/main/java/tars/commons/util/DateTimeUtil.java new file mode 100644 index 000000000000..adf0ab84f779 --- /dev/null +++ b/src/main/java/tars/commons/util/DateTimeUtil.java @@ -0,0 +1,272 @@ +package tars.commons.util; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +import tars.model.task.DateTime; + +/** + * Date Time Utility package + */ +public class DateTimeUtil { + + public static final int DATETIME_FIRST_HOUR_OF_DAY = 0; + public static final int DATETIME_FIRST_MINUTE_OF_DAY = 0; + public static final int DATETIME_FIRST_SECOND_OF_DAY = 0; + public static final int DATETIME_LAST_HOUR_OF_DAY = 23; + public static final int DATETIME_LAST_MINUTE_OF_DAY = 59; + public static final int DATETIME_LAST_SECOND_OF_DAY = 59; + + private static final String DATETIME_DAY = "day"; + private static final String DATETIME_WEEK = "week"; + private static final String DATETIME_MONTH = "month"; + private static final String DATETIME_YEAR = "year"; + private static final int DATETIME_INCREMENT = 1; + private static final DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("d/M/uuuu HHmm"); + private static final DateTimeFormatter stringFormatter = + DateTimeFormatter.ofPattern("dd/MM/uuuu HHmm"); + private static final DateTimeFormatter stringFormatterWithoutTime = + DateTimeFormatter.ofPattern("dd/MM/uuuu"); + private static final DateTimeFormatter stringFormatterWithoutDate = + DateTimeFormatter.ofPattern("HHmm"); + + public static String MESSAGE_FREE_TIME_SLOT = + StringUtil.STRING_NEWLINE + "%1$s. %2$shrs to %3$shrs (%4$s)"; + private static String MESSAGE_DURATION = "%1$s hr %2$s min"; + + // @@author A0139924W + /** + * Extracts the new task's dateTime from the string arguments. + * + * @return String[] with first index being the startDate time and second index being the end + * date time + */ + public static String[] parseStringToDateTime(String dateTimeArg) { + return NattyDateTimeUtil.parseStringToDateTime(dateTimeArg); + } + // @@author + + // @@author A0121533W + /** + * Checks if given endDateTime is within today and the end of this week + */ + public static boolean isWithinWeek(LocalDateTime endDateTime) { + if (endDateTime == null) { + return false; + } else { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime endThisWeek = + now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) + .withHour(0).withMinute(0).withSecond(0); + return endDateTime.isAfter(now) + && endDateTime.isBefore(endThisWeek); + } + } + + // @@author A0121533W + /** + * Checks if given endDateTime is before the end of today + */ + public static boolean isOverDue(LocalDateTime endDateTime) { + if (endDateTime == null) { + return false; + } else { + LocalDateTime now = LocalDateTime.now(); + return endDateTime.isBefore(now); + } + } + + // @@author A0124333U + /** + * Checks whether the dateTimeQuery falls within the range of the dateTimeSource i.e. + * dateTimeQuery startDateTime is equals to or before the dateTimeSource endDateTime && + * dateTimeQuery endDateTime is equals to or after the dateTimeSource startDateTime + * + * @param dateTimeSource + * @param dateTimeQuery + */ + public static boolean isDateTimeWithinRange(DateTime dateTimeSource, + DateTime dateTimeQuery) { + + // Return false if task is a floating task (i.e. no start or end + // dateTime + if (dateTimeSource.getEndDate() == null) { + return false; + } + + DateTime dateTime1 = fillDateTime(dateTimeSource); + DateTime dateTime2 = fillDateTime(dateTimeQuery); + + return !dateTime1.getEndDate().isBefore(dateTime2.getStartDate()) + && !dateTime1.getStartDate().isAfter(dateTime2.getEndDate()); + } + + /** + * Checks whether the dateTimeQuery conflicts with the dateTimeSource i.e. dateTimeQuery + * endDateTime occurs after the dateTimeSource startDateTime && dateTimeQuery startDateTime + * occurs before the dateTimeSource endDateTime + */ + public static boolean isDateTimeConflicting(DateTime dateTimeSource, + DateTime dateTimeQuery) { + + // Return false if task is a floating task (i.e. no start or end + // dateTime + if (dateTimeSource.getEndDate() == null) { + return false; + } + + DateTime dateTime1 = fillDateTime(dateTimeSource); + DateTime dateTime2 = fillDateTime(dateTimeQuery); + + return dateTime1.getEndDate().isAfter(dateTime2.getStartDate()) + && dateTime1.getStartDate().isBefore(dateTime2.getEndDate()); + } + + private static DateTime fillDateTime(DateTime filledDateTime) { + DateTime dateTimeToFill = new DateTime(); + + dateTimeToFill.setEndDateTime(filledDateTime.getEndDate()); + + if (filledDateTime.getStartDate() != null) { + dateTimeToFill.setStartDateTime(filledDateTime.getStartDate()); + } else { + dateTimeToFill.setStartDateTime(filledDateTime.getEndDate()); + } + return dateTimeToFill; + } + + + /** + * Returns an arraylist of free datetime slots in a specified date + */ + public static ArrayList getListOfFreeTimeSlotsInDate( + DateTime dateToCheck, + ArrayList listOfFilledTimeSlotsInDate) { + ArrayList listOfFreeTimeSlots = new ArrayList(); + LocalDateTime startDateTime = dateToCheck.getStartDate(); + LocalDateTime endDateTime; + + for (DateTime dt : listOfFilledTimeSlotsInDate) { + if (dt.getStartDate() == null) { + continue; + } else { + endDateTime = dt.getStartDate(); + } + + if (startDateTime.isBefore(endDateTime)) { + listOfFreeTimeSlots + .add(new DateTime(startDateTime, endDateTime)); + } + + if (startDateTime.isBefore(dt.getEndDate())) { + startDateTime = dt.getEndDate(); + } + } + + if (startDateTime.isBefore(dateToCheck.getEndDate())) { + listOfFreeTimeSlots + .add(new DateTime(startDateTime, dateToCheck.getEndDate())); + } + + return listOfFreeTimeSlots; + } + + + public static String getDayAndDateString(DateTime dateTime) { + StringBuilder sb = new StringBuilder(); + + sb.append(dateTime.getEndDate().getDayOfWeek() + .getDisplayName(TextStyle.FULL, Locale.ENGLISH)) + .append(StringUtil.STRING_COMMA).append(dateTime.getEndDate() + .format(stringFormatterWithoutTime)); + + return sb.toString(); + } + + + public static String getStringOfFreeDateTimeInDate(DateTime dateToCheck, + ArrayList listOfFreeTimeSlotsInDate) { + StringBuilder sb = new StringBuilder(); + + sb.append(getDayAndDateString(dateToCheck)) + .append(StringUtil.STRING_COLON); + + int counter = 1; + + for (DateTime dt : listOfFreeTimeSlotsInDate) { + sb.append(String.format(MESSAGE_FREE_TIME_SLOT, counter, + dt.getStartDate().format(stringFormatterWithoutDate), + dt.getEndDate().format(stringFormatterWithoutDate), + getDurationBetweenTwoLocalDateTime(dt.getStartDate(), + dt.getEndDate()))); + counter++; + } + + return sb.toString(); + } + + public static String getDurationBetweenTwoLocalDateTime( + LocalDateTime startDateTime, LocalDateTime endDateTime) { + Duration duration = Duration.between(startDateTime, endDateTime); + long hours = duration.toHours(); + long minutes = duration.toMinutes() % 60; + + return String.format(MESSAGE_DURATION, hours, minutes); + } + + // @@author A0139924W + /** + * Modify the date based on the new hour, min and sec + */ + public static Date setDateTime(Date toBeEdit, int hour, int min, int sec) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(toBeEdit); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, min); + calendar.set(Calendar.SECOND, sec); + toBeEdit = calendar.getTime(); + + return toBeEdit; + } + + // @@author A0140022H + /** + * Modifies the date based on the frequency for recurring tasks. + */ + public static String modifyDate(String dateToModify, String frequency) { + LocalDateTime date = LocalDateTime.parse(dateToModify, formatter); + + switch (frequency.toLowerCase()) { + case DATETIME_DAY: + date = date.plusDays(DATETIME_INCREMENT); + break; + case DATETIME_WEEK: + date = date.plusWeeks(DATETIME_INCREMENT); + break; + case DATETIME_MONTH: + date = date.plusMonths(DATETIME_INCREMENT); + break; + case DATETIME_YEAR: + date = date.plusYears(DATETIME_INCREMENT); + break; + } + + dateToModify = date.format(stringFormatter); + return dateToModify; + } + + public static LocalDateTime setLocalTime(LocalDateTime dateTime, int hour, + int min, int sec) { + return LocalDateTime.of(dateTime.getYear(), dateTime.getMonth(), + dateTime.getDayOfMonth(), hour, min, sec); + } +} diff --git a/src/main/java/tars/commons/util/ExtractorUtil.java b/src/main/java/tars/commons/util/ExtractorUtil.java new file mode 100644 index 000000000000..2db6ef9cf9b8 --- /dev/null +++ b/src/main/java/tars/commons/util/ExtractorUtil.java @@ -0,0 +1,25 @@ +package tars.commons.util; + +import tars.commons.exceptions.IllegalValueException; +import tars.logic.parser.Prefix; + +// @@author A0140022H +/** + * Container for methods which extract data from string + */ +public class ExtractorUtil { + + /** + * Extracts the new task's recurring args from add command. + */ + public static String[] getRecurringFromArgs(String recurringArguments, + Prefix prefix) throws IllegalValueException { + recurringArguments = recurringArguments + .replaceFirst(prefix.value, StringUtil.EMPTY_STRING).trim(); + String[] recurringString = + recurringArguments.split(StringUtil.STRING_WHITESPACE); + + return recurringString; + } + +} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/tars/commons/util/FileUtil.java similarity index 73% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/tars/commons/util/FileUtil.java index ca8221250de4..2bc62e0af398 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/tars/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tars.commons.util; import java.io.File; import java.io.IOException; @@ -8,7 +8,10 @@ * Writes and reads file */ public class FileUtil { + private static final String CHARSET = "UTF-8"; + private static String MESSAGE_DIRECTORY_CREATE_FAILURE = + "Failed to make directories of %s"; public static boolean isFileExists(File file) { return file.exists() && file.isFile(); @@ -43,7 +46,8 @@ public static boolean createFile(File file) throws IOException { */ public static void createDirs(File dir) throws IOException { if (!dir.exists() && !dir.mkdirs()) { - throw new IOException("Failed to make directories of " + dir.getName()); + throw new IOException(String + .format(MESSAGE_DIRECTORY_CREATE_FAILURE, dir.getName())); } } @@ -66,30 +70,35 @@ public static String readFromFile(File file) throws IOException { } /** - * Writes given string to a file. - * Will create the file if it does not exist yet. + * Writes given string to a file. Will create the file if it does not exist yet. */ - public static void writeToFile(File file, String content) throws IOException { + public static void writeToFile(File file, String content) + throws IOException { Files.write(file.toPath(), content.getBytes(CHARSET)); } /** * Converts a string to a platform-specific file path + * * @param pathWithForwardSlash A String representing a file path but using '/' as the separator * @return {@code pathWithForwardSlash} but '/' replaced with {@code File.separator} */ public static String getPath(String pathWithForwardSlash) { assert pathWithForwardSlash != null; - assert pathWithForwardSlash.contains("/"); - return pathWithForwardSlash.replace("/", File.separator); + assert pathWithForwardSlash.contains(StringUtil.STRING_FORWARD_SLASH); + return pathWithForwardSlash.replace(StringUtil.STRING_FORWARD_SLASH, + File.separator); } - public static void serializeObjectToJsonFile(File jsonFile, T objectToSerialize) throws IOException { - FileUtil.writeToFile(jsonFile, JsonUtil.toJsonString(objectToSerialize)); + public static void serializeObjectToJsonFile(File jsonFile, + T objectToSerialize) throws IOException { + FileUtil.writeToFile(jsonFile, + JsonUtil.toJsonString(objectToSerialize)); } - public static T deserializeObjectFromJsonFile(File jsonFile, Class classOfObjectToDeserialize) - throws IOException { - return JsonUtil.fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize); + public static T deserializeObjectFromJsonFile(File jsonFile, + Class classOfObjectToDeserialize) throws IOException { + return JsonUtil.fromJsonString(FileUtil.readFromFile(jsonFile), + classOfObjectToDeserialize); } } diff --git a/src/main/java/seedu/address/commons/util/FxViewUtil.java b/src/main/java/tars/commons/util/FxViewUtil.java similarity index 81% rename from src/main/java/seedu/address/commons/util/FxViewUtil.java rename to src/main/java/tars/commons/util/FxViewUtil.java index 900efa6bf5c3..c3165595c569 100644 --- a/src/main/java/seedu/address/commons/util/FxViewUtil.java +++ b/src/main/java/tars/commons/util/FxViewUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tars.commons.util; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; @@ -8,7 +8,8 @@ */ public class FxViewUtil { - public static void applyAnchorBoundaryParameters(Node node, double left, double right, double top, double bottom) { + public static void applyAnchorBoundaryParameters(Node node, double left, + double right, double top, double bottom) { AnchorPane.setBottomAnchor(node, bottom); AnchorPane.setLeftAnchor(node, left); AnchorPane.setRightAnchor(node, right); diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/tars/commons/util/JsonUtil.java similarity index 80% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/tars/commons/util/JsonUtil.java index 80b67de5b7e8..ab87d4397b25 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/tars/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tars.commons.util; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; @@ -18,6 +18,19 @@ * Converts a Java object instance to JSON and vice versa */ public class JsonUtil { + + private static final String REGISTER_SIMPLE_MODULE = "SimpleModule"; + private static ObjectMapper objectMapper = new ObjectMapper() + .findAndRegisterModules() + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .registerModule(new SimpleModule(REGISTER_SIMPLE_MODULE) + .addSerializer(Level.class, new ToStringSerializer()) + .addDeserializer(Level.class, + new LevelDeserializer(Level.class))); + private static class LevelDeserializer extends FromStringDeserializer { protected LevelDeserializer(Class vc) { @@ -25,13 +38,13 @@ protected LevelDeserializer(Class vc) { } @Override - protected Level _deserialize(String value, DeserializationContext ctxt) throws IOException { + protected Level _deserialize(String value, DeserializationContext ctxt) + throws IOException { return getLoggingLevel(value); } /** - * Gets the logging level that matches loggingLevelString - *

+ * Gets the logging level that matches loggingLevelString
* Returns null if there are no matches * * @param loggingLevelString @@ -47,32 +60,28 @@ public Class handledType() { } } - private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules() - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .registerModule(new SimpleModule("SimpleModule") - .addSerializer(Level.class, new ToStringSerializer()) - .addDeserializer(Level.class, new LevelDeserializer(Level.class))); - /** * Converts a given string representation of a JSON data to instance of a class + * * @param The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ - public static T fromJsonString(String json, Class instanceClass) throws IOException { + public static T fromJsonString(String json, Class instanceClass) + throws IOException { return objectMapper.readValue(json, instanceClass); } /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ - public static String toJsonString(T instance) throws JsonProcessingException { - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(instance); + public static String toJsonString(T instance) + throws JsonProcessingException { + return objectMapper.writerWithDefaultPrettyPrinter() + .writeValueAsString(instance); } } diff --git a/src/main/java/tars/commons/util/NattyDateTimeUtil.java b/src/main/java/tars/commons/util/NattyDateTimeUtil.java new file mode 100644 index 000000000000..f8ff67e5ac25 --- /dev/null +++ b/src/main/java/tars/commons/util/NattyDateTimeUtil.java @@ -0,0 +1,165 @@ +package tars.commons.util; + +import java.text.SimpleDateFormat; +import java.time.DateTimeException; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import com.joestelmach.natty.DateGroup; +import com.joestelmach.natty.Parser; + +import tars.commons.core.Messages; + +// @@author A0139924W +/** + * Natty date time utility + */ +public class NattyDateTimeUtil { + private static final SimpleDateFormat CONVERT_NATTY_TIME_FORMAT = + new SimpleDateFormat("dd/MM/yyyy HHmm"); + private static final String DASH_DATE_FORMAT = "(\\b\\d{1,2})-(\\d{1,2})"; + private static final String SLASH_DATE_FORMAT = "(\\b\\d{1,2})/(\\d{1,2})"; + private static final String DASH_DATE_REPLACEMENT = "$2-$1"; + private static final String SLASH_DATE_REPLACEMENT = "$2/$1"; + + private static final int EMPTY_GROUP_SIZE = 0; + private static final int START_DATE_SIZE = 1; + private static final int START_END_DATE_SIZE = 2; + private static final int FIRST_GROUP = 0; + private static final int SECOND_GROUP = 1; + private static final int FIRST_CHILD = 0; + private static final int SECOND_CHILD = 1; + private static final String NATTY_TIME_PREFIX = "EXPLICIT_TIME"; + + /** + * Extracts the new task's dateTime from the string arguments using natty. + * + * @return String[] with first index being the startDate time and second index being the end + * date time + */ + public static String[] parseStringToDateTime(String dateTimeArg) { + String endDateTime = StringUtil.EMPTY_STRING; + String startDateTime = StringUtil.EMPTY_STRING; + String formattedDateTimeArg = convertToUsDateFormat(dateTimeArg); + + Parser parser = new Parser(TimeZone.getDefault()); + List groups = parser.parse(formattedDateTimeArg); + + if (isInvalidDateTimeArg(dateTimeArg, groups)) { + throw new DateTimeException(Messages.MESSAGE_INVALID_DATE); + } + + if (groups.size() > EMPTY_GROUP_SIZE) { + DateGroup group = groups.get(FIRST_GROUP); + if (group.getDates().size() == START_DATE_SIZE) { + return extractStartDate(group); + } + + if (group.getDates().size() == START_END_DATE_SIZE) { + return extractStartAndEndDate(group); + } + } + + return new String[] {startDateTime, endDateTime}; + } + + /** + * Change the date format to US date format. + * + * @return formatted datetime in US format + */ + private static String convertToUsDateFormat(String rawDateTime) { + String formattedDateTime = rawDateTime.trim() + .replaceAll(DASH_DATE_FORMAT, DASH_DATE_REPLACEMENT) + .replaceAll(SLASH_DATE_FORMAT, SLASH_DATE_REPLACEMENT); + return formattedDateTime; + } + + /** + * Change the date format to Asia date format. + * + * @return formatted datetime in Asia format + */ + private static String convertToAsiaDateFormat(Date toBeFormattedDateTime) { + return CONVERT_NATTY_TIME_FORMAT.format(toBeFormattedDateTime); + } + + /** + * Checks if the datetime is a invalid format. + * + * @return true if the given datetime is invalid + */ + private static boolean isInvalidDateTimeArg(String dateTimeArg, + List groups) { + return (dateTimeArg.trim().length() > StringUtil.EMPTY_STRING_LENGTH + && groups.size() == EMPTY_GROUP_SIZE); + } + + /** + * Extracts start date time from natty group + */ + private static String[] extractStartDate(DateGroup group) { + String treeString = StringUtil.EMPTY_STRING; + String endDateTime = StringUtil.EMPTY_STRING; + Date date; + + treeString = group.getSyntaxTree().getChild(FIRST_CHILD).toStringTree(); + date = group.getDates().get(FIRST_GROUP); + if (!isTimePresent(treeString)) { + date = DateTimeUtil.setDateTime(date, + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + } + + endDateTime = convertToAsiaDateFormat(date); + + return new String[] {StringUtil.EMPTY_STRING, endDateTime}; + } + + /** + * Extracts start and end date time from natty group + */ + private static String[] extractStartAndEndDate(DateGroup group) { + String firstTreeString = StringUtil.EMPTY_STRING; + String secondTreeString = StringUtil.EMPTY_STRING; + String startDateTime = StringUtil.EMPTY_STRING; + String endDateTime = StringUtil.EMPTY_STRING; + Date firstDate; + Date secondDate; + + firstTreeString = + group.getSyntaxTree().getChild(FIRST_CHILD).toStringTree(); + secondTreeString = + group.getSyntaxTree().getChild(SECOND_CHILD).toStringTree(); + firstDate = group.getDates().get(FIRST_GROUP); + secondDate = group.getDates().get(SECOND_GROUP); + + if (!isTimePresent(firstTreeString)) { + firstDate = DateTimeUtil.setDateTime(firstDate, + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + } + + if (!isTimePresent(secondTreeString)) { + secondDate = DateTimeUtil.setDateTime(secondDate, + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + } + + startDateTime = CONVERT_NATTY_TIME_FORMAT.format(firstDate); + endDateTime = CONVERT_NATTY_TIME_FORMAT.format(secondDate); + + return new String[] {startDateTime, endDateTime}; + } + + /** + * Checks if time is present + */ + private static boolean isTimePresent(String treeString) { + return treeString.contains(NATTY_TIME_PREFIX); + } +} diff --git a/src/main/java/tars/commons/util/StringUtil.java b/src/main/java/tars/commons/util/StringUtil.java new file mode 100644 index 000000000000..47df61ed6b96 --- /dev/null +++ b/src/main/java/tars/commons/util/StringUtil.java @@ -0,0 +1,157 @@ +package tars.commons.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; + +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidRangeException; + +/** + * Helper functions for handling strings. + */ +public class StringUtil { + + public static final String REGEX_WHITESPACE = "\\s+"; + public static final String STRING_NEWLINE = "\n"; + public static final String STRING_COLON = ": "; + public static final String STRING_COMMA = ", "; + public static final String STRING_FULLSTOP = ". "; + public static final String STRING_FORWARD_SLASH = "/"; + public static final String STRING_SQUARE_BRACKET_OPEN = "["; + public static final String STRING_SQUARE_BRACKET_CLOSE = "]"; + public static final String EMPTY_STRING = ""; + public static final String STRING_WHITESPACE = " "; + public static final int EMPTY_STRING_LENGTH = 0; + public static final int START_INDEX = 0; + public static final int LAST_INDEX = 1; + public static final int INVALID_POSITION = -1; + /** Offset required to convert between 1-indexing and 0-indexing. */ + public static final int DISPLAYED_INDEX_OFFSET = 1; + + private static final String REGEX_UNSIGNED_INTEGER = "^0*[1-9]\\d*$"; + private static final String INVALID_INDEX_ENTERED = "Invalid index entered"; + private static final String UNEXPECTED_ERROR_IN_GETING_INDEX_FROM_STRING = + "Unexpected error in geting index from String."; + private static final int EMPTY_STREAM_LIST = 0; + private static final String RANGE_SEPARATOR = ".."; + + public static boolean containsIgnoreCase(String source, String query) { + String[] split = source.toLowerCase().split(REGEX_WHITESPACE); + List strings = Arrays.asList(split); + return strings.stream().filter(s -> s.equals(query.toLowerCase())) + .count() > EMPTY_STREAM_LIST; + } + + /** + * Returns a detailed message of the t, including the stack trace. + */ + public static String getDetails(Throwable t) { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + return t.getMessage() + STRING_NEWLINE + sw.toString(); + } + + /** + * Returns true if s represents an unsigned integer e.g. 1, 2, 3, ...
+ * Will return false for null, empty string, "-1", "0", "+1", and " 2 " (untrimmed) "3 0" + * (contains whitespace). + * + * @param s should be trimmed. + */ + public static boolean isUnsignedInteger(String s) { + return s != null && s.matches(REGEX_UNSIGNED_INTEGER); + } + + // @@author A0121533W + /** + * Handles three different cases of strings and return them in the appropriate format + */ + public static String indexString(String s) + throws InvalidRangeException, IllegalValueException { + if (s.isEmpty()) { + return s; + } + if (isSingleNumber(s)) { + return getIndexForSingleNumber(s); + } else if (isListOfIndexes(s)) { + return getIndexesForList(s); + } else if (isRangeOfIndexes(s)) { + return getIndexesForRange(s); + } else { + throw new IllegalValueException( + UNEXPECTED_ERROR_IN_GETING_INDEX_FROM_STRING); + } + } + + private static boolean isSingleNumber(String s) { + return (s.indexOf(STRING_WHITESPACE) == INVALID_POSITION + && !s.contains(RANGE_SEPARATOR)); + } + + /** + * Returns a valid single index + */ + private static String getIndexForSingleNumber(String s) + throws IllegalValueException { + if (!isUnsignedInteger(s)) { + throw new IllegalValueException(INVALID_INDEX_ENTERED); + } + return s; + } + + private static boolean isListOfIndexes(String s) { + return (s.indexOf(STRING_WHITESPACE) != INVALID_POSITION + && !s.contains(RANGE_SEPARATOR)); + } + + // @@author A0121533W + /** + * Returns a valid list of indexes + */ + private static String getIndexesForList(String s) + throws IllegalValueException { + String indexString = EMPTY_STRING; + String[] indexArray = s.split(STRING_WHITESPACE); + for (int i = START_INDEX; i < indexArray.length; i++) { + String index = getIndexForSingleNumber(indexArray[i]); + indexString += index + STRING_WHITESPACE; + } + return indexString.trim(); + } + + private static boolean isRangeOfIndexes(String s) { + return s.contains(RANGE_SEPARATOR); + } + + // @@author A0121533W + /** + * Returns a list of indexes separated by white space from a range of indexes + */ + private static String getIndexesForRange(String s) + throws IllegalValueException, InvalidRangeException { + String rangeToReturn = EMPTY_STRING; + + int toIndex = s.indexOf(RANGE_SEPARATOR); + String start = s.substring(START_INDEX, toIndex); + String end = s.substring(toIndex + RANGE_SEPARATOR.length()); + + start = getIndexForSingleNumber(start); + end = getIndexForSingleNumber(end); + + int startInt = Integer.parseInt(start); + int endInt = Integer.parseInt(end); + + if (startInt > endInt) { + throw new InvalidRangeException(); + } + + for (int i = startInt; i <= endInt; i++) { + rangeToReturn += String.valueOf(i) + STRING_WHITESPACE; + } + + return rangeToReturn.trim(); + } + +} diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/tars/commons/util/XmlUtil.java similarity index 60% rename from src/main/java/seedu/address/commons/util/XmlUtil.java rename to src/main/java/tars/commons/util/XmlUtil.java index 2087e7628a1d..00d48171cb3c 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/tars/commons/util/XmlUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tars.commons.util; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -12,17 +12,19 @@ */ public class XmlUtil { + private static final String SUPRESSWARNING_UNCHECKED = "unchecked"; + private static String MESSAGE_FILE_NOT_FOUND = "File not found: %s"; + /** * Returns the xml data in the file as an object of the specified type. * - * @param file Points to a valid xml file containing data that match the {@code classToConvert}. - * Cannot be null. - * @param classToConvert The class corresponding to the xml data. - * Cannot be null. + * @param file Points to a valid xml file containing data that match the {@code classToConvert}. + * Cannot be null. + * @param classToConvert The class corresponding to the xml data. Cannot be null. * @throws FileNotFoundException Thrown if the file is missing. - * @throws JAXBException Thrown if the file is empty or does not have the correct format. + * @throws JAXBException Thrown if the file is empty or does not have the correct format. */ - @SuppressWarnings("unchecked") + @SuppressWarnings(SUPRESSWARNING_UNCHECKED) public static T getDataFromFile(File file, Class classToConvert) throws FileNotFoundException, JAXBException { @@ -30,7 +32,8 @@ public static T getDataFromFile(File file, Class classToConvert) assert classToConvert != null; if (!FileUtil.isFileExists(file)) { - throw new FileNotFoundException("File not found : " + file.getAbsolutePath()); + throw new FileNotFoundException(String + .format(MESSAGE_FILE_NOT_FOUND, file.getAbsolutePath())); } JAXBContext context = JAXBContext.newInstance(classToConvert); @@ -43,18 +46,20 @@ public static T getDataFromFile(File file, Class classToConvert) * Saves the data in the file in xml format. * * @param file Points to a valid xml file containing data that match the {@code classToConvert}. - * Cannot be null. + * Cannot be null. * @throws FileNotFoundException Thrown if the file is missing. - * @throws JAXBException Thrown if there is an error during converting the data - * into xml and writing to the file. + * @throws JAXBException Thrown if there is an error during converting the data into xml and + * writing to the file. */ - public static void saveDataToFile(File file, T data) throws FileNotFoundException, JAXBException { + public static void saveDataToFile(File file, T data) + throws FileNotFoundException, JAXBException { assert file != null; assert data != null; if (!file.exists()) { - throw new FileNotFoundException("File not found : " + file.getAbsolutePath()); + throw new FileNotFoundException(String + .format(MESSAGE_FILE_NOT_FOUND, file.getAbsolutePath())); } JAXBContext context = JAXBContext.newInstance(data.getClass()); diff --git a/src/main/java/tars/logic/Logic.java b/src/main/java/tars/logic/Logic.java new file mode 100644 index 000000000000..edfb09328066 --- /dev/null +++ b/src/main/java/tars/logic/Logic.java @@ -0,0 +1,30 @@ +package tars.logic; + +import java.util.List; + +import javafx.collections.ObservableList; +import tars.logic.commands.CommandResult; +import tars.model.task.ReadOnlyTask; +import tars.model.task.rsv.RsvTask; + +/** + * 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 unmodifiable list of tasks */ + List getTaskList(); + + /** Returns the filtered list of tasks */ + ObservableList getFilteredTaskList(); + + /** Returns the filtered list of RsvTasks */ + ObservableList getFilteredRsvTaskList(); + +} diff --git a/src/main/java/tars/logic/LogicManager.java b/src/main/java/tars/logic/LogicManager.java new file mode 100644 index 000000000000..f443907d4407 --- /dev/null +++ b/src/main/java/tars/logic/LogicManager.java @@ -0,0 +1,73 @@ +package tars.logic; + +import java.util.List; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import tars.commons.core.ComponentManager; +import tars.commons.core.LogsCenter; +import tars.logic.commands.Command; +import tars.logic.commands.CommandResult; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.UndoCommand; +import tars.logic.parser.Parser; +import tars.model.Model; +import tars.model.task.ReadOnlyTask; +import tars.model.task.rsv.RsvTask; +import tars.storage.Storage; + +/** + * The main LogicManager of the app. + */ +public class LogicManager extends ComponentManager implements Logic { + + private static final String LOG_USER_COMMAND = + "----------------[USER COMMAND][%1$s]"; + 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(String.format(LOG_USER_COMMAND, commandText)); + Command command = parser.parseCommand(commandText); + command.setData(model); + + if (!isReUndoAbleCommand(command)) { + model.getRedoableCmdHist().clear(); + } + + return command.execute(); + } + + /** + * Checks if the command is an instance of redo or undo command + * + * @param command + */ + private boolean isReUndoAbleCommand(Command command) { + return (command instanceof UndoCommand + || command instanceof RedoCommand); + } + + @Override + public List getTaskList() { + return model.getTars().getTaskList(); + } + + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + + @Override + public ObservableList getFilteredRsvTaskList() { + return model.getFilteredRsvTaskList(); + } + +} diff --git a/src/main/java/tars/logic/commands/AddCommand.java b/src/main/java/tars/logic/commands/AddCommand.java new file mode 100644 index 000000000000..e9269790392f --- /dev/null +++ b/src/main/java/tars/logic/commands/AddCommand.java @@ -0,0 +1,209 @@ +package tars.logic.commands; + +import java.time.DateTimeException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import tars.commons.core.Messages; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.model.task.DateTime; +import tars.model.task.DateTime.IllegalDateException; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.model.task.Status; +import tars.model.task.Task; +import tars.model.task.UniqueTaskList.TaskNotFoundException; + +// @@author A0140022H +/** + * Adds a task to tars. + */ +public class AddCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a task to tars.\n" + + "Parameters: [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]\n " + + "Example: " + COMMAND_WORD + + " cs2103 project meeting /dt 05/09/2016 1400 to 06/09/2016 2200 /p h /t project /r 2 every week"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_UNDO = "Removed %1$s"; + public static final String MESSAGE_REDO = "Added %1$s"; + + private static final int DATETIME_INDEX_OF_ENDDATE = 1; + private static final int DATETIME_INDEX_OF_STARTDATE = 0; + private static final int DATETIME_EMPTY_DATE = 0; + + private static final int ADDTASK_FIRST_ITERATION = 0; + private static final int ADDTASK_DEFAULT_NUMTASK = 1; + private static final String ADDTASK_STRING_EMPTY = ""; + private static final String ADDTASK_STRING_NEWLINE = "\n"; + + private static final int RECURRINGSTRING_NOT_EMPTY = 1; + private static final int RECURRINGSTRING_INDEX_OF_NUMTASK = 0; + private static final int RECURRINGSTRING_INDEX_OF_FREQUENCY = 2; + + private Task toAdd; + private ArrayList taskArray; + + private String conflictingTaskList = ""; + + // @@author A0140022H + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + * @throws DateTimeException if given dateTime string is invalid. + */ + public AddCommand(String name, String[] dateTime, String priority, + Set tags, String[] recurringString) + throws IllegalValueException, DateTimeException { + + taskArray = new ArrayList(); + + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + + addToTaskArray(name, dateTime, priority, recurringString, tagSet); + + } + // @@author + + // @@author A0140022H + private void addToTaskArray(String name, String[] dateTime, String priority, + String[] recurringString, final Set tagSet) + throws IllegalValueException, IllegalDateException { + int numTask = ADDTASK_DEFAULT_NUMTASK; + if (recurringString != null + && recurringString.length > RECURRINGSTRING_NOT_EMPTY) { + numTask = Integer.parseInt( + recurringString[RECURRINGSTRING_INDEX_OF_NUMTASK]); + } + + for (int i = ADDTASK_FIRST_ITERATION; i < numTask; i++) { + if (i != ADDTASK_FIRST_ITERATION) { + if (recurringString != null + && recurringString.length > RECURRINGSTRING_NOT_EMPTY) { + modifyDateTime(dateTime, recurringString, + DATETIME_INDEX_OF_STARTDATE); + modifyDateTime(dateTime, recurringString, + DATETIME_INDEX_OF_ENDDATE); + } + } + this.toAdd = new Task(new Name(name), + new DateTime(dateTime[DATETIME_INDEX_OF_STARTDATE], + dateTime[DATETIME_INDEX_OF_ENDDATE]), + new Priority(priority), new Status(), + new UniqueTagList(tagSet)); + taskArray.add(toAdd); + } + } + // @@author + + // @@author A0140022H + private void modifyDateTime(String[] dateTime, String[] recurringString, + int dateTimeIndex) { + if (dateTime[dateTimeIndex] != null + && dateTime[dateTimeIndex].length() > DATETIME_EMPTY_DATE) { + dateTime[dateTimeIndex] = DateTimeUtil.modifyDate( + dateTime[dateTimeIndex], + recurringString[RECURRINGSTRING_INDEX_OF_FREQUENCY]); + } + } + // @@author + + // @@author A0140022H + @Override + public CommandResult execute() { + assert model != null; + try { + addTasks(); + model.getUndoableCmdHist().push(this); + return new CommandResult(messageSummary()); + } catch (DuplicateTaskException e) { + return new CommandResult(Messages.MESSAGE_DUPLICATE_TASK); + } + } + // @@author + + // @@author A0140022H + private void addTasks() throws DuplicateTaskException { + for (Task toAdd : taskArray) { + conflictingTaskList += + model.getTaskConflictingDateTimeWarningMessage( + toAdd.getDateTime()); + model.addTask(toAdd); + + if (taskArray.size() == ADDTASK_DEFAULT_NUMTASK && ((toAdd + .getDateTime().getStartDate() == null + && toAdd.getDateTime().getEndDate() != null) + || (toAdd.getDateTime().getStartDate() != null + && toAdd.getDateTime().getEndDate() != null))) { + model.updateFilteredTaskListUsingDate(toAdd.getDateTime()); + } + } + } + + // @@author A0139924W + @Override + public CommandResult undo() { + assert model != null; + try { + for (Task toAdd : taskArray) { + model.deleteTask(toAdd); + } + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO, toAdd))); + } catch (TaskNotFoundException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_TASK_CANNOT_BE_FOUND)); + } + } + + // @@author A0139924W + @Override + public CommandResult redo() { + assert model != null; + try { + for (Task toAdd : taskArray) { + model.addTask(toAdd); + } + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + messageSummary())); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + + // @@author A0140022H + private String messageSummary() { + String summary = ADDTASK_STRING_EMPTY; + + for (Task toAdd : taskArray) { + summary += String.format(MESSAGE_SUCCESS, + toAdd + ADDTASK_STRING_NEWLINE); + } + + if (!conflictingTaskList.isEmpty()) { + summary += StringUtil.STRING_NEWLINE + + Messages.MESSAGE_CONFLICTING_TASKS_WARNING + + conflictingTaskList; + } + return summary; + } + // @@author + +} diff --git a/src/main/java/tars/logic/commands/CdCommand.java b/src/main/java/tars/logic/commands/CdCommand.java new file mode 100644 index 000000000000..4db5d062955f --- /dev/null +++ b/src/main/java/tars/logic/commands/CdCommand.java @@ -0,0 +1,122 @@ +package tars.logic.commands; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +import tars.commons.core.Config; +import tars.commons.core.EventsCenter; +import tars.commons.events.ui.ScrollToTopEvent; +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.ConfigUtil; +import tars.commons.util.FileUtil; +import tars.model.ReadOnlyTars; +import tars.storage.Storage; +import tars.storage.StorageManager; + +// @@author A0124333U +/** + * Changes the directory of the Tars storage file, tars.xml + */ +public class CdCommand extends Command { + + public static final String COMMAND_WORD = "cd"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Changes the directory of the " + + "TARS storage file.\n" + "Parameters: \n" + + "Example: " + COMMAND_WORD + " data/tars.xml"; + + public static final String MESSAGE_INVALID_FILEPATH = + "Invalid file path. File paths should end with the file type .xml \n" + + "Example: " + COMMAND_WORD + " data/tars.xml"; + + public static final String MESSAGE_SUCCESS_NEW_FILE = + "Change Directory Success! New file created! Directory of TARS storage file" + + " changed to: '%1$s'."; + + public static final String MESSAGE_SUCCESS_EXISTING_FILE = + "Change Directory Success! File read successfully! Directory of TARS storage " + + "file changed to : '%1$s'."; + + public static final String MESSAGE_FAILURE_WRITE_FILE = + "Unable to write to location, please choose another directory"; + + public static final String MESSAGE_FAILURE_READ_FILE = + "Unable to read from location, please choose another directory"; + + private static final String xmlFileExt = "xml"; + + private final String newFilePath; + private Storage storageUpdater = new StorageManager(); + private Config newConfig = new Config(); + + public CdCommand(String filepath) { + this.newFilePath = filepath; + } + + public final static String getXmlFileExt() { + return xmlFileExt; + } + + @Override + public String toString() { + return this.newFilePath; + } + + @Override + public CommandResult execute() { + + newConfig.setTarsFilePath(newFilePath); + File file = new File(newFilePath); + + EventsCenter.getInstance().post(new ScrollToTopEvent()); + return FileUtil.isFileExists(file) ? readTarsFromNewFilePath() + : saveTarsToNewFilePath(); + } + + private CommandResult saveTarsToNewFilePath() { + try { + // try to save TARS data into new file + storageUpdater.saveTarsInNewFilePath(model.getTars(), newFilePath); + if (storageUpdater.isFileSavedSuccessfully(newFilePath)) { + updateTarsSystemWithNewFilePath(); + return new CommandResult( + String.format(MESSAGE_SUCCESS_NEW_FILE, newFilePath)); + } else { + return new CommandResult(MESSAGE_FAILURE_WRITE_FILE); + } + } catch (IOException ioe) { + return new CommandResult(MESSAGE_FAILURE_WRITE_FILE); + } + + } + + private CommandResult readTarsFromNewFilePath() { + Optional tarsOptional; + ReadOnlyTars tarsDataToOverwrite; + try { + tarsOptional = storageUpdater.readTarsFromNewFilePath(newFilePath); + + tarsDataToOverwrite = tarsOptional.orElse(null); + + if (tarsDataToOverwrite != null) { + model.overwriteDataFromNewFilePath(tarsDataToOverwrite); + updateTarsSystemWithNewFilePath(); + return new CommandResult(String + .format(MESSAGE_SUCCESS_EXISTING_FILE, newFilePath)); + } else { + return new CommandResult(MESSAGE_FAILURE_READ_FILE); + } + } catch (DataConversionException | IOException e) { + return new CommandResult(MESSAGE_FAILURE_READ_FILE); + } + + } + + private void updateTarsSystemWithNewFilePath() throws IOException { + storageUpdater.updateTarsStorageDirectory(newFilePath, newConfig); + ConfigUtil.updateConfig(newConfig); + } + +} diff --git a/src/main/java/tars/logic/commands/ClearCommand.java b/src/main/java/tars/logic/commands/ClearCommand.java new file mode 100644 index 000000000000..a1bd3dd3b74c --- /dev/null +++ b/src/main/java/tars/logic/commands/ClearCommand.java @@ -0,0 +1,24 @@ +package tars.logic.commands; + +import tars.model.Tars; + +// @@author A0139924W +/** + * Clears tars. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "TARS has been cleared!"; + + @Override + public CommandResult execute() { + assert model != null; + model.resetData(Tars.getEmptyTars()); + + model.getUndoableCmdHist().clear(); + model.getRedoableCmdHist().clear(); + + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/tars/logic/commands/Command.java similarity index 72% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/tars/logic/commands/Command.java index 7c0ba2fd0161..66048efb2db1 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/tars/logic/commands/Command.java @@ -1,9 +1,9 @@ -package seedu.address.logic.commands; +package tars.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; +import tars.commons.core.EventsCenter; +import tars.commons.core.Messages; +import tars.commons.events.ui.IncorrectCommandAttemptedEvent; +import tars.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. @@ -12,12 +12,12 @@ public abstract class Command { protected Model model; /** - * Constructs a feedback message to summarise an operation that displayed a listing of persons. + * Constructs a feedback message to summarise an operation that displayed a listing of tasks. * * @param displaySize used to generate summary - * @return summary message for persons displayed + * @return summary message for tasks displayed */ - public static String getMessageForPersonListShownSummary(int displaySize) { + public static String getMessageForTaskListShownSummary(int displaySize) { return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/tars/logic/commands/CommandResult.java similarity index 87% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/tars/logic/commands/CommandResult.java index f46f2f31353e..f9400c8f4915 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/tars/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package tars.logic.commands; /** * Represents the result of a command execution. diff --git a/src/main/java/tars/logic/commands/ConfirmCommand.java b/src/main/java/tars/logic/commands/ConfirmCommand.java new file mode 100644 index 000000000000..1cdfe6f597b5 --- /dev/null +++ b/src/main/java/tars/logic/commands/ConfirmCommand.java @@ -0,0 +1,164 @@ +package tars.logic.commands; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.HashSet; +import java.util.Set; + +import tars.commons.core.Messages; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.StringUtil; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.model.task.Priority; +import tars.model.task.Status; +import tars.model.task.Task; +import tars.model.task.UniqueTaskList.TaskNotFoundException; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList.RsvTaskNotFoundException; + +// @@author A0124333U +/** + * Confirms a specified datetime for a reserved task and add it into the task list + */ +public class ConfirmCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "confirm"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Confirms a datetime for a reserved task" + + " and adds it to the task list.\n" + + "Parameters: [/p PRIORITY] [/t TAG_NAME ...]\n" + + "Example: " + COMMAND_WORD + " 1 3 /p h /t tag"; + + public static final String MESSAGE_CONFIRM_SUCCESS = + "Task Confirmation Success! New task added: %1$s"; + + private final int taskIndex; + private final int dateTimeIndex; + private final String priority; + private final Set tagSet = new HashSet<>(); + + private String conflictingTaskList = StringUtil.EMPTY_STRING; + private Task toConfirm; + private RsvTask rsvTask; + + public ConfirmCommand(int taskIndex, int dateTimeIndex, String priority, + Set tags) throws IllegalValueException { + this.taskIndex = taskIndex; + this.dateTimeIndex = dateTimeIndex; + this.priority = priority; + + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + } + + @Override + public CommandResult execute() { + assert model != null; + UnmodifiableObservableList lastShownList = + model.getFilteredRsvTaskList(); + + if (lastShownList.size() < taskIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult( + Messages.MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX); + } + + rsvTask = lastShownList + .get(taskIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + + if (rsvTask.getDateTimeList().size() < dateTimeIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult( + Messages.MESSAGE_INVALID_DATETIME_DISPLAYED_INDEX); + } + + try { + toConfirm = new Task(rsvTask.getName(), rsvTask.getDateTimeList() + .get((dateTimeIndex - StringUtil.DISPLAYED_INDEX_OFFSET)), + new Priority(priority), new Status(), + new UniqueTagList(tagSet)); + } catch (IllegalValueException ive) { + return new CommandResult(String + .format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + try { + model.deleteRsvTask(rsvTask); + } catch (RsvTaskNotFoundException rtnfe) { + return new CommandResult(Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND); + } + + try { + conflictingTaskList += + model.getTaskConflictingDateTimeWarningMessage( + toConfirm.getDateTime()); + model.addTask(toConfirm); + } catch (DuplicateTaskException e) { + return new CommandResult(Messages.MESSAGE_DUPLICATE_TASK); + } + + model.getUndoableCmdHist().push(this); + return new CommandResult(getSuccessMessageSummary()); + } + + private String getSuccessMessageSummary() { + String summary = + String.format(MESSAGE_CONFIRM_SUCCESS, toConfirm.toString()); + + if (!conflictingTaskList.isEmpty()) { + summary += StringUtil.STRING_NEWLINE + + Messages.MESSAGE_CONFLICTING_TASKS_WARNING + + conflictingTaskList; + } + + return summary; + } + + // @@author + + // @@author A0139924W + @Override + public CommandResult undo() { + try { + model.addRsvTask(rsvTask); + model.deleteTask(toConfirm); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } catch (TaskNotFoundException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_TASK_CANNOT_BE_FOUND)); + } + + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + + // @@author A0139924W + @Override + public CommandResult redo() { + try { + model.deleteRsvTask(rsvTask); + model.addTask(toConfirm); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } catch (RsvTaskNotFoundException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND)); + } + + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + +} diff --git a/src/main/java/tars/logic/commands/DeleteCommand.java b/src/main/java/tars/logic/commands/DeleteCommand.java new file mode 100644 index 000000000000..8c93c85fe0e1 --- /dev/null +++ b/src/main/java/tars/logic/commands/DeleteCommand.java @@ -0,0 +1,128 @@ +package tars.logic.commands; + +import java.util.ArrayList; + +import tars.commons.core.Messages; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.InvalidTaskDisplayedException; +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Task; +import tars.model.task.UniqueTaskList.TaskNotFoundException; +import tars.ui.formatter.Formatter; + +// @@author A0121533W +/** + * Deletes a task identified using it's last displayed index from tars. + */ +public class DeleteCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "del"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task based on its index in the task list.\n" + + "Parameters: [INDEX ...]\n" + "Example: " + + COMMAND_WORD + " 1\n" + COMMAND_WORD + " 1..3"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = + "Deleted Task:\n%1$s"; + public static final String MESSAGE_UNDO = "Added Task:\n%1$s"; + public static final String MESSAGE_REDO = "Deleted Task:\n%1$s"; + + private static final String MESSAGE_MISSING_TARGET_TASK = + "The target task cannot be missing"; + + private final String arguments; + private ArrayList deletedTasks = + new ArrayList(); + + public DeleteCommand(String args) { + this.arguments = args; + } + + @Override + public CommandResult execute() { + ArrayList tasksToDelete; + try { + tasksToDelete = getTasksFromIndexes( + this.arguments.split(StringUtil.STRING_WHITESPACE)); + } catch (InvalidTaskDisplayedException itde) { + return new CommandResult(itde.getMessage()); + } + for (ReadOnlyTask t : tasksToDelete) { + try { + model.deleteTask(t); + } catch (TaskNotFoundException tnfe) { + assert false : MESSAGE_MISSING_TARGET_TASK; + } + deletedTasks.add(t); + } + model.getUndoableCmdHist().push(this); + String formattedTaskList = new Formatter().formatTaskList(deletedTasks); + return new CommandResult( + String.format(MESSAGE_DELETE_TASK_SUCCESS, formattedTaskList)); + } + + /** + * Gets Tasks to delete from indexes + */ + private ArrayList getTasksFromIndexes(String[] indexes) + throws InvalidTaskDisplayedException { + UnmodifiableObservableList lastShownList = + model.getFilteredTaskList(); + ArrayList tasksList = new ArrayList(); + + for (int i = StringUtil.START_INDEX; i < indexes.length; i++) { + int targetIndex = Integer.parseInt(indexes[i]); + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new InvalidTaskDisplayedException( + Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + ReadOnlyTask task = + lastShownList.get(targetIndex - StringUtil.LAST_INDEX); + tasksList.add(task); + } + return tasksList; + } + + // @@author A0139924W + @Override + public CommandResult undo() { + try { + for (ReadOnlyTask t : deletedTasks) { + Task taskToAdd = new Task(t); + model.addTask(taskToAdd); + } + String formattedTaskList = + new Formatter().formatTaskList(deletedTasks); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO, formattedTaskList))); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + + // @@author A0139924W + @Override + public CommandResult redo() { + try { + for (ReadOnlyTask t : deletedTasks) { + Task taskToAdd = new Task(t); + model.deleteTask(taskToAdd); + } + String formattedTaskList = + new Formatter().formatTaskList(deletedTasks); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO, formattedTaskList))); + } catch (TaskNotFoundException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_TASK_CANNOT_BE_FOUND)); + } + } + +} diff --git a/src/main/java/tars/logic/commands/DoCommand.java b/src/main/java/tars/logic/commands/DoCommand.java new file mode 100644 index 000000000000..3af1d01580ab --- /dev/null +++ b/src/main/java/tars/logic/commands/DoCommand.java @@ -0,0 +1,63 @@ +package tars.logic.commands; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.exceptions.InvalidTaskDisplayedException; +import tars.commons.util.StringUtil; +import tars.model.task.*; + +import java.util.ArrayList; + +// @@author A0121533W +/** + * Marks a task identified using it's last displayed index from tars as done. + */ +public class DoCommand extends Command { + + public static final String COMMAND_WORD = "do"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the task based on its index in the task list as done.\n" + + "Parameters: [INDEX ...]\n" + "Example: " + + COMMAND_WORD + " 3 5 7\n" + "OR " + COMMAND_WORD + " 1..3"; + + private String toDo; + + private MarkTaskUtil tracker; + + /** + * Convenience constructor using raw values. + * + * @throws InvalidRangeException + */ + public DoCommand(String toDo) { + this.toDo = toDo; + this.tracker = new MarkTaskUtil(); + } + + @Override + public CommandResult execute() { + assert model != null; + + try { + handleMarkDone(); + } catch (InvalidTaskDisplayedException e) { + return new CommandResult(e.getMessage()); + } catch (DuplicateTaskException dte) { + return new CommandResult(dte.getMessage()); + } + return new CommandResult(tracker.getResultFromTracker()); + } + + /** + * Marks status of task in model as done + */ + private void handleMarkDone() + throws InvalidTaskDisplayedException, DuplicateTaskException { + Status done = new Status(true); + ArrayList markDoneTasks = tracker.getTasksFromIndexes( + model, this.toDo.split(StringUtil.STRING_WHITESPACE), done); + model.mark(markDoneTasks, done); + } + +} diff --git a/src/main/java/tars/logic/commands/EditCommand.java b/src/main/java/tars/logic/commands/EditCommand.java new file mode 100644 index 000000000000..31ea754fd86f --- /dev/null +++ b/src/main/java/tars/logic/commands/EditCommand.java @@ -0,0 +1,241 @@ +package tars.logic.commands; + +import java.time.DateTimeException; +import java.util.HashSet; +import java.util.Set; + +import tars.commons.core.Messages; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.logic.parser.ArgumentTokenizer; +import tars.logic.parser.Prefix; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.model.tag.UniqueTagList.DuplicateTagException; +import tars.model.tag.UniqueTagList.TagNotFoundException; +import tars.model.task.DateTime; +import tars.model.task.DateTime.IllegalDateException; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Task; + +// @@author A0121533W +/** + * Edits a task identified using it's last displayed index from tars. + */ +public class EditCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits any component of a particular task.\n" + + "Parameters: [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] " + + "[/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]\n" + "Example: " + COMMAND_WORD + + " 1 /n Lunch with John /dt 10/09/2016 1200 to 10/09/2016 1300 /p l /ta lunch /tr dinner"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited task: %1$s"; + public static final String MESSAGE_UNDO = "Edited to %1$s to %1$s"; + public static final String MESSAGE_REDO = "Edited to %1$s to %1$s"; + + private static final int DATETIME_INDEX_OF_ENDDATE = 1; + private static final int DATETIME_INDEX_OF_STARTDATE = 0; + private static final Prefix NAME_PREFIX = new Prefix("/n"); + private static final Prefix DATETIME_PREFIX = new Prefix("/dt"); + private static final Prefix PRIORITY_PREFIX = new Prefix("/p"); + private static final Prefix ADD_TAG_PREFIX = new Prefix("/ta"); + private static final Prefix REMOVE_TAG_PREFIX = new Prefix("/tr"); + + public final int targetIndex; + private ReadOnlyTask toBeReplacedTask; + private Task editedTask; + private ArgumentTokenizer argsTokenizer; + + /** + * Convenience constructor using raw values. + */ + public EditCommand(int targetIndex, ArgumentTokenizer argsTokenizer) { + this.targetIndex = targetIndex; + this.argsTokenizer = argsTokenizer; + } + + @Override + public CommandResult execute() { + assert model != null; + + UnmodifiableObservableList lastShownList = + model.getFilteredTaskList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + return new CommandResult( + Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + toBeReplacedTask = lastShownList + .get(targetIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + editedTask = new Task(toBeReplacedTask); + + try { + updateTask(); + model.replaceTask(toBeReplacedTask, editedTask); + model.getUndoableCmdHist().push(this); + return new CommandResult( + String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + } catch (DateTimeException dte) { + return new CommandResult(Messages.MESSAGE_INVALID_DATE); + } catch (IllegalValueException | TagNotFoundException e) { + return new CommandResult(e.getMessage()); + } + } + + // @@author A0139924W + /** + * Update task if there is a change + * + * @throws IllegalValueException + * @throws TagNotFoundException + */ + private void updateTask() throws IllegalValueException, TagNotFoundException { + updateNameIfChanged(); + updatePriorityIfChanged(); + updateDateTimeIfChanged(); + addTagsIfFound(); + deleteTagsIfFound(); + } + + /** + * Update the name field if there is a change + * + * @throws IllegalValueException + */ + private void updateNameIfChanged() throws IllegalValueException { + if (isFieldChanged(NAME_PREFIX)) { + Name editedName = + new Name(argsTokenizer.getValue(NAME_PREFIX).get()); + editedTask.setName(editedName); + } + } + + /** + * Update the priority if there is a change + * + * @throws IllegalValueException + */ + private void updatePriorityIfChanged() throws IllegalValueException { + if (isFieldChanged(PRIORITY_PREFIX)) { + Priority editedPriority = + new Priority(argsTokenizer.getValue(PRIORITY_PREFIX).get()); + editedTask.setPriority(editedPriority); + } + } + + /** + * Update the date time if there is a change + * + * @throws IllegalDateException + */ + private void updateDateTimeIfChanged() throws IllegalDateException { + if (isFieldChanged(DATETIME_PREFIX)) { + String[] dateTimeArray = DateTimeUtil.parseStringToDateTime( + argsTokenizer.getValue(DATETIME_PREFIX).get()); + DateTime editedDateTime = + new DateTime(dateTimeArray[DATETIME_INDEX_OF_STARTDATE], + dateTimeArray[DATETIME_INDEX_OF_ENDDATE]); + editedTask.setDateTime(editedDateTime); + } + } + + /** + * Add tag if there is a change + * + * @throws IllegalValueException + * @throws DuplicateTagException + * @throws TagNotFoundException + */ + private void addTagsIfFound() throws IllegalValueException, DuplicateTagException, + TagNotFoundException { + Set tagsToAdd = argsTokenizer.getMultipleValues(ADD_TAG_PREFIX) + .orElse(new HashSet<>()); + updateTagList(ADD_TAG_PREFIX, tagsToAdd); + } + + /** + * Remove tag if there is a change + * + * @throws IllegalValueException + * @throws DuplicateTagException + * @throws TagNotFoundException + */ + private void deleteTagsIfFound() throws IllegalValueException, DuplicateTagException, + TagNotFoundException { + Set tagsToAdd = argsTokenizer.getMultipleValues(REMOVE_TAG_PREFIX) + .orElse(new HashSet<>()); + updateTagList(REMOVE_TAG_PREFIX, tagsToAdd); + } + + /** + * Update tag list + * + * @throws IllegalValueException + * @throws TagNotFoundException + */ + private void updateTagList(Prefix mutatorPrefix, Set mutateTagNames) + throws IllegalValueException, TagNotFoundException { + UniqueTagList replacement = editedTask.getTags(); + + for (String mutateTagName : mutateTagNames) { + Tag mutateTag = new Tag(mutateTagName); + + if (ADD_TAG_PREFIX.equals(mutatorPrefix)) { + replacement.add(mutateTag); + } + + if (REMOVE_TAG_PREFIX.equals(mutatorPrefix)) { + replacement.remove(mutateTag); + } + } + + editedTask.setTags(replacement); + } + + /** + * Checks if the field need to be updated + * + * @return true if the field need update + */ + private boolean isFieldChanged(Prefix prefix) { + return !argsTokenizer.getValue(prefix).orElse(StringUtil.EMPTY_STRING) + .equals(StringUtil.EMPTY_STRING); + } + + @Override + public CommandResult undo() { + assert model != null; + try { + model.replaceTask(editedTask, new Task(toBeReplacedTask)); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO, toBeReplacedTask))); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + + @Override + public CommandResult redo() { + assert model != null; + try { + model.replaceTask(toBeReplacedTask, editedTask); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO, toBeReplacedTask))); + } catch (DuplicateTaskException e) { + return new CommandResult(String + .format(RedoCommand.MESSAGE_UNSUCCESS, e.getMessage())); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/tars/logic/commands/ExitCommand.java similarity index 64% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/tars/logic/commands/ExitCommand.java index d98233ce2a0b..109199869f02 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/tars/logic/commands/ExitCommand.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package tars.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; +import tars.commons.core.EventsCenter; +import tars.commons.events.ui.ExitAppRequestEvent; /** * Terminates the program. @@ -10,9 +10,7 @@ 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() {} + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting TARS as requested ..."; @Override public CommandResult execute() { diff --git a/src/main/java/tars/logic/commands/FindCommand.java b/src/main/java/tars/logic/commands/FindCommand.java new file mode 100644 index 000000000000..330c44722fa0 --- /dev/null +++ b/src/main/java/tars/logic/commands/FindCommand.java @@ -0,0 +1,69 @@ +package tars.logic.commands; + +import java.util.ArrayList; + +import tars.commons.core.EventsCenter; +import tars.commons.events.ui.ScrollToTopEvent; +import tars.commons.util.StringUtil; +import tars.model.task.TaskQuery; + +// @@author A0124333U +/** + * Finds and lists all tasks 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 tasks containing a list of keywords (i.e. AND search)." + + "keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters (Quick Search Mode): [KEYWORD ...]\n" + + "Parameters (Filter Search Mode): [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]\n" + + "Examples (Quick Serach Mode): " + COMMAND_WORD + + " CS2103 projects" + "Examples (Filter Search Mode): " + + COMMAND_WORD + " /n CS2103 projects /dt 10/09/2016 1000 to " + + "20/09/2016 0100 /t school projects /do"; + + private static String MESSAGE_QUICK_SEARCH_KEYWORDS = + "\nQuick Search Keywords: %s"; + + private TaskQuery taskQuery = null; + private ArrayList quickSearchKeywords = null; + private String searchKeywords = ""; + + public FindCommand(TaskQuery taskQuery) { + this.taskQuery = taskQuery; + } + + public FindCommand(ArrayList quickSearchKeywords) { + this.quickSearchKeywords = quickSearchKeywords; + } + + @Override + public CommandResult execute() { + if (isFilterSearchModeUsed()) { + model.updateFilteredTaskListUsingFlags(taskQuery); + searchKeywords = StringUtil.STRING_NEWLINE + taskQuery.toString(); + } + + if (isQuickSearchModeUsed()) { + model.updateFilteredTaskListUsingQuickSearch(quickSearchKeywords); + searchKeywords = String.format(MESSAGE_QUICK_SEARCH_KEYWORDS, + quickSearchKeywords.toString()); + } + EventsCenter.getInstance().post(new ScrollToTopEvent()); + return new CommandResult(getMessageForTaskListShownSummary( + model.getFilteredTaskList().size()) + searchKeywords); + } + + private Boolean isFilterSearchModeUsed() { + return taskQuery != null; + } + + private Boolean isQuickSearchModeUsed() { + return quickSearchKeywords != null; + } + +} diff --git a/src/main/java/tars/logic/commands/FreeCommand.java b/src/main/java/tars/logic/commands/FreeCommand.java new file mode 100644 index 000000000000..3c10e6d2ba4b --- /dev/null +++ b/src/main/java/tars/logic/commands/FreeCommand.java @@ -0,0 +1,68 @@ +package tars.logic.commands; + +import java.util.ArrayList; +import tars.commons.util.DateTimeUtil; +import tars.model.task.DateTime; + +/** + * @@author A0124333U + * + * Suggests free time slots on a specified date + */ +public class FreeCommand extends Command { + + public static final String COMMAND_WORD = "free"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Suggests free timeslots in a specified day.\n" + + "Parameters: \n" + "Example: free 29/10/2016"; + + public static final String MESSAGE_DATE_RANGE_DETECTED = + "Range of datetime detected. Please only input a single datetime"; + public static final String MESSAGE_SUCCESS = "Free timeslots on %1$s"; + public static final String MESSAGE_FREE_DAY = + "You have no event tasks or reserved event tasks on %1$s"; + public static final String MESSAGE_NO_FREE_TIMESLOTS = + "You have no free time slots on %1$s"; + + private DateTime dateToCheck; + + public FreeCommand(DateTime dateToCheck) { + this.dateToCheck = dateToCheck; + + // Ensure that dateToCheck covers the whole day + this.dateToCheck.setStartDateTime(dateToCheck.getEndDate() + .withHour(DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY) + .withMinute(DateTimeUtil.DATETIME_FIRST_MINUTE_OF_DAY) + .withSecond(DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY)); + + this.dateToCheck.setEndDateTime(dateToCheck.getEndDate() + .withHour(DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY) + .withMinute(DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY) + .withSecond(DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY)); + } + + @Override + public CommandResult execute() { + ArrayList listOfFilledTimeSlots = + model.getListOfFilledTimeSlotsInDate(dateToCheck); + ArrayList listOfFreeTimeSlots = + DateTimeUtil.getListOfFreeTimeSlotsInDate(dateToCheck, + listOfFilledTimeSlots); + + model.updateFilteredTaskListUsingDate(dateToCheck); + + if (listOfFilledTimeSlots.isEmpty()) { + return new CommandResult(String.format(MESSAGE_FREE_DAY, + DateTimeUtil.getDayAndDateString(dateToCheck))); + } else if (listOfFreeTimeSlots.isEmpty()) { + return new CommandResult(String.format(MESSAGE_NO_FREE_TIMESLOTS, + DateTimeUtil.getDayAndDateString(dateToCheck))); + } else { + return new CommandResult(String.format(MESSAGE_SUCCESS, + DateTimeUtil.getStringOfFreeDateTimeInDate(dateToCheck, + listOfFreeTimeSlots))); + } + } + +} diff --git a/src/main/java/tars/logic/commands/HelpCommand.java b/src/main/java/tars/logic/commands/HelpCommand.java new file mode 100644 index 000000000000..6bdb3f8397c9 --- /dev/null +++ b/src/main/java/tars/logic/commands/HelpCommand.java @@ -0,0 +1,33 @@ +package tars.logic.commands; + +import tars.commons.core.EventsCenter; +import tars.commons.events.ui.ShowHelpRequestEvent; + +// @@author A0140022H +/** + * 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 in help panel.\n" + + "Parameters: [COMMAND WORD]\n" + "Example: " + + COMMAND_WORD + " add"; + + public static final String SHOWING_HELP_MESSAGE = + "Switched to Help tab pane."; + + private String args; + + public HelpCommand(String args) { + this.args = args; + } + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ShowHelpRequestEvent(args)); + return new CommandResult(SHOWING_HELP_MESSAGE); + } +} diff --git a/src/main/java/seedu/address/logic/commands/IncorrectCommand.java b/src/main/java/tars/logic/commands/IncorrectCommand.java similarity index 91% rename from src/main/java/seedu/address/logic/commands/IncorrectCommand.java rename to src/main/java/tars/logic/commands/IncorrectCommand.java index 491d9cb9da35..4215f6ee866c 100644 --- a/src/main/java/seedu/address/logic/commands/IncorrectCommand.java +++ b/src/main/java/tars/logic/commands/IncorrectCommand.java @@ -1,5 +1,4 @@ -package seedu.address.logic.commands; - +package tars.logic.commands; /** * Represents an incorrect command. Upon execution, produces some feedback to the user. @@ -18,5 +17,4 @@ public CommandResult execute() { return new CommandResult(feedbackToUser); } -} - +} \ No newline at end of file diff --git a/src/main/java/tars/logic/commands/ListCommand.java b/src/main/java/tars/logic/commands/ListCommand.java new file mode 100644 index 000000000000..0e26f57b0b95 --- /dev/null +++ b/src/main/java/tars/logic/commands/ListCommand.java @@ -0,0 +1,81 @@ +package tars.logic.commands; + +import java.util.Set; + +import tars.commons.core.EventsCenter; +import tars.commons.events.ui.ScrollToTopEvent; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +// @@author A0140022H +/** + * Lists all tasks in tars to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "ls"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + public static final String MESSAGE_SUCCESS_DATETIME = + "Listed all tasks by earliest datetime first"; + public static final String MESSAGE_SUCCESS_DATETIME_DESCENDING = + "Listed all tasks by latest datetime first"; + public static final String MESSAGE_SUCCESS_PRIORITY = + "Listed all tasks by priority from low to high"; + public static final String MESSAGE_SUCCESS_PRIORITY_DESCENDING = + "Listed all tasks by priority from high to low"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Lists all tasks.\n" + "Parameters: [KEYWORD] " + + "Example: " + COMMAND_WORD + " /dt"; + + private static final String LIST_ARG_DATETIME = "/dt"; + private static final String LIST_ARG_PRIORITY = "/p"; + private static final String LIST_KEYWORD_DESCENDING = "dsc"; + + private Set keywords; + + public ListCommand() {} + + public ListCommand(Set arguments) { + this.keywords = arguments; + } + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ScrollToTopEvent()); + if (keywords != null && !keywords.isEmpty()) { + return listByKeyword(); + } else { + model.updateFilteredListToShowAll(); + return new CommandResult(MESSAGE_SUCCESS); + } + } + + private CommandResult listByKeyword() { + if (keywords.contains(LIST_ARG_DATETIME) + || keywords.contains(LIST_ARG_PRIORITY) + || keywords.contains(LIST_KEYWORD_DESCENDING)) { + + model.sortFilteredTaskList(keywords); + + if (keywords.contains(LIST_KEYWORD_DESCENDING)) { + if (keywords.contains(LIST_ARG_DATETIME)) + return new CommandResult( + MESSAGE_SUCCESS_DATETIME_DESCENDING); + else + return new CommandResult( + MESSAGE_SUCCESS_PRIORITY_DESCENDING); + } else { + if (keywords.contains(LIST_ARG_DATETIME)) + return new CommandResult(MESSAGE_SUCCESS_DATETIME); + else + return new CommandResult(MESSAGE_SUCCESS_PRIORITY); + } + } else { + model.updateFilteredListToShowAll(); + return new CommandResult(String + .format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/tars/logic/commands/MarkTaskUtil.java b/src/main/java/tars/logic/commands/MarkTaskUtil.java new file mode 100644 index 000000000000..2408e6766f68 --- /dev/null +++ b/src/main/java/tars/logic/commands/MarkTaskUtil.java @@ -0,0 +1,182 @@ +package tars.logic.commands; + +import java.util.ArrayList; + +import tars.commons.core.Messages; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.exceptions.InvalidTaskDisplayedException; +import tars.commons.util.StringUtil; +import tars.model.Model; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Status; + +// @@author A0121533W +/** + * Tracks changes made (if any) during do and ud command + */ +public class MarkTaskUtil { + + public static final String SUCCESS_DONE = + "Task: %1$s marked done successfully.\n"; + public static final String SUCCESS_UNDONE = + "Task: %1$s marked undone successfully.\n"; + public static final String ALREADY_DONE = + "Task: %1$s already marked done.\n"; + public static final String ALREADY_UNDONE = + "Task: %1$s already marked undone.\n"; + + private ArrayList markDoneTasks; + private ArrayList markUndoneTasks; + private ArrayList alreadyDoneTasks; + private ArrayList alreadyUndoneTasks; + + /** + * Constructor + */ + public MarkTaskUtil() { + this.markDoneTasks = new ArrayList(); + this.markUndoneTasks = new ArrayList(); + this.alreadyDoneTasks = new ArrayList(); + this.alreadyUndoneTasks = new ArrayList(); + } + + /** + * Adds target index of task to relevant "To Mark List" based on status + */ + public void addToMark(int targetIndex, Status status) { + if (status.status) { + addToMarkDoneTask(targetIndex); + } else { + addToMarkUndoneTask(targetIndex); + } + + } + + /** + * Adds target index of task to relevant "Already Marked List" based on status + */ + public void addAlreadyMarked(int targetIndex, Status status) { + if (status.status) { + addToAlreadyDoneTasks(targetIndex); + } else { + addToAlreadyUndoneTasks(targetIndex); + } + } + + /** + * Return string for each tasks index in the specific ArrayLists + */ + public String getResult() { + String markDoneTasksString = getIndexesString(markDoneTasks); + String markUndoneTasksString = getIndexesString(markUndoneTasks); + String alreadyDoneTasksString = getIndexesString(alreadyDoneTasks); + String alreadyUndoneTasksString = getIndexesString(alreadyUndoneTasks); + + String result = + formatResults(markDoneTasksString, markUndoneTasksString, + alreadyDoneTasksString, alreadyUndoneTasksString); + + return result; + } + + /** + * Main results formatter that will perform the formatting for all 4 cases i.e. Mark Done, Mark + * Undone, Already Done and Already Undone + */ + private String formatResults(String markDoneTasksString, + String markUndoneTasksString, String alreadyDoneTasksString, + String alreadyUndoneTasksString) { + + String markDoneResult = + getResultFromString(markDoneTasksString, SUCCESS_DONE); + String markUndoneResult = + getResultFromString(markUndoneTasksString, SUCCESS_UNDONE); + String alreadyDoneResult = + getResultFromString(alreadyDoneTasksString, ALREADY_DONE); + String aldreadyUndoneResult = + getResultFromString(alreadyUndoneTasksString, ALREADY_UNDONE); + + return markDoneResult + markUndoneResult + alreadyDoneResult + + aldreadyUndoneResult; + } + + /** + * Formats results of changes made + */ + private String getResultFromString(String tasksString, String format) { + String result = StringUtil.EMPTY_STRING; + if (!tasksString.isEmpty()) { + result = String.format(format, tasksString); + } + return result; + } + + /** + * Gets String of indexes separated by comma + */ + private String getIndexesString(ArrayList list) { + String toReturn = StringUtil.EMPTY_STRING; + if (!list.isEmpty()) { + for (int i = StringUtil.START_INDEX; i < list.size() - 1; i++) { + toReturn += + Integer.toString(list.get(i)) + StringUtil.STRING_COMMA; + } + // Add last index + toReturn += Integer.toString(list.get(list.size() - 1)); + } + return toReturn; + } + + private void addToMarkDoneTask(int index) { + this.markDoneTasks.add(index); + } + + private void addToMarkUndoneTask(int index) { + this.markUndoneTasks.add(index); + } + + private void addToAlreadyDoneTasks(int index) { + this.alreadyDoneTasks.add(index); + } + + private void addToAlreadyUndoneTasks(int index) { + this.alreadyUndoneTasks.add(index); + } + + /** + * Returns feedback message of mark command to user + */ + public String getResultFromTracker() { + String commandResult = getResult(); + return commandResult; + } + + /** + * Gets Tasks to mark from indexes + */ + public ArrayList getTasksFromIndexes(Model model, + String[] indexes, Status status) + throws InvalidTaskDisplayedException { + UnmodifiableObservableList lastShownList = + model.getFilteredTaskList(); + ArrayList tasksList = new ArrayList(); + + for (int i = 0; i < indexes.length; i++) { + int targetIndex = Integer.valueOf(indexes[i]); + if (lastShownList.size() < targetIndex) { + throw new InvalidTaskDisplayedException( + Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + ReadOnlyTask task = lastShownList.get(targetIndex - 1); + if (!task.getStatus().equals(status)) { + tasksList.add(task); + addToMark(targetIndex, status); + } else { + addAlreadyMarked(targetIndex, status); + } + } + return tasksList; + } + +} diff --git a/src/main/java/tars/logic/commands/RedoCommand.java b/src/main/java/tars/logic/commands/RedoCommand.java new file mode 100644 index 000000000000..3d228dd6cdf5 --- /dev/null +++ b/src/main/java/tars/logic/commands/RedoCommand.java @@ -0,0 +1,31 @@ +package tars.logic.commands; + +// @@author A0139924W +/** + * Redo an undoable command. + */ +public class RedoCommand extends Command { + + public static final String COMMAND_WORD = "redo"; + public static final String MESSAGE_SUCCESS = "Redo successfully.\n%1$s"; + public static final String MESSAGE_UNSUCCESS = "Redo unsuccessfully.\n%1$s"; + + public static final String MESSAGE_EMPTY_REDO_CMD_HIST = + "No more actions that can be redo."; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Redo a previous command.\n" + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute() { + assert model != null; + + if (model.getRedoableCmdHist().isEmpty()) { + return new CommandResult(MESSAGE_EMPTY_REDO_CMD_HIST); + } + + UndoableCommand command = + (UndoableCommand) model.getRedoableCmdHist().pop(); + model.getUndoableCmdHist().push(command); + return command.redo(); + } +} diff --git a/src/main/java/tars/logic/commands/RsvCommand.java b/src/main/java/tars/logic/commands/RsvCommand.java new file mode 100644 index 000000000000..ee46bfd62058 --- /dev/null +++ b/src/main/java/tars/logic/commands/RsvCommand.java @@ -0,0 +1,261 @@ +package tars.logic.commands; + +import java.time.DateTimeException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import tars.commons.core.Messages; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidTaskDisplayedException; +import tars.commons.util.StringUtil; +import tars.model.task.DateTime; +import tars.model.task.Name; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList.RsvTaskNotFoundException; +import tars.ui.formatter.Formatter; + +// @@author A0124333U +/** + * Adds a reserved task which has a list of reserved date times that can confirmed later on. + */ +public class RsvCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "rsv"; + public static final String COMMAND_WORD_DEL = "rsv /del"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reserves one or more timeslot for a task.\n" + + "Parameters: [/dt DATETIME ...]\n" + + "Example: " + COMMAND_WORD + + " Meet John Doe /dt 26/09/2016 0900 to 1030, 28/09/2016 1000 to 1130"; + + public static final String MESSAGE_USAGE_DEL = COMMAND_WORD_DEL + + ": Deletes a reserved task in the last reserved task listing \n" + + "Parameters: INDEX (must be a positive integer)\n " + "Example: " + + COMMAND_WORD_DEL + " 1\n" + "OR " + COMMAND_WORD_DEL + " 1..3"; + + public static final String MESSAGE_DATETIME_NOT_FOUND = + "At least one DateTime is required!\n" + MESSAGE_USAGE; + + public static final String MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX = + "The Reserved Task Index is invalid!"; + + public static final String MESSAGE_SUCCESS = "New task reserved: %1$s"; + public static final String MESSAGE_SUCCESS_DEL = + "Deleted reserved tasks:\n%1$s"; + public static final String MESSAGE_UNDO_DELETE = "Deleted %1$s"; + public static final String MESSAGE_UNDO_ADD = "Added:\n%1$s"; + public static final String MESSAGE_REDO_DELETE = "Deleted:%1$s"; + public static final String MESSAGE_REDO_ADD = "Added %1$s"; + + private static final String MESSAGE_CONFLICT_FOR = "\nConflicts for "; + private static final int INDEX_OF_ENDDATE = 1; + private static final int INDEX_OF_STARTDATE = 0; + + private RsvTask toReserve = null; + private String rangeIndexString = ""; + private String conflictingTaskList = ""; + private ArrayList rsvTasksToDelete; + + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + * @throws DateTimeException if given dateTime string is invalid. + */ + public RsvCommand(String name, Set dateTimeStringSet) + throws IllegalValueException { + + Set dateTimeSet = new HashSet<>(); + for (String[] dateTimeStringArray : dateTimeStringSet) { + dateTimeSet + .add(new DateTime(dateTimeStringArray[INDEX_OF_STARTDATE], + dateTimeStringArray[INDEX_OF_ENDDATE])); + } + + this.toReserve = new RsvTask(new Name(name), + new ArrayList(dateTimeSet)); + } + + public RsvCommand(String rangeIndexString) { + this.rangeIndexString = rangeIndexString; + } + + @Override + public CommandResult execute() { + assert model != null; + + if (toReserve != null) { + return addRsvTask(); + } else { + return delRsvTask(); + } + + } + + private CommandResult addRsvTask() { + try { + for (DateTime dt : toReserve.getDateTimeList()) { + if (!model.getTaskConflictingDateTimeWarningMessage(dt) + .isEmpty()) { + conflictingTaskList += MESSAGE_CONFLICT_FOR + dt.toString() + + StringUtil.STRING_COLON; + conflictingTaskList += + model.getTaskConflictingDateTimeWarningMessage(dt); + } + } + model.addRsvTask(toReserve); + model.getUndoableCmdHist().push(this); + return new CommandResult(getSuccessMessageSummary()); + } catch (DuplicateTaskException e) { + return new CommandResult(Messages.MESSAGE_DUPLICATE_TASK); + } + } + + private CommandResult delRsvTask() { + rsvTasksToDelete = new ArrayList(); + + try { + rsvTasksToDelete = getRsvTasksFromIndexes( + this.rangeIndexString.split(StringUtil.STRING_WHITESPACE)); + } catch (InvalidTaskDisplayedException itde) { + return new CommandResult(itde.getMessage()); + } + + for (RsvTask t : rsvTasksToDelete) { + try { + model.deleteRsvTask(t); + } catch (RsvTaskNotFoundException rtnfe) { + return new CommandResult( + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND); + } + } + + model.getUndoableCmdHist().push(this); + String deletedRsvTasksList = + new Formatter().formatRsvTaskList(rsvTasksToDelete); + return new CommandResult( + String.format(MESSAGE_SUCCESS_DEL, deletedRsvTasksList)); + } + + /** + * Gets Tasks to delete + * + * @param indexes + * @throws InvalidTaskDisplayedException + */ + private ArrayList getRsvTasksFromIndexes(String[] indexes) + throws InvalidTaskDisplayedException { + UnmodifiableObservableList lastShownList = + model.getFilteredRsvTaskList(); + ArrayList rsvTasksList = new ArrayList(); + + for (int i = StringUtil.START_INDEX; i < indexes.length; i++) { + int targetIndex = Integer.parseInt(indexes[i]); + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new InvalidTaskDisplayedException( + Messages.MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX); + } + RsvTask rsvTask = + lastShownList.get(targetIndex - StringUtil.LAST_INDEX); + rsvTasksList.add(rsvTask); + } + return rsvTasksList; + } + + private String getSuccessMessageSummary() { + String summary = String.format(MESSAGE_SUCCESS, toReserve.toString()); + + if (!conflictingTaskList.isEmpty()) { + summary += StringUtil.STRING_NEWLINE + + Messages.MESSAGE_CONFLICTING_TASKS_WARNING + + conflictingTaskList; + } + + return summary; + } + + // @@author + + // @@author A0139924W + @Override + public CommandResult undo() { + if (toReserve != null) { + try { + return undoRsvAdd(); + } catch (RsvTaskNotFoundException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND)); + } + } else { + try { + return undoRsvDelete(); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(UndoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } + } + + // @@author A0139924W + @Override + public CommandResult redo() { + if (toReserve != null) { + try { + return redoRsvAdd(); + } catch (DuplicateTaskException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_DUPLICATE_TASK)); + } + } else { + try { + return redoRsvDelete(); + } catch (RsvTaskNotFoundException e) { + return new CommandResult( + String.format(RedoCommand.MESSAGE_UNSUCCESS, + Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND)); + } + } + } + + private CommandResult undoRsvAdd() throws RsvTaskNotFoundException { + model.deleteRsvTask(toReserve); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO_DELETE, toReserve))); + } + + private CommandResult undoRsvDelete() throws DuplicateTaskException { + for (RsvTask rsvTask : rsvTasksToDelete) { + model.addRsvTask(rsvTask); + } + + String addedRsvTasksList = + new Formatter().formatRsvTaskList(rsvTasksToDelete); + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_UNDO_ADD, addedRsvTasksList))); + } + + private CommandResult redoRsvAdd() throws DuplicateTaskException { + model.addRsvTask(toReserve); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO_ADD, toReserve))); + } + + private CommandResult redoRsvDelete() throws RsvTaskNotFoundException { + for (RsvTask rsvTask : rsvTasksToDelete) { + model.deleteRsvTask(rsvTask); + } + + String deletedRsvTasksList = + new Formatter().formatRsvTaskList(rsvTasksToDelete); + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(MESSAGE_REDO_DELETE, deletedRsvTasksList))); + } +} diff --git a/src/main/java/tars/logic/commands/TagCommand.java b/src/main/java/tars/logic/commands/TagCommand.java new file mode 100644 index 000000000000..5bdedb9cad36 --- /dev/null +++ b/src/main/java/tars/logic/commands/TagCommand.java @@ -0,0 +1,165 @@ +package tars.logic.commands; + +import java.util.ArrayList; + +import javafx.collections.ObservableList; +import tars.commons.core.Messages; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.StringUtil; +import tars.logic.parser.Prefix; +import tars.model.tag.ReadOnlyTag; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList.DuplicateTagException; +import tars.model.tag.UniqueTagList.TagNotFoundException; +import tars.model.task.ReadOnlyTask; +import tars.ui.formatter.Formatter; + +// @@author A0139924W +/** + * Rename and delete tag from a list of tags in TARS + */ +public class TagCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "tag"; + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": [/ls] [/e ] [/del ]"; + public static final String MESSAGE_RENAME_TAG_SUCCESS = + "%1$s renamed to [%2$s]"; + public static final String MESSAGE_DELETE_TAG_SUCCESS = "Deleted Tag: %1$s"; + + private static final int TAG_SECOND_INDEX = 1; + private static final int TAG_FIRST_INDEX = 0; + private static final Prefix listPrefix = new Prefix("/ls"); + private static final Prefix editPrefix = new Prefix("/e"); + private static final Prefix deletePrefix = new Prefix("/del"); + + private final Prefix prefix; + private final String[] args; + + private ReadOnlyTag toBeRenamed; + private ReadOnlyTag toBeDeleted; + private Tag newTag; + private ArrayList editedTaskList; + + public TagCommand(Prefix prefix, String... args) { + this.prefix = prefix; + this.args = args; + } + + @Override + public CommandResult execute() { + CommandResult result = null; + + try { + if (listPrefix.equals(prefix)) { + result = executeListTag(); + } else if (editPrefix.equals(prefix)) { + result = executeEditTag(); + } else if (deletePrefix.equals(prefix)) { + result = executeDeleteTag(); + } + } catch (DuplicateTagException e) { + return new CommandResult(e.getMessage()); + } catch (TagNotFoundException e) { + return new CommandResult(e.getMessage()); + } catch (IllegalValueException e) { + return new CommandResult(Tag.MESSAGE_TAG_CONSTRAINTS); + } catch (NumberFormatException e) { + return new CommandResult( + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + TagCommand.MESSAGE_USAGE)); + } + + return result; + } + + private CommandResult executeListTag() { + ObservableList allTags = + model.getUniqueTagList(); + return new CommandResult(new Formatter().formatTags(allTags)); + } + + private CommandResult executeEditTag() throws DuplicateTagException, + IllegalValueException, TagNotFoundException { + int targetedIndex = Integer.parseInt(args[TAG_FIRST_INDEX]); + String newTagName = args[TAG_SECOND_INDEX]; + + if (isInValidIndex(targetedIndex)) { + return new CommandResult( + Messages.MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + } + + toBeRenamed = model.getUniqueTagList() + .get(targetedIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + newTag = new Tag(newTagName); + model.renameTasksWithNewTag(toBeRenamed, newTag); + + model.getUndoableCmdHist().push(this); + return new CommandResult( + String.format(String.format(MESSAGE_RENAME_TAG_SUCCESS, + toBeRenamed.getAsText(), newTagName))); + } + + private CommandResult executeDeleteTag() throws DuplicateTagException, + IllegalValueException, TagNotFoundException { + int targetedIndex = Integer.parseInt(args[TAG_FIRST_INDEX]); + + if (isInValidIndex(targetedIndex)) { + return new CommandResult( + Messages.MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + } + + toBeDeleted = model.getUniqueTagList() + .get(targetedIndex - StringUtil.DISPLAYED_INDEX_OFFSET); + editedTaskList = model.removeTagFromAllTasks(toBeDeleted); + + model.getUndoableCmdHist().push(this); + return new CommandResult( + String.format(MESSAGE_DELETE_TAG_SUCCESS, toBeDeleted)); + } + + /** + * Checks if the targetedIndex is a valid index + * + * @param targetedIndex + * @return true if targetedIndex is an invalid index + */ + private boolean isInValidIndex(int targetedIndex) { + return targetedIndex < 1 + || model.getUniqueTagList().size() < targetedIndex; + } + + @Override + public CommandResult undo() { + try { + if (editPrefix.equals(prefix)) { + model.renameTasksWithNewTag(newTag, new Tag(toBeRenamed)); + + } else if (deletePrefix.equals(prefix)) { + model.addTagToAllTasks(toBeDeleted, editedTaskList); + } + } catch (Exception e) { + return new CommandResult(UndoCommand.MESSAGE_UNSUCCESS); + } + + return new CommandResult(String.format(UndoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + + @Override + public CommandResult redo() { + try { + if (editPrefix.equals(prefix)) { + model.renameTasksWithNewTag(toBeRenamed, newTag); + } else if (deletePrefix.equals(prefix)) { + editedTaskList = model.removeTagFromAllTasks(toBeDeleted); + } + } catch (Exception e) { + return new CommandResult(RedoCommand.MESSAGE_UNSUCCESS); + } + + return new CommandResult(String.format(RedoCommand.MESSAGE_SUCCESS, + StringUtil.EMPTY_STRING)); + } + +} diff --git a/src/main/java/tars/logic/commands/UdCommand.java b/src/main/java/tars/logic/commands/UdCommand.java new file mode 100644 index 000000000000..8a0c0f2fa6c2 --- /dev/null +++ b/src/main/java/tars/logic/commands/UdCommand.java @@ -0,0 +1,63 @@ +package tars.logic.commands; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.exceptions.InvalidTaskDisplayedException; +import tars.commons.util.StringUtil; +import tars.model.task.*; + +import java.util.ArrayList; + +// @@author A0121533W +/** + * Marks a task identified using it's last displayed index from tars as undone. + */ +public class UdCommand extends Command { + + public static final String COMMAND_WORD = "ud"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the task based on its index in the task list as undone.\n" + + "Parameters: [INDEX ...]\n" + "Example: " + + COMMAND_WORD + " 3 5 7" + "OR " + COMMAND_WORD + " 1..3\n"; + + private String toUndo; + + private MarkTaskUtil tracker; + + /** + * Convenience constructor using raw values. + * + * @throws InvalidRangeException + */ + public UdCommand(String toUndo) { + this.toUndo = toUndo; + this.tracker = new MarkTaskUtil(); + } + + @Override + public CommandResult execute() { + assert model != null; + + try { + handleMarkUndone(); + } catch (InvalidTaskDisplayedException e) { + return new CommandResult(e.getMessage()); + } catch (DuplicateTaskException dte) { + return new CommandResult(dte.getMessage()); + } + return new CommandResult(tracker.getResultFromTracker()); + } + + /** + * Marks status of task in model as undone + */ + private void handleMarkUndone() + throws InvalidTaskDisplayedException, DuplicateTaskException { + Status undone = new Status(false); + ArrayList markUndoneTasks = tracker.getTasksFromIndexes( + model, this.toUndo.split(StringUtil.STRING_WHITESPACE), undone); + model.mark(markUndoneTasks, undone); + } + +} diff --git a/src/main/java/tars/logic/commands/UndoCommand.java b/src/main/java/tars/logic/commands/UndoCommand.java new file mode 100644 index 000000000000..a646500a6e28 --- /dev/null +++ b/src/main/java/tars/logic/commands/UndoCommand.java @@ -0,0 +1,33 @@ +package tars.logic.commands; + +// @@author A0139924W +/** + * Undo an undoable command. + */ +public class UndoCommand extends Command { + + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_SUCCESS = "Undo successfully.\n%1$s"; + public static final String MESSAGE_UNSUCCESS = "Undo unsuccessfully.\n%1$s"; + public static final String MESSAGE_EMPTY_UNDO_CMD_HIST = + "No more actions that can be undo."; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Undo a previous command\n" + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute() { + assert model != null; + + if (model.getUndoableCmdHist().isEmpty()) { + return new CommandResult(MESSAGE_EMPTY_UNDO_CMD_HIST); + } + + UndoableCommand command = + (UndoableCommand) model.getUndoableCmdHist().pop(); + model.getRedoableCmdHist().push(command); + + return command.undo(); + } + +} diff --git a/src/main/java/tars/logic/commands/UndoableCommand.java b/src/main/java/tars/logic/commands/UndoableCommand.java new file mode 100644 index 000000000000..a551401773e7 --- /dev/null +++ b/src/main/java/tars/logic/commands/UndoableCommand.java @@ -0,0 +1,12 @@ +package tars.logic.commands; + +// @@author A0139924W +/** + * Represents a undoable command with hidden internal logic and the ability to be executed. + */ +public abstract class UndoableCommand extends Command { + + public abstract CommandResult undo(); + + public abstract CommandResult redo(); +} diff --git a/src/main/java/tars/logic/parser/AddCommandParser.java b/src/main/java/tars/logic/parser/AddCommandParser.java new file mode 100644 index 000000000000..fad94b940ce5 --- /dev/null +++ b/src/main/java/tars/logic/parser/AddCommandParser.java @@ -0,0 +1,55 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.time.DateTimeException; +import java.util.HashSet; +import java.util.NoSuchElementException; + +import tars.commons.core.Messages; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.ExtractorUtil; +import tars.commons.util.StringUtil; +import tars.logic.commands.AddCommand; +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; + +// @@author A0139924W +/** + * Add command parser + */ +public class AddCommandParser extends CommandParser { + + /** + * Parses arguments in the context of the add task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(tagPrefix, priorityPrefix, dateTimePrefix, recurringPrefix); + argsTokenizer.tokenize(args); + + try { + return new AddCommand(argsTokenizer.getPreamble().get(), + DateTimeUtil.parseStringToDateTime( + argsTokenizer.getValue(dateTimePrefix).orElse(StringUtil.EMPTY_STRING)), + argsTokenizer.getValue(priorityPrefix).orElse(StringUtil.EMPTY_STRING), + argsTokenizer.getMultipleValues(tagPrefix).orElse(new HashSet()), + ExtractorUtil.getRecurringFromArgs( + argsTokenizer.getValue(recurringPrefix).orElse(StringUtil.EMPTY_STRING), + recurringPrefix)); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } catch (NoSuchElementException nse) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/tars/logic/parser/ArgumentTokenizer.java b/src/main/java/tars/logic/parser/ArgumentTokenizer.java new file mode 100644 index 000000000000..d8a4ca1825d1 --- /dev/null +++ b/src/main/java/tars/logic/parser/ArgumentTokenizer.java @@ -0,0 +1,184 @@ +package tars.logic.parser; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; + +import tars.commons.util.StringUtil; + +// @@author A0139924W +/** + * Tokenizes arguments string of the form: {@code preamble value value ...}
+ * e.g. {@code some preamble text /dt today at 3pm /t tag1 /t tag2 /ls} where prefixes are + * {@code /dt /t}.
+ * 1. An argument's value can be an empty string e.g. the value of {@code /ls} in the above + * example.
+ * 2. Leading and trailing whitespaces of an argument value will be discarded.
+ * 3. A prefix need to have leading and trailing spaces e.g. the {@code /d today at 3pm /t tag1} in + * the above example
+ * 4. An argument may be repeated and all its values will be accumulated e.g. the value of + * {@code /t} in the above example.
+ */ +public class ArgumentTokenizer { + private static final int EMPTY_SIZE = 0; + private static final int INVALID_POS = -1; + private static final int START_INDEX_POS = -1; + + private final Prefix[] prefixes; + private HashMap prefixValueMap; + private TreeMap prefixPosMap; + private String args; + + public ArgumentTokenizer(Prefix... prefixes) { + this.prefixes = prefixes; + init(); + } + + public void tokenize(String args) { + resetExtractorState(); + this.args = args; + this.prefixPosMap = getPrefixPositon(); + extractArguments(); + } + + private void init() { + this.args = StringUtil.EMPTY_STRING; + this.prefixValueMap = new HashMap(); + this.prefixPosMap = new TreeMap(); + } + + private void resetExtractorState() { + this.prefixValueMap.clear(); + } + + /** + * Gets all prefix positions from arguments string + */ + private TreeMap getPrefixPositon() { + prefixPosMap = new TreeMap(); + + for (int i = StringUtil.START_INDEX; i < prefixes.length; i++) { + int curIndexPos = START_INDEX_POS; + + do { + curIndexPos = args.indexOf( + StringUtil.STRING_WHITESPACE + prefixes[i].value, + curIndexPos + StringUtil.LAST_INDEX); + + if (curIndexPos >= StringUtil.START_INDEX) { + prefixPosMap.put(curIndexPos, prefixes[i]); + } + } while (curIndexPos >= StringUtil.START_INDEX); + } + + return prefixPosMap; + } + + /** + * Extracts the option's prefix and arg from arguments string. + */ + private HashMap extractArguments() { + prefixValueMap = new HashMap(); + + int endPos = args.length(); + + for (Map.Entry entry : prefixPosMap.descendingMap() + .entrySet()) { + Prefix prefix = entry.getValue(); + Integer pos = entry.getKey(); + + if (pos == INVALID_POS) { + continue; + } + + String arg = args.substring(pos, endPos).trim(); + endPos = pos; + + if (prefixValueMap.containsKey(prefix)) { + prefixValueMap.put(prefix, prefixValueMap.get(prefix) + .concat(StringUtil.STRING_WHITESPACE).concat(arg)); + } else { + prefixValueMap.put(prefix, arg); + } + + } + + return prefixValueMap; + } + + public Optional getValue(Prefix prefix) { + if (!prefixValueMap.containsKey(prefix)) { + return Optional.empty(); + } + + return Optional + .of(getMultipleValues(prefix).get().iterator().next().trim()); + } + + public Optional> getMultipleValues(Prefix prefix) { + if (!prefixValueMap.containsKey(prefix)) { + return Optional.empty(); + } + return Optional + .of(getMultipleFromArgs(prefixValueMap.get(prefix), prefix)); + } + + public Optional getMultipleRawValues(Prefix prefix) { + if (!prefixValueMap.containsKey(prefix)) { + return Optional.empty(); + } + + return Optional.of(prefixValueMap.get(prefix).replaceAll( + prefix.value + StringUtil.STRING_WHITESPACE, + StringUtil.EMPTY_STRING)); + } + + public int numPrefixFound() { + return prefixPosMap.size(); + } + + public Optional getPreamble() { + if (args.trim().length() == StringUtil.EMPTY_STRING_LENGTH) { + return Optional.empty(); + } + + if (prefixPosMap.size() == EMPTY_SIZE) { + return Optional.of(args.trim()); + } else if (prefixPosMap.firstKey() == StringUtil.START_INDEX) { + return Optional.empty(); + } + + return Optional.of( + args.substring(StringUtil.START_INDEX, prefixPosMap.firstKey()) + .trim()); + } + + private Set getMultipleFromArgs(String multipleArguments, + Prefix prefix) { + if (multipleArguments.isEmpty()) { + return Collections.emptySet(); + } + + multipleArguments = multipleArguments.trim(); + + // replace first delimiter prefix, then split + List multipleArgList = Arrays.asList(multipleArguments + .replaceFirst(prefix.value + StringUtil.STRING_WHITESPACE, + StringUtil.EMPTY_STRING) + .split(StringUtil.STRING_WHITESPACE + prefix.value + + StringUtil.STRING_WHITESPACE)); + + for (int i = StringUtil.START_INDEX; i < multipleArgList.size(); i++) { + multipleArgList.set(i, multipleArgList.get(i).trim()); + } + + return new HashSet<>(multipleArgList); + } + +} diff --git a/src/main/java/tars/logic/parser/CdCommandParser.java b/src/main/java/tars/logic/parser/CdCommandParser.java new file mode 100644 index 000000000000..f452fcb71921 --- /dev/null +++ b/src/main/java/tars/logic/parser/CdCommandParser.java @@ -0,0 +1,51 @@ +package tars.logic.parser; + +import tars.commons.util.StringUtil; +import tars.logic.commands.CdCommand; +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; + +// @author A0124333U +/** + * Change directory command parser + */ +public class CdCommandParser extends CommandParser { + + private static final int CD_NEXT_INDEX = 1; + + /** + * Parses arguments in the context of the change storage file directory (cd) command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + + if (!isFileTypeValid(args.trim())) { + return new IncorrectCommand( + String.format(CdCommand.MESSAGE_INVALID_FILEPATH)); + } + + return new CdCommand(args.trim()); + } + + /** + * Checks if new file type is a valid file type + * + * @param args + * @return Boolean variable of whether the file type is valid + **/ + private Boolean isFileTypeValid(String args) { + String filePath = args.trim(); + String extension = filePath.substring( + filePath.lastIndexOf(StringUtil.STRING_FULLSTOP.trim()) + + CD_NEXT_INDEX, + filePath.length()); + if (extension.equals(CdCommand.getXmlFileExt())) { + return true; + } + return false; + } + +} diff --git a/src/main/java/tars/logic/parser/ClearCommandParser.java b/src/main/java/tars/logic/parser/ClearCommandParser.java new file mode 100644 index 000000000000..8370dc307884 --- /dev/null +++ b/src/main/java/tars/logic/parser/ClearCommandParser.java @@ -0,0 +1,16 @@ +package tars.logic.parser; + +import tars.logic.commands.ClearCommand; +import tars.logic.commands.Command; + +/** + * Clear command parser + */ +public class ClearCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + return new ClearCommand(); + } + +} diff --git a/src/main/java/tars/logic/parser/CommandParser.java b/src/main/java/tars/logic/parser/CommandParser.java new file mode 100644 index 000000000000..e3f27e58f2ee --- /dev/null +++ b/src/main/java/tars/logic/parser/CommandParser.java @@ -0,0 +1,24 @@ +package tars.logic.parser; + +import tars.logic.commands.Command; + +// @@author A0139924W +/** + * Represents a parser command with hidden internal logic and the ability to be executed. + */ +public abstract class CommandParser { + protected static final Prefix namePrefix = new Prefix("/n"); + protected static final Prefix tagPrefix = new Prefix("/t"); + protected static final Prefix priorityPrefix = new Prefix("/p"); + protected static final Prefix dateTimePrefix = new Prefix("/dt"); + protected static final Prefix recurringPrefix = new Prefix("/r"); + protected static final Prefix deletePrefix = new Prefix("/del"); + protected static final Prefix addTagPrefix = new Prefix("/ta"); + protected static final Prefix removeTagPrefix = new Prefix("/tr"); + protected static final Prefix donePrefix = new Prefix("/do"); + protected static final Prefix undonePrefix = new Prefix("/ud"); + protected static final Prefix listPrefix = new Prefix("/ls"); + protected static final Prefix editPrefix = new Prefix("/e"); + + public abstract Command prepareCommand(String args); +} diff --git a/src/main/java/tars/logic/parser/ConfirmCommandParser.java b/src/main/java/tars/logic/parser/ConfirmCommandParser.java new file mode 100644 index 000000000000..f6aebe09748c --- /dev/null +++ b/src/main/java/tars/logic/parser/ConfirmCommandParser.java @@ -0,0 +1,75 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.HashSet; +import java.util.NoSuchElementException; + +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.ConfirmCommand; +import tars.logic.commands.IncorrectCommand; + +// @@author A0124333U +/** + * Confirm command parser + */ +public class ConfirmCommandParser extends CommandParser { + + private static final int EXPECTED_INDEX_STRING_ARRAY_LENGTH = 2; + private static final int INDEX_OF_TASK = 0; + private static final int INDEX_OF_DATETIME = 1; + + @Override + public Command prepareCommand(String args) { + // there is no arguments + if (args.trim().isEmpty()) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } + + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(priorityPrefix, tagPrefix); + argsTokenizer.tokenize(args); + + int taskIndex; + int dateTimeIndex; + + try { + String indexArgs = argsTokenizer.getPreamble().get(); + String[] indexStringArray = StringUtil.indexString(indexArgs) + .split(StringUtil.STRING_WHITESPACE); + if (indexStringArray.length != EXPECTED_INDEX_STRING_ARRAY_LENGTH) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } else { + taskIndex = Integer.parseInt(indexStringArray[INDEX_OF_TASK]); + dateTimeIndex = + Integer.parseInt(indexStringArray[INDEX_OF_DATETIME]); + } + } catch (IllegalValueException | NoSuchElementException e) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } catch (InvalidRangeException ire) { + return new IncorrectCommand(ire.getMessage()); + } + + try { + return new ConfirmCommand(taskIndex, dateTimeIndex, + argsTokenizer.getValue(priorityPrefix) + .orElse(StringUtil.EMPTY_STRING), + argsTokenizer.getMultipleValues(tagPrefix) + .orElse(new HashSet<>())); + } catch (IllegalValueException ive) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/tars/logic/parser/DeleteCommandParser.java b/src/main/java/tars/logic/parser/DeleteCommandParser.java new file mode 100644 index 000000000000..5adf3ac65e56 --- /dev/null +++ b/src/main/java/tars/logic/parser/DeleteCommandParser.java @@ -0,0 +1,47 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.DeleteCommand; +import tars.logic.commands.IncorrectCommand; + +/** + * Delete command parser + */ +public class DeleteCommandParser extends CommandParser { + + /** + * Parses arguments in the context of the delete task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + args = args.trim(); + + if (StringUtil.EMPTY_STRING.equals(args)) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + } + + try { + String rangeIndex = StringUtil.indexString(args); + args = rangeIndex; + } catch (InvalidRangeException ire) { + return new IncorrectCommand(ire.getMessage()); + } catch (IllegalValueException ive) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + } + + return new DeleteCommand(args); + } + +} diff --git a/src/main/java/tars/logic/parser/DoCommandParser.java b/src/main/java/tars/logic/parser/DoCommandParser.java new file mode 100644 index 000000000000..9275628a7efb --- /dev/null +++ b/src/main/java/tars/logic/parser/DoCommandParser.java @@ -0,0 +1,49 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.DoCommand; +import tars.logic.commands.IncorrectCommand; + +/** + * Do command parser + */ +public class DoCommandParser extends CommandParser { + + private static final String INVALID_RANGE = + "Start index should be before end index."; + + /** + * Parses arguments in the context of the delete task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + args = args.trim(); + + if (StringUtil.EMPTY_STRING.equals(args)) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DoCommand.MESSAGE_USAGE)); + } + + try { + String rangeIndex = StringUtil.indexString(args); + args = rangeIndex; + } catch (InvalidRangeException ire) { + return new IncorrectCommand(String.format(INVALID_RANGE + + StringUtil.STRING_NEWLINE + DoCommand.MESSAGE_USAGE)); + } catch (IllegalValueException ive) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, DoCommand.MESSAGE_USAGE)); + } + + return new DoCommand(args); + } + +} diff --git a/src/main/java/tars/logic/parser/EditCommandParser.java b/src/main/java/tars/logic/parser/EditCommandParser.java new file mode 100644 index 000000000000..2f04ff89397e --- /dev/null +++ b/src/main/java/tars/logic/parser/EditCommandParser.java @@ -0,0 +1,60 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.EditCommand; +import tars.logic.commands.IncorrectCommand; + +// @@author A0121533W +/** + * Edit command parser + */ +public class EditCommandParser extends CommandParser { + + private static final int START_INDEX = 0; + private static final int EMPTY_SIZE = 0; + + /** + * Parses arguments in the context of the edit task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + args = args.trim(); + int targetIndex = START_INDEX; + if (args.indexOf( + StringUtil.STRING_WHITESPACE) != StringUtil.INVALID_POSITION) { + targetIndex = args.indexOf(StringUtil.STRING_WHITESPACE); + } + + String index; + try { + index = StringUtil.indexString( + (args.substring(StringUtil.START_INDEX, targetIndex))); + } catch (Exception e) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + if (index.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + ArgumentTokenizer argsTokenizer = new ArgumentTokenizer(namePrefix, + priorityPrefix, dateTimePrefix, addTagPrefix, removeTagPrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.numPrefixFound() == EMPTY_SIZE) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + return new EditCommand(Integer.parseInt(index), argsTokenizer); + } + +} diff --git a/src/main/java/tars/logic/parser/ExitCommandParser.java b/src/main/java/tars/logic/parser/ExitCommandParser.java new file mode 100644 index 000000000000..3946c6c791c1 --- /dev/null +++ b/src/main/java/tars/logic/parser/ExitCommandParser.java @@ -0,0 +1,16 @@ +package tars.logic.parser; + +import tars.logic.commands.Command; +import tars.logic.commands.ExitCommand; + +/** + * Exit command parser + */ +public class ExitCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + return new ExitCommand(); + } + +} diff --git a/src/main/java/tars/logic/parser/FindCommandParser.java b/src/main/java/tars/logic/parser/FindCommandParser.java new file mode 100644 index 000000000000..1ec2702b198d --- /dev/null +++ b/src/main/java/tars/logic/parser/FindCommandParser.java @@ -0,0 +1,107 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.time.DateTimeException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tars.commons.core.Messages; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.FindCommand; +import tars.logic.commands.IncorrectCommand; +import tars.model.task.TaskQuery; + +/** + * Find command parser + */ +public class FindCommandParser extends CommandParser { + private static final String REGEX_BRACKETS = "( )+"; + private static final int EMPTY_SIZE = 0; + private static final Pattern KEYWORDS_ARGS_FORMAT = + Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); // one or more whitespace + + /** + * Parses arguments in the context of the find task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(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)); + } + + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(namePrefix, priorityPrefix, + dateTimePrefix, donePrefix, undonePrefix, tagPrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.numPrefixFound() == EMPTY_SIZE) { + return new FindCommand(generateKeywordSetFromArgs(args.trim())); + } + + TaskQuery taskQuery; + try { + taskQuery = createTaskQuery(argsTokenizer); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } + + return new FindCommand(taskQuery); + } + + private ArrayList generateKeywordSetFromArgs(String keywordsArgs) { + String[] keywordsArray = + keywordsArgs.split(StringUtil.REGEX_WHITESPACE); + return new ArrayList(Arrays.asList(keywordsArray)); + } + + private TaskQuery createTaskQuery(ArgumentTokenizer argsTokenizer) + throws DateTimeException, IllegalValueException { + TaskQuery taskQuery = new TaskQuery(); + Boolean statusDone = true; + Boolean statusUndone = false; + + taskQuery.createNameQuery(argsTokenizer.getValue(namePrefix) + .orElse(StringUtil.EMPTY_STRING) + .replaceAll(REGEX_BRACKETS, StringUtil.STRING_WHITESPACE)); + taskQuery.createDateTimeQuery(DateTimeUtil + .parseStringToDateTime(argsTokenizer.getValue(dateTimePrefix) + .orElse(StringUtil.EMPTY_STRING))); + taskQuery.createPriorityQuery(argsTokenizer.getValue(priorityPrefix) + .orElse(StringUtil.EMPTY_STRING)); + if (!argsTokenizer.getValue(donePrefix).orElse(StringUtil.EMPTY_STRING) + .isEmpty() + && !argsTokenizer.getValue(undonePrefix) + .orElse(StringUtil.EMPTY_STRING).isEmpty()) { + throw new IllegalValueException( + TaskQuery.MESSAGE_BOTH_STATUS_SEARCHED_ERROR); + } else { + if (!argsTokenizer.getValue(donePrefix) + .orElse(StringUtil.EMPTY_STRING).isEmpty()) { + taskQuery.createStatusQuery(statusDone); + } + if (!argsTokenizer.getValue(undonePrefix) + .orElse(StringUtil.EMPTY_STRING).isEmpty()) { + taskQuery.createStatusQuery(statusUndone); + } + } + taskQuery.createTagsQuery(argsTokenizer.getMultipleRawValues(tagPrefix) + .orElse(StringUtil.EMPTY_STRING) + .replaceAll(REGEX_BRACKETS, StringUtil.STRING_WHITESPACE)); + + return taskQuery; + } + +} diff --git a/src/main/java/tars/logic/parser/FreeCommandParser.java b/src/main/java/tars/logic/parser/FreeCommandParser.java new file mode 100644 index 000000000000..f72120d97bec --- /dev/null +++ b/src/main/java/tars/logic/parser/FreeCommandParser.java @@ -0,0 +1,57 @@ +package tars.logic.parser; + +import java.time.DateTimeException; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.commons.core.Messages; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.FreeCommand; +import tars.logic.commands.IncorrectCommand; +import tars.model.task.DateTime; +import tars.model.task.DateTime.IllegalDateException; + +// @@author A0124333U +/** + * Free command parser + */ +public class FreeCommandParser extends CommandParser { + + public static final int FIRST_DATETIME_INDEX = 0; + public static final int SECOND_DATETIME_INDEX = 1; + + @Override + public Command prepareCommand(String args) { + args = args.trim(); + + if (args.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + } + + String[] dateTimeStringArray = {StringUtil.EMPTY_STRING}; + + try { + dateTimeStringArray = DateTimeUtil.parseStringToDateTime(args); + } catch (DateTimeException dte) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + } + + if (!dateTimeStringArray[FIRST_DATETIME_INDEX].isEmpty()) { + return new IncorrectCommand( + FreeCommand.MESSAGE_DATE_RANGE_DETECTED); + } else { + try { + return new FreeCommand(new DateTime(dateTimeStringArray[FIRST_DATETIME_INDEX], + dateTimeStringArray[SECOND_DATETIME_INDEX])); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } catch (IllegalDateException ide) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } + } + } + +} diff --git a/src/main/java/tars/logic/parser/HelpCommandParser.java b/src/main/java/tars/logic/parser/HelpCommandParser.java new file mode 100644 index 000000000000..4c119393b374 --- /dev/null +++ b/src/main/java/tars/logic/parser/HelpCommandParser.java @@ -0,0 +1,64 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.ArrayList; + +import tars.logic.commands.Command; +import tars.logic.commands.HelpCommand; +import tars.logic.commands.IncorrectCommand; +import tars.ui.UserGuide; + +// @@author A0140022H +/** + * Help command parser + */ +public class HelpCommandParser extends CommandParser { + + private static final int EMPTY_ARGS = 0; + + @Override + public Command prepareCommand(String args) { + + args = args.trim().toLowerCase(); + + if (args.length() > EMPTY_ARGS) { + ArrayList keywordArray = fillKeywordArray(); + + if (!keywordArray.contains(args)) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + HelpCommand.MESSAGE_USAGE)); + } + } + + return new HelpCommand(args); + } + + private ArrayList fillKeywordArray() { + ArrayList keywordArray = new ArrayList(); + keywordArray.add(UserGuide.ADD); + keywordArray.add(UserGuide.CD); + keywordArray.add(UserGuide.CLEAR); + keywordArray.add(UserGuide.CONFIRM); + keywordArray.add(UserGuide.DELETE); + keywordArray.add(UserGuide.DONE); + keywordArray.add(UserGuide.EDIT); + keywordArray.add(UserGuide.EXIT); + keywordArray.add(UserGuide.FIND); + keywordArray.add(UserGuide.FREE); + keywordArray.add(UserGuide.HELP); + keywordArray.add(UserGuide.LIST); + keywordArray.add(UserGuide.REDO); + keywordArray.add(UserGuide.RSV); + keywordArray.add(UserGuide.RSV_DELETE); + keywordArray.add(UserGuide.TAG_EDIT); + keywordArray.add(UserGuide.TAG_DELETE); + keywordArray.add(UserGuide.TAG_LIST); + keywordArray.add(UserGuide.UNDONE); + keywordArray.add(UserGuide.UNDO); + keywordArray.add(UserGuide.SUMMARY); + return keywordArray; + } + +} diff --git a/src/main/java/tars/logic/parser/IncorrectCommandParser.java b/src/main/java/tars/logic/parser/IncorrectCommandParser.java new file mode 100644 index 000000000000..a40ac755e4f2 --- /dev/null +++ b/src/main/java/tars/logic/parser/IncorrectCommandParser.java @@ -0,0 +1,18 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; + +/** + * Incorrect command parser + */ +public class IncorrectCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + return new IncorrectCommand(MESSAGE_UNKNOWN_COMMAND); + } + +} diff --git a/src/main/java/tars/logic/parser/ListCommandParser.java b/src/main/java/tars/logic/parser/ListCommandParser.java new file mode 100644 index 000000000000..fa7f78c6f755 --- /dev/null +++ b/src/main/java/tars/logic/parser/ListCommandParser.java @@ -0,0 +1,50 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.ListCommand; + +// @@author A0140022H +/** + * List command parser + */ +public class ListCommandParser extends CommandParser { + private static final Pattern KEYWORDS_ARGS_FORMAT = + Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); // one or more whitespace + + /** + * Parses arguments in the context of the list task command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + + if (args.isEmpty()) { + return new ListCommand(); + } + + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = + matcher.group("keywords").split(StringUtil.REGEX_WHITESPACE); + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + return new ListCommand(keywordSet); + } + +} diff --git a/src/main/java/tars/logic/parser/Parser.java b/src/main/java/tars/logic/parser/Parser.java new file mode 100644 index 000000000000..cd8d70e48332 --- /dev/null +++ b/src/main/java/tars/logic/parser/Parser.java @@ -0,0 +1,106 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tars.logic.commands.AddCommand; +import tars.logic.commands.CdCommand; +import tars.logic.commands.ClearCommand; +import tars.logic.commands.Command; +import tars.logic.commands.ConfirmCommand; +import tars.logic.commands.DeleteCommand; +import tars.logic.commands.DoCommand; +import tars.logic.commands.EditCommand; +import tars.logic.commands.ExitCommand; +import tars.logic.commands.FindCommand; +import tars.logic.commands.FreeCommand; +import tars.logic.commands.HelpCommand; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.ListCommand; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.RsvCommand; +import tars.logic.commands.TagCommand; +import tars.logic.commands.UdCommand; +import tars.logic.commands.UndoCommand; + +// @@author A0139924W +/** + * Parses user input. + */ +public class Parser { + + private static final String PARSER_MATCHER_ARGUMENTS = "arguments"; + + private static final String PARSER_MATCHER_COMMANDWORD = "commandWord"; + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = + Pattern.compile("(?\\S+)(?.*)"); + + /** + * Used for mapping a list of known command + */ + private static Map> commandParserMap = + new HashMap>(); + + static { + fillCommandMap(); + } + + private static void fillCommandMap() { + commandParserMap.put(AddCommand.COMMAND_WORD, AddCommandParser.class); + commandParserMap.put(RsvCommand.COMMAND_WORD, RsvCommandParser.class); + commandParserMap.put(EditCommand.COMMAND_WORD, EditCommandParser.class); + commandParserMap.put(DeleteCommand.COMMAND_WORD, + DeleteCommandParser.class); + commandParserMap.put(ConfirmCommand.COMMAND_WORD, + ConfirmCommandParser.class); + commandParserMap.put(ClearCommand.COMMAND_WORD, + ClearCommandParser.class); + commandParserMap.put(FindCommand.COMMAND_WORD, FindCommandParser.class); + commandParserMap.put(ListCommand.COMMAND_WORD, ListCommandParser.class); + commandParserMap.put(UndoCommand.COMMAND_WORD, UndoCommandParser.class); + commandParserMap.put(RedoCommand.COMMAND_WORD, RedoCommandParser.class); + commandParserMap.put(DoCommand.COMMAND_WORD, DoCommandParser.class); + commandParserMap.put(UdCommand.COMMAND_WORD, UdCommandParser.class); + commandParserMap.put(CdCommand.COMMAND_WORD, CdCommandParser.class); + commandParserMap.put(TagCommand.COMMAND_WORD, TagCommandParser.class); + commandParserMap.put(FreeCommand.COMMAND_WORD, FreeCommandParser.class); + commandParserMap.put(ExitCommand.COMMAND_WORD, ExitCommandParser.class); + commandParserMap.put(HelpCommand.COMMAND_WORD, HelpCommandParser.class); + } + + /** + * 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(PARSER_MATCHER_COMMANDWORD); + final String arguments = matcher.group(PARSER_MATCHER_ARGUMENTS); + + if (!commandParserMap.containsKey(commandWord)) { + return new IncorrectCommandParser().prepareCommand(arguments); + } + + try { + return commandParserMap.get(commandWord).newInstance() + .prepareCommand(arguments); + } catch (Exception ex) { + return new IncorrectCommandParser().prepareCommand(arguments); + } + } + +} diff --git a/src/main/java/tars/logic/parser/Prefix.java b/src/main/java/tars/logic/parser/Prefix.java new file mode 100644 index 000000000000..6fe16f35610a --- /dev/null +++ b/src/main/java/tars/logic/parser/Prefix.java @@ -0,0 +1,35 @@ +package tars.logic.parser; + +// @@author A0139924W +/** + * A prefix that marks the beginning of an argument e.g. '/t' in 'add CS2103 Project Meeting /t + * meeting' + */ +public class Prefix { + private static final int HASHCODE_NULL_VALUE = 0; + public final String value; + + public Prefix(String value) { + this.value = value; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Prefix)) { + return false; + } + + if (other == this) { + return true; + } + + Prefix otherPrefix = (Prefix) other; + return otherPrefix.value.equals(this.value); + } + + @Override + public int hashCode() { + return this.value == null ? HASHCODE_NULL_VALUE : this.value.hashCode(); + } + +} diff --git a/src/main/java/tars/logic/parser/RedoCommandParser.java b/src/main/java/tars/logic/parser/RedoCommandParser.java new file mode 100644 index 000000000000..198bb2925ab8 --- /dev/null +++ b/src/main/java/tars/logic/parser/RedoCommandParser.java @@ -0,0 +1,24 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.RedoCommand; + +// @@author A0139924W +/** + * Redo command parser + */ +public class RedoCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + if (!args.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, RedoCommand.MESSAGE_USAGE)); + } + return new RedoCommand(); + } + +} diff --git a/src/main/java/tars/logic/parser/RsvCommandParser.java b/src/main/java/tars/logic/parser/RsvCommandParser.java new file mode 100644 index 000000000000..202c078e4e5f --- /dev/null +++ b/src/main/java/tars/logic/parser/RsvCommandParser.java @@ -0,0 +1,92 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.time.DateTimeException; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; + +import tars.commons.core.Messages; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.RsvCommand; + +// @@author A0124333U +/** + * Reserve command parser + */ +public class RsvCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + // there is no arguments + if (args.trim().isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, RsvCommand.MESSAGE_USAGE)); + } + + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(dateTimePrefix, deletePrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.getValue(deletePrefix).isPresent()) { + return prepareRsvDel(argsTokenizer); + } else { + return prepareRsvAdd(argsTokenizer); + } + } + + // Parses arguments for adding a reserved task + private Command prepareRsvAdd(ArgumentTokenizer argsTokenizer) { + if (!argsTokenizer.getValue(dateTimePrefix).isPresent()) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_DATETIME_NOT_FOUND)); + } + + Set dateTimeStringSet = new HashSet<>(); + + try { + for (String dateTimeString : argsTokenizer + .getMultipleValues(dateTimePrefix).get()) { + dateTimeStringSet.add( + DateTimeUtil.parseStringToDateTime(dateTimeString)); + } + + return new RsvCommand(argsTokenizer.getPreamble().get(), + dateTimeStringSet); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (DateTimeException dte) { + return new IncorrectCommand(Messages.MESSAGE_INVALID_DATE); + } catch (NoSuchElementException nse) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, RsvCommand.MESSAGE_USAGE)); + } + } + + // Parses arguments for deleting one or more reserved tasks + private Command prepareRsvDel(ArgumentTokenizer argsTokenizer) { + try { + if (argsTokenizer.getPreamble().isPresent()) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE_DEL)); + } + + String rangeIndex = StringUtil + .indexString(argsTokenizer.getValue(deletePrefix).get()); + return new RsvCommand(rangeIndex); + } catch (InvalidRangeException | IllegalValueException ie) { + return new IncorrectCommand( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE_DEL)); + } + } + +} diff --git a/src/main/java/tars/logic/parser/TagCommandParser.java b/src/main/java/tars/logic/parser/TagCommandParser.java new file mode 100644 index 000000000000..630091aa7604 --- /dev/null +++ b/src/main/java/tars/logic/parser/TagCommandParser.java @@ -0,0 +1,55 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.TagCommand; + +// @@author A0139924W +/** + * Tag command parser + */ +public class TagCommandParser extends CommandParser { + private static final Pattern TAG_EDIT_COMMAND_FORMAT = + Pattern.compile("\\d+ \\w+$"); + + /** + * Parses arguments in the context of the tag command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + ArgumentTokenizer argsTokenizer = + new ArgumentTokenizer(listPrefix, editPrefix, deletePrefix); + argsTokenizer.tokenize(args); + + if (argsTokenizer.getValue(listPrefix).isPresent()) { + return new TagCommand(listPrefix); + } + + if (argsTokenizer.getValue(editPrefix).isPresent()) { + String editArgs = argsTokenizer.getValue(editPrefix).get(); + final Matcher matcher = TAG_EDIT_COMMAND_FORMAT.matcher(editArgs); + if (matcher.matches()) { + return new TagCommand(editPrefix, + editArgs.split(StringUtil.STRING_WHITESPACE)); + } + } + + if (argsTokenizer.getValue(deletePrefix).isPresent()) { + String index = argsTokenizer.getValue(deletePrefix).get(); + return new TagCommand(deletePrefix, index); + } + + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/main/java/tars/logic/parser/UdCommandParser.java b/src/main/java/tars/logic/parser/UdCommandParser.java new file mode 100644 index 000000000000..6ba7a3d6810d --- /dev/null +++ b/src/main/java/tars/logic/parser/UdCommandParser.java @@ -0,0 +1,49 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.InvalidRangeException; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.UdCommand; + +/** + * Undone command parser + */ +public class UdCommandParser extends CommandParser { + + private static final String INVALID_RANGE = + "Start index should be before end index."; + + /** + * Parses arguments in the context of the ud command. + * + * @param args full command args string + * @return the prepared command + */ + @Override + public Command prepareCommand(String args) { + args = args.trim(); + + if (StringUtil.EMPTY_STRING.equals(args)) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, UdCommand.MESSAGE_USAGE)); + } + + try { + String rangeIndex = StringUtil.indexString(args); + args = rangeIndex; + } catch (InvalidRangeException ire) { + return new IncorrectCommand(String.format(INVALID_RANGE + + StringUtil.STRING_NEWLINE + UdCommand.MESSAGE_USAGE)); + } catch (IllegalValueException ive) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, UdCommand.MESSAGE_USAGE)); + } + + return new UdCommand(args); + } + +} diff --git a/src/main/java/tars/logic/parser/UndoCommandParser.java b/src/main/java/tars/logic/parser/UndoCommandParser.java new file mode 100644 index 000000000000..38c5f2513b1a --- /dev/null +++ b/src/main/java/tars/logic/parser/UndoCommandParser.java @@ -0,0 +1,24 @@ +package tars.logic.parser; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tars.logic.commands.Command; +import tars.logic.commands.IncorrectCommand; +import tars.logic.commands.UndoCommand; + +// @@author A0139924W +/** + * Undo command parser + */ +public class UndoCommandParser extends CommandParser { + + @Override + public Command prepareCommand(String args) { + if (!args.isEmpty()) { + return new IncorrectCommand(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, UndoCommand.MESSAGE_USAGE)); + } + return new UndoCommand(); + } + +} diff --git a/src/main/java/tars/model/Model.java b/src/main/java/tars/model/Model.java new file mode 100644 index 000000000000..e56f9fd4913f --- /dev/null +++ b/src/main/java/tars/model/Model.java @@ -0,0 +1,149 @@ +package tars.model; + +import java.util.ArrayList; +import java.util.Set; +import java.util.Stack; + +import javafx.collections.ObservableList; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.logic.commands.Command; +import tars.model.tag.ReadOnlyTag; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList.DuplicateTagException; +import tars.model.tag.UniqueTagList.TagNotFoundException; +import tars.model.task.DateTime; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Status; +import tars.model.task.Task; +import tars.model.task.TaskQuery; +import tars.model.task.UniqueTaskList; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList.RsvTaskNotFoundException; + +/** + * The API of the Model component. + */ +public interface Model { + /** Clears existing backing model and replaces with the provided new data. */ + void resetData(ReadOnlyTars newData); + + // @@author A0124333U + /** + * Overwrites current data with data from a new file path. + */ + public void overwriteDataFromNewFilePath(ReadOnlyTars newData); + + // @@author + + /** + * Returns the Tars + */ + ReadOnlyTars getTars(); + + /** Undo an edited task */ + void replaceTask(ReadOnlyTask toUndo, Task replacement) + throws DuplicateTaskException; + + /** Deletes the given task. */ + void deleteTask(ReadOnlyTask target) + throws UniqueTaskList.TaskNotFoundException; + + /** Adds the given task */ + void addTask(Task task) throws DuplicateTaskException; + + // @@author A0124333U + /** + * Deletes the reserved task. + */ + void deleteRsvTask(RsvTask target) throws RsvTaskNotFoundException; + + /** Adds the given reserved task */ + void addRsvTask(RsvTask rsvTask) throws DuplicateTaskException; + + /** Checks for tasks with conflicting datetime and returns a string of all conflicting tasks */ + String getTaskConflictingDateTimeWarningMessage(DateTime dateTimeToCheck); + + // @@author A0139924W + /** + * Rename all task with the old tag with new tag name + */ + void renameTasksWithNewTag(ReadOnlyTag toBeRenamed, Tag newTag) + throws IllegalValueException, TagNotFoundException, + DuplicateTagException; + + /** Remove the tag from all task */ + ArrayList removeTagFromAllTasks(ReadOnlyTag toBeDeleted) + throws DuplicateTagException, IllegalValueException, + TagNotFoundException; + + /** Add tag to all task */ + void addTagToAllTasks(ReadOnlyTag toBeAdded, + ArrayList toBeEdited) throws DuplicateTagException, + IllegalValueException, TagNotFoundException; + + // @@author A0121533W + /** + * Marks tasks as done or undone. + */ + void mark(ArrayList toMarkList, Status status) + throws DuplicateTaskException; + // @@author + + /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredTaskList(); + + /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredRsvTaskList(); + + /** Updates the filter of the filtered task list to show all tasks */ + void updateFilteredListToShowAll(); + + // @@author A0124333U + /** + * Updates the filter of the filtered task list to filter by the given keywords of each given + * task attribute + */ + void updateFilteredTaskListUsingFlags(TaskQuery taskQuery); + + /** + * Updates the filter of the filtered task list to filter by the given keywords of a string + * consisting of all the attributes of each task + */ + void updateFilteredTaskListUsingQuickSearch( + ArrayList lazySearchKeywords); + + // @@author A0139924W + /** + * Returns the undoable command history stack + */ + Stack getUndoableCmdHist(); + + /** Returns the redoable command history stack */ + Stack getRedoableCmdHist(); + + // @@author + + /** Returns the unique tag list as an {@code ObservableList} */ + ObservableList getUniqueTagList(); + + // @@author A0124333U + /** + * Returns an ArrayList of DateTime in a specified date + */ + public ArrayList getListOfFilledTimeSlotsInDate( + DateTime dateToCheck); + + // @@author A0140022H + /** + * Sorts the filtered task list by the given keywords + */ + void sortFilteredTaskList(Set keywords); + + /** + * Updates the filtered task list by the given dateTime + */ + void updateFilteredTaskListUsingDate(DateTime dateTime); + +} diff --git a/src/main/java/tars/model/ModelManager.java b/src/main/java/tars/model/ModelManager.java new file mode 100644 index 000000000000..56fe43975761 --- /dev/null +++ b/src/main/java/tars/model/ModelManager.java @@ -0,0 +1,398 @@ +package tars.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import tars.commons.core.ComponentManager; +import tars.commons.core.LogsCenter; +import tars.commons.core.UnmodifiableObservableList; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.events.ui.RsvTaskAddedEvent; +import tars.commons.events.ui.TaskAddedEvent; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.logic.commands.Command; +import tars.model.qualifiers.DateQualifier; +import tars.model.qualifiers.FlagSearchQualifier; +import tars.model.qualifiers.Qualifier; +import tars.model.qualifiers.QuickSearchQualifier; +import tars.model.tag.ReadOnlyTag; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList.DuplicateTagException; +import tars.model.tag.UniqueTagList.TagNotFoundException; +import tars.model.task.DateTime; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Status; +import tars.model.task.Task; +import tars.model.task.TaskQuery; +import tars.model.task.UniqueTaskList.TaskNotFoundException; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList.RsvTaskNotFoundException; + +/** + * Represents the in-memory model of tars 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 static final String LIST_ARG_DATETIME = "/dt"; + private static final String LIST_ARG_PRIORITY = "/p"; + private static final String LIST_KEYWORD_DESCENDING = "dsc"; + + private static String MESSAGE_INITIALIZING_TARS = + "Initializing with tars %1$s and user prefs %2$s"; + private static String CONFLICTING_TASK = "\nTask %1$s: %2$s"; + private static String CONFLICTING_RSV_TASK = "\nRsvTask %1$s: %2$s"; + + private final Tars tars; + private final FilteredList filteredTasks; + private final FilteredList filteredRsvTasks; + private final Stack undoableCmdHistStack; + private final Stack redoableCmdHistStack; + + /** + * Initializes a ModelManager with the given Tars Tars and its variables should not be null + */ + public ModelManager(Tars src, UserPrefs userPrefs) { + super(); + assert src != null; + assert userPrefs != null; + + logger.fine(String.format(MESSAGE_INITIALIZING_TARS, src, userPrefs)); + + tars = new Tars(src); + filteredTasks = new FilteredList<>(tars.getTasks()); + filteredRsvTasks = new FilteredList<>(tars.getRsvTasks()); + undoableCmdHistStack = new Stack<>(); + redoableCmdHistStack = new Stack<>(); + } + + public ModelManager() { + this(new Tars(), new UserPrefs()); + } + + public ModelManager(ReadOnlyTars initialData) { + tars = new Tars(initialData); + filteredTasks = new FilteredList<>(tars.getTasks()); + filteredRsvTasks = new FilteredList<>(tars.getRsvTasks()); + undoableCmdHistStack = new Stack<>(); + redoableCmdHistStack = new Stack<>(); + } + + @Override + public void resetData(ReadOnlyTars newData) { + tars.resetData(newData); + indicateTarsChanged(); + } + + @Override + public void overwriteDataFromNewFilePath(ReadOnlyTars newData) { + tars.resetData(newData); + } + + @Override + public ReadOnlyTars getTars() { + return tars; + } + + @Override + public Stack getUndoableCmdHist() { + return undoableCmdHistStack; + } + + @Override + public Stack getRedoableCmdHist() { + return redoableCmdHistStack; + } + + @Override + public ObservableList getUniqueTagList() { + return tars.getUniqueTagList().getInternalList(); + } + + /** Raises an event to indicate the model has changed */ + private void indicateTarsChanged() { + raise(new TarsChangedEvent(tars)); + } + + + // @@author A0139924W + @Override + public synchronized void renameTasksWithNewTag(ReadOnlyTag toBeRenamed, + Tag newTag) throws IllegalValueException, TagNotFoundException, + DuplicateTagException { + + tars.getUniqueTagList().update(toBeRenamed, newTag); + tars.renameTasksWithNewTag(toBeRenamed, newTag); + + indicateTarsChanged(); + } + + // @@author A0139924W + @Override + public synchronized ArrayList removeTagFromAllTasks( + ReadOnlyTag toBeDeleted) + throws TagNotFoundException, IllegalValueException { + + ArrayList editedTasks = + tars.removeTagFromAllTasks(toBeDeleted); + tars.getUniqueTagList().remove(new Tag(toBeDeleted)); + + indicateTarsChanged(); + return editedTasks; + } + + // @@author A0139924W + @Override + public synchronized void addTagToAllTasks(ReadOnlyTag toBeAdded, + ArrayList allTasks) throws DuplicateTagException, + IllegalValueException, TagNotFoundException { + tars.addTagToAllTasks(toBeAdded, allTasks); + tars.getUniqueTagList().add(new Tag(toBeAdded)); + + indicateTarsChanged(); + } + + @Override + public synchronized void deleteTask(ReadOnlyTask target) + throws TaskNotFoundException { + tars.removeTask(target); + indicateTarsChanged(); + } + + @Override + public synchronized void addTask(Task task) throws DuplicateTaskException { + tars.addTask(task); + raise(new TaskAddedEvent(tars.getTaskList().size(), task)); + updateFilteredListToShowAll(); + indicateTarsChanged(); + } + + @Override + public synchronized void deleteRsvTask(RsvTask target) + throws RsvTaskNotFoundException { + tars.removeRsvTask(target); + indicateTarsChanged(); + } + + @Override + public synchronized void addRsvTask(RsvTask rsvTask) + throws DuplicateTaskException { + tars.addRsvTask(rsvTask); + raise(new RsvTaskAddedEvent(tars.getRsvTaskList().size(), rsvTask)); + raise(new RsvTaskAddedEvent(tars.getRsvTaskList().size(), rsvTask)); + indicateTarsChanged(); + } + + // @@author A0121533W + @Override + public synchronized void mark(ArrayList toMarkList, + Status status) throws DuplicateTaskException { + tars.mark(toMarkList, status); + indicateTarsChanged(); + } + + // @@author A0124333U + /** + * Returns a string of tasks and rsv tasks whose datetime conflicts with a specified datetime + */ + public String getTaskConflictingDateTimeWarningMessage( + DateTime dateTimeToCheck) { + StringBuilder conflictingTasksStringBuilder = + new StringBuilder(StringUtil.EMPTY_STRING); + + if (dateTimeToCheck.getEndDate() == null) { + return StringUtil.EMPTY_STRING; + } + + appendConflictingTasks(conflictingTasksStringBuilder, dateTimeToCheck); + appendConflictingRsvTasks(conflictingTasksStringBuilder, + dateTimeToCheck); + + return conflictingTasksStringBuilder.toString(); + } + + private void appendConflictingTasks( + StringBuilder conflictingTasksStringBuilder, + DateTime dateTimeToCheck) { + + int taskCount = 1; + for (ReadOnlyTask t : tars.getTaskList()) { + + if (t.getStatus().status == Status.UNDONE && DateTimeUtil + .isDateTimeConflicting(t.getDateTime(), dateTimeToCheck)) { + conflictingTasksStringBuilder.append(String + .format(CONFLICTING_TASK, taskCount, t.getAsText())); + taskCount++; + } + } + } + + private void appendConflictingRsvTasks( + StringBuilder conflictingTasksStringBuilder, + DateTime dateTimeToCheck) { + + int rsvCount = 1; + + for (RsvTask rt : tars.getRsvTaskList()) { + if (rt.getDateTimeList().stream() + .filter(dateTimeSource -> DateTimeUtil + .isDateTimeConflicting(dateTimeSource, + dateTimeToCheck)) + .count() > 0) { + conflictingTasksStringBuilder.append(String + .format(CONFLICTING_RSV_TASK, rsvCount, rt.toString())); + rsvCount++; + + } + } + + } + + /** + * Returns a sorted arraylist of filled datetime slots in a specified date Datetimes with no + * startdate are not added into the list + */ + public ArrayList getListOfFilledTimeSlotsInDate( + DateTime dateToCheck) { + ArrayList listOfDateTime = new ArrayList(); + + addTimeSlotsFromTasks(listOfDateTime, dateToCheck); + addTimeSlotsFromRsvTasks(listOfDateTime, dateToCheck); + + Collections.sort(listOfDateTime); + + return listOfDateTime; + } + + private void addTimeSlotsFromTasks(ArrayList listOfDateTime, + DateTime dateToCheck) { + for (ReadOnlyTask t : tars.getTaskList()) { + if (t.getStatus().status == Status.UNDONE + && t.getDateTime().getStartDate() != null + && DateTimeUtil.isDateTimeWithinRange(t.getDateTime(), + dateToCheck)) { + listOfDateTime.add(t.getDateTime()); + } + } + } + + private void addTimeSlotsFromRsvTasks(ArrayList listOfDateTime, + DateTime dateToCheck) { + + for (RsvTask rt : tars.getRsvTaskList()) { + for (DateTime dt : rt.getDateTimeList()) { + if (dt.getStartDate() != null && DateTimeUtil + .isDateTimeWithinRange(dt, dateToCheck)) { + listOfDateTime.add(dt); + } + } + } + } + + + // @@author A0139924W + @Override + public synchronized void replaceTask(ReadOnlyTask toUndo, Task replacement) + throws DuplicateTaskException { + tars.replaceTask(toUndo, replacement); + indicateTarsChanged(); + } + + // @@author + + // =========== Filtered Task List Accessors =========== + + @Override + public UnmodifiableObservableList getFilteredTaskList() { + return new UnmodifiableObservableList<>(filteredTasks); + } + + @Override + public UnmodifiableObservableList getFilteredRsvTaskList() { + return new UnmodifiableObservableList<>(filteredRsvTasks); + } + + @Override + public void updateFilteredListToShowAll() { + filteredTasks.setPredicate(null); + } + + public void updateFilteredTaskListUsingQuickSearch( + ArrayList quickSearchKeywords) { + updateFilteredTaskList(new PredicateExpression( + new QuickSearchQualifier(quickSearchKeywords))); + } + + public void updateFilteredTaskListUsingFlags(TaskQuery taskQuery) { + updateFilteredTaskList( + new PredicateExpression(new FlagSearchQualifier(taskQuery))); + } + + private void updateFilteredTaskList(Expression expression) { + filteredTasks.setPredicate(expression::satisfies); + } + + // @@author A0140022H + public void updateFilteredTaskListUsingDate(DateTime dateTime) { + updateFilteredTaskList( + new PredicateExpression(new DateQualifier(dateTime))); + } + + /** + * Sorts filtered list based on keywords + */ + public void sortFilteredTaskList(Set keywords) { + if (keywords.contains(LIST_ARG_PRIORITY)) { + if (keywords.contains(LIST_KEYWORD_DESCENDING)) { + tars.sortByPriorityDescending(); + } else { + tars.sortByPriority(); + } + } else if (keywords.contains(LIST_ARG_DATETIME)) { + if (keywords.contains(LIST_KEYWORD_DESCENDING)) { + tars.sortByDatetimeDescending(); + } else { + tars.sortByDatetime(); + } + } + } + + // @@author + + // ========== Inner class/interface 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(); + + } + } + +} diff --git a/src/main/java/tars/model/ReadOnlyTars.java b/src/main/java/tars/model/ReadOnlyTars.java new file mode 100644 index 000000000000..45fa7568b426 --- /dev/null +++ b/src/main/java/tars/model/ReadOnlyTars.java @@ -0,0 +1,38 @@ +package tars.model; + +import tars.model.task.ReadOnlyTask; +import tars.model.task.UniqueTaskList; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; + +import java.util.List; + +/** + * Unmodifiable view of tars + */ +public interface ReadOnlyTars { + + UniqueTagList getUniqueTagList(); + + UniqueTaskList getUniqueTaskList(); + + UniqueRsvTaskList getUniqueRsvTaskList(); + + /** + * Returns an unmodifiable view of tasks list + */ + List getTaskList(); + + /** + * Returns an unmodifiable view of tasks list + */ + List getRsvTaskList(); + + /** + * Returns an unmodifiable view of tags list + */ + List getTagList(); + +} diff --git a/src/main/java/tars/model/Tars.java b/src/main/java/tars/model/Tars.java new file mode 100644 index 000000000000..165490cc5148 --- /dev/null +++ b/src/main/java/tars/model/Tars.java @@ -0,0 +1,407 @@ +package tars.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.StringUtil; +import tars.model.tag.ReadOnlyTag; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.model.tag.UniqueTagList.DuplicateTagException; +import tars.model.tag.UniqueTagList.TagNotFoundException; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Status; +import tars.model.task.Task; +import tars.model.task.UniqueTaskList; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList; +import tars.model.task.rsv.UniqueRsvTaskList.RsvTaskNotFoundException; + +/** + * Wraps all data at the tars level Duplicates are not allowed (by .equals comparison) + */ +public class Tars implements ReadOnlyTars { + + private static String TARS_COMPONENTS_SIZES = + "%1$s tasks, %2$s reserved tasks, %3$s tags"; + + private final UniqueTaskList tasks; + private final UniqueTagList tags; + private final UniqueRsvTaskList rsvTasks; + + { + tasks = new UniqueTaskList(); + tags = new UniqueTagList(); + rsvTasks = new UniqueRsvTaskList(); + } + + public Tars() {} + + /** + * Tasks and Tags are copied into this tars + */ + public Tars(ReadOnlyTars toBeCopied) { + this(toBeCopied.getUniqueTaskList(), toBeCopied.getUniqueTagList(), + toBeCopied.getUniqueRsvTaskList()); + } + + /** + * Tasks and Tags are copied into this tars + */ + public Tars(UniqueTaskList tasks, UniqueTagList tags, + UniqueRsvTaskList rsvTasks) { + resetData(tasks.getInternalList(), rsvTasks.getInternalList(), + tags.getInternalList()); + } + + public static ReadOnlyTars getEmptyTars() { + return new Tars(); + } + + //// list overwrite operations + + public ObservableList getTasks() { + return tasks.getInternalList(); + } + + public ObservableList getRsvTasks() { + return rsvTasks.getInternalList(); + } + + public void setTasks(List tasks) { + this.tasks.getInternalList().setAll(tasks); + } + + public void setRsvTasks(List rsvTasks) { + this.rsvTasks.getInternalList().setAll(rsvTasks); + } + + // @@author A0121533W + /** + * Replaces task in tars internal list + * + * @throws DuplicateTaskException if replacement task is the same as the task to replace + */ + public void replaceTask(ReadOnlyTask toReplace, Task replacement) + throws DuplicateTaskException { + if (toReplace.isSameStateAs(replacement)) { + throw new DuplicateTaskException(); + } + + ObservableList list = this.tasks.getInternalList(); + for (int i = StringUtil.START_INDEX; i < list.size(); i++) { + if (list.get(i).isSameStateAs(toReplace)) { + syncTagsWithMasterList(replacement); + list.set(i, replacement); + break; + } + } + } + // @@author + + public void setTags(Collection tags) { + this.tags.getInternalList().setAll(tags); + } + + public void resetData(Collection newTasks, + Collection newRsvTasks, Collection newTags) { + setTasks(newTasks.stream().map(Task::new).collect(Collectors.toList())); + setRsvTasks(newRsvTasks.stream().collect(Collectors.toList())); + setTags(newTags); + } + + public void resetData(ReadOnlyTars newData) { + resetData(newData.getTaskList(), newData.getRsvTaskList(), + newData.getTagList()); + } + + //// task-level operations + + /** + * Adds a task to tars. Also checks the new task's tags and updates {@link #tags} with any new + * tags found, and updates the Tag objects in the task to point to those in {@link #tags}. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent task already exists. + */ + public void addTask(Task p) throws DuplicateTaskException { + syncTagsWithMasterList(p); + tasks.add(p); + } + + // @@author A0124333U + /** + * Adds a reserved task to tars. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent reserved task already exists. + */ + public void addRsvTask(RsvTask rt) throws DuplicateTaskException { + rsvTasks.add(rt); + } + + // @@author A0121533W + /** + * Marks every task in respective lists as done or undone + * + * @throws DuplicateTaskException + */ + public void mark(ArrayList toMarkList, Status status) + throws DuplicateTaskException { + for (ReadOnlyTask t : toMarkList) { + if (!t.getStatus().equals(status)) { + // prevent marking tasks which are already marked + Task toMark = new Task(t); + toMark.setStatus(status); + replaceTask(t, toMark); + } + } + } + // @@author + + /** + * Ensures that every tag in this task: - exists in the master list {@link #tags} - points to a + * Tag object in the master list + */ + private void syncTagsWithMasterList(Task task) { + final UniqueTagList taskTags = task.getTags(); + tags.mergeFrom(taskTags); + + // 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 task tags using references from the master list + final Set commonTagReferences = new HashSet<>(); + for (Tag tag : taskTags) { + commonTagReferences.add(masterTagObjects.get(tag)); + } + task.setTags(new UniqueTagList(commonTagReferences)); + } + + public boolean removeTask(ReadOnlyTask key) + throws UniqueTaskList.TaskNotFoundException { + if (tasks.remove(key)) { + return true; + } else { + throw new UniqueTaskList.TaskNotFoundException(); + } + } + + public boolean removeRsvTask(RsvTask key) throws RsvTaskNotFoundException { + if (rsvTasks.remove(key)) { + return true; + } else { + throw new RsvTaskNotFoundException(); + } + } + + // @@author A0140022H + /** + * Sorts internal list by priority from low to high + */ + public void sortByPriority() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o1.getPriority().compareTo(o2.getPriority()); + } + }); + } + + /** + * Sorts internal list by priority from high to low + */ + public void sortByPriorityDescending() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o2.getPriority().compareTo(o1.getPriority()); + } + }); + } + + /** + * Sorts internal list by earliest end dateTime first + */ + public void sortByDatetime() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o1.getDateTime().compareTo(o2.getDateTime()); + } + }); + } + + /** + * Sorts internal list by latest end dateTime first + */ + public void sortByDatetimeDescending() { + this.tasks.getInternalList().sort(new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o2.getDateTime().compareTo(o1.getDateTime()); + } + }); + } + // @@author + + //// tag-level operations + + // @@author A0139924W + public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { + tags.add(t); + } + + // @@author A0139924W + public void removeTag(Tag t) throws UniqueTagList.TagNotFoundException { + tags.remove(t); + } + + // @@author A0139924W + /** + * Rename all task with the new tag + * + * @param toBeRenamed tag to be replaced with new the new tag + * @param newTag new tag + * @throws IllegalValueException if the given tag name string is invalid. + * @throws TagNotFoundException if there is no matching tags. + */ + public void renameTasksWithNewTag(ReadOnlyTag toBeRenamed, Tag newTag) + throws IllegalValueException, TagNotFoundException { + + for (int i = StringUtil.START_INDEX; i < tasks.getInternalList() + .size(); i++) { + Task toEdit = new Task(tasks.getInternalList().get(i)); + UniqueTagList tags = toEdit.getTags(); + if (tags.contains(new Tag(toBeRenamed))) { + tags.update(toBeRenamed, newTag); + toEdit.setTags(tags); + tasks.getInternalList().set(i, toEdit); + } + } + } + + /** + * Remove the tag from all tasks + * + * @param toBeDeleted + * @throws IllegalValueException if the given tag name string is invalid. + * @throws TagNotFoundException if there is no matching tags. + */ + public ArrayList removeTagFromAllTasks( + ReadOnlyTag toBeDeleted) throws IllegalValueException, + TagNotFoundException, DuplicateTagException { + ArrayList editedTasks = new ArrayList(); + + for (int i = StringUtil.START_INDEX; i < tasks.getInternalList() + .size(); i++) { + Task toEdit = new Task(tasks.getInternalList().get(i)); + UniqueTagList tags = toEdit.getTags(); + if (tags.contains(new Tag(toBeDeleted))) { + tags.remove(new Tag(toBeDeleted)); + toEdit.setTags(tags); + tasks.getInternalList().set(i, toEdit); + editedTasks.add(toEdit); + } + } + + return editedTasks; + } + + /** + * Remove the tag from all tasks + * + * @param toBeDeleted + * @throws IllegalValueException if the given tag name string is invalid. + * @throws TagNotFoundException if there is no matching tags. + */ + public void addTagToAllTasks(ReadOnlyTag toBeAdded, + ArrayList allTasks) throws IllegalValueException, + TagNotFoundException, DuplicateTagException { + + for (int i = StringUtil.START_INDEX; i < allTasks.size(); i++) { + for (int j = StringUtil.START_INDEX; j < tasks.getInternalList() + .size(); j++) { + Task toEdit = new Task(tasks.getInternalList().get(j)); + if (toEdit.equals(allTasks.get(i))) { + UniqueTagList tags = toEdit.getTags(); + tags.add(new Tag(toBeAdded)); + toEdit.setTags(tags); + tasks.getInternalList().set(i, toEdit); + } + } + } + } + + // @@author + + //// util methods + + @Override + public String toString() { + return String.format(TARS_COMPONENTS_SIZES, + tasks.getInternalList().size(), + rsvTasks.getInternalList().size(), + tags.getInternalList().size()); + } + + @Override + public List getTaskList() { + return Collections.unmodifiableList(tasks.getInternalList()); + } + + @Override + public List getRsvTaskList() { + return Collections.unmodifiableList(rsvTasks.getInternalList()); + } + + @Override + public List getTagList() { + return Collections.unmodifiableList(tags.getInternalList()); + } + + @Override + public UniqueTaskList getUniqueTaskList() { + return this.tasks; + } + + @Override + public UniqueRsvTaskList getUniqueRsvTaskList() { + return this.rsvTasks; + } + + @Override + public UniqueTagList getUniqueTagList() { + return this.tags; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Tars // instanceof handles nulls + && this.tasks.equals(((Tars) other).tasks) + && this.rsvTasks.equals(((Tars) other).rsvTasks) + && this.tags.equals(((Tars) other).tags)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing + // your own + return Objects.hash(tasks, tags, rsvTasks); + } + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/tars/model/UserPrefs.java similarity index 67% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/tars/model/UserPrefs.java index da9c8037f495..c6049d06a3ae 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/tars/model/UserPrefs.java @@ -1,14 +1,18 @@ -package seedu.address.model; - -import seedu.address.commons.core.GuiSettings; +package tars.model; import java.util.Objects; +import tars.commons.core.GuiSettings; + /** * Represents User's preferences. */ public class UserPrefs { + private static final int GUI_SETTINGS_DEFAULT_Y_POSITION = 0; + private static final int GUI_SETTINGS_DEFAULT_X_POSITION = 0; + private static final int GUI_SETTINGS_DEFAULT_HEIGHT = 500; + private static final int GUI_SETTINGS_DEFAULT_WIDTH = 500; public GuiSettings guiSettings; public GuiSettings getGuiSettings() { @@ -20,7 +24,9 @@ public void updateLastUsedGuiSetting(GuiSettings guiSettings) { } public UserPrefs(){ - this.setGuiSettings(500, 500, 0, 0); + this.setGuiSettings(GUI_SETTINGS_DEFAULT_WIDTH, + GUI_SETTINGS_DEFAULT_HEIGHT, GUI_SETTINGS_DEFAULT_X_POSITION, + GUI_SETTINGS_DEFAULT_Y_POSITION); } public void setGuiSettings(double width, double height, int x, int y) { diff --git a/src/main/java/tars/model/qualifiers/DateQualifier.java b/src/main/java/tars/model/qualifiers/DateQualifier.java new file mode 100644 index 000000000000..5e29ee69baf3 --- /dev/null +++ b/src/main/java/tars/model/qualifiers/DateQualifier.java @@ -0,0 +1,47 @@ +package tars.model.qualifiers; + +// @@author A0140022H +import java.time.LocalDateTime; + +import tars.commons.util.DateTimeUtil; +import tars.model.task.DateTime; +import tars.model.task.ReadOnlyTask; + +public class DateQualifier implements Qualifier { + + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + private final DateTime dateTimeQuery; + + public DateQualifier(DateTime dateTime) { + if (dateTime.getStartDate() != null) { + startDateTime = DateTimeUtil.setLocalTime(dateTime.getStartDate(), + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_FIRST_SECOND_OF_DAY); + endDateTime = DateTimeUtil.setLocalTime(dateTime.getEndDate(), + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_LAST_SECOND_OF_DAY); + } else { + startDateTime = DateTimeUtil.setLocalTime(dateTime.getEndDate(), + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_FIRST_HOUR_OF_DAY); + endDateTime = DateTimeUtil.setLocalTime(dateTime.getEndDate(), + DateTimeUtil.DATETIME_LAST_HOUR_OF_DAY, + DateTimeUtil.DATETIME_LAST_MINUTE_OF_DAY, + DateTimeUtil.DATETIME_LAST_SECOND_OF_DAY); + } + + dateTimeQuery = new DateTime(); + dateTimeQuery.setStartDateTime(startDateTime); + dateTimeQuery.setEndDateTime(endDateTime); + } + + @Override + public boolean run(ReadOnlyTask task) { + return DateTimeUtil.isDateTimeWithinRange(task.getDateTime(), + dateTimeQuery); + } +} diff --git a/src/main/java/tars/model/qualifiers/FlagSearchQualifier.java b/src/main/java/tars/model/qualifiers/FlagSearchQualifier.java new file mode 100644 index 000000000000..9164f49dc0a0 --- /dev/null +++ b/src/main/java/tars/model/qualifiers/FlagSearchQualifier.java @@ -0,0 +1,86 @@ +package tars.model.qualifiers; + +//@@author A0124333U + +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; +import tars.model.task.TaskQuery; + +public class FlagSearchQualifier implements Qualifier { + + private TaskQuery taskQuery; + + public FlagSearchQualifier(TaskQuery taskQuery) { + this.taskQuery = taskQuery; + } + + @Override + public boolean run(ReadOnlyTask task) { + + return isNameFound(task) && isDateTimeFound(task) + && isPriorityFound(task) && isStatusFound(task) + && isTagFound(task); + } + + private Boolean isNameFound(ReadOnlyTask task) { + if (taskQuery.getNameKeywordsAsList().get(StringUtil.START_INDEX) + .isEmpty()) { + return true; + } else { + return taskQuery.getNameKeywordsAsList().stream() + .filter(keyword -> StringUtil.containsIgnoreCase( + task.getName().taskName, keyword)) + .count() == taskQuery.getNameKeywordsAsList().size(); + } + } + + private Boolean isDateTimeFound(ReadOnlyTask task) { + if (taskQuery.getDateTimeQueryRange() == null) { + return true; + } else { + return DateTimeUtil.isDateTimeWithinRange(task.getDateTime(), + taskQuery.getDateTimeQueryRange()); + } + } + + private Boolean isPriorityFound(ReadOnlyTask task) { + if (taskQuery.getPriorityKeywordsAsList().get(StringUtil.START_INDEX) + .isEmpty()) { + return true; + } else { + return taskQuery.getPriorityKeywordsAsList().stream() + .filter(keyword -> StringUtil + .containsIgnoreCase(task.priorityString(), keyword)) + .count() == taskQuery.getPriorityKeywordsAsList().size(); + } + } + + private Boolean isStatusFound(ReadOnlyTask task) { + if (taskQuery.getStatusQuery().isEmpty()) { + return true; + } else { + return taskQuery.getStatusQuery() == task.getStatus().toString(); + } + } + + private Boolean isTagFound(ReadOnlyTask task) { + if (taskQuery.getTagKeywordsAsList().get(StringUtil.START_INDEX) + .isEmpty()) { + return true; + } else { + String stringOfTags = task.tagsString() + .replace(StringUtil.STRING_COMMA.trim(), + StringUtil.EMPTY_STRING) + .replace(StringUtil.STRING_SQUARE_BRACKET_OPEN, + StringUtil.EMPTY_STRING) + .replace(StringUtil.STRING_SQUARE_BRACKET_CLOSE, + StringUtil.EMPTY_STRING); + return taskQuery.getTagKeywordsAsList().stream() + .filter(keyword -> StringUtil + .containsIgnoreCase(stringOfTags, keyword)) + .count() == taskQuery.getTagKeywordsAsList().size(); + } + } + +} diff --git a/src/main/java/tars/model/qualifiers/Qualifier.java b/src/main/java/tars/model/qualifiers/Qualifier.java new file mode 100644 index 000000000000..f7b79db1c135 --- /dev/null +++ b/src/main/java/tars/model/qualifiers/Qualifier.java @@ -0,0 +1,10 @@ +package tars.model.qualifiers; + +import tars.model.task.ReadOnlyTask; + +public interface Qualifier { + + boolean run(ReadOnlyTask task); + + String toString(); +} diff --git a/src/main/java/tars/model/qualifiers/QuickSearchQualifier.java b/src/main/java/tars/model/qualifiers/QuickSearchQualifier.java new file mode 100644 index 000000000000..03fe8961907f --- /dev/null +++ b/src/main/java/tars/model/qualifiers/QuickSearchQualifier.java @@ -0,0 +1,43 @@ +package tars.model.qualifiers; + +//@@author A0124333U + +import java.util.ArrayList; + +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; + +public class QuickSearchQualifier implements Qualifier { + + private static final String LABEL_TAGS = "Tags: "; + private static final String LABEL_STATUS = "Status: "; + private static final String LABEL_PRIORITY = "Priority: "; + private static final String LABEL_DATETIME = "DateTime: "; + private final ArrayList quickSearchKeywords; + + public QuickSearchQualifier(ArrayList quickSearchKeywords) { + this.quickSearchKeywords = quickSearchKeywords; + } + + private String removeLabels(String taskAsString) { + String editedString = taskAsString + .replace(StringUtil.STRING_SQUARE_BRACKET_OPEN, + StringUtil.EMPTY_STRING) + .replace(StringUtil.STRING_SQUARE_BRACKET_CLOSE, + StringUtil.STRING_WHITESPACE) + .replace(LABEL_DATETIME, StringUtil.EMPTY_STRING) + .replace(LABEL_PRIORITY, StringUtil.EMPTY_STRING) + .replace(LABEL_STATUS, StringUtil.EMPTY_STRING) + .replace(LABEL_TAGS, StringUtil.EMPTY_STRING); + return editedString; + } + + @Override + public boolean run(ReadOnlyTask task) { + String taskAsString = removeLabels(task.getAsText()); + return quickSearchKeywords.stream().filter( + keyword -> StringUtil.containsIgnoreCase(taskAsString, keyword)) + .count() == quickSearchKeywords.size(); + } + +} diff --git a/src/main/java/tars/model/tag/ReadOnlyTag.java b/src/main/java/tars/model/tag/ReadOnlyTag.java new file mode 100644 index 000000000000..90edce3c05b5 --- /dev/null +++ b/src/main/java/tars/model/tag/ReadOnlyTag.java @@ -0,0 +1,9 @@ +package tars.model.tag; + +// @@author A0139924W +/** + * Unmodifiable view of tars + */ +public interface ReadOnlyTag { + public String getAsText(); +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/tars/model/tag/Tag.java similarity index 54% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/tars/model/tag/Tag.java index 5bcffdb5ddf1..a9b55a4e65c8 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/tars/model/tag/Tag.java @@ -1,21 +1,21 @@ -package seedu.address.model.tag; +package tars.model.tag; - -import seedu.address.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.StringUtil; /** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} + * Represents a Tag in tars. Guarantees: immutable; name is valid as declared in + * {@link #isValidTagName(String)} */ -public class Tag { +public class Tag implements ReadOnlyTag { - public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; + 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() { - } + public Tag() {} /** * Validates given tag name. @@ -31,6 +31,15 @@ public Tag(String name) throws IllegalValueException { this.tagName = name; } + /** + * Copy constructor. + * + * @throws IllegalValueException + */ + public Tag(ReadOnlyTag source) throws IllegalValueException { + this(source.getAsText()); + } + /** * Returns true if a given string is a valid tag name. */ @@ -42,7 +51,7 @@ public static boolean isValidTagName(String test) { 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 + && this.tagName.equals(((Tag) other).tagName)); // state check } @Override @@ -54,7 +63,13 @@ public int hashCode() { * Format state as text for viewing. */ public String toString() { - return '[' + tagName + ']'; + return String.format(StringUtil.STRING_SQUARE_BRACKET_OPEN + "%s" + + StringUtil.STRING_SQUARE_BRACKET_CLOSE, tagName); + } + + @Override + public String getAsText() { + return tagName; } } diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/tars/model/tag/UniqueTagList.java similarity index 68% rename from src/main/java/seedu/address/model/tag/UniqueTagList.java rename to src/main/java/tars/model/tag/UniqueTagList.java index 76fb7ff3dc5d..bdd62bfde799 100644 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ b/src/main/java/tars/model/tag/UniqueTagList.java @@ -1,9 +1,11 @@ -package seedu.address.model.tag; +package tars.model.tag; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.DuplicateDataException; +import tars.commons.core.Messages; +import tars.commons.exceptions.DuplicateDataException; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.CollectionUtil; import java.util.*; @@ -16,17 +18,27 @@ * @see CollectionUtil#elementsAreUnique(Collection) */ public class UniqueTagList implements Iterable { + + private static final int INVALID_INDEX = -1; + private final ObservableList internalList = FXCollections.observableArrayList(); /** * 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"); + super(Messages.MESSAGE_DUPLICATE_TAG); + } + } + + /** + * Signals that tag does not exist in the list. + */ + public static class TagNotFoundException extends Exception { + protected TagNotFoundException() { + super(Messages.MESSAGE_TAG_CANNOT_BE_FOUND); } } - - private final ObservableList internalList = FXCollections.observableArrayList(); /** * Constructs empty TagList. @@ -118,6 +130,40 @@ public void add(Tag toAdd) throws DuplicateTagException { } internalList.add(toAdd); } + + /** + * Remove a Tag from the list. + * + * @throws TagNotFoundException if no such tag could be found. + */ + public void remove(Tag toRemove) throws TagNotFoundException { + assert toRemove != null; + if (!contains(toRemove)) { + throw new TagNotFoundException(); + } + internalList.remove(toRemove); + } + + /** + * Update the equivalent Tag from the list. + * + * @throws TagNotFoundException if no such Tag could be found in the list. + * @throws IllegalValueException if the given tag name string is invalid. + */ + public void update(ReadOnlyTag toBeUpdated, Tag newTag) + throws TagNotFoundException, IllegalValueException { + int selectedIndex = internalList.indexOf(new Tag(toBeUpdated)); + + if (selectedIndex == INVALID_INDEX) { + throw new TagNotFoundException(); + } + + if (contains(newTag)) { + throw new DuplicateTagException(); + } + + internalList.set(selectedIndex, newTag); + } @Override public Iterator iterator() { @@ -132,8 +178,8 @@ public ObservableList getInternalList() { 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)); + && this.internalList.containsAll(((UniqueTagList) other).internalList) + && ((UniqueTagList) other).internalList.containsAll(this.internalList)); } @Override diff --git a/src/main/java/tars/model/task/DateTime.java b/src/main/java/tars/model/task/DateTime.java new file mode 100644 index 000000000000..64055795864a --- /dev/null +++ b/src/main/java/tars/model/task/DateTime.java @@ -0,0 +1,158 @@ +package tars.model.task; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import tars.commons.core.Messages; +import tars.commons.exceptions.IllegalValueException; + +/** + * Represents a Task's dateTime in tars. + */ +public class DateTime implements Comparable { + + public static final String MESSAGE_DATETIME_CONSTRAINTS = + "Task datetime should be spaces or alphanumeric characters"; + + private static final int COMPARE_TO_EQUALS = 0; + private static final int COMPARE_TO_SMALLER = -1; + private static final int COMPARE_TO_GREATER = 1; + + private static final DateTimeFormatter formatter = DateTimeFormatter + .ofPattern("d/M/uuuu HHmm").withResolverStyle(ResolverStyle.STRICT); + private static final DateTimeFormatter stringFormatter = + DateTimeFormatter.ofPattern("dd/MM/uuuu HHmm"); + + private static final String DATETIME_STRING_TO = " to "; + private static final String DATETIME_STRING_EMPTY = ""; + private static final int DATETIME_SIZE_EMPTY = 0; + + public String startDateString; + public String endDateString; + private LocalDateTime startDate; + private LocalDateTime endDate; + + + /** + * Default constructor + */ + public DateTime() {} + + /** + * Validates given task dateTime. + * + * @throws DateTimeException if given dateTime string is invalid. + * @throws IllegalDateException end date occurring before start date. + */ + public DateTime(String startDate, String endDate) + throws DateTimeException, IllegalDateException { + if (endDate != null && endDate.length() > DATETIME_SIZE_EMPTY) { + this.endDate = LocalDateTime.parse(endDate, formatter); + this.endDateString = this.endDate.format(stringFormatter); + } + + if (startDate != null && startDate.length() > DATETIME_SIZE_EMPTY) { + this.startDate = LocalDateTime.parse(startDate, formatter); + this.startDateString = this.startDate.format(stringFormatter); + if (this.endDate.isBefore(this.startDate) + || this.endDate.isEqual(this.startDate)) { + throw new IllegalDateException( + Messages.MESSAGE_INVALID_END_DATE); + } + } + } + + public DateTime(LocalDateTime startDate, LocalDateTime endDate) { + assert (startDate != null && endDate != null); + + this.startDate = startDate; + this.endDate = endDate; + this.endDateString = this.endDate.format(stringFormatter); + this.startDateString = this.startDate.format(stringFormatter); + } + + public LocalDateTime getStartDate() { + return this.startDate; + } + + public LocalDateTime getEndDate() { + return this.endDate; + } + + @Override + public String toString() { + if (this.startDate != null && this.endDate != null) { + return startDateString + DATETIME_STRING_TO + endDateString; + } else if (this.endDate != null) { + return endDateString; + } else { + return DATETIME_STRING_EMPTY; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTime // instanceof handles nulls + && this.toString() + .equals(((DateTime) other).toString())); // state check + } + + /** + * Signals an error caused by end date occurring before start date + */ + public class IllegalDateException extends IllegalValueException { + public IllegalDateException(String message) { + super(message); + } + } + + @Override + public int compareTo(DateTime o) { + if (this.startDate == null && this.endDate == null) { + if (o.startDate == null && o.endDate == null) { + return COMPARE_TO_EQUALS; + } else { + return COMPARE_TO_SMALLER; + } + } + + if (this.startDate == null && this.endDate != null) { + if (o.startDate != null && o.endDate != null) { + return this.endDate.compareTo(o.startDate); + } else if (o.endDate != null) { + return this.endDate.compareTo(o.endDate); + } else { + return COMPARE_TO_GREATER; + } + } + + if (this.startDate != null && this.endDate != null) { + if (o.startDate != null && o.endDate != null) { + int result = this.startDate.compareTo(o.startDate); + if (result == COMPARE_TO_EQUALS) { + return this.endDate.compareTo(o.endDate); + } else { + return result; + } + } else if (o.startDate == null && o.endDate != null) { + return this.startDate.compareTo(o.endDate); + } else { + return COMPARE_TO_GREATER; + } + } + + return COMPARE_TO_EQUALS; + } + + public void setStartDateTime(LocalDateTime startDate) { + this.startDate = startDate; + } + + public void setEndDateTime(LocalDateTime endDate) { + this.endDate = endDate; + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/tars/model/task/Name.java similarity index 56% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/tars/model/task/Name.java index 4f30033e70fe..b55a7030cf69 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/tars/model/task/Name.java @@ -1,20 +1,22 @@ -package seedu.address.model.person; +package tars.model.task; -import seedu.address.commons.exceptions.IllegalValueException; +import tars.commons.exceptions.IllegalValueException; /** - * Represents a Person's name in the address book. + * Represents a Task's name in tars. * 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 static final String MESSAGE_NAME_CONSTRAINTS = "Task names should be spaces " + + "or alphanumeric characters " + + "or these special characters: !@#$%^&*)("; + public static final String NAME_VALIDATION_REGEX = "^[a-zA-Z0-9 !@#$%^&*)(']*$"; - public final String fullName; + public String taskName; /** - * Validates given name. + * Validates given task name. * * @throws IllegalValueException if given name string is invalid. */ @@ -24,32 +26,31 @@ public Name(String name) throws IllegalValueException { if (!isValidName(name)) { throw new IllegalValueException(MESSAGE_NAME_CONSTRAINTS); } - this.fullName = name; + this.taskName = name; } /** - * Returns true if a given string is a valid person name. + * Returns true if a given string is a valid task name. */ public static boolean isValidName(String test) { return test.matches(NAME_VALIDATION_REGEX); } - - + @Override public String toString() { - return fullName; + return taskName; } @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 + && this.taskName.equals(((Name) other).taskName)); // state check } @Override public int hashCode() { - return fullName.hashCode(); + return taskName.hashCode(); } } diff --git a/src/main/java/tars/model/task/Priority.java b/src/main/java/tars/model/task/Priority.java new file mode 100644 index 000000000000..f7104fe7c594 --- /dev/null +++ b/src/main/java/tars/model/task/Priority.java @@ -0,0 +1,89 @@ +package tars.model.task; + +import tars.commons.exceptions.IllegalValueException; + +/** + * Represents a Task's priority in tars. + */ +public class Priority implements Comparable { + + + public static final String MESSAGE_PRIORITY_CONSTRAINTS = "Task priority should be h / m / l"; + public static final String PRIORITY_VALIDATION_REGEX = "^[hml]$"; + + private static final String PRIORITY_EMPTY_STRING = ""; + + private static final String PRIORITY_LEVEL_LOW = "l"; + private static final String PRIORITY_LEVEL_MEDIUM = "m"; + private static final String PRIORITY_LEVEL_HIGH = "h"; + + private static final int PRIORITY_LEVEL_ONE = 1; + private static final int PRIORITY_LEVEL_TWO = 2; + private static final int PRIORITY_LEVEL_THREE = 3; + + private static final int PRIORITY_COMPARE_EQUAL = 0; + private static final int PRIORITY_COMPARE_SMALLER = -1; + private static final int PRIORITY_COMPARE_BIGGER = 1; + + public String priorityLevel; + private int level; + + /** + * Validates given task priority level. + * + * @throws IllegalValueException if given priority level string is invalid. + */ + public Priority(String priorityLevel) throws IllegalValueException { + assert priorityLevel != null; + priorityLevel = priorityLevel.trim(); + if (!isValidPriorityLevel(priorityLevel)) { + throw new IllegalValueException(MESSAGE_PRIORITY_CONSTRAINTS); + } + this.priorityLevel = priorityLevel; + + switch (this.priorityLevel) { + case PRIORITY_LEVEL_LOW : + level = PRIORITY_LEVEL_ONE; + break; + case PRIORITY_LEVEL_MEDIUM : + level = PRIORITY_LEVEL_TWO; + break; + case PRIORITY_LEVEL_HIGH : + level = PRIORITY_LEVEL_THREE; + break; + default : + break; + } + } + + /** + * Returns true if a given string is a valid task priority level. + */ + public static boolean isValidPriorityLevel(String level) { + return level.equals(PRIORITY_EMPTY_STRING) ? true : level.matches(PRIORITY_VALIDATION_REGEX); + } + + @Override + public String toString() { + return priorityLevel; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Priority // instanceof handles nulls + && this.toString().equals(((Priority) other).toString())); // state check + } + + @Override + public int compareTo(Priority o) { + if (this.level > o.level) { + return PRIORITY_COMPARE_BIGGER; + } else if (this.level < o.level) { + return PRIORITY_COMPARE_SMALLER; + } else { + return PRIORITY_COMPARE_EQUAL; + } + } + +} diff --git a/src/main/java/tars/model/task/ReadOnlyTask.java b/src/main/java/tars/model/task/ReadOnlyTask.java new file mode 100644 index 000000000000..7a97e54a92b7 --- /dev/null +++ b/src/main/java/tars/model/task/ReadOnlyTask.java @@ -0,0 +1,102 @@ +package tars.model.task; + +import tars.commons.util.StringUtil; +import tars.model.tag.UniqueTagList; + +/** + * A read-only immutable interface for a Task in TARS. Implementations should guarantee: details are + * present and not null, field values are validated. + */ +public interface ReadOnlyTask { + + public static final int BUFFER_LENGTH_ZERO = 0; + public static final String STRING_TAGS = " Tags: "; + public static final String STRING_STATUS = " Status: "; + public static final String STRING_PRIORITY = " Priority: "; + public static final String STRING_DATE_TIME = " DateTime: "; + public static final String PRIORITY_HIGH = "high"; + public static final String PRIORITY_MEDIUM = "medium"; + public static final String PRIORITY_LOW = "low"; + + public static final String PRIORITY_H = "h"; + public static final String PRIORITY_M = "m"; + public static final String PRIORITY_L = "l"; + + Name getName(); + + DateTime getDateTime(); + + Priority getPriority(); + + Status getStatus(); + + + /** + * The returned TagList is a deep copy of the internal TagList, changes on the returned list + * will not affect the task's internal tags. + */ + UniqueTagList getTags(); + + /** + * Returns true if both have the same state. (interfaces cannot override .equals) + */ + default boolean isSameStateAs(ReadOnlyTask 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.getDateTime().equals(this.getDateTime()) + && other.getPriority().equals(this.getPriority()) + && other.getTags().equals(this.getTags()) + && other.getStatus().equals(this.getStatus())); + } + + /** + * Formats the task as text, showing all contact details. + */ + default String getAsText() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()).append(STRING_DATE_TIME).append(getDateTime()) + .append(STRING_PRIORITY).append(priorityString()) + .append(STRING_STATUS).append(getStatus().toString()) + .append(STRING_TAGS); + getTags().forEach(builder::append); + return builder.toString(); + } + + /** + * Returns a string representation of this Task's priority + */ + default String priorityString() { + String level = StringUtil.EMPTY_STRING; + switch (getPriority().toString()) { + case PRIORITY_H: + level = PRIORITY_HIGH; + break; + case PRIORITY_M: + level = PRIORITY_MEDIUM; + break; + case PRIORITY_L: + level = PRIORITY_LOW; + break; + default: + level = StringUtil.EMPTY_STRING; + break; + } + return level; + } + + /** + * Returns a string representation of this Task's tags + */ + default String tagsString() { + final StringBuffer buffer = new StringBuffer(); + final String separator = StringUtil.STRING_COMMA; + getTags().forEach(tag -> buffer.append(tag).append(separator)); + if (buffer.length() == BUFFER_LENGTH_ZERO) { + return StringUtil.EMPTY_STRING; + } else { + return buffer.substring(0, buffer.length() - separator.length()); + } + } + +} diff --git a/src/main/java/tars/model/task/Status.java b/src/main/java/tars/model/task/Status.java new file mode 100644 index 000000000000..c8b812378b1a --- /dev/null +++ b/src/main/java/tars/model/task/Status.java @@ -0,0 +1,45 @@ +package tars.model.task; + +/** + * Represents a Task's status in tars. + */ +public class Status { + + public static final boolean DONE = true; + public static final boolean UNDONE = false; + private static final String MESSAGE_STATUS_DONE = "Done"; + private static final String MESSAGE_STATUS_UNDONE = "Undone"; + + public boolean status; + + /** + * Default constructor + */ + public Status() { + status = UNDONE; + } + + /** + * For storage + */ + public Status(boolean status) { + this.status = status; + } + + @Override + public String toString() { + if (status) { + return MESSAGE_STATUS_DONE; + } else { + return MESSAGE_STATUS_UNDONE; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Status // instanceof handles nulls + && this.toString().equals(((Status) other).toString())); // state check + } + +} diff --git a/src/main/java/tars/model/task/Task.java b/src/main/java/tars/model/task/Task.java new file mode 100644 index 000000000000..99087a89eef5 --- /dev/null +++ b/src/main/java/tars/model/task/Task.java @@ -0,0 +1,113 @@ +package tars.model.task; + +import tars.commons.util.CollectionUtil; +import tars.model.tag.UniqueTagList; + +import java.util.Objects; + +/** + * Represents a Task in tars. + * Guarantees: details are present and not null, field values are validated. + */ +public class Task implements ReadOnlyTask { + + protected Name name; + protected DateTime dateTime; + protected Status status; + protected Priority priority; + + private UniqueTagList tags; + + /** + * Every field must be present and not null. + */ + + public Task(Name name, DateTime dateTime, Priority priority, Status status, UniqueTagList tags) { + assert !CollectionUtil.isAnyNull(name, dateTime, priority, status, tags); + this.name = name; + this.dateTime = dateTime; + this.priority = priority; + this.status = status; + this.tags = new UniqueTagList(tags); // protect internal tags from changes in the arg list + } + + /** + * Copy constructor. + */ + public Task(ReadOnlyTask source) { + this(source.getName(), source.getDateTime(), source.getPriority(), source.getStatus(), source.getTags()); + } + + /** + * Default constructor. + */ + public Task() { + } + + @Override + public Name getName() { + return name; + } + + @Override + public DateTime getDateTime() { + return dateTime; + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public Priority getPriority() { + return priority; + } + + @Override + public UniqueTagList getTags() { + return new UniqueTagList(tags); + } + + public void setName(Name name) { + this.name = name; + } + + public void setDateTime(DateTime dateTime) { + this.dateTime = dateTime; + } + + public void setPriority(Priority priority) { + this.priority = priority; + } + + public void setStatus(Status status) { + this.status = status; + } + + /** + * Replaces this task'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 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(name, dateTime, priority, status, tags); + } + + @Override + public String toString() { + return getAsText(); + } + +} diff --git a/src/main/java/tars/model/task/TaskQuery.java b/src/main/java/tars/model/task/TaskQuery.java new file mode 100644 index 000000000000..ed49f07a555b --- /dev/null +++ b/src/main/java/tars/model/task/TaskQuery.java @@ -0,0 +1,133 @@ +package tars.model.task; + +import java.time.DateTimeException; +import java.util.ArrayList; +import java.util.Arrays; + +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.StringUtil; +import tars.model.tag.UniqueTagList; +import tars.model.task.DateTime.IllegalDateException; + +// @@author A0124333U +public class TaskQuery extends Task { + + public final static String MESSAGE_BOTH_STATUS_SEARCHED_ERROR = + "Both '-do (Done)' and '-ud (Undone)' flags " + + "have been detected.\n" + + "Please search for either '-do (Done)' or '-ud (Undone)' status"; + + private String tagQuery = ""; + private String statusString = ""; + + public TaskQuery() {} + + public TaskQuery(Name name, DateTime dateTime, Priority priority, + Status status, UniqueTagList tags) { + super(name, dateTime, priority, status, tags); + } + + /* --------------- SETTER METHODS -------------------- */ + + public void createNameQuery(String nameQueryString) + throws IllegalValueException { + name = new Name(nameQueryString); + } + + public void createDateTimeQuery(String[] dateTimeQueryString) + throws DateTimeException, IllegalDateException { + dateTime = new DateTime(dateTimeQueryString[0], dateTimeQueryString[1]); + } + + public void createPriorityQuery(String priorityString) + throws IllegalValueException { + + /* + * To convert long versions of priority strings (i.e. high, medium, low) into characters + * (i.e. h, m, l) + */ + switch (priorityString) { + case PRIORITY_HIGH: + priorityString = PRIORITY_H; + break; + + case PRIORITY_MEDIUM: + priorityString = PRIORITY_M; + break; + + case PRIORITY_LOW: + priorityString = PRIORITY_L; + break; + + default: + break; + } + + priority = new Priority(priorityString); + } + + public void createStatusQuery(Boolean statusQuery) { + status = new Status(); + status.status = statusQuery; + } + + public void createTagsQuery(String tagQueryString) { + tagQuery = tagQueryString; + } + + /* --------------- GETTER METHODS -------------------- */ + + public ArrayList getNameKeywordsAsList() { + return new ArrayList(Arrays.asList( + getName().taskName.split(StringUtil.STRING_WHITESPACE))); + } + + public DateTime getDateTimeQueryRange() { + if (getDateTime().getEndDate() != null) { + return getDateTime(); + } else { + return null; + } + } + + public ArrayList getPriorityKeywordsAsList() { + return new ArrayList(Arrays.asList(priorityString())); + } + + public String getStatusQuery() { + if (status != null) { + statusString = status.toString(); + } + return statusString; + } + + public ArrayList getTagKeywordsAsList() { + return new ArrayList( + Arrays.asList(tagQuery.split(StringUtil.STRING_WHITESPACE))); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Filter Search Keywords: "); + + if (!getName().toString().isEmpty()) { + builder.append("[Task Name: ").append(getName()).append("] "); + } + if (getDateTime().getEndDate() != null) { + builder.append("[DateTime: ").append(getDateTime()).append("] "); + } + if (!priorityString().isEmpty()) { + builder.append("[Priority: ").append(priorityString()).append("] "); + } + if (!statusString.isEmpty()) { + builder.append("[Status: ").append(statusString).append("] "); + } + if (!tagQuery.isEmpty()) { + builder.append("[Tags: ").append(tagQuery).append("]"); + } + + return builder.toString(); + } + +} diff --git a/src/main/java/tars/model/task/UniqueTaskList.java b/src/main/java/tars/model/task/UniqueTaskList.java new file mode 100644 index 000000000000..e15adf0d7e00 --- /dev/null +++ b/src/main/java/tars/model/task/UniqueTaskList.java @@ -0,0 +1,85 @@ +package tars.model.task; + +import java.util.Collection; +import java.util.Iterator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.util.CollectionUtil; + +/** + * 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) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * 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 {} + + /** + * 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); + } + + /** + * Adds a task to the list. + * + * @throws DuplicateTaskException if the task to add is a duplicate of an existing task in the list. + */ + public void add(Task toAdd) throws DuplicateTaskException { + assert toAdd != null; + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + assert toRemove != null; + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + 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 UniqueTaskList // instanceof handles nulls + && this.internalList.equals( + ((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/tars/model/task/rsv/RsvTask.java b/src/main/java/tars/model/task/rsv/RsvTask.java new file mode 100644 index 000000000000..14450b4b1476 --- /dev/null +++ b/src/main/java/tars/model/task/rsv/RsvTask.java @@ -0,0 +1,93 @@ +package tars.model.task.rsv; + +import java.util.ArrayList; +import java.util.Objects; +import tars.commons.util.CollectionUtil; +import tars.model.task.DateTime; +import tars.model.task.Name; + +// @@author A0124333U +/** + * A task that has unconfirmed, reserved dates. + */ +public class RsvTask { + + private static String RSV_TASK_STRING = "%1$s DateTime: %2$s"; + + protected Name name; + protected ArrayList dateTimeList = new ArrayList(); + + public RsvTask() { + + } + + public RsvTask(Name name, ArrayList dateTimeList) { + assert !CollectionUtil.isAnyNull(name, dateTimeList); + + this.name = name; + this.dateTimeList = dateTimeList; + + } + + /** + * Copy constructor. + */ + public RsvTask(RsvTask source) { + this(source.getName(), source.getDateTimeList()); + } + + /* + * Accessors + */ + + public Name getName() { + return name; + } + + public ArrayList getDateTimeList() { + return dateTimeList; + } + + /* + * Mutators + */ + public void setName(Name name) { + this.name = name; + } + + public void setDateTimeList(ArrayList dateTimeList) { + this.dateTimeList = dateTimeList; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RsvTask // instanceof handles nulls + && this.isSameStateAs((RsvTask) other)); + } + + public boolean isSameStateAs(RsvTask other) { + return other == this // short circuit if same object + || (other != null // this is first to avoid NPE below + && other.getName().equals(this.getName()) + && other.getDateTimeList() + .equals(this.getDateTimeList())); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing + // your own + return Objects.hash(name, dateTimeList); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(String.format(RSV_TASK_STRING, getName(), + getDateTimeList().toString())); + + return builder.toString(); + } + +} diff --git a/src/main/java/tars/model/task/rsv/UniqueRsvTaskList.java b/src/main/java/tars/model/task/rsv/UniqueRsvTaskList.java new file mode 100644 index 000000000000..58f49bfaeb6d --- /dev/null +++ b/src/main/java/tars/model/task/rsv/UniqueRsvTaskList.java @@ -0,0 +1,94 @@ +package tars.model.task.rsv; + +import java.util.Collection; +import java.util.Iterator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.util.CollectionUtil; + +// @@author A0124333U +/** + * A list of reserved tasks that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see RsvTask#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueRsvTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * 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 RsvTaskNotFoundException extends Exception {} + + /** + * Constructs empty RsvTaskList. + */ + public UniqueRsvTaskList() { + } + + /** + * Returns true if the list contains an equivalent reserved task as the given argument. + */ + public boolean contains(RsvTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Adds a reserved task to the list. + * + * @throws DuplicateTaskException if the reserved task to add is a duplicate of an existing + * reserved task in the list. + */ + public void add(RsvTask toAdd) throws DuplicateTaskException { + assert toAdd != null; + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent reserved task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public boolean remove(RsvTask toRemove) throws RsvTaskNotFoundException { + assert toRemove != null; + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new RsvTaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + 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 UniqueRsvTaskList // instanceof handles nulls + && this.internalList.equals( + ((UniqueRsvTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/address/storage/JsonUserPrefStorage.java b/src/main/java/tars/storage/JsonUserPrefStorage.java similarity index 57% rename from src/main/java/seedu/address/storage/JsonUserPrefStorage.java rename to src/main/java/tars/storage/JsonUserPrefStorage.java index 3a145ce35f15..e05aad874039 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefStorage.java +++ b/src/main/java/tars/storage/JsonUserPrefStorage.java @@ -1,30 +1,36 @@ -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 tars.storage; import java.io.File; import java.io.IOException; import java.util.Optional; import java.util.logging.Logger; +import tars.commons.core.LogsCenter; +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.FileUtil; +import tars.model.UserPrefs; + /** * A class to access UserPrefs stored in the hard disk as a json file */ -public class JsonUserPrefStorage implements UserPrefsStorage{ +public class JsonUserPrefStorage implements UserPrefsStorage { - private static final Logger logger = LogsCenter.getLogger(JsonUserPrefStorage.class); + private static final Logger logger = + LogsCenter.getLogger(JsonUserPrefStorage.class); + private static String LOG_MESSAGE_PREF_FILE_NOT_FOUND = + "Prefs file %s not found"; + private static String LOG_MESSAGE_PREF_FILE_READING_ERROR = + "Error reading from prefs file %1$s: %2$s"; private String filePath; - public JsonUserPrefStorage(String filePath){ + public JsonUserPrefStorage(String filePath) { this.filePath = filePath; } @Override - public Optional readUserPrefs() throws DataConversionException, IOException { + public Optional readUserPrefs() + throws DataConversionException, IOException { return readUserPrefs(filePath); } @@ -35,25 +41,30 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataConversionException if the file format is not as expected. */ - public Optional readUserPrefs(String prefsFilePath) throws DataConversionException { + public Optional readUserPrefs(String prefsFilePath) + throws DataConversionException { assert prefsFilePath != null; File prefsFile = new File(prefsFilePath); if (!prefsFile.exists()) { - logger.info("Prefs file " + prefsFile + " not found"); + logger.info( + String.format(LOG_MESSAGE_PREF_FILE_NOT_FOUND, prefsFile)); return Optional.empty(); } UserPrefs prefs; try { - prefs = FileUtil.deserializeObjectFromJsonFile(prefsFile, UserPrefs.class); + prefs = FileUtil.deserializeObjectFromJsonFile(prefsFile, + UserPrefs.class); } catch (IOException e) { - logger.warning("Error reading from prefs file " + prefsFile + ": " + e); + logger.warning(String.format(LOG_MESSAGE_PREF_FILE_READING_ERROR, + prefsFile, e)); throw new DataConversionException(e); } @@ -62,9 +73,11 @@ public Optional readUserPrefs(String prefsFilePath) throws DataConver /** * Similar to {@link #saveUserPrefs(UserPrefs)} + * * @param prefsFilePath location of the data. Cannot be null. */ - public void saveUserPrefs(UserPrefs userPrefs, String prefsFilePath) throws IOException { + public void saveUserPrefs(UserPrefs userPrefs, String prefsFilePath) + throws IOException { assert userPrefs != null; assert prefsFilePath != null; diff --git a/src/main/java/tars/storage/Storage.java b/src/main/java/tars/storage/Storage.java new file mode 100644 index 000000000000..5dcde2bbf283 --- /dev/null +++ b/src/main/java/tars/storage/Storage.java @@ -0,0 +1,55 @@ +package tars.storage; + +import tars.commons.core.Config; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.events.storage.DataSavingExceptionEvent; +import tars.commons.exceptions.DataConversionException; +import tars.model.ReadOnlyTars; +import tars.model.UserPrefs; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; + +/** + * API of the Storage component + */ +public interface Storage extends TarsStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(UserPrefs userPrefs) throws IOException; + + @Override + String getTarsFilePath(); + + @Override + Optional readTars() throws DataConversionException, FileNotFoundException; + + @Override + void saveTars(ReadOnlyTars tars) 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 handleTarsChangedEvent(TarsChangedEvent abce); + + // @@author A0124333U + /** + * Updates Tars Storage Directory + * + * @param newFilePath + * @param newConfig + */ + void updateTarsStorageDirectory(String newFilePath, Config newConfig); + + void saveTarsInNewFilePath(ReadOnlyTars tars, String newFilePath) throws IOException; + + boolean isFileSavedSuccessfully(String filePath); + + Optional readTarsFromNewFilePath(String newFilePath) + throws DataConversionException, FileNotFoundException; +} diff --git a/src/main/java/tars/storage/StorageManager.java b/src/main/java/tars/storage/StorageManager.java new file mode 100644 index 000000000000..2dca46445ddd --- /dev/null +++ b/src/main/java/tars/storage/StorageManager.java @@ -0,0 +1,129 @@ +package tars.storage; + +import com.google.common.eventbus.Subscribe; + +import tars.commons.core.ComponentManager; +import tars.commons.core.Config; +import tars.commons.core.LogsCenter; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.events.storage.DataSavingExceptionEvent; +import tars.commons.events.storage.TarsStorageDirectoryChangedEvent; +import tars.commons.exceptions.DataConversionException; +import tars.model.ReadOnlyTars; +import tars.model.UserPrefs; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * Manages storage of Tars data in local storage. + */ +public class StorageManager extends ComponentManager implements Storage { + + private static final Logger logger = + LogsCenter.getLogger(StorageManager.class); + private static String LOG_MESSAGE_READING_DATA_FROM_FILE = + "Attempting to read data from file: %s"; + private static String LOG_MESSAGE_READING_DATA_FROM_CHANGED_FILEPATH = + "Attempting to read data from changed file path: %s"; + private static final String LOG_MESSAGE_LOCAL_DATA_CHANGED = + "Local data changed, saving to file"; + + private XmlTarsStorage tarsStorage; + private JsonUserPrefStorage userPrefStorage; + + public StorageManager(String tarsFilePath, String userPrefsFilePath) { + super(); + tarsStorage = new XmlTarsStorage(tarsFilePath); + this.userPrefStorage = new JsonUserPrefStorage(userPrefsFilePath); + } + + // ================ UserPrefs methods ============================== + + public StorageManager() {} + + @Override + public Optional readUserPrefs() + throws DataConversionException, IOException { + return userPrefStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(UserPrefs userPrefs) throws IOException { + userPrefStorage.saveUserPrefs(userPrefs); + } + + + // ================ Tars methods ============================== + + @Override + public String getTarsFilePath() { + return tarsStorage.getTarsFilePath(); + } + + @Override + public Optional readTars() + throws DataConversionException, FileNotFoundException { + logger.fine(String.format(LOG_MESSAGE_READING_DATA_FROM_FILE, + tarsStorage.getTarsFilePath())); + return tarsStorage.readTars(tarsStorage.getTarsFilePath()); + } + + public Optional readTarsFromNewFilePath(String newFilePath) + throws DataConversionException, FileNotFoundException { + tarsStorage = new XmlTarsStorage(newFilePath); + logger.fine(String.format( + LOG_MESSAGE_READING_DATA_FROM_CHANGED_FILEPATH, newFilePath)); + return tarsStorage.readTars(newFilePath); + } + + @Override + public void saveTars(ReadOnlyTars tars) throws IOException { + tarsStorage.saveTars(tars, tarsStorage.getTarsFilePath()); + } + + // @@author A0124333U + @Override + public void saveTarsInNewFilePath(ReadOnlyTars tars, String newFilePath) + throws IOException { + tarsStorage = new XmlTarsStorage(newFilePath); + tarsStorage.saveTars(tars, newFilePath); + } + + public boolean isFileSavedSuccessfully(String filePath) { + Path path = Paths.get(filePath); + return Files.exists(path); + } + + public void updateTarsStorageDirectory(String newFilePath, + Config newConfig) { + tarsStorage = new XmlTarsStorage(newFilePath); + indicateTarsStorageDirectoryChanged(newFilePath, newConfig); + } + + // Raise an event that the tars storage directory has changed + private void indicateTarsStorageDirectoryChanged(String newFilePath, + Config newConfig) { + raise(new TarsStorageDirectoryChangedEvent(newFilePath, newConfig)); + } + + // @@author + + @Override + @Subscribe + public void handleTarsChangedEvent(TarsChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_LOCAL_DATA_CHANGED)); + try { + saveTars(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + +} diff --git a/src/main/java/tars/storage/TarsStorage.java b/src/main/java/tars/storage/TarsStorage.java new file mode 100644 index 000000000000..3fe852eadb5b --- /dev/null +++ b/src/main/java/tars/storage/TarsStorage.java @@ -0,0 +1,36 @@ +package tars.storage; + +import tars.commons.exceptions.DataConversionException; +import tars.model.ReadOnlyTars; + +import java.io.IOException; +import java.util.Optional; + +/** + * Represents a storage for {@link tars.model.Tars}. + */ +public interface TarsStorage { + + /** + * Returns the file path of the data file. + */ + String getTarsFilePath(); + + /** + * Returns Tars data as a {@link ReadOnlyTars}. 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 readTars() throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTars} to the storage. + * + * @param tars cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTars(ReadOnlyTars tars) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/tars/storage/UserPrefsStorage.java similarity index 59% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/tars/storage/UserPrefsStorage.java index ad2dc935187c..2365a9cbb199 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/tars/storage/UserPrefsStorage.java @@ -1,26 +1,28 @@ -package seedu.address.storage; +package tars.storage; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; +import tars.commons.exceptions.DataConversionException; +import tars.model.UserPrefs; import java.io.IOException; import java.util.Optional; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link tars.model.UserPrefs}. */ public interface UserPrefsStorage { /** - * Returns UserPrefs data from storage. - * Returns {@code Optional.empty()} if storage file is not found. + * Returns UserPrefs 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 readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * Saves the given {@link tars.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/tars/storage/XmlAdaptedRsvTask.java b/src/main/java/tars/storage/XmlAdaptedRsvTask.java new file mode 100644 index 000000000000..b7f958e6f35c --- /dev/null +++ b/src/main/java/tars/storage/XmlAdaptedRsvTask.java @@ -0,0 +1,60 @@ +package tars.storage; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlElement; + +import tars.commons.exceptions.IllegalValueException; +import tars.model.task.DateTime; +import tars.model.task.Name; +import tars.model.task.rsv.RsvTask; + +/** + * JAXB-friendly version of the RsvTask. + */ +public class XmlAdaptedRsvTask { + + @XmlElement(required = true) + private String name; + + @XmlElement + private List reservedDateTime; + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedRsvTask() { + } + + /** + * Converts a given Reserved Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedRsvTask + */ + public XmlAdaptedRsvTask(RsvTask source) { + name = source.getName().taskName; + reservedDateTime = new ArrayList<>(); + for (DateTime dt : source.getDateTimeList()) { + reservedDateTime.add(dt); + } + } + + /** + * Converts this jaxb-friendly adapted task object into the model's RsvTask object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted + * rsvTask + */ + public RsvTask toModelType() throws IllegalValueException { + + ArrayList dateTimeList = new ArrayList(); + for (DateTime dt : this.reservedDateTime) { + dateTimeList.add(new DateTime(dt.startDateString, dt.endDateString)); + } + + final Name name = new Name(this.name); + + return new RsvTask(name, dateTimeList); + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/tars/storage/XmlAdaptedTag.java similarity index 77% rename from src/main/java/seedu/address/storage/XmlAdaptedTag.java rename to src/main/java/tars/storage/XmlAdaptedTag.java index b9723fafbc67..a470725ae0db 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/tars/storage/XmlAdaptedTag.java @@ -1,8 +1,7 @@ -package seedu.address.storage; +package tars.storage; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import tars.commons.exceptions.IllegalValueException; +import tars.model.tag.Tag; import javax.xml.bind.annotation.XmlValue; @@ -31,7 +30,7 @@ public XmlAdaptedTag(Tag source) { /** * 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 + * @throws IllegalValueException if there were any data constraints violated in the adapted task */ public Tag toModelType() throws IllegalValueException { return new Tag(tagName); diff --git a/src/main/java/tars/storage/XmlAdaptedTask.java b/src/main/java/tars/storage/XmlAdaptedTask.java new file mode 100644 index 000000000000..0fdb00421743 --- /dev/null +++ b/src/main/java/tars/storage/XmlAdaptedTask.java @@ -0,0 +1,69 @@ +package tars.storage; + +import tars.commons.exceptions.IllegalValueException; +import tars.model.task.*; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; + +import javax.xml.bind.annotation.XmlElement; +import java.util.ArrayList; +import java.util.List; + +/** + * JAXB-friendly version of the Task. + */ +public class XmlAdaptedTask { + + @XmlElement(required = true) + private String name; + @XmlElement + private String priority; + @XmlElement + private DateTime dateTime; + @XmlElement + private boolean status; + + + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * 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) { + name = source.getName().taskName; + priority = source.getPriority().priorityLevel; + dateTime = source.getDateTime(); + status = source.getStatus().status; + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted task object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + final Name name = new Name(this.name); + final Priority priority = new Priority(this.priority); + final DateTime dateTime = new DateTime(this.dateTime.startDateString, this.dateTime.endDateString); + final Status status = new Status(this.status); + final UniqueTagList tags = new UniqueTagList(taskTags); + return new Task(name, dateTime, priority, status, tags); + } +} diff --git a/src/main/java/tars/storage/XmlFileStorage.java b/src/main/java/tars/storage/XmlFileStorage.java new file mode 100644 index 000000000000..d186616a00ac --- /dev/null +++ b/src/main/java/tars/storage/XmlFileStorage.java @@ -0,0 +1,42 @@ +package tars.storage; + +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.XmlUtil; + +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileNotFoundException; + +/** + * Stores TARS data in an XML file + */ +public class XmlFileStorage { + private static String MESSAGE_UNEXPECTED_EXCEPTION = + "Unexpected exception %s"; + + /** + * Saves the given TARS data to the specified file. + */ + public static void saveDataToFile(File file, XmlSerializableTars tars) + throws FileNotFoundException { + try { + XmlUtil.saveDataToFile(file, tars); + } catch (JAXBException e) { + assert false : String.format(MESSAGE_UNEXPECTED_EXCEPTION, + e.getMessage()); + } + } + + /** + * Returns address book in the file or an empty address book + */ + public static XmlSerializableTars loadDataFromSaveFile(File file) + throws DataConversionException, FileNotFoundException { + try { + return XmlUtil.getDataFromFile(file, XmlSerializableTars.class); + } catch (JAXBException e) { + throw new DataConversionException(e); + } + } + +} diff --git a/src/main/java/tars/storage/XmlSerializableTars.java b/src/main/java/tars/storage/XmlSerializableTars.java new file mode 100644 index 000000000000..a4744e6376cb --- /dev/null +++ b/src/main/java/tars/storage/XmlSerializableTars.java @@ -0,0 +1,119 @@ +package tars.storage; + +import tars.commons.exceptions.IllegalValueException; +import tars.model.ReadOnlyTars; +import tars.model.task.ReadOnlyTask; +import tars.model.task.UniqueTaskList; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; + +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 Tars that is serializable to XML format + */ +@XmlRootElement(name = "tars") +public class XmlSerializableTars implements ReadOnlyTars { + + @XmlElement + private List tasks; + @XmlElement + private List tags; + @XmlElement + private List rsvTasks; + + { + tasks = new ArrayList<>(); + tags = new ArrayList<>(); + rsvTasks = new ArrayList<>(); + } + + /** + * Empty constructor required for marshalling + */ + public XmlSerializableTars() {} + + /** + * Conversion + */ + public XmlSerializableTars(ReadOnlyTars src) { + tasks.addAll(src.getTaskList().stream().map(XmlAdaptedTask::new) + .collect(Collectors.toList())); + rsvTasks.addAll(src.getRsvTaskList().stream() + .map(XmlAdaptedRsvTask::new).collect(Collectors.toList())); + tags = src.getTagList(); + } + + @Override + public UniqueTagList getUniqueTagList() { + try { + return new UniqueTagList(tags); + } catch (UniqueTagList.DuplicateTagException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public UniqueTaskList getUniqueTaskList() { + UniqueTaskList lists = new UniqueTaskList(); + for (XmlAdaptedTask p : tasks) { + try { + lists.add(p.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + @Override + public List getTaskList() { + return tasks.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + + @Override + public UniqueRsvTaskList getUniqueRsvTaskList() { + UniqueRsvTaskList lists = new UniqueRsvTaskList(); + for (XmlAdaptedRsvTask rt : rsvTasks) { + try { + lists.add(rt.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + @Override + public List getRsvTaskList() { + return rsvTasks.stream().map(rt -> { + try { + return rt.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + +} diff --git a/src/main/java/tars/storage/XmlTarsStorage.java b/src/main/java/tars/storage/XmlTarsStorage.java new file mode 100644 index 000000000000..895ca37ef360 --- /dev/null +++ b/src/main/java/tars/storage/XmlTarsStorage.java @@ -0,0 +1,84 @@ +package tars.storage; + +import tars.commons.core.LogsCenter; +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.FileUtil; +import tars.model.ReadOnlyTars; + +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 Tars data stored as an xml file on the hard disk. + */ +public class XmlTarsStorage implements TarsStorage { + + private static final Logger logger = + LogsCenter.getLogger(XmlTarsStorage.class); + + private static String LOG_MESSAGE_TARS_FILE_NOT_FOUND = + "Tars file %s not found"; + + private String filePath; + + public XmlTarsStorage(String filePath) { + this.filePath = filePath; + } + + public String getTarsFilePath() { + return filePath; + } + + /** + * Similar to {@link #readTars()} + * + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readTars(String filePath) + throws DataConversionException, FileNotFoundException { + assert filePath != null; + + File tarsFile = new File(filePath); + + if (!tarsFile.exists()) { + logger.info( + String.format(LOG_MESSAGE_TARS_FILE_NOT_FOUND, tarsFile)); + return Optional.empty(); + } + + ReadOnlyTars tarsOptional = + XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + + return Optional.of(tarsOptional); + } + + /** + * Similar to {@link #saveTars(ReadOnlyTars)} + * + * @param filePath location of the data. Cannot be null + */ + public void saveTars(ReadOnlyTars tars, String filePath) + throws IOException { + assert tars != null; + assert filePath != null; + + File file = new File(filePath); + FileUtil.createIfMissing(file); + XmlFileStorage.saveDataToFile(file, new XmlSerializableTars(tars)); + } + + @Override + public Optional readTars() + throws DataConversionException, IOException { + return readTars(filePath); + } + + @Override + public void saveTars(ReadOnlyTars tars) throws IOException { + saveTars(tars, filePath); + } +} diff --git a/src/main/java/tars/ui/CommandBox.java b/src/main/java/tars/ui/CommandBox.java new file mode 100644 index 000000000000..45700c582a29 --- /dev/null +++ b/src/main/java/tars/ui/CommandBox.java @@ -0,0 +1,258 @@ +package tars.ui; + +import com.google.common.eventbus.Subscribe; + +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; +import tars.commons.core.KeyCombinations; +import tars.commons.core.LogsCenter; +import tars.commons.events.ui.CommandBoxTextFieldValueChangedEvent; +import tars.commons.events.ui.IncorrectCommandAttemptedEvent; +import tars.commons.events.ui.KeyCombinationPressedEvent; +import tars.commons.util.FxViewUtil; +import tars.commons.util.StringUtil; +import tars.logic.Logic; +import tars.logic.commands.CommandResult; +import tars.logic.commands.ConfirmCommand; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.RsvCommand; +import tars.logic.commands.UndoCommand; + +import java.util.Stack; +import java.util.logging.Logger; + +public class CommandBox extends UiPart { + + private static String LOG_MESSAGE_RESULT = "Result: %s"; + private static String LOG_MESSAGE_INVALID_COMMAND = "Invalid Command: %s"; + + private static final String COMMAND_TEXT_FIELD_ERROR = "error"; + private static final String FXML = "CommandBox.fxml"; + + private final Stack prevCmdTextHistStack = new Stack(); + private final Stack nextCmdTextHistStack = new Stack(); + + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + + private AnchorPane placeHolderPane; + private AnchorPane commandPane; + private ResultDisplay resultDisplay; + private 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(); + commandBox.setTextFieldKeyPressedHandler(); + commandBox.setTextFieldValueHandler(); + 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); + commandTextField.requestFocus(); + FxViewUtil.applyAnchorBoundaryParameters(commandPane, 0.0, 0.0, 0.0, + 0.0); + FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, + 0.0, 0.0); + } + + // @@author A0124333U + private void setTextFieldKeyPressedHandler() { + commandTextField.setOnKeyPressed(new EventHandler() { + public void handle(KeyEvent ke) { + if (ke.getCode().equals(KeyCode.UP)) { + setTextToShowPrevCmdText(ke); + } else if (ke.getCode().equals(KeyCode.DOWN)) { + setTextToShowNextCmdText(ke); + } else if (KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW + .match(ke)) { + raise(new KeyCombinationPressedEvent( + KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW)); + ke.consume(); + } else if (KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW.match(ke)) { + raise(new KeyCombinationPressedEvent( + KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW)); + ke.consume(); + } else if (KeyCombinations.KEY_COMB_CTRL_Z.match(ke)) { + handleUndoAndRedoKeyRequest(UndoCommand.COMMAND_WORD); + } else if (KeyCombinations.KEY_COMB_CTRL_Y.match(ke)) { + handleUndoAndRedoKeyRequest(RedoCommand.COMMAND_WORD); + } + } + }); + } + + private void setTextFieldValueHandler() { + commandTextField.textProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue.equals(RsvCommand.COMMAND_WORD) + || newValue.equals(ConfirmCommand.COMMAND_WORD)) { + raise(new CommandBoxTextFieldValueChangedEvent( + newValue)); + } + }); + + } + + // @@author + @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(); + addCmdTextToPrevStack(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(String.format(LOG_MESSAGE_RESULT, + mostRecentResult.feedbackToUser)); + } + + // @@author A0139924W + /** + * Handle any undo and redo request + */ + private void handleUndoAndRedoKeyRequest(String commandWord) { + if (UndoCommand.COMMAND_WORD.equals(commandWord)) { + mostRecentResult = logic.execute(UndoCommand.COMMAND_WORD); + } else if (RedoCommand.COMMAND_WORD.equals(commandWord)) { + mostRecentResult = logic.execute(RedoCommand.COMMAND_WORD); + } + resultDisplay.postMessage(mostRecentResult.feedbackToUser); + logger.info(String.format(LOG_MESSAGE_RESULT, + mostRecentResult.feedbackToUser)); + } + + // @@author A0124333U + /** + * Adds the user input command text into the "prev" stack + */ + private void addCmdTextToPrevStack(String cmdText) { + if (!prevCmdTextHistStack.contains(cmdText)) { + prevCmdTextHistStack.push(cmdText); + } + } + + /** + * Adds the user input command text into the "next" stack + */ + private void addCmdTextToNextStack(String cmdText) { + if (!nextCmdTextHistStack.contains(cmdText)) { + nextCmdTextHistStack.push(cmdText); + } + } + + /** + * Shows the prev cmdtext in the CommandBox. Does nothing if "prev" stack is empty + */ + private void setTextToShowPrevCmdText(KeyEvent ke) { + if (!prevCmdTextHistStack.isEmpty()) { + if (nextCmdTextHistStack.isEmpty()) { + nextCmdTextHistStack.push(commandTextField.getText()); + } + String cmdTextToShow = prevCmdTextHistStack.pop(); + addCmdTextToNextStack(cmdTextToShow); + if (commandTextField.getText().equals(cmdTextToShow) + && !prevCmdTextHistStack.isEmpty()) { + cmdTextToShow = prevCmdTextHistStack.pop(); + addCmdTextToNextStack(cmdTextToShow); + } + ke.consume(); + commandTextField.setText(cmdTextToShow); + } + } + + /** + * Shows the next cmdtext in the CommandBox. Does nothing if "next" stack is empty + */ + private void setTextToShowNextCmdText(KeyEvent ke) { + if (!nextCmdTextHistStack.isEmpty()) { + String cmdTextToShow = nextCmdTextHistStack.pop(); + addCmdTextToPrevStack(cmdTextToShow); + if (commandTextField.getText().equals(cmdTextToShow) + && !nextCmdTextHistStack.isEmpty()) { + cmdTextToShow = nextCmdTextHistStack.pop(); + if (!nextCmdTextHistStack.isEmpty()) { + addCmdTextToNextStack(cmdTextToShow); + } + } + ke.consume(); + commandTextField.setText(cmdTextToShow); + } + } + + /** + * Sets the command box style to indicate a correct command. + */ + private void setStyleToIndicateCorrectCommand() { + commandTextField.getStyleClass().remove(COMMAND_TEXT_FIELD_ERROR); + commandTextField.setText(StringUtil.EMPTY_STRING); + } + + @Subscribe + private void handleIncorrectCommandAttempted( + IncorrectCommandAttemptedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, String + .format(LOG_MESSAGE_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(COMMAND_TEXT_FIELD_ERROR); + } + +} diff --git a/src/main/java/tars/ui/HelpPanel.java b/src/main/java/tars/ui/HelpPanel.java new file mode 100644 index 000000000000..99eb992e5dbc --- /dev/null +++ b/src/main/java/tars/ui/HelpPanel.java @@ -0,0 +1,140 @@ +package tars.ui; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.scene.web.WebView; +import javafx.stage.Stage; + +// @@author A0121533W +/** + * UI Controller for a help page + */ +public class HelpPanel extends UiPart { + + private static final String FXML = "HelpPanel.fxml"; + private static final String USERGUIDE_URL = "/html/UserGuide.md.html"; + + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private WebView browser = new WebView(); + + public static HelpPanel load(Stage primaryStage, + AnchorPane helpPanelPlaceHolder) { + HelpPanel helpPanel = UiPartLoader.loadUiPart(primaryStage, + helpPanelPlaceHolder, new HelpPanel()); + helpPanel.configure(); + return helpPanel; + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void configure() { + addToPlaceholder(); + browser.getEngine().load(configureURL(UserGuide.DEFAULT)); + + } + + // @@author A0140022H + private String configureURL(String args) { + String url = + HelpPanel.class.getResource(USERGUIDE_URL).toExternalForm(); + + switch (args) { + case UserGuide.ADD: + url = url.concat(UserGuide.ADD_ID); + break; + case UserGuide.CD: + url = url.concat(UserGuide.CD_ID); + break; + case UserGuide.CLEAR: + url = url.concat(UserGuide.CLEAR_ID); + break; + case UserGuide.CONFIRM: + url = url.concat(UserGuide.CONFIRM_ID); + break; + case UserGuide.DELETE: + url = url.concat(UserGuide.DELETE_ID); + break; + case UserGuide.DONE: + url = url.concat(UserGuide.DONE_ID); + break; + case UserGuide.EDIT: + url = url.concat(UserGuide.EDIT_ID); + break; + case UserGuide.EXIT: + url = url.concat(UserGuide.EXIT_ID); + break; + case UserGuide.FIND: + url = url.concat(UserGuide.FIND_ID); + break; + case UserGuide.FREE: + url = url.concat(UserGuide.FREE_ID); + break; + case UserGuide.HELP: + url = url.concat(UserGuide.HELP_ID); + break; + case UserGuide.LIST: + url = url.concat(UserGuide.LIST_ID); + break; + case UserGuide.REDO: + url = url.concat(UserGuide.REDO_ID); + break; + case UserGuide.RSV: + url = url.concat(UserGuide.RSV_ID); + break; + case UserGuide.RSV_DELETE: + url = url.concat(UserGuide.RSV_DELETE_ID); + break; + case UserGuide.TAG_EDIT: + url = url.concat(UserGuide.TAG_EDIT_ID); + break; + case UserGuide.TAG_DELETE: + url = url.concat(UserGuide.TAG_DELETE_ID); + break; + case UserGuide.TAG_LIST: + url = url.concat(UserGuide.TAG_LIST_ID); + break; + case UserGuide.UNDONE: + url = url.concat(UserGuide.UNDONE_ID); + break; + case UserGuide.UNDO: + url = url.concat(UserGuide.UNDO_ID); + break; + case UserGuide.SUMMARY: + url = url.concat(UserGuide.SUMMARY_ID); + break; + default: + break; + } + + return url; + } + + public void loadUserGuide(String args) { + browser.getEngine().load(configureURL(args)); + } + +} diff --git a/src/main/java/tars/ui/MainWindow.java b/src/main/java/tars/ui/MainWindow.java new file mode 100644 index 000000000000..cccee90ff843 --- /dev/null +++ b/src/main/java/tars/ui/MainWindow.java @@ -0,0 +1,216 @@ +package tars.ui; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.TabPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import tars.commons.core.Config; +import tars.commons.core.GuiSettings; +import tars.logic.Logic; +import tars.model.UserPrefs; + +/** + * 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/tars_icon_32.png"; + private static final String FXML = "MainWindow.fxml"; + public static final int MIN_HEIGHT = 600; + public static final int MIN_WIDTH = 450; + public static final int OVERVIEW_PANEL_TAB_PANE_INDEX = 0; + public static final int RSV_TASK_LIST_PANEL_TAB_PANE_INDEX = 1; + public static final int HELP_PANEL_TAB_PANE_INDEX = 2; + + private Logic logic; + private MainWindowEventsHandler mainWindowEventsHandler; + + // Independent Ui parts residing in this Ui container + private TaskListPanel taskListPanel; + private RsvTaskListPanel rsvTaskListPanel; + private ResultDisplay resultDisplay; + private StatusBarFooter statusBarFooter; + private HelpPanel helpPanel; + private ThisWeekPanel thisWeekPanel; + private CommandBox commandBox; + private Config config; + private UserPrefs userPrefs; + + // Handles to elements of this Ui container + private VBox rootLayout; + private Scene scene; + + @FXML + private AnchorPane commandBoxPlaceholder; + @FXML + private AnchorPane headerPlaceholder; + @FXML + private AnchorPane taskListPanelPlaceholder; + @FXML + private AnchorPane rsvTaskListPanelPlaceholder; + @FXML + private AnchorPane resultDisplayPlaceholder; + @FXML + private AnchorPane statusbarPlaceholder; + @FXML + private AnchorPane thisWeekPanelPlaceholder; + @FXML + private AnchorPane helpPanelPlaceholder; + + @FXML + private Label taskListLabel; + + @FXML + private TabPane tabPane; + @FXML + private AnchorPane thisWeekTabAnchorPane; + @FXML + private AnchorPane rsvTabAnchorPane; + @FXML + private AnchorPane helpTabAnchorPane; + + public static MainWindow load(Stage primaryStage, Config config, + UserPrefs prefs, Logic logic) { + + MainWindow mainWindow = + UiPartLoader.loadUiPart(primaryStage, new MainWindow()); + mainWindow.configure(config.getAppTitle(), config, prefs, logic); + return mainWindow; + } + + private void configure(String appTitle, Config config, UserPrefs prefs, + Logic logic) { + + // Set dependencies + this.logic = logic; + this.config = config; + this.userPrefs = prefs; + + // Configure the UI + setTitle(appTitle); + setIcon(ICON); + setWindowMinSize(); + setWindowDefaultSize(userPrefs); + + // Configure event handling + this.mainWindowEventsHandler = + new MainWindowEventsHandler(primaryStage, rootLayout, tabPane); + + scene = new Scene(rootLayout); + primaryStage.setScene(scene); + + } + + @Override + public void setNode(Node node) { + rootLayout = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + protected void fillInnerParts() { + taskListPanel = TaskListPanel.load(primaryStage, + getTaskListPlaceholder(), logic.getFilteredTaskList()); + rsvTaskListPanel = RsvTaskListPanel.load(primaryStage, + getRsvTaskListPlaceholder(), logic.getFilteredRsvTaskList()); + resultDisplay = + ResultDisplay.load(primaryStage, getResultDisplayPlaceholder()); + statusBarFooter = StatusBarFooter.load(primaryStage, + getStatusbarPlaceholder(), config.getTarsFilePath()); + commandBox = CommandBox.load(primaryStage, getCommandBoxPlaceholder(), + resultDisplay, logic); + helpPanel = HelpPanel.load(primaryStage, getHelpPanelPlaceholder()); + thisWeekPanel = ThisWeekPanel.load(primaryStage, + getThisWeekPanelPlaceholder(), logic.getTaskList()); + } + + private AnchorPane getCommandBoxPlaceholder() { + return commandBoxPlaceholder; + } + + private AnchorPane getStatusbarPlaceholder() { + return statusbarPlaceholder; + } + + private AnchorPane getResultDisplayPlaceholder() { + return resultDisplayPlaceholder; + } + + private AnchorPane getTaskListPlaceholder() { + return taskListPanelPlaceholder; + } + + private AnchorPane getRsvTaskListPlaceholder() { + return rsvTaskListPanelPlaceholder; + } + + private AnchorPane getHelpPanelPlaceholder() { + return helpPanelPlaceholder; + } + + private AnchorPane getThisWeekPanelPlaceholder() { + return thisWeekPanelPlaceholder; + } + + public TaskListPanel getTaskListPanel() { + return this.taskListPanel; + } + + public HelpPanel getHelpPanel() { + return this.helpPanel; + } + + public MainWindowEventsHandler getEventsHandler() { + return this.mainWindowEventsHandler; + } + + 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()); + } + + + public void show() { + primaryStage.show(); + } + +} diff --git a/src/main/java/tars/ui/MainWindowEventsHandler.java b/src/main/java/tars/ui/MainWindowEventsHandler.java new file mode 100644 index 000000000000..26c4f204c0b4 --- /dev/null +++ b/src/main/java/tars/ui/MainWindowEventsHandler.java @@ -0,0 +1,132 @@ +package tars.ui; + +import javafx.scene.input.KeyEvent; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.event.EventHandler; +import javafx.scene.control.TabPane; +import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import tars.commons.core.EventsCenter; +import tars.commons.core.KeyCombinations; +import tars.commons.core.LogsCenter; +import tars.commons.events.ui.CommandBoxTextFieldValueChangedEvent; +import tars.commons.events.ui.KeyCombinationPressedEvent; +import tars.logic.commands.ConfirmCommand; +import tars.logic.commands.RsvCommand; + +// @@author A0121533W +/** + * Handles all events that require main window. + */ +public class MainWindowEventsHandler { + + protected static Stage primaryStage; + + private static String LOG_MESSAGE_COMMAND_DETECTED = "%s command detected."; + + private static double xOffset = 0; + private static double yOffset = 0; + + private static VBox rootLayout; + private static TabPane tabPane; + + private static final Logger logger = LogsCenter.getLogger(MainWindow.class); + + public MainWindowEventsHandler(Stage primaryStage, VBox rootLayout, + TabPane tabPane) { + MainWindowEventsHandler.rootLayout = rootLayout; + MainWindowEventsHandler.primaryStage = primaryStage; + MainWindowEventsHandler.tabPane = tabPane; + EventsCenter.getInstance().registerHandler(this); + } + + public static void addMouseEventHandler() { + rootLayout.setOnMousePressed(new EventHandler() { + @Override + public void handle(MouseEvent event) { + xOffset = event.getSceneX(); + yOffset = event.getSceneY(); + } + }); + rootLayout.setOnMouseDragged(new EventHandler() { + @Override + public void handle(MouseEvent event) { + primaryStage.setX(event.getScreenX() - xOffset); + primaryStage.setY(event.getScreenY() - yOffset); + } + }); + } + + public static void addTabPaneHandler() { + rootLayout.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent event) { + if (event.getCode() == KeyCode.RIGHT) { + cycleTabPaneRight(); + event.consume(); + } else if (event.getCode() == KeyCode.LEFT) { + cycleTabPaneLeft(); + event.consume(); + } + } + }); + } + + @Subscribe + private void KeyCombinationPressedEventHandler( + KeyCombinationPressedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + event.getKeyCombination().getDisplayText())); + if (event + .getKeyCombination() == KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW) { + cycleTabPaneRight(); + } else if (event + .getKeyCombination() == KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW) { + cycleTabPaneLeft(); + } + } + + @Subscribe + private void CommandBoxTextFieldValueChangedEventHandler( + CommandBoxTextFieldValueChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, String.format( + LOG_MESSAGE_COMMAND_DETECTED, event.getTextFieldValue()))); + if (event.getTextFieldValue().equals(RsvCommand.COMMAND_WORD) || event + .getTextFieldValue().equals(ConfirmCommand.COMMAND_WORD)) { + tabPane.getSelectionModel() + .select(MainWindow.RSV_TASK_LIST_PANEL_TAB_PANE_INDEX); + } + } + + private static void cycleTabPaneRight() { + if (tabPane.getSelectionModel() + .isSelected((MainWindow.HELP_PANEL_TAB_PANE_INDEX))) { + tabPane.getSelectionModel().selectFirst(); + } else { + tabPane.getSelectionModel().selectNext(); + } + } + + private static void cycleTabPaneLeft() { + if (tabPane.getSelectionModel() + .isSelected((MainWindow.OVERVIEW_PANEL_TAB_PANE_INDEX))) { + tabPane.getSelectionModel().selectLast(); + } else { + tabPane.getSelectionModel().selectPrevious(); + } + } + + // @@author A0140022H + public static void handleHelp(HelpPanel helpPanel, String args) { + helpPanel.loadUserGuide(args); + tabPane.getSelectionModel() + .select(MainWindow.HELP_PANEL_TAB_PANE_INDEX); + } + +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/tars/ui/ResultDisplay.java similarity index 59% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/tars/ui/ResultDisplay.java index 37284ee6c696..337ad5a53b7a 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/tars/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tars.ui; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -6,27 +6,30 @@ import javafx.scene.control.TextArea; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import seedu.address.commons.util.FxViewUtil; +import tars.commons.util.FxViewUtil; /** - * A ui for the status bar that is displayed at the header of the application. + * A UI for the results display that is displayed above the command box of the application. */ public class ResultDisplay extends UiPart { - public static final String RESULT_DISPLAY_ID = "resultDisplay"; - private static final String STATUS_BAR_STYLE_SHEET = "result-display"; - private TextArea resultDisplayArea; - private final StringProperty displayed = new SimpleStringProperty(""); + public static final String RESULT_DISPLAY_ID = "resultDisplay"; + private static final String RESULT_DISPLAY_STYLE_SHEET = "result-display"; private static final String FXML = "ResultDisplay.fxml"; + private static final double BOUNDARY_PARAMETERS_ZERO = 0.0; private AnchorPane placeHolder; - + private TextArea resultDisplayArea; private AnchorPane mainPane; + + private final StringProperty displayed = new SimpleStringProperty(""); - public static ResultDisplay load(Stage primaryStage, AnchorPane placeHolder) { - ResultDisplay statusBar = UiPartLoader.loadUiPart(primaryStage, placeHolder, new ResultDisplay()); - statusBar.configure(); - return statusBar; + public static ResultDisplay load(Stage primaryStage, + AnchorPane placeHolder) { + ResultDisplay resultDisplay = UiPartLoader.loadUiPart(primaryStage, + placeHolder, new ResultDisplay()); + resultDisplay.configure(); + return resultDisplay; } public void configure() { @@ -34,12 +37,16 @@ public void configure() { resultDisplayArea.setEditable(false); resultDisplayArea.setId(RESULT_DISPLAY_ID); resultDisplayArea.getStyleClass().removeAll(); - resultDisplayArea.getStyleClass().add(STATUS_BAR_STYLE_SHEET); + resultDisplayArea.getStyleClass().add(RESULT_DISPLAY_STYLE_SHEET); resultDisplayArea.setText(""); resultDisplayArea.textProperty().bind(displayed); - FxViewUtil.applyAnchorBoundaryParameters(resultDisplayArea, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(resultDisplayArea, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); mainPane.getChildren().add(resultDisplayArea); - FxViewUtil.applyAnchorBoundaryParameters(mainPane, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(mainPane, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); placeHolder.getChildren().add(mainPane); } diff --git a/src/main/java/tars/ui/RsvTaskCard.java b/src/main/java/tars/ui/RsvTaskCard.java new file mode 100644 index 000000000000..9da316ac881a --- /dev/null +++ b/src/main/java/tars/ui/RsvTaskCard.java @@ -0,0 +1,105 @@ +package tars.ui; + +import java.util.ArrayList; + +import edu.emory.mathcs.backport.java.util.Collections; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import tars.commons.util.StringUtil; +import tars.model.task.DateTime; +import tars.model.task.rsv.RsvTask; +import tars.ui.formatter.Formatter; + +// @@author A0121533W +/** + * UI Controller for Reserve Task Card + */ +public class RsvTaskCard extends UiPart { + + private static final int PREF_SIZE_HEIGHT = 75; + private static final int PREF_SIZE_WIDTH = 200; + private static final String DATE_TIME_LIST_AREA = "dateTimeListArea"; + private static final String FXML = "RsvTaskListCard.fxml"; + private static final String DATETIMELIST_ID = "dateTimeList"; + + private TextArea dateTimeListArea; + private AnchorPane dateTimeListPane; + + private StringProperty dateTimeListdisplayed = + new SimpleStringProperty(StringUtil.EMPTY_STRING); + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + + private RsvTask rsvTask; + private int displayedIndex; + + public RsvTaskCard() { + + } + + public static RsvTaskCard load(RsvTask rsvTask, int displayedIndex) { + RsvTaskCard card = new RsvTaskCard(); + card.cardPane = new HBox(); + card.dateTimeListPane = new AnchorPane(); + + card.rsvTask = rsvTask; + card.displayedIndex = displayedIndex; + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + name.setText(rsvTask.getName().taskName); + id.setText(displayedIndex + StringUtil.STRING_FULLSTOP); + setDateTimeList(); + configure(); + } + + public void configure() { + dateTimeListArea = new TextArea(); + dateTimeListArea.setEditable(false); + dateTimeListArea.setId(DATETIMELIST_ID); + dateTimeListArea.getStyleClass().removeAll(); + dateTimeListArea.getStyleClass().add(DATE_TIME_LIST_AREA); + dateTimeListArea.setWrapText(true); + dateTimeListArea.setPrefSize(PREF_SIZE_WIDTH, PREF_SIZE_HEIGHT); + dateTimeListArea.textProperty().bind(dateTimeListdisplayed); + dateTimeListArea.autosize(); + + dateTimeListPane.getChildren().add(dateTimeListArea); + cardPane.getChildren().add(dateTimeListPane); + } + + private void setDateTimeList() { + ArrayList dateTimeList = rsvTask.getDateTimeList(); + Collections.sort(dateTimeList); + String toSet = Formatter.formatDateTimeList(dateTimeList); + dateTimeListdisplayed.setValue(toSet); + } + + public HBox getLayout() { + return cardPane; + } + + @Override + public void setNode(Node node) { + cardPane = (HBox) node; + dateTimeListPane = (AnchorPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} diff --git a/src/main/java/tars/ui/RsvTaskListPanel.java b/src/main/java/tars/ui/RsvTaskListPanel.java new file mode 100644 index 000000000000..27b931c73436 --- /dev/null +++ b/src/main/java/tars/ui/RsvTaskListPanel.java @@ -0,0 +1,126 @@ +package tars.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +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.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import tars.commons.core.LogsCenter; +import tars.commons.events.ui.RsvTaskAddedEvent; +import tars.model.task.rsv.RsvTask; + +// @@author A0121533W +/** + * UI Controller for panel containing the list of reserved tasks. + */ +public class RsvTaskListPanel extends UiPart { + + private static String LOG_MESSAGE_LAYOUT_UPDATING = + "Updating layout for %s"; + private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final String FXML = "RsvTaskListPanel.fxml"; + private static final int START_INDEX = 1; + + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView rsvTaskListView; + + public RsvTaskListPanel() { + 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 RsvTaskListPanel load(Stage primaryStage, + AnchorPane rsvTaskListPlaceholder, + ObservableList rsvTaskList) { + RsvTaskListPanel rsvTaskListPanel = UiPartLoader.loadUiPart( + primaryStage, rsvTaskListPlaceholder, new RsvTaskListPanel()); + rsvTaskListPanel.configure(rsvTaskList); + return rsvTaskListPanel; + } + + private void configure(ObservableList rsvTaskList) { + setConnections(rsvTaskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList rsvTaskList) { + rsvTaskListView.setItems(rsvTaskList); + rsvTaskListView.setCellFactory(listView -> new RsvTaskListViewCell()); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + rsvTaskListView.scrollTo(index); + }); + } + + class RsvTaskListViewCell extends ListCell { + private RsvTask newlyAddedRsvTask; + + public RsvTaskListViewCell() { + registerAsAnEventHandler(this); + } + + @Override + protected void updateItem(RsvTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + RsvTaskCard card = + RsvTaskCard.load(task, getIndex() + START_INDEX); + HBox layout = card.getLayout(); + if (this.newlyAddedRsvTask != null + && this.newlyAddedRsvTask.isSameStateAs(task)) { + layout.setStyle(UiColor.TASK_CARD_NEWLY_ADDED_BORDER); + } else { + layout.setStyle(UiColor.TASK_CARD_DEFAULT_BORDER); + } + setGraphic(layout); + } + } + + @Subscribe + private void handleRsvTaskAddedEvent(RsvTaskAddedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + String.format(LOG_MESSAGE_LAYOUT_UPDATING, + event.task.toString()))); + this.newlyAddedRsvTask = event.task; + } + } + +} diff --git a/src/main/java/tars/ui/StatusBarFooter.java b/src/main/java/tars/ui/StatusBarFooter.java new file mode 100644 index 000000000000..4d97582f52b0 --- /dev/null +++ b/src/main/java/tars/ui/StatusBarFooter.java @@ -0,0 +1,142 @@ +package tars.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.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.stage.Stage; +import tars.commons.core.LogsCenter; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.events.storage.TarsStorageDirectoryChangedEvent; +import tars.commons.util.FxViewUtil; +import tars.commons.util.StringUtil; + +/** + * A UI for the status bar that is displayed at the footer of the application. + */ +public class StatusBarFooter extends UiPart { + + private static final double BOUNDARY_PARAMETERS_ZERO = 0.0; + private static final String SYNC_STATUS_NO_UPDATE = + "Not updated yet in this session"; + private static final String SYNC_STATUS_UPDATE_INFO = "Last Updated: %s"; + private static final String LOG_MESSAGE_SETTINGS_LAST_UPDATE = + "Setting last updated status to %s"; + private static final String LOG_MESSAGE_STORAGE_LOCATION_CHANGED = + "Storage Location Changed: %s"; + private static final String STORAGE_DIRECTORY_INFO = "Storage Directory: %s"; + private static final Logger logger = + LogsCenter.getLogger(StatusBarFooter.class); + private static final String FXML = "StatusBarFooter.fxml"; + + private StatusBar syncStatus; + private StatusBar saveLocationStatus; + private GridPane mainPane; + + @FXML + private AnchorPane saveLocStatusBarPane; + + @FXML + private AnchorPane syncStatusBarPane; + + private AnchorPane placeHolder; + private Label saveLocationLabel; + private Label syncStatusLabel; + + public static StatusBarFooter load(Stage stage, AnchorPane placeHolder, + String saveLocation) { + StatusBarFooter statusBarFooter = UiPartLoader.loadUiPart(stage, + placeHolder, new StatusBarFooter()); + statusBarFooter.configure(saveLocation); + return statusBarFooter; + } + + public void configure(String saveLocation) { + addMainPane(); + addSyncStatus(); + setSyncStatus(SYNC_STATUS_NO_UPDATE); + addSaveLocation(); + setSaveLocation(String.format(STORAGE_DIRECTORY_INFO, saveLocation)); + registerAsAnEventHandler(this); + } + + private void addMainPane() { + FxViewUtil.applyAnchorBoundaryParameters(mainPane, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); + placeHolder.getChildren().add(mainPane); + } + + // @@author A0139924W + private void setSaveLocation(String location) { + this.saveLocationLabel.setText(location); + } + + private void addSaveLocation() { + this.saveLocationStatus = new StatusBar(); + this.saveLocationLabel = new Label(); + this.saveLocationStatus.setText(StringUtil.EMPTY_STRING); + this.saveLocationStatus.getRightItems().add(saveLocationLabel); + FxViewUtil.applyAnchorBoundaryParameters(saveLocationStatus, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); + saveLocStatusBarPane.getChildren().add(saveLocationStatus); + } + + private void setSyncStatus(String status) { + this.syncStatusLabel.setText(status); + } + + private void addSyncStatus() { + this.syncStatus = new StatusBar(); + this.syncStatusLabel = new Label(); + this.syncStatus.setText(StringUtil.EMPTY_STRING); + this.syncStatus.getLeftItems().add(syncStatusLabel); + FxViewUtil.applyAnchorBoundaryParameters(syncStatus, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO, + BOUNDARY_PARAMETERS_ZERO, BOUNDARY_PARAMETERS_ZERO); + syncStatusBarPane.getChildren().add(syncStatus); + } + + // @@author + + @Override + public void setNode(Node node) { + mainPane = (GridPane) node; + } + + @Override + public void setPlaceholder(AnchorPane placeholder) { + this.placeHolder = placeholder; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Subscribe + public void handleTarsChangedEvent(TarsChangedEvent event) { + String lastUpdated = (new Date()).toString(); + logger.info(LogsCenter.getEventHandlingLogMessage(event, + String.format(LOG_MESSAGE_SETTINGS_LAST_UPDATE, lastUpdated))); + setSyncStatus(String.format(SYNC_STATUS_UPDATE_INFO, lastUpdated)); + } + + // @@author A0124333U + @Subscribe + private void handleTarsStorageChangeDirectoryEvent( + TarsStorageDirectoryChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setSaveLocation(String.format(LOG_MESSAGE_STORAGE_LOCATION_CHANGED, + event.getNewFilePath())); + } +} diff --git a/src/main/java/tars/ui/TaskCard.java b/src/main/java/tars/ui/TaskCard.java new file mode 100644 index 000000000000..3dcd1c018587 --- /dev/null +++ b/src/main/java/tars/ui/TaskCard.java @@ -0,0 +1,195 @@ +package tars.ui; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; +import tars.ui.formatter.DateFormatter; + +// @@author A0121533W +/** + * UI Controller for Task Card + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + private static final String STATUS_UNDONE = "Undone"; + private static final String STATUS_DONE = "Done"; + private static final String PRIORITY_HIGH = "h"; + private static final String PRIORITY_MEDIUM = "m"; + private static final String PRIORITY_LOW = "l"; + private static final String LABEL_HIGH = "H"; + private static final String LABEL_MEDIUM = "M"; + private static final String LABEL_LOW = "L"; + private static final String LABEL_DONE = "✔"; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label date; + @FXML + private Label circleLabel; + @FXML + private Label tags; + @FXML + private Circle priorityCircle; + + private ReadOnlyTask task; + private int displayedIndex; + + public TaskCard() { + + } + + public static TaskCard load(ReadOnlyTask task, int displayedIndex) { + TaskCard card = new TaskCard(); + card.task = task; + card.displayedIndex = displayedIndex; + card.registerAsAnEventHandler(card); + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + setName(); + setIndex(); + setDate(); + setTags(); + setCircle(); + setCardTextColorByStatus(); + } + + private void setName() { + name.setText(task.getName().taskName); + } + + private void setIndex() { + id.setText(displayedIndex + StringUtil.STRING_FULLSTOP); + } + + private void setDate() { + date.setText(DateFormatter.formatDate(task.getDateTime())); + } + + /** + * Sets UI for Task Card Circle + */ + private void setCircle() { + String priority = task.getPriority().priorityLevel; + String status = task.getStatus().toString(); + + Color circleColor = getColorBasedOnPriorityAndStatus(priority, status); + String label = getLabelBasedOnPriorityAndStatus(priority, status); + + priorityCircle.setFill(circleColor); + + circleLabel.setText(label); + circleLabel.setStyle(UiColor.CIRCLE_LABEL_COLOR); + } + + // @@author A0121533W + /** + * Gets color for circle based on task's priority and status + */ + private Color getColorBasedOnPriorityAndStatus(String priority, + String status) { + if (status.equals(STATUS_DONE)) { + return UiColor.CircleColor.DONE.getCircleColor(); + } else { + switch (priority) { + case PRIORITY_HIGH: + return UiColor.CircleColor.HIGH.getCircleColor(); + case PRIORITY_MEDIUM: + return UiColor.CircleColor.MEDIUM.getCircleColor(); + case PRIORITY_LOW: + return UiColor.CircleColor.LOW.getCircleColor(); + default: + return UiColor.CircleColor.NONE.getCircleColor(); + } + } + } + + // @@author A0121533W + /** + * Gets label for circle based on task's priority and status + */ + private String getLabelBasedOnPriorityAndStatus(String priority, + String status) { + if (status.equals(STATUS_DONE)) { + return LABEL_DONE; + } else { + switch (priority) { + case PRIORITY_HIGH: + return LABEL_HIGH; + case PRIORITY_MEDIUM: + return LABEL_MEDIUM; + case PRIORITY_LOW: + return LABEL_LOW; + default: + return StringUtil.EMPTY_STRING; + } + } + } + + /** + * Sets text to different color based on the status of a task + */ + private void setCardTextColorByStatus() { + String taskStatus = task.getStatus().toString(); + String color = StringUtil.EMPTY_STRING; + switch (taskStatus) { + case STATUS_UNDONE: + color = UiColor.STATUS_UNDONE_TEXT_FILL_DARK; + break; + case STATUS_DONE: + color = UiColor.STATUS_DONE_TEXT_FILL; + break; + } + id.setStyle(color); + name.setStyle(color); + date.setStyle(color); + tags.setStyle(color); + + if (taskStatus.equals(STATUS_UNDONE)) { + date.setStyle(UiColor.STATUS_UNDONE_TEXT_FILL_LIGHT); + tags.setStyle(UiColor.STATUS_UNDONE_TEXT_FILL_LIGHT); + } + } + + @Subscribe + private void handleTarsChangeEvent(TarsChangedEvent event) { + setCircle(); + setCardTextColorByStatus(); + } + + + private void setTags() { + tags.setText(task.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/tars/ui/TaskListPanel.java b/src/main/java/tars/ui/TaskListPanel.java new file mode 100644 index 000000000000..fbdf97bd4bf1 --- /dev/null +++ b/src/main/java/tars/ui/TaskListPanel.java @@ -0,0 +1,123 @@ +package tars.ui; + +import com.google.common.eventbus.Subscribe; + +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.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import tars.commons.core.LogsCenter; +import tars.commons.events.ui.TaskAddedEvent; +import tars.model.task.ReadOnlyTask; + +import java.util.logging.Logger; + +// @@author A0121533W +/** + * UI Controller for panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static String LOG_MESSAGE_LAYOUT_UPDATING = + "Updating layout for %s"; + + private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final String FXML = "TaskListPanel.fxml"; + private static final int START_INDEX = 1; + + @FXML + private ListView taskListView; + @FXML + private VBox panel; + + private AnchorPane placeHolderPane; + + public static TaskListPanel load(Stage primaryStage, + AnchorPane taskListPlaceholder, + ObservableList taskList) { + TaskListPanel taskListPanel = UiPartLoader.loadUiPart(primaryStage, + taskListPlaceholder, new TaskListPanel()); + taskListPanel.configure(taskList); + return taskListPanel; + } + + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + private void configure(ObservableList taskList) { + setConnections(taskList); + addToPlaceholder(); + } + + private void setConnections(ObservableList taskList) { + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + }); + } + + class TaskListViewCell extends ListCell { + private ReadOnlyTask newlyAddedTask; + + public TaskListViewCell() { + registerAsAnEventHandler(this); + } + + @Override + protected void updateItem(ReadOnlyTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + TaskCard card = TaskCard.load(task, getIndex() + START_INDEX); + HBox layout = card.getLayout(); + if (this.newlyAddedTask != null + && this.newlyAddedTask.isSameStateAs(task)) { + layout.setStyle(UiColor.TASK_CARD_NEWLY_ADDED_BORDER); + } else { + layout.setStyle(UiColor.TASK_CARD_DEFAULT_BORDER); + } + setGraphic(layout); + } + } + + @Subscribe + private void handleTaskAddedEvent(TaskAddedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + String.format(LOG_MESSAGE_LAYOUT_UPDATING, + event.task.toString()))); + this.newlyAddedTask = event.task; + } + } + +} diff --git a/src/main/java/tars/ui/ThisWeekPanel.java b/src/main/java/tars/ui/ThisWeekPanel.java new file mode 100644 index 000000000000..85d1fec5ea43 --- /dev/null +++ b/src/main/java/tars/ui/ThisWeekPanel.java @@ -0,0 +1,185 @@ +package tars.ui; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import tars.commons.core.LogsCenter; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.events.storage.TarsStorageDirectoryChangedEvent; +import tars.commons.util.DateTimeUtil; +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; +import tars.ui.formatter.Formatter; + +// @@author A0121533W +/** + * UI Controller for this week panel + */ +public class ThisWeekPanel extends UiPart { + + private static List list; + private static List upcomingTasks = + new ArrayList(); + private static List overduedTasks = + new ArrayList(); + + private static final String LOG_MESSAGE_UPDATE_THIS_WEEK_PANEL = + "Update this week panel"; + private static final Logger logger = + LogsCenter.getLogger(ThisWeekPanel.class); + private static final String FXML = "ThisWeekPanel.fxml"; + private static final String THISWEEK_PANEL_STYLE_SHEET = "thisWeek-panel"; + private static final String STATUS_UNDONE = "Undone"; + private static final String TASK_LIST_ELLIPSIS = "\n...\n"; + private static final DateFormat df = new SimpleDateFormat("E, MMM dd"); + private static final int MIN_SIZE = 5; + + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private Label date; + @FXML + private Label numUpcoming; + @FXML + private Label numOverdue; + @FXML + private Label overduedTasksList; + @FXML + private Label upcomingTasksList; + + public static ThisWeekPanel load(Stage primaryStage, + AnchorPane thisWeekPanelPlaceHolder, List taskList) { + ThisWeekPanel thisWeekPanel = UiPartLoader.loadUiPart(primaryStage, + thisWeekPanelPlaceHolder, new ThisWeekPanel()); + list = taskList; + thisWeekPanel.configure(); + return thisWeekPanel; + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void configure() { + panel.getStyleClass().add(THISWEEK_PANEL_STYLE_SHEET); + setDate(); + handleUpcomingTasks(); + handleOverdueTasks(); + addToPlaceholder(); + registerAsAnEventHandler(this); + } + + private void setDate() { + Date today = new Date(); + date.setText(df.format(today)); + } + + /** + * Updates number of upcoming tasks and lists them + */ + private void handleUpcomingTasks() { + int count = 0; + for (ReadOnlyTask t : list) { + if (DateTimeUtil.isWithinWeek(t.getDateTime().getEndDate()) + && t.getStatus().toString().equals(STATUS_UNDONE)) { + count++; + upcomingTasks.add(t); + } + } + numUpcoming.setText(String.valueOf(count)); + if (count == 0) { + upcomingTasksList.setText(StringUtil.EMPTY_STRING); + } else { + setThisWeekPanelTaskList(count, upcomingTasks, upcomingTasksList); + } + } + + /** + * Updates number of overdued tasks and lists them + */ + private void handleOverdueTasks() { + int count = 0; + for (ReadOnlyTask t : list) { + if (DateTimeUtil.isOverDue(t.getDateTime().getEndDate()) + && t.getStatus().toString().equals(STATUS_UNDONE)) { + count++; + overduedTasks.add(t); + } + } + numOverdue.setText(String.valueOf(count)); + if (count == 0) { + overduedTasksList.setText(StringUtil.EMPTY_STRING); + } else { + setThisWeekPanelTaskList(count, overduedTasks, overduedTasksList); + } + } + + /** + * Set text for tasksLists to display top five tasks + */ + private void setThisWeekPanelTaskList(int count, + List tasksList, Label taskListLabel) { + List topFiveTasks = tasksList.subList( + StringUtil.START_INDEX, Math.min(tasksList.size(), MIN_SIZE)); + String list = Formatter.formatThisWeekPanelTasksList(topFiveTasks); + if (tasksList.size() > MIN_SIZE) { + list = list + TASK_LIST_ELLIPSIS; + } + taskListLabel.setText(list); + } + + /** + * Updates panel with latest data + */ + private void updateThisWeekPanel() { + upcomingTasks.clear(); + handleUpcomingTasks(); + overduedTasks.clear(); + handleOverdueTasks(); + } + + @Subscribe + private void handleTarsChangedEvent(TarsChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_UPDATE_THIS_WEEK_PANEL)); + updateThisWeekPanel(); + } + + @Subscribe + private void handleTarsStorageChangeDirectoryEvent( + TarsStorageDirectoryChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_UPDATE_THIS_WEEK_PANEL)); + updateThisWeekPanel(); + } +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/tars/ui/Ui.java similarity index 71% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/tars/ui/Ui.java index e6a67fe8c027..722019337d95 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/tars/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tars.ui; import javafx.stage.Stage; @@ -7,7 +7,7 @@ */ public interface Ui { - /** Starts the UI (and the App). */ + /** Starts the UI (and the App). */ void start(Stage primaryStage); /** Stops the UI. */ diff --git a/src/main/java/tars/ui/UiColor.java b/src/main/java/tars/ui/UiColor.java new file mode 100644 index 000000000000..cd1d6967ac7e --- /dev/null +++ b/src/main/java/tars/ui/UiColor.java @@ -0,0 +1,38 @@ +package tars.ui; + +import javafx.scene.paint.Color; + +/** + * Manages color for UI parts + * + * @@author A0121533W + * + */ +public class UiColor { + + public static final String STATUS_UNDONE_TEXT_FILL_DARK = + "-fx-text-fill: #212121"; + public static final String STATUS_UNDONE_TEXT_FILL_LIGHT = + "-fx-text-fill: #757575"; + public static final String STATUS_DONE_TEXT_FILL = + "-fx-text-fill: lightgrey"; + public static final String CIRCLE_LABEL_COLOR = "-fx-text-fill: white;"; + public static final String TASK_CARD_NEWLY_ADDED_BORDER = + "-fx-border-color: #2E8AF7"; + public static final String TASK_CARD_DEFAULT_BORDER = + "-fx-border-color: lightgrey"; + + public enum CircleColor { + HIGH(Color.RED), MEDIUM(Color.ORANGE), LOW(Color.GREEN), DONE( + Color.LIGHTGREY), NONE(Color.TRANSPARENT); + private Color circleColor; + + CircleColor(Color circleColor) { + this.circleColor = circleColor; + } + + Color getCircleColor() { + return circleColor; + } + } +} diff --git a/src/main/java/tars/ui/UiManager.java b/src/main/java/tars/ui/UiManager.java new file mode 100644 index 000000000000..c5d1587bae57 --- /dev/null +++ b/src/main/java/tars/ui/UiManager.java @@ -0,0 +1,168 @@ +package tars.ui; + +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 tars.MainApp; +import tars.commons.core.ComponentManager; +import tars.commons.core.Config; +import tars.commons.core.LogsCenter; +import tars.commons.events.storage.DataSavingExceptionEvent; +import tars.commons.events.ui.RsvTaskAddedEvent; +import tars.commons.events.ui.ScrollToTopEvent; +import tars.commons.events.ui.ShowHelpRequestEvent; +import tars.commons.events.ui.TaskAddedEvent; +import tars.commons.util.StringUtil; +import tars.logic.Logic; +import tars.model.UserPrefs; + +import java.util.logging.Logger; + +/** + * The manager of the UI component. + */ +public class UiManager extends ComponentManager implements Ui { + + private static final String LOG_MESSAGE_STARTING_UI = "Starting UI..."; + private static final String LOG_MESSAGE_INITIALIZING_FATAL_ERROR = + "Fatal error during initializing"; + private static final String LOG_MESSAGE_SCROLL_TO_RSVTASK = + "Scrolling to newly added rsvtask"; + private static final String LOG_MESSAGE_SCROLL_TO_TASK = + "Scrolling to newly added task"; + private static final String LOG_MESSAGE_SCROLL_TO_TOP = "Scrolling to top"; + private static final String STYLESHEETS_VIEW_TARS_THEME_CSS = + "view/TarsTheme.css"; + private static final String ALERT_SAVE_DATA_TO_FILE_FAILURE = + "Could not save data to file"; + private static final String ALERT_SAVE_DATA_FAILURE = "Could not save data"; + private static final String ALERT_FILE_OP_ERROR = "File Op Error"; + + private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final String ICON_APPLICATION = "/images/tars_icon_32.png"; + private static final int TOP_OF_LIST = 0; + + private Logic logic; + private Config config; + private UserPrefs prefs; + private MainWindow mainWindow; + + public UiManager(Logic logic, Config config, UserPrefs prefs) { + super(); + this.logic = logic; + this.config = config; + this.prefs = prefs; + } + + @Override + public void start(Stage primaryStage) { + logger.info(LOG_MESSAGE_STARTING_UI); + primaryStage.setTitle(config.getAppTitle()); + + // Set the application icon. + primaryStage.getIcons().add(getImage(ICON_APPLICATION)); + + try { + mainWindow = MainWindow.load(primaryStage, config, prefs, logic); + mainWindow.show(); // This should be called before creating other UI parts + mainWindow.fillInnerParts(); + + } catch (Throwable e) { + logger.severe(StringUtil.getDetails(e)); + showFatalErrorDialogAndShutdown( + LOG_MESSAGE_INITIALIZING_FATAL_ERROR, e); + } + } + + @Override + public void stop() { + prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); + mainWindow.hide(); + } + + private void showFileOperationAlertAndWait(String description, + String details, Throwable cause) { + // final String content = details + ":\n" + cause.toString(); + final String content = details + StringUtil.STRING_COLON + + StringUtil.STRING_NEWLINE + cause.toString(); + showAlertDialogAndWait(AlertType.ERROR, ALERT_FILE_OP_ERROR, + description, content); + } + + private Image getImage(String imagePath) { + return new Image(MainApp.class.getResourceAsStream(imagePath)); + } + + private void showAlertDialogAndWait(Alert.AlertType type, String title, + String headerText, String contentText) { + showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, + headerText, contentText); + } + + private static void showAlertDialogAndWait(Stage owner, AlertType type, + String title, String headerText, String contentText) { + final Alert alert = new Alert(type); + alert.getDialogPane().getStylesheets() + .add(STYLESHEETS_VIEW_TARS_THEME_CSS); + alert.initOwner(owner); + alert.setTitle(title); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + + alert.showAndWait(); + } + + private void showFatalErrorDialogAndShutdown(String title, Throwable e) { + logger.severe(title + StringUtil.STRING_WHITESPACE + e.getMessage() + + StringUtil.getDetails(e)); + showAlertDialogAndWait(Alert.AlertType.ERROR, title, e.getMessage(), + e.toString()); + Platform.exit(); + System.exit(1); + } + + // ==================== Event Handling Code ==================== + + @Subscribe + private void handleDataSavingExceptionEvent( + DataSavingExceptionEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + showFileOperationAlertAndWait(ALERT_SAVE_DATA_FAILURE, + ALERT_SAVE_DATA_TO_FILE_FAILURE, event.exception); + } + + // @@author A0140022H + @Subscribe + private void handleShowHelpEvent(ShowHelpRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getEventsHandler(); + MainWindowEventsHandler.handleHelp(mainWindow.getHelpPanel(), + event.getHelpRequestEventArgs()); + } + // @@author + + @Subscribe + private void handleTaskAddedEvent(TaskAddedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_SCROLL_TO_TASK)); + mainWindow.getTaskListPanel().scrollTo(event.targetIndex); + } + + @Subscribe + private void handleRsvTaskAddedEvent(RsvTaskAddedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_SCROLL_TO_RSVTASK)); + mainWindow.getTaskListPanel().scrollTo(event.targetIndex); + } + + @Subscribe + private void handleScrollToTopEvent(ScrollToTopEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, + LOG_MESSAGE_SCROLL_TO_TOP)); + mainWindow.getTaskListPanel().scrollTo(TOP_OF_LIST); + } + +} diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/tars/ui/UiPart.java similarity index 82% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/tars/ui/UiPart.java index 0a4ceb33e9b7..6304cab1f21e 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/tars/ui/UiPart.java @@ -1,39 +1,37 @@ -package seedu.address.ui; +package tars.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 tars.commons.core.EventsCenter; +import tars.commons.events.BaseEvent; +import tars.commons.util.AppUtil; /** - * Base class for UI parts. - * A 'UI part' represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. + * Base class for UI parts. A 'UI part' represents a distinct part of the UI. e.g. Windows, dialogs, + * panels, status bars, etc. */ public abstract class UiPart { /** * The primary stage for the UI Part. */ - Stage primaryStage; - - public UiPart(){ - - } + protected Stage primaryStage; /** * Raises the event via {@link EventsCenter#post(BaseEvent)} + * * @param event */ - protected void raise(BaseEvent event){ + protected void raise(BaseEvent event) { EventsCenter.getInstance().post(event); } /** * Registers the object as an event handler at the {@link EventsCenter} + * * @param handler usually {@code this} */ protected void registerAsAnEventHandler(Object handler) { @@ -41,13 +39,16 @@ protected void registerAsAnEventHandler(Object handler) { } /** - * Override this method to receive the main Node generated while loading the view from the .fxml file. + * Override this method to receive the main Node generated while loading the view from the .fxml + * file. + * * @param node */ public abstract void setNode(Node node); /** * Override this method to return the name of the fxml file. e.g. {@code "MainWindow.fxml"} + * * @return */ public abstract String getFxmlPath(); @@ -59,12 +60,14 @@ public void setStage(Stage primaryStage) { /** * Creates a modal dialog. + * * @param title Title of the dialog. * @param parentStage The owner stage of the dialog. * @param scene The scene that will contain the dialog. * @return the created dialog, not yet made visible. */ - protected Stage createDialogStage(String title, Stage parentStage, Scene scene) { + protected Stage createDialogStage(String title, Stage parentStage, + Scene scene) { Stage dialogStage = new Stage(); dialogStage.setTitle(title); dialogStage.initModality(Modality.WINDOW_MODAL); @@ -75,6 +78,7 @@ protected Stage createDialogStage(String title, Stage parentStage, Scene scene) /** * Sets the given image as the icon for the primary stage of this UI Part. + * * @param iconSource e.g. {@code "/images/help_icon.png"} */ protected void setIcon(String iconSource) { @@ -83,6 +87,7 @@ protected void setIcon(String iconSource) { /** * Sets the given image as the icon for the given stage. + * * @param stage * @param iconSource e.g. {@code "/images/help_icon.png"} */ @@ -92,10 +97,11 @@ protected void setIcon(Stage stage, String iconSource) { /** * Sets the placeholder for UI parts that reside inside another UI part. + * * @param placeholder */ public void setPlaceholder(AnchorPane placeholder) { - //Do nothing by default. + // Do nothing by default. } public Stage getPrimaryStage() { diff --git a/src/main/java/seedu/address/ui/UiPartLoader.java b/src/main/java/tars/ui/UiPartLoader.java similarity index 74% rename from src/main/java/seedu/address/ui/UiPartLoader.java rename to src/main/java/tars/ui/UiPartLoader.java index f880685a5b15..ac23ce3f378d 100644 --- a/src/main/java/seedu/address/ui/UiPartLoader.java +++ b/src/main/java/tars/ui/UiPartLoader.java @@ -1,18 +1,21 @@ -package seedu.address.ui; +package tars.ui; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import seedu.address.MainApp; +import tars.MainApp; /** * A utility class to load UiParts from FXML files. */ public class UiPartLoader { + private final static String FXML_FILE_FOLDER = "/view/"; + private final static String MESSAGE_FXML_LOAD_ERROR = "FXML Load Error for %s"; - public static T loadUiPart(Stage primaryStage, T controllerSeed) { + public static T loadUiPart(Stage primaryStage, + T controllerSeed) { return loadUiPart(primaryStage, null, controllerSeed); } @@ -24,15 +27,17 @@ public static T loadUiPart(Stage primaryStage, T controllerSe * @param sampleUiPart The sample of the expected UiPart class. * @param The type of the UiPart */ - public static T loadUiPart(Stage primaryStage, AnchorPane placeholder, T sampleUiPart) { + public static T loadUiPart(Stage primaryStage, + AnchorPane placeholder, T sampleUiPart) { FXMLLoader loader = new FXMLLoader(); - loader.setLocation(MainApp.class.getResource(FXML_FILE_FOLDER + sampleUiPart.getFxmlPath())); + loader.setLocation(MainApp.class + .getResource(FXML_FILE_FOLDER + sampleUiPart.getFxmlPath())); Node mainNode = loadLoader(loader, sampleUiPart.getFxmlPath()); UiPart controller = loader.getController(); controller.setStage(primaryStage); controller.setPlaceholder(placeholder); controller.setNode(mainNode); - return (T)controller; + return (T) controller; } /** @@ -44,7 +49,8 @@ public static T loadUiPart(Stage primaryStage, AnchorPane pla public static T loadUiPart(T seedUiPart) { FXMLLoader loader = new FXMLLoader(); - loader.setLocation(MainApp.class.getResource(FXML_FILE_FOLDER + seedUiPart.getFxmlPath())); + loader.setLocation(MainApp.class + .getResource(FXML_FILE_FOLDER + seedUiPart.getFxmlPath())); loader.setController(seedUiPart); loadLoader(loader, seedUiPart.getFxmlPath()); return seedUiPart; @@ -55,7 +61,8 @@ private static Node loadLoader(FXMLLoader loader, String fxmlFileName) { try { return loader.load(); } catch (Exception e) { - String errorMessage = "FXML Load Error for " + fxmlFileName; + String errorMessage = + String.format(MESSAGE_FXML_LOAD_ERROR, fxmlFileName); throw new RuntimeException(errorMessage, e); } } diff --git a/src/main/java/tars/ui/UserGuide.java b/src/main/java/tars/ui/UserGuide.java new file mode 100644 index 000000000000..35fa77e70e5f --- /dev/null +++ b/src/main/java/tars/ui/UserGuide.java @@ -0,0 +1,59 @@ +package tars.ui; + +// @@author A0140022H +/** + * Container for help command and user guide + */ +public class UserGuide { + + public static final String DEFAULT = ""; + + public static final String ADD = "add"; + public static final String CD = "cd"; + public static final String CLEAR = "clear"; + public static final String CONFIRM = "confirm"; + public static final String DELETE = "del"; + public static final String DONE = "do"; + public static final String EDIT = "edit"; + public static final String EXIT = "exit"; + public static final String FIND = "find"; + public static final String FREE = "free"; + public static final String HELP = "help"; + public static final String LIST = "ls"; + public static final String REDO = "redo"; + public static final String RSV = "rsv"; + public static final String RSV_DELETE = "rsv /del"; + public static final String TAG_EDIT = "tag /e"; + public static final String TAG_DELETE = "tag /del"; + public static final String TAG_LIST = "tag /ls"; + public static final String UNDONE = "ud"; + public static final String UNDO = "undo"; + public static final String SUMMARY = "summary"; + + public static final String ADD_ID = "#Adding_a_task__add_38"; + public static final String CD_ID = "#Changing_data_storage_location__cd_52"; + public static final String CLEAR_ID = + "#Clearing_the_data_storage_file__clear_63"; + public static final String CONFIRM_ID = + "#Confirming_a_reserved_timeslot__confirm_68"; + public static final String DELETE_ID = "#Deleting_a_task__del_82"; + public static final String DONE_ID = "#Marking_tasks_as_done__do_97"; + public static final String EDIT_ID = "#Editing_a_task__edit_111"; + public static final String EXIT_ID = "#Exiting_the_program__exit_127"; + public static final String FIND_ID = "#Finding_tasks__find_132"; + public static final String FREE_ID = "#Suggesting_free_timeslots__free_164"; + public static final String HELP_ID = + "#Displaying_a_list_of_available_commands__help_175"; + public static final String LIST_ID = "#Listing_tasks__ls_182"; + public static final String REDO_ID = "#Redoing_a_command__redo_205"; + public static final String RSV_ID = + "#Reserving_timeslots_for_a_task__rsv_212"; + public static final String RSV_DELETE_ID = + "#Deleting_a_task_with_reserved_timeslots__rsv_del_222"; + public static final String TAG_EDIT_ID = "#Editing_a_tags_name__tag_e_236"; + public static final String TAG_DELETE_ID = "#Deleting_a_tag__tag_del_246"; + public static final String TAG_LIST_ID = "#Listing_all_tags__tag_ls_256"; + public static final String UNDONE_ID = "#Marking_tasks_as_undone__ud_261"; + public static final String UNDO_ID = "#Undoing_a_command__undo_275"; + public static final String SUMMARY_ID = "#Command_Summary_365"; +} diff --git a/src/main/java/tars/ui/formatter/DateFormatter.java b/src/main/java/tars/ui/formatter/DateFormatter.java new file mode 100644 index 000000000000..05cd70671d7b --- /dev/null +++ b/src/main/java/tars/ui/formatter/DateFormatter.java @@ -0,0 +1,79 @@ +package tars.ui.formatter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import tars.commons.util.StringUtil; +import tars.model.task.DateTime; + +// @@author A0139924W +/** + * Container for formatting dates + */ +public class DateFormatter { + private static final String DATE_FORMAT_DASH = " - "; + private static final DateTimeFormatter DATE_FORMAT = + DateTimeFormatter.ofPattern("E, MMM dd yyyy"); + private static final DateTimeFormatter TIME_FORMAT = + DateTimeFormatter.ofPattern("hh:mm a"); + private static final DateTimeFormatter NORMAL_DATETIME_FORMAT = + DateTimeFormatter.ofPattern("E, MMM dd yyyy hh:mm a"); + private static final DateTimeFormatter SAME_DAY_DATE_FORMAT = + DateTimeFormatter.ofPattern("ddMMyyyy"); + + private static final String TODAY_PREFIX_TEXT = "Today at "; + private static final String TOMORROW_PREFIX_TEXT = "Tomorrow at "; + + public static String formatDate(DateTime dateTime) { + LocalDateTime startDateTime = dateTime.getStartDate(); + LocalDateTime endDateTime = dateTime.getEndDate(); + + if (startDateTime != null && endDateTime == null) { + return DateFormatter.generateSingleDateFormat(startDateTime); + } else if (startDateTime == null && endDateTime != null) { + return DateFormatter.generateSingleDateFormat(endDateTime); + } else if (startDateTime != null && endDateTime != null) { + return DateFormatter.generateDateRangeFormat(startDateTime, + endDateTime); + } else { + return StringUtil.EMPTY_STRING; + } + } + + public static String generateSingleDateFormat(LocalDateTime firstDate) { + if (isToday(firstDate)) { + return TODAY_PREFIX_TEXT + TIME_FORMAT.format(firstDate); + } else if (isTomorrow(firstDate)) { + return TOMORROW_PREFIX_TEXT + TIME_FORMAT.format(firstDate); + } else { + return NORMAL_DATETIME_FORMAT.format(firstDate); + } + } + + public static String generateDateRangeFormat(LocalDateTime firstDate, + LocalDateTime secondDate) { + if (isSameDay(firstDate, secondDate)) { + return DATE_FORMAT.format(firstDate) + StringUtil.STRING_WHITESPACE + + TIME_FORMAT.format(firstDate) + DATE_FORMAT_DASH + + TIME_FORMAT.format(secondDate); + } else { + return NORMAL_DATETIME_FORMAT.format(firstDate) + DATE_FORMAT_DASH + + NORMAL_DATETIME_FORMAT.format(secondDate); + } + } + + private static boolean isToday(LocalDateTime firstDate) { + return isSameDay(firstDate, LocalDateTime.now()); + } + + private static boolean isTomorrow(LocalDateTime firstDate) { + return isSameDay(firstDate, LocalDateTime.now().plusDays(1)); + } + + private static boolean isSameDay(LocalDateTime firstDate, + LocalDateTime secondDate) { + return SAME_DAY_DATE_FORMAT.format(firstDate) + .equals(SAME_DAY_DATE_FORMAT.format(secondDate)); + } + +} diff --git a/src/main/java/tars/ui/formatter/Formatter.java b/src/main/java/tars/ui/formatter/Formatter.java new file mode 100644 index 000000000000..6f78ea86dc21 --- /dev/null +++ b/src/main/java/tars/ui/formatter/Formatter.java @@ -0,0 +1,119 @@ +package tars.ui.formatter; + +import java.util.ArrayList; +import java.util.List; + +import tars.commons.util.StringUtil; +import tars.model.tag.ReadOnlyTag; +import tars.model.task.DateTime; +import tars.model.task.ReadOnlyTask; +import tars.model.task.rsv.RsvTask; + +// @@author A0139924W +/** + * Container for formatting + */ +public class Formatter { + private static final int INITIAL_COUNT = 1; + private static final String STRING_TASKS = "tasks"; + private static final String STRING_TAGS = "tags"; + + /** Format of indexed list item */ + private static final String MESSAGE_INDEXED_LIST_ITEM = "%1$d.\t%2$s"; + + public static final String EMPTY_LIST_MESSAGE = "0 %1$s listed."; + public static final String DATETIME_FORMAT_STRING = "[%1$s] %2$s\n\n"; + private static String OVERDUED_TASKS_STRING = "[%1$s] %2$s\n"; + + public String formatTags(List tags) { + final List formattedTags = new ArrayList<>(); + + if (tags.size() == 0) { + return String.format(EMPTY_LIST_MESSAGE, STRING_TAGS); + } + + for (ReadOnlyTag tag : tags) { + formattedTags.add(tag.getAsText()); + } + return asIndexedList(formattedTags); + } + + public String formatTaskList(List taskList) { + final List formattedTasks = new ArrayList<>(); + + if (taskList.size() == 0) { + return String.format(EMPTY_LIST_MESSAGE, STRING_TASKS); + } + + for (ReadOnlyTask task : taskList) { + formattedTasks.add(task.getAsText()); + } + return asIndexedList(formattedTasks); + } + + public String formatRsvTaskList(List rsvTaskList) { + final List formattedTasks = new ArrayList<>(); + + if (rsvTaskList.size() == 0) { + return String.format(EMPTY_LIST_MESSAGE, STRING_TASKS); + } + + for (RsvTask task : rsvTaskList) { + formattedTasks.add(task.toString()); + } + return asIndexedList(formattedTasks); + } + + /** + * Formats a list of strings as an indexed list. + */ + private static String asIndexedList(List listItems) { + final StringBuilder formatted = new StringBuilder(); + int displayIndex = StringUtil.DISPLAYED_INDEX_OFFSET; + for (String listItem : listItems) { + formatted.append(getIndexedListItem(displayIndex++, listItem)) + .append(StringUtil.STRING_NEWLINE); + } + return formatted.toString(); + } + + /** + * Formats a string as an indexed list item. + * + * @param visibleIndex index for this listing + */ + private static String getIndexedListItem(int visibleIndex, + String listItem) { + return String.format(MESSAGE_INDEXED_LIST_ITEM, visibleIndex, listItem); + } + + // @@author A0121533W + /** + * Formats a given RsvTask dateTime list to display on rsvTaskCard + */ + public static String formatDateTimeList(ArrayList dateTimeList) { + String formatted = StringUtil.EMPTY_STRING; + int count = 1; + for (DateTime dt : dateTimeList) { + formatted += String.format(DATETIME_FORMAT_STRING, count, + DateFormatter.formatDate(dt)); + count++; + } + return formatted; + } + + /** + * Formats a given tasks list to display on This Week Panel + */ + public static String formatThisWeekPanelTasksList( + List overduedTasks) { + String formatted = StringUtil.EMPTY_STRING; + int count = INITIAL_COUNT; + for (ReadOnlyTask t : overduedTasks) { + String taskName = t.getName().toString(); + formatted += String.format(OVERDUED_TASKS_STRING, count, taskName); + count++; + } + return formatted.trim(); + } +} diff --git a/src/main/resources/fonts/roboto/Apache License.txt b/src/main/resources/fonts/roboto/Apache License.txt new file mode 100644 index 000000000000..a485dab76dee --- /dev/null +++ b/src/main/resources/fonts/roboto/Apache License.txt @@ -0,0 +1,204 @@ + +Font data copyright Google 2012 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/main/resources/fonts/roboto/Roboto-Black.ttf b/src/main/resources/fonts/roboto/Roboto-Black.ttf new file mode 100644 index 000000000000..71f01ac2b216 Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Black.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-BlackItalic.ttf b/src/main/resources/fonts/roboto/Roboto-BlackItalic.ttf new file mode 100644 index 000000000000..ec309c78557b Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-BlackItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-Bold.ttf b/src/main/resources/fonts/roboto/Roboto-Bold.ttf new file mode 100644 index 000000000000..aaf374d2cc00 Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Bold.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-BoldItalic.ttf b/src/main/resources/fonts/roboto/Roboto-BoldItalic.ttf new file mode 100644 index 000000000000..dcd0f800730b Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-BoldItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-Italic.ttf b/src/main/resources/fonts/roboto/Roboto-Italic.ttf new file mode 100644 index 000000000000..f382c6874359 Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Italic.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-Light.ttf b/src/main/resources/fonts/roboto/Roboto-Light.ttf new file mode 100644 index 000000000000..664e1b2f9dba Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Light.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-LightItalic.ttf b/src/main/resources/fonts/roboto/Roboto-LightItalic.ttf new file mode 100644 index 000000000000..b8f52963716d Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-LightItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-Medium.ttf b/src/main/resources/fonts/roboto/Roboto-Medium.ttf new file mode 100644 index 000000000000..aa00de0ef929 Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Medium.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-MediumItalic.ttf b/src/main/resources/fonts/roboto/Roboto-MediumItalic.ttf new file mode 100644 index 000000000000..67e25f019746 Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-MediumItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-Regular.ttf b/src/main/resources/fonts/roboto/Roboto-Regular.ttf new file mode 100644 index 000000000000..3e6e2e76134c Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Regular.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-Thin.ttf b/src/main/resources/fonts/roboto/Roboto-Thin.ttf new file mode 100644 index 000000000000..d262d1446fbd Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-Thin.ttf differ diff --git a/src/main/resources/fonts/roboto/Roboto-ThinItalic.ttf b/src/main/resources/fonts/roboto/Roboto-ThinItalic.ttf new file mode 100644 index 000000000000..63e9f97186f8 Binary files /dev/null and b/src/main/resources/fonts/roboto/Roboto-ThinItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/RobotoCondensed-Bold.ttf b/src/main/resources/fonts/roboto/RobotoCondensed-Bold.ttf new file mode 100644 index 000000000000..48dd63534bae Binary files /dev/null and b/src/main/resources/fonts/roboto/RobotoCondensed-Bold.ttf differ diff --git a/src/main/resources/fonts/roboto/RobotoCondensed-BoldItalic.ttf b/src/main/resources/fonts/roboto/RobotoCondensed-BoldItalic.ttf new file mode 100644 index 000000000000..ad728646a17f Binary files /dev/null and b/src/main/resources/fonts/roboto/RobotoCondensed-BoldItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/RobotoCondensed-Italic.ttf b/src/main/resources/fonts/roboto/RobotoCondensed-Italic.ttf new file mode 100644 index 000000000000..a232513d592c Binary files /dev/null and b/src/main/resources/fonts/roboto/RobotoCondensed-Italic.ttf differ diff --git a/src/main/resources/fonts/roboto/RobotoCondensed-Light.ttf b/src/main/resources/fonts/roboto/RobotoCondensed-Light.ttf new file mode 100644 index 000000000000..a6e368d40eac Binary files /dev/null and b/src/main/resources/fonts/roboto/RobotoCondensed-Light.ttf differ diff --git a/src/main/resources/fonts/roboto/RobotoCondensed-LightItalic.ttf b/src/main/resources/fonts/roboto/RobotoCondensed-LightItalic.ttf new file mode 100644 index 000000000000..5b2b6ae087b3 Binary files /dev/null and b/src/main/resources/fonts/roboto/RobotoCondensed-LightItalic.ttf differ diff --git a/src/main/resources/fonts/roboto/RobotoCondensed-Regular.ttf b/src/main/resources/fonts/roboto/RobotoCondensed-Regular.ttf new file mode 100644 index 000000000000..65bf32a19f9f Binary files /dev/null and b/src/main/resources/fonts/roboto/RobotoCondensed-Regular.ttf differ diff --git a/src/main/resources/html/UserGuide.md.html b/src/main/resources/html/UserGuide.md.html new file mode 100644 index 000000000000..fe7f24fa567c --- /dev/null +++ b/src/main/resources/html/UserGuide.md.html @@ -0,0 +1,460 @@ +UserGuide.md +

User Guide

+ +

Quick Start

+
    +
  1. +

    Ensure you have Java version 1.8.0_60 or later installed in your Computer.

    +
    +

    Having any Java 8 version is not enough.
    +This app will not work with earlier versions of Java 8.

    +
    +

    Click here to download the latest Java version.

    +
  2. +
  3. +

    Download the latest tars.jar from the ’releases’ tab.

    +
  4. +
  5. +

    Copy the file to the folder you want to use as the home folder for your TARS App.

    +
  6. +
  7. +

    Double-click the file to start the app. The GUI should appear in a few seconds.

    +
  8. +
  9. +

    Type the command in the command box and press <kbd>Enter</kbd> to execute it.
    +e.g. typing help and pressing <kbd>Enter</kbd> will open the help window.

    +
  10. +
  11. +

    Some example commands you can try:

    +
      +
    • ls : lists all tasks
    • +
    • addComplete CS2103 Quiz 3 /dt 23/09/2016 /p h /t Quiz /t CS2103 : +adds a task Complete CS2103 Quiz 3 to TARS.
    • +
    • del3 : deletes the 3rd task shown in TARS.
    • +
    • exit : exits the app
    • +
    +
  12. +
  13. +

    Refer to the Features section below for details of each command.

    +
  14. +
  15. +

    NOTE

    +
      +
    • All text in < > are required fields whereas those in [ ] are optional.
    • +
    • <INDEX> refers to the index number of a task shown in the task list.
    • +
    • The index must be a positive integer 1, 2, 3, …
    • +
    • Priority options are: h for High, m for Medium, l for Low.
    • +
    +
  16. +
+

Features

+

Adding a task : add

+

Adds a task to TARS
+Format: <TASK_NAME> [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]

+
+

Support for events (i.e., has a start time and end time), deadlines (tasks that have to be done before a specific deadline), and floating tasks (tasks without specific times).

+

Parameters can be in any order.

+
+

Examples:

+
    +
  • add Meet John Doe /dt 26/09/2016 0900 to 26/09/2016 1030 /t catchup
  • +
  • add Complete CS2103 Quiz /dt 23/09/2016 /p h /t Quiz /t CS2103, /r 13 EVERY WEEK
  • +
  • add Floating Task
  • +
+

Changing data storage location : cd

+

Changes the directory of the TARS storage file. +Format: cd <FILE_PATH>

+
+

Returns an error if the directory chosen is invalid.

+

<FILE_PATH> must end with the file type extension, .xml

+
+

Examples:

+
    +
  • cd C:\Users\John_Doe\Documents\tars.xml
  • +
+

Clearing the data storage file : clear

+

Clears the whole To-Do List storage file.
+Format: clear

+

Confirming a reserved timeslot : confirm

+

Confirms a dateTime for a reserved task and adds it to the task list.
+Format: confirm <RSV_TASK_INDEX> <DATETIME_INDEX> [/p PRIORITY] [/t TAG_NAME ...]

+
+

Confirm the task of a specific <RSV_TASK_INDEX> at a dateTime of a specific <DATETIME_INDEX>.

+

The <RSV_TASK_INDEX> refers to the index number shown in the reserved task list.

+

The <DATETIME_INDEX> refers to the index number of the dateTime.

+
+

Examples:

+
    +
  • confirm 3 2 /p l /t Tag
  • +
+

Deleting a task : del

+

Deletes the task based on its index in the task list.
+Formats:

+
    +
  • del <INDEX> [INDEX ...]
  • +
  • del <START_INDEX>..<END_INDEX>
  • +
+
+

Deletes the task at the specific <INDEX>.

+

Start index of range must be before end index.

+
+

Examples:

+
    +
  • del 3 6
  • +
  • del 1..3
  • +
+

Marking tasks as done : do

+

Marks the task based on its index in the task list as done. +Format: do <INDEX> [INDEX ...]
+Format: do <START_INDEX>..<END_INDEX>

+
+

Marks the task at the specific <INDEX> as done.

+

Start index of range must be before end index.

+
+

Examples:

+
    +
  • do 2 4 6
  • +
  • do 1..3
  • +
+

Editing a task : edit

+

Edits any component of a particular task.
+Format: edit <INDEX> [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] [/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]

+
+

Edits the task at the specific <INDEX>.

+

/ta adds a tag to the task.

+

/tr removes a tag from the task.

+

Parameters can be in any order.

+
+

Examples:

+
    +
  • edit 3 /n Meet John Tan /dt 08/10/2016 1000 to 1200 /p h /ta friend
  • +
+

Exiting the program : exit

+

Exits the program.
+Format: exit

+

Finding tasks : find

+

Finds all tasks containing a list of keywords (i.e. AND search).
+Two modes: Quick Search & Filter Search.
+Format:

+
    +
  • [Quick Search]: find <KEYWORD> [KEYWORD ...]
  • +
  • [Filter Search]: find [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]
  • +
+
+

Quick Search Mode: Find tasks quickly by entering keywords that match what is displayed in the task list.

+

Filter Search Mode: Find tasks using task filters (i.e. /n, /p, /dt, /do, /ud, /t).

+

Use /n to filter tasks by task name.

+

Use /p to filter tasks by priority level.

+

Use /dt to filter tasks by date (in a date range).

+

Use /do to filter all done tasks (Cannot be used together with /ud).

+

Use /ud to filter all undone tasks (Cannot be used together with /do).

+

Use /t to filter tasks by tags.

+

<KEYWORD> are case-insensitive.

+

Parameters can be in any order.

+
+

Examples:

+
    +
  • find meet John uses Quick Search and returns all tasks containing BOTH the keywords “meet” and “John” (e.g. meet John Doe)
  • +
  • find /n meet /dt 17/10/2016 1300 to 18/10/2016 1400 uses Filter Search and returns all tasks whose name contains “meet” and whose task date falls within the range “17/10/2016 1300 to 18/10/2016 1400” (e.g. meet Tim for dinner, 17/10/2016 1800 to 17/10/2016 1900)
  • +
+

Suggesting free timeslots : free

+

Suggests free timeslots in a specified day. +Format: free <DATETIME>

+
+

Does not check for tasks without dateTime nor tasks without a start dateTime.

+
+

Examples:

+
    +
  • free next tuesdsay
  • +
  • free 26/10/2016
  • +
+

Displaying a list of available commands : help

+

Shows program usage instructions in help panel. +Format: help [COMMAND_WORD]

+
+

Help is also shown if you enter an incorrect command e.g. abcd.

+
+

Examples:

+
    +
  • help add
  • +
  • help summary
  • +
+

Listing tasks : ls

+

Lists all tasks. +Format:

+
    +
  • ls
  • +
  • ls /dt [dsc]
  • +
  • ls /p [dsc]
  • +
+
+

All tasks listed by default.

+

Use /dt to list all tasks by earliest end dateTime.

+

Use /p to list all task by priority from low to high.

+

Use dsc with previous two prefixes to reverse the order.

+
+

Examples:

+
    +
  • ls
  • +
  • ls /dt
  • +
  • ls /dt dsc
  • +
  • ls /p
  • +
  • ls /p dsc
  • +
+

Redoing a command : redo

+

Redo a previous command +Format: redo

+
+

Able to redo all add, delete, edit, tag, rsv, confirm and del commands from the time the app starts running.

+
+

Reserving timeslots for a task : rsv

+

Reserves one or more timeslot for a task
+Format: rsv <TASK_NAME> </dt DATETIME> [/dt DATETIME ...]

+
+

Multiple dateTimes can be added.

+
+

Examples:

+
    +
  • rsv Meet John Doe /dt 26/09/2016 0900 to 1030 /dt 28/09/2016 1000 to 1130
  • +
+

Deleting a task with reserved timeslots : rsv /del

+

Deletes a task with all its reserved time slots
+Format: rsv /del <INDEX> +Format: rsv /del <START_INDEX>..<END_INDEX>

+
+

Deletes the task at the specific <INDEX>.

+

Start index of range must be before end index.

+
+

Examples:

+
    +
  • rsv /del 5
  • +
  • rsv /del 1..4
  • +
+

Editing a tag’s name : tag /e

+

Edits a tag’s name
+Format: tag /e <INDEX> <TAG_NAME>

+
+

Edits the name of the tag at the specific <INDEX>.

+
+

Examples:

+
    +
  • tag /e 5 Assignment
  • +
+

Deleting a tag : tag /del

+

Deletes a particular tag
+Format: tag /del <INDEX>

+
+

Deletes the tag at the specific <INDEX>.

+
+

Examples:

+
    +
  • tag /del 4 deletes the tag at Index 4
  • +
+

Listing all tags : tag /ls

+

Lists all tags in TARS
+Format: tag /ls

+

Marking tasks as undone : ud

+

Marks the task based on its index in the task list as undone. +Format: ud <INDEX> [INDEX ...]
+Format: ud <START_INDEX>..<END_INDEX>

+
+

Marks the task at the specific <INDEX> as undone.

+

Start index of range must be before end index.

+
+

Examples:

+
    +
  • ud 2 4 6
  • +
  • ud 1..3
  • +
+

Undoing a command : undo

+

Undo a command executed by the user.
+Format: undo

+
+

Able to undo all add, delete, edit, tag, rsv, confirm and del commands from the time the app starts running.

+
+

Saving the data

+

TARS data are saved in the hard disk automatically after any command that changes the data.
+There is no need to save manually.

+

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 TARS app.

+

Supported Date Formats

+

formal dates

+

Formal dates are those in which the day, month, and year are represented as integers separated by a common separator character. The year is optional and may preceed the month or succeed the day of month. If a two-digit year is given, it must succeed the day of month.

+

Examples:

+
    +
  • 28-01-2016
  • +
  • 28/01/2016
  • +
  • 1/02/2016
  • +
  • 2/2/16
  • +
+

relaxed dates

+

Relaxed dates are those in which the month, day of week, day of month, and year may be given in a loose, non-standard manner, with most parts being optional.

+

Examples:

+
    +
  • The 31st of April in the year 2008
  • +
  • Fri, 21 Nov 1997
  • +
  • Jan 21, '97
  • +
  • Sun, Nov 21
  • +
  • jan 1st
  • +
  • february twenty-eighth
  • +
+

relative dates

+

Relative dates are those that are relative to the current date.

+

Examples:

+
    +
  • next thursday
  • +
  • last wednesday
  • +
  • today
  • +
  • tomorrow
  • +
  • yesterday
  • +
  • next week
  • +
  • next month
  • +
  • next year
  • +
  • 3 days from now
  • +
  • three weeks ago
  • +
+

prefixes

+

Most of the above date formats may be prefixed with a modifier.

+

Examples: +day after +the day before +the monday after +the monday before +2 fridays before +4 tuesdays after

+

time

+

The above date formats may be prefixed or suffixed with time information.

+

Examples:

+
    +
  • 0600h
  • +
  • 06:00 hours
  • +
  • 6pm
  • +
  • 5:30 a.m.
  • +
  • 5
  • +
  • 12:59
  • +
  • 23:59
  • +
  • 8p
  • +
  • noon
  • +
  • afternoon
  • +
  • midnight
  • +
+

relative times

+

Examples:

+
    +
  • 10 seconds ago
  • +
  • in 5 minutes
  • +
  • 4 minutes from now
  • +
+

Command Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandFormat
Addadd <TASK_NAME> [/dt DATETIME] [/p PRIORITY] [/t TAG_NAME ...] [/r NUM_TIMES FREQUENCY]
Change Storage Locationcd <FILE_PATH.xml>
Clearclear
Confirmconfirm <RSV_TASK_INDEX> <DATETIME_INDEX> [/p PRIORITY] [/t TAG_NAME ...]
Deletedel <INDEX> [INDEX ...] <br> del <START_INDEX>..<END_INDEX>
Donedo <INDEX> [INDEX ...] <br> do <START_INDEX>..<END_INDEX>
Editedit <INDEX> [/n TASK_NAME] [/dt DATETIME] [/p PRIORITY] [/ta TAG_TO_ADD ...] [/tr TAG_TO_REMOVE ...]
Exitexit
Find [Quick Search]find <KEYWORD> [KEYWORD ...]
Find [Filter Search]find [/n NAME_KEYWORD ...] [/dt DATETIME] [/p PRIORITY] [/do] [/ud] [/t TAG_KEYWORD ...]
Freefree <DATETIME>
Helphelp [COMMAND_WORD]
Listls
List [Date]ls /dt
List [Priority]ls /p
Redoredo
Reserversv <TASK_NAME> </dt DATETIME> [/dt DATETIME ...]
Reserve [Delete]rsv /del <INDEX> <br> rsv /del <START_INDEX>..<END_INDEX>
Tag [Delete]tag /del <INDEX>
Tag [Edit]tag /e <INDEX> <TAG_NAME>
Tag [List]tag /ls
Undoneud <INDEX> [INDEX ...] <br> ud <START_INDEX>..<END_INDEX>
Undoundo
+ + diff --git a/src/main/resources/images/tars_icon_32.png b/src/main/resources/images/tars_icon_32.png new file mode 100644 index 000000000000..4ed515c27df1 Binary files /dev/null and b/src/main/resources/images/tars_icon_32.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 575de420b994..da0bdbef9a02 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,11 @@ - - - - + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css deleted file mode 100644 index 8043b344253a..000000000000 --- a/src/main/resources/view/DarkTheme.css +++ /dev/null @@ -1,288 +0,0 @@ -.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; -} - -.cell_big_label { - -fx-font-size: 16px; - -fx-text-fill: #010504; -} - -.cell_small_label { - -fx-font-size: 11px; - -fx-text-fill: #010504; -} - -.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: #ffffff; -} - -.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-background-color: transparent; - -fx-border-color: #d6d6d6; - -fx-border-width: 1 1 1 1; -} - -#commandTypeLabel { - -fx-font-size: 11px; - -fx-text-fill: #F70D1A; -} - -#filterField, #personListPanel, #personWebpage { - -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); -} \ No newline at end of file diff --git a/src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml b/src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml deleted file mode 100644 index bc761118235a..000000000000 --- a/src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css deleted file mode 100644 index 606c927d9a42..000000000000 --- a/src/main/resources/view/Extensions.css +++ /dev/null @@ -1,16 +0,0 @@ - -.error { - -fx-background-color: red; -} - - -.tag-selector { - -fx-border-width: 1; - -fx-border-color: white; - -fx-border-radius: 3; - -fx-background-radius: 3; -} - -.tooltip-text { - -fx-text-fill: white; -} \ No newline at end of file diff --git a/src/main/resources/view/Header.fxml b/src/main/resources/view/Header.fxml new file mode 100644 index 000000000000..ff707369239c --- /dev/null +++ b/src/main/resources/view/Header.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpPanel.fxml b/src/main/resources/view/HelpPanel.fxml new file mode 100644 index 000000000000..8b1c6c7dc209 --- /dev/null +++ b/src/main/resources/view/HelpPanel.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml deleted file mode 100644 index c4cbd84cac28..000000000000 --- a/src/main/resources/view/HelpWindow.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 2f9235c621d8..76c7dc542e22 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,56 +1,105 @@ - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml deleted file mode 100644 index 13d4b149651b..000000000000 --- a/src/main/resources/view/PersonListCard.fxml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 000c4c999b65..000000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index cc650d739e22..5cbd87fecd5a 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -2,10 +2,9 @@ - - - - - + + + + diff --git a/src/main/resources/view/RsvTaskListCard.fxml b/src/main/resources/view/RsvTaskListCard.fxml new file mode 100644 index 000000000000..cfd102f0deaa --- /dev/null +++ b/src/main/resources/view/RsvTaskListCard.fxml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/RsvTaskListPanel.fxml b/src/main/resources/view/RsvTaskListPanel.fxml new file mode 100644 index 000000000000..0099236f38d3 --- /dev/null +++ b/src/main/resources/view/RsvTaskListPanel.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/view/StatusBarFooter.fxml b/src/main/resources/view/StatusBarFooter.fxml index 2656558b6eb7..881273258cf4 100644 --- a/src/main/resources/view/StatusBarFooter.fxml +++ b/src/main/resources/view/StatusBarFooter.fxml @@ -1,13 +1,20 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TarsTheme.css b/src/main/resources/view/TarsTheme.css new file mode 100644 index 000000000000..dc753b5dfb74 --- /dev/null +++ b/src/main/resources/view/TarsTheme.css @@ -0,0 +1,322 @@ +/* @@author A0121533W */ + +/* + * ==================================================================== + * ============================Fonts=================================== + * ==================================================================== + */ + +@font-face { + font-family: Roboto; + src: url("../fonts/roboto/Roboto-Regular.ttf"); +} + +@font-face { + font-family: "Roboto Medium"; + src: url("../fonts/roboto/Roboto-Medium.ttf"); +} + +@font-face { + font-family: "Roboto Light"; + src: url("../fonts/roboto/Roboto-Light.ttf"); +} + +@font-face { + font-family: "Roboto Black"; + src: url("../fonts/roboto/Roboto-Black.ttf"); +} + +@font-face { + font-family: "Roboto Bold"; + src: url("../fonts/roboto/Roboto-Bold.ttf"); +} + +.root { + -fx-focus-color: transparent; +} + +.background { + -fx-background-color: #455A64; +} + +.list-label { + -fx-font-size: 18px; + -fx-text-fill: #212121; + -fx-background-color: white; + -fx-font-family: "Roboto Black"; +} + +.error { + -fx-background-color: #F44336; +} + +/* + * ==================================================================== + * ========================SplitPane=================================== + * ==================================================================== + */ + +.split-pane:horizontal .split-pane-divider { + -fx-border-color: transparent; + -fx-background-color: lightgrey; + -fx-padding: 0 0.1 0 0.1; +} + +.split-pane { + -fx-border-radius: 0; + -fx-border-width: 0; + -fx-background-color: white; +} + +/* + * ==================================================================== + * ==========================TabPane=================================== + * ==================================================================== + */ + +.tab-pane { + -fx-padding: 0 10 0 5; +} + +.tab-pane .tab { + -fx-background-color: white; + -fx-border-width: 0 0 2 0; +} + +.tab .tab-label { + -fx-font-size: 18px; + -fx-text-fill: #212121; + -fx-font-family: "Roboto Black"; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.tab:selected { + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-background-color: white; + -fx-border-width: 0 0 2 0; + -fx-border-color: transparent transparent #2E8AF7 transparent; +} + +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; +} + +.tab-pane *.tab-header-background { + -fx-background-color: white; + -fx-border-width: 1 0 1 0; +} + + +.thisWeek-panel { + -fx-background-color: white; +} + +.thisWeek-panel-label { + -fx-font-size: 24px; + -fx-text-fill: #212121; +} + +.date-label { + -fx-font-size: 32px; + -fx-text-fill: #212121; +} + +/* + * ==================================================================== + * =========================CommandBox================================= + * ==================================================================== + */ + +.text-field { + -fx-font-size: 15px; + -fx-border-color: transparent; + -fx-font-family: "Roboto"; + -fx-background-insets: 0; +} + +.text-field:focused { + -fx-focus-color: transparent; +} + +/* + * ==================================================================== + * ==============Results Display & RsvTask DateTimeList================ + * ==================================================================== + */ + + .anchor-pane { + -fx-background-color: #F6F6F6; +} + +.result-display { + -fx-background-color: #F6F6F6; + -fx-border-color: transparent; + -fx-focus-color: transparent; + -fx-background-insets: 0; +} + +.result-display .label { + -fx-text-fill: #212121; +} + +.text-area { + -fx-background-insets: 0; + -fx-background-color: transparent; +} + +.text-area .content { + -fx-background-color: #F6F6F6; + -fx-border-color: transparent; + -fx-background-insets: 0; +} + +.dateTimeListArea .content { + -fx-background-color: white; +} + +/* + * ==================================================================== + * ========ListView and ListCell for TaskList/RsvTaskList============== + * ==================================================================== + */ + +.taskList { + -fx-border-width: 1 0 0 0; + -fx-border-color: lightgrey; +} + +.list-view { + -fx-background-color: white; + -fx-background-insets: 0; + -fx-padding: 0px; +} + +#cardPane { + -fx-background-color: white; + -fx-border-color: lightgrey; + -fx-border-width: 0 0 2 0; +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:empty { + -fx-background-color: white; +} + +.cell_index_label { + -fx-font-size: 16px; + -fx-font-family: "Roboto Bold"; +} + +.cell_big_label { + -fx-font-size: 16px; + -fx-font-family: "Roboto"; + -fx-text-fill: #212121; +} + +.cell_medium_label { + -fx-font-size: 14px; + -fx-font-family: "Roboto"; + -fx-text-fill: #757575; +} + +.cell_small_label { + -fx-font-size: 14px; + -fx-text-fill: #757575; + -fx-font-family: "Roboto"; +} + +.circle_label { + -fx-font-weight: bold; + -fx-font-size: 16px; +} + + +/* + * ==================================================================== + * ===========================Status Bar=============================== + * ==================================================================== + */ + + .status-bar { + -fx-background-color: #2e8af7; + -fx-text-fill: white; +} + +.status-bar .label { + -fx-text-fill: white; + -fx-font-size: 12px; +} + +/* + * ==================================================================== + * ============================Dialog================================== + * ==================================================================== + */ + +.dialog-pane { + -fx-background-color: white; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: lightgrey; +} + +.dialog-pane > *.label.content { + -fx-font-size: 12px; + -fx-font-weight: bold; + -fx-text-fill: #212121; + -fx-font-family: "Roboto"; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: white; +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 12px; + -fx-font-style: italic; + -fx-fill: lightgrey; + -fx-text-fill: #212121; + -fx-font-family: "Roboto"; +} + +/* + * ==================================================================== + * =========================Scroll-Bar================================= + * ==================================================================== + */ + +.scroll-bar .thumb { + -fx-background-color: darkgrey; + -fx-background-insets: 2; +} + +.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 5 1 5; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 5 1 5 1; +} + diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 000000000000..fbed08bf9a18 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 000000000000..f4ed47e94672 --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ThisWeekPanel.fxml b/src/main/resources/view/ThisWeekPanel.fxml new file mode 100644 index 000000000000..a44e8ac315cc --- /dev/null +++ b/src/main/resources/view/ThisWeekPanel.fxml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/jfoenix-fonts.css b/src/main/resources/view/jfoenix-fonts.css new file mode 100644 index 000000000000..0c8128fa0143 --- /dev/null +++ b/src/main/resources/view/jfoenix-fonts.css @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +@font-face { + font-family: Roboto; + src: url("../font/roboto/Roboto-Regular.ttf"); +} + +@font-face { + font-family: "Roboto Medium"; + src: url("../font/roboto/Roboto-Medium.ttf"); +} + +@font-face { + font-family: "Roboto Light"; + src: url("../font/roboto/Roboto-Light.ttf"); +} + +@font-face { + font-family: "Roboto Bold"; + src: url("../font/roboto/Roboto-Bold.ttf"); +} + + +/* NOTE: the file font must be included as the 1st css file in order to work */ \ No newline at end of file diff --git a/src/test/data/ConfigUtilTest/ExtraValuesConfig.json b/src/test/data/ConfigUtilTest/ExtraValuesConfig.json index 578b4445204b..7edd0ed1bae0 100644 --- a/src/test/data/ConfigUtilTest/ExtraValuesConfig.json +++ b/src/test/data/ConfigUtilTest/ExtraValuesConfig.json @@ -2,7 +2,7 @@ "appTitle" : "Typical App Title", "logLevel" : "INFO", "userPrefsFilePath" : "C:\\preferences.json", - "addressBookFilePath" : "addressbook.xml", - "addressBookName" : "TypicalAddressBookName", + "tarsFilePath" : "tars.xml", + "tarsName" : "TypicalTarsName", "extra" : "extra value" } \ No newline at end of file diff --git a/src/test/data/ConfigUtilTest/TypicalConfig.json b/src/test/data/ConfigUtilTest/TypicalConfig.json index 195b2bf33033..60e47f0e8f4a 100644 --- a/src/test/data/ConfigUtilTest/TypicalConfig.json +++ b/src/test/data/ConfigUtilTest/TypicalConfig.json @@ -2,6 +2,6 @@ "appTitle" : "Typical App Title", "logLevel" : "INFO", "userPrefsFilePath" : "C:\\preferences.json", - "addressBookFilePath" : "addressbook.xml", - "addressBookName" : "TypicalAddressBookName" + "tarsFilePath" : "tars.xml", + "tarsName" : "TypicalTarsName" } \ 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..d465b69f6705 --- /dev/null +++ b/src/test/data/ManualTesting/SampleData.xml @@ -0,0 +1,537 @@ + + + + CS3244 Cheatsheet + h + + 19/11/2016 2359 + + false + cs3244 + finals + + + NEST Boot Camp 2016 + m + + 19/12/2016 0000 + 21/12/2016 2359 + + false + + + NUS Museum Talk + + + 10/11/2016 1900 + 10/11/2016 2000 + + false + + + Launch of i5Lab + + + 07/11/2016 0930 + + false + + + Hackware v24 + + + 02/11/2016 1930 + 02/11/2016 2230 + + false + + + Design Talk + + + 18/11/2016 1600 + 18/11/2016 1700 + + false + + + Kopi Chat 500 Durians + + + 09/11/2016 1600 + 09/11/2016 1730 + + false + + + Modern Ageing Singapore Pitch + + + 25/11/2016 1500 + 25/11/2016 1800 + + false + + + Global Innovators Talk + + + 22/11/2016 1630 + 22/11/2016 1800 + + false + + + ST3240 Slides + m + + false + presentation + st3240 + + + 9th STePS + + + 11/11/2016 1800 + + false + + + Guitar audtions + l + + 14/11/2016 1100 + 15/11/2016 1100 + + false + + + Collect Welfare pack + h + + 02/11/2016 0930 + 02/11/2016 1430 + + true + + + Cut Hair + l + + 19/11/2016 1000 + + true + + + Feedback Exercise + h + + 04/11/2016 0000 + 18/11/2016 2359 + + false + + + Push SMS Subscription + + + 05/12/2016 2359 + + true + + + MA3269 Lecture Quiz + h + + 07/11/2016 1000 + 07/11/2016 1200 + + false + + + Duke NUS Open House + + + 22/11/2016 2359 + + false + + + UCC usher interview + l + + 16/11/2016 1400 + 16/11/2016 1800 + + false + + + NUS CFA proposal + + + 08/11/2016 2359 + + false + + + A Math Tuition + + + 19/11/2016 1900 + 19/11/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 26/11/2016 1900 + 26/11/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 03/12/2016 1900 + 03/12/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 10/12/2016 1900 + 10/12/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 17/12/2016 1900 + 17/12/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 24/12/2016 1900 + 24/12/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 31/12/2016 1900 + 31/12/2016 2100 + + false + Jeremy + + + A Math Tuition + + + 07/01/2017 1900 + 07/01/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 14/01/2017 1900 + 14/01/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 21/01/2017 1900 + 21/01/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 28/01/2017 1900 + 28/01/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 04/02/2017 1900 + 04/02/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 11/02/2017 1900 + 11/02/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 18/02/2017 1900 + 18/02/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 25/02/2017 1900 + 25/02/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 04/03/2017 1900 + 04/03/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 11/03/2017 1900 + 11/03/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 18/03/2017 1900 + 18/03/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 25/03/2017 1900 + 25/03/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 01/04/2017 1900 + 01/04/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 08/04/2017 1900 + 08/04/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 15/04/2017 1900 + 15/04/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 22/04/2017 1900 + 22/04/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 29/04/2017 1900 + 29/04/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 06/05/2017 1900 + 06/05/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 13/05/2017 1900 + 13/05/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 20/05/2017 1900 + 20/05/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 27/05/2017 1900 + 27/05/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 03/06/2017 1900 + 03/06/2017 2100 + + false + Jeremy + + + A Math Tuition + + + 10/06/2017 1900 + 10/06/2017 2100 + + false + Jeremy + + + Gym + m + + 14/11/2016 1400 + 14/11/2016 1600 + + false + + + Gym + m + + 21/11/2016 1400 + 21/11/2016 1600 + + false + + + Gym + m + + 28/11/2016 1400 + 28/11/2016 1600 + + false + + + Gym + m + + 05/12/2016 1400 + 05/12/2016 1600 + + false + + + Gym + m + + 12/12/2016 1400 + 12/12/2016 1600 + + false + + + cs3244 + + + finals + + + presentation + + + st3240 + + + Jeremy + + diff --git a/src/test/data/ManualTesting/TestScript.md b/src/test/data/ManualTesting/TestScript.md new file mode 100644 index 000000000000..dcfed37a9b7c --- /dev/null +++ b/src/test/data/ManualTesting/TestScript.md @@ -0,0 +1,130 @@ +# Test Script + +## How to load sample data + +1. Download [F10-C1][TARS].jar +2. Download [SampleData.xml](https://github.com/CS2103AUG2016-F10-C1/main/blob/develop/src/test/data/ManualTesting/SampleData.xml) +3. Launch the app +4. Enter `cd SampleData.xml` in the command box + +## Script + +1. Add command + - Add an event: `add Sam's birthday /dt 20/11/2016 5pm to 20/11/2016 9pm /p m` + > 1 Task will appear on the left panel with events that happen on the same day. + + - Add a deadline: `add Finish assignment /dt 20 Nov 5pm /p m` + > 2 Tasks will appear on the left panel with events that happen on the same day. + + - Add a floating task: `add Buy clothes` + > Task will appear on the left panel with index 58. + + - recurring task: `add Swim lesson /dt 20 Nov 8am to 9am /r 10 every week` + > 10 task will be added from index 59 - 68. + +2. List command + - List by earliest end date: `ls /dt` + > Earliest task (ST3240 Slides) will appears at the top of the list. + + - list by latest end date: `ls /dt dsc` + > Latest task (A Math Tuition) will appears at the top. + + - list by lowest priority (low to high): `ls /p` + > Task without any priority will be listed at the top. + + - list by priority (high to low): `ls /p dsc` + > Task with high priority will be listed at the top. + +3. Find command + - Find task by name: `find talk` + > 3 events with names that contain `talk` will be listed. + > Results display indicate quick search is performed as no prefixes are specified. + + - Find tasks by tags: `find /t Jeremy` + > 30 Events which has the tag [jeremy] will be listed. + + - Find tasks by priority: `find /p h` + > 4 Tasks with high priority will be listed. + + - Find tasks which are done `find /do` + > 3 tasks (Collect Welfare pack, Cut hair and Push SMS Subscription) which are done are listed. + + - Find tasks which are undone `find /ud` + > Remaining undone tasks will be listed. + + - Find tasks by date range: `find /dt 20 nov to 21 nov` + > 4 events (Gym, Sam's birthday, Finish Assignment, Swim Lesson) will be listed. + +4. Edit command + - Edit task name and priority: `edit 1 /n Gym with Sean /p l` + + > The name of the first task will be changed to `Gym with Sean` and the priority is changed to `l`. + +5. Delete command + - Delete the first task: `del 1` + + > The first task, `Gym with Sean` is removed. + +6. Undo command + - Undo the previous command: `undo` + + > The removed task, `Gym with Sean` is added back to the list. + +7. Redo command + - Redo the previous command: `redo` + + > The task, `Gym with Sean` is removed. + +8. Do command + - Marks a task as done: + - Scroll to the top of the task list + - `do 1` + + > The task `CS3244 Cheatsheet` is mark as done (the card is greyed with a tick). + +9. Ud command + - Marks a task as undone: `ud 1` + + > The task `CS3244 Cheatsheet` is mark as undone (the card is ungreyed and the tick is removed). + +10. Help command + - Show help add: `help add` + > The help panel is shown on the right panel and scrolled to the add section. + + - Show help summary: `help summary` + > The help panel is shown on the right panel and scrolled to the summary section. + +11. Rsv command + - Add a reserve task: `rsv Prepare project demo /dt 14/11 2pm to 3pm /dt 15/11 2pm to 3pm /dt 16/11 2pm to 3pm` + + > The reserve panel on the right is selected. The reserve task is added with 3 reserved dates. + +12. Confirm command + - Confirm reserve task: `confirm 1 3` + + > The task, `Prepare project demo` is added to the task list panel on the left with the datetime, `Wed, Nov 16 2016`. + +13. Delete command + - Deletes a range of task: `del 64..67` + + > 4 tasks, `A Math Tuition` is removed. + +14. Tag command + - List tags: `tag /ls` + > 5 tags, `cs3244`, `finals`, `presentation`, `st3240`, `Jeremy` is shown at the result display box. + + - Rename tag: `tag /e 2 final` + > All tasks that have the `Jeremy` tag is being replaced with the `JTuition` tag. + + - Delete tag: `tag /del 1` + > All tasks that have the tag, `cs3244`, is removed. + +15. Free command + - Find free timeslots: `free 20 nov` + + > 3 tasks is shown on the left panel. 3 free timeslots is shown at the result display box. + +16. Clear command + - Clears all data: `clear` + + > All data will be cleared. diff --git a/src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml b/src/test/data/XmlTarsStorageTest/NotXmlFormatTars.xml similarity index 100% rename from src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml rename to src/test/data/XmlTarsStorageTest/NotXmlFormatTars.xml diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempTars.xml similarity index 84% rename from src/test/data/XmlUtilTest/tempAddressBook.xml rename to src/test/data/XmlUtilTest/tempTars.xml index 41eeb8eb391a..ed4639923460 100644 --- a/src/test/data/XmlUtilTest/tempAddressBook.xml +++ b/src/test/data/XmlUtilTest/tempTars.xml @@ -1,6 +1,6 @@ - - + + 1 John Doe @@ -8,8 +8,8 @@ - + Friends - + 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/validTars.xml b/src/test/data/XmlUtilTest/validTars.xml new file mode 100644 index 000000000000..cab9f6be0e86 --- /dev/null +++ b/src/test/data/XmlUtilTest/validTars.xml @@ -0,0 +1,12 @@ + + + + Task 1 + h + + 01/09/2016 1400 + 02/09/2016 1400 + + false + + diff --git a/src/test/java/guitests/AddCommandTest.java b/src/test/java/guitests/AddCommandTest.java index 3b2e1844bd0d..fec0d497bca8 100644 --- a/src/test/java/guitests/AddCommandTest.java +++ b/src/test/java/guitests/AddCommandTest.java @@ -1,53 +1,84 @@ 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 tars.commons.core.Messages; +import tars.commons.exceptions.IllegalValueException; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.testutil.TestTask; +import tars.testutil.TestUtil; import static org.junit.Assert.assertTrue; -public class AddCommandTest extends AddressBookGuiTest { +// @@author A0140022H +/** + * GUI test for add command + */ +public class AddCommandTest extends TarsGuiTest { @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 to empty list + // add one task + TestTask[] currentList = td.getTypicalTasks(); + TestTask[] expectedList1 = {td.taskG}; + TestTask taskToAdd = td.taskH; + assertAddSuccess(taskToAdd, expectedList1); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + // add another task + taskToAdd = td.taskI; + TestTask[] expectedList2 = {td.taskH}; + assertAddSuccess(taskToAdd, expectedList2); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + expectedList2 = TestUtil.addTasksToList(expectedList2, taskToAdd); + + // add duplicate task + commandBox.runCommand(td.taskH.getAddCommand()); + assertResultMessage(Messages.MESSAGE_DUPLICATE_TASK); + assertTrue(taskListPanel.isListMatching(expectedList2)); + + // add to empty list commandBox.runCommand("clear"); - assertAddSuccess(td.alice); + assertAddSuccess(td.taskA); - //invalid command + // invalid command commandBox.runCommand("adds Johnny"); assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + + assertAddSuccess(td.taskA); } - 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); + // confirm the new card contains the right data + TaskCardHandle addedCard = + taskListPanel.navigateToTask(taskToAdd.getName().taskName); + 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)); } + @Test + public void addRecurring() { + TestTask[] recurringList = new TestTask[0]; + recurringList = + TestUtil.addTasksToList(recurringList, td.taskC, td.taskD); + try { + recurringList[1].setName(new Name("Task C")); + recurringList[1].setPriority(new Priority("l")); + } catch (IllegalValueException e) { + e.printStackTrace(); + } + + commandBox.runCommand("clear"); + commandBox.runCommand( + "add Task C /dt 03/09/2016 1400 to 04/09/2016 1400 /p l /r 2 every day"); + assertTrue(taskListPanel.isListMatching(recurringList)); + } } diff --git a/src/test/java/guitests/ClearCommandTest.java b/src/test/java/guitests/ClearCommandTest.java index 9d52b427659c..ae24a199ea58 100644 --- a/src/test/java/guitests/ClearCommandTest.java +++ b/src/test/java/guitests/ClearCommandTest.java @@ -4,19 +4,19 @@ import static org.junit.Assert.assertTrue; -public class ClearCommandTest extends AddressBookGuiTest { +public class ClearCommandTest extends TarsGuiTest { @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("delete 1"); + commandBox.runCommand(td.taskH.getAddCommand()); + assertTrue(taskListPanel.isListMatching(td.taskH)); + commandBox.runCommand("del 1"); assertListSize(0); //verify clear command works when the list is empty @@ -26,6 +26,6 @@ public void clear() { private void assertClearCommandSuccess() { commandBox.runCommand("clear"); assertListSize(0); - assertResultMessage("Address book has been cleared!"); + assertResultMessage("TARS has been cleared!"); } } diff --git a/src/test/java/guitests/CommandBoxTest.java b/src/test/java/guitests/CommandBoxTest.java index 1379198bf8b0..75898a56e24f 100644 --- a/src/test/java/guitests/CommandBoxTest.java +++ b/src/test/java/guitests/CommandBoxTest.java @@ -2,21 +2,83 @@ import org.junit.Test; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.UndoCommand; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -public class CommandBoxTest extends AddressBookGuiTest { +public class CommandBoxTest extends TarsGuiTest { @Test public void commandBox_commandSucceeds_textCleared() { - commandBox.runCommand(td.benson.getAddCommand()); + commandBox.runCommand(td.taskB.getAddCommand()); assertEquals(commandBox.getCommandInput(), ""); } @Test - public void commandBox_commandFails_textStays(){ + public void commandBox_commandFails_textStays() { commandBox.runCommand("invalid command"); assertEquals(commandBox.getCommandInput(), "invalid command"); - //TODO: confirm the text box color turns to red + } + + // @@author A0124333U + @Test + public void commandBox_cycleThroughCommandTextHist() { + commandBox.runCommand("User Input Command"); + commandBox.runCommand("User Input Command 2"); + commandBox.runCommand("User Input Command 3"); + commandBox.enterCommand("Temp User Input Command"); + + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 3"); + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 2"); + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command"); + commandBox.pressUpKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command"); + commandBox.pressDownKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 2"); + commandBox.pressDownKey(); + assertEquals(commandBox.getCommandInput(), "User Input Command 3"); + commandBox.pressDownKey(); + assertEquals(commandBox.getCommandInput(), "Temp User Input Command"); + + } + + @Test + public void commandBox_cycleThroughTabPane() { + commandBox.pressCtrlRightArrowKeys(); + assertTrue(commandBox.getRsvTaskListPanelHandle().isTabSelected()); + commandBox.pressCtrlRightArrowKeys(); + assertTrue(commandBox.getHelpPanelHandle().isTabSelected()); + commandBox.pressCtrlRightArrowKeys(); + assertTrue(commandBox.getOverviewPanelHandle().isTabSelected()); + commandBox.pressCtrlLeftArrowKeys(); + assertTrue(commandBox.getHelpPanelHandle().isTabSelected()); + commandBox.pressCtrlLeftArrowKeys(); + assertTrue(commandBox.getRsvTaskListPanelHandle().isTabSelected()); + } + + @Test + public void commandBox_textFieldValueChangesEvents_success() { + commandBox.enterCommand("rsv"); + assertTrue(commandBox.getRsvTaskListPanelHandle().isTabSelected()); + commandBox.pressCtrlLeftArrowKeys(); + commandBox.enterCommand("confirm"); + } + + @Test + public void commandBox_undoShortcut() { + commandBox.pressCtrlZArrowKeys(); + assertEquals(resultDisplay.getText(), UndoCommand.MESSAGE_EMPTY_UNDO_CMD_HIST); + } + + @Test + public void commandBox_redoShortcut() { + commandBox.pressCtrlYArrowKeys(); + assertEquals(resultDisplay.getText(), RedoCommand.MESSAGE_EMPTY_REDO_CMD_HIST); } } diff --git a/src/test/java/guitests/ConfirmCommandTest.java b/src/test/java/guitests/ConfirmCommandTest.java new file mode 100644 index 000000000000..571da90b4269 --- /dev/null +++ b/src/test/java/guitests/ConfirmCommandTest.java @@ -0,0 +1,42 @@ +package guitests; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import guitests.guihandles.TaskCardHandle; +import tars.testutil.TestRsvTask; +import tars.testutil.TestTask; +import tars.testutil.TestUtil; + +// @@author A0124333U +/** + * GUI test for confirm command + */ +public class ConfirmCommandTest extends TarsGuiTest { + + @Test + public void confirm() { + TestTask[] currentTaskList = td.getTypicalTasks(); + TestRsvTask[] currentRsvTaskList = td.getTypicalRsvTasks(); + TestRsvTask rsvTaskToConfirm = td.rsvTaskA; + TestTask confirmedTask = td.cfmTaskA; + commandBox.runCommand("confirm 1 2 /p h"); + TestTask[] expectedTaskList = + TestUtil.addTasksToList(currentTaskList, confirmedTask); + TestRsvTask[] expectedRsvTaskList = TestUtil + .delRsvTaskFromList(currentRsvTaskList, rsvTaskToConfirm); + + // confirm the new card contains the right data + TaskCardHandle addedCard = + taskListPanel.navigateToTask(confirmedTask.getName().taskName); + assertMatching(confirmedTask, addedCard); + + // confirm that the rsv task list does not contain the confirmed task, + // and that the task list contains the confirmed task + assertTrue(taskListPanel.isListMatching(expectedTaskList)); + assertTrue(rsvTaskListPanel.isListMatching(expectedRsvTaskList)); + + } + +} diff --git a/src/test/java/guitests/DeleteCommandTest.java b/src/test/java/guitests/DeleteCommandTest.java index 10c7b9e0dbea..7a6feb63b79f 100644 --- a/src/test/java/guitests/DeleteCommandTest.java +++ b/src/test/java/guitests/DeleteCommandTest.java @@ -1,54 +1,67 @@ package guitests; import org.junit.Test; -import seedu.address.testutil.TestPerson; -import seedu.address.testutil.TestUtil; + +import tars.testutil.TestTask; +import tars.testutil.TestUtil; import static org.junit.Assert.assertTrue; -import static seedu.address.logic.commands.DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS; +import static tars.logic.commands.DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS; -public class DeleteCommandTest extends AddressBookGuiTest { +// @@author A0121533W +/** + * GUI test for delete command + */ +public class DeleteCommandTest extends TarsGuiTest { @Test public void delete() { - //delete the first in the list - TestPerson[] currentList = td.getTypicalPersons(); + // delete the first in the list + TestTask[] currentList = td.getTypicalTasks(); int targetIndex = 1; assertDeleteSuccess(targetIndex, currentList); - //delete the last in the list - currentList = TestUtil.removePersonFromList(currentList, targetIndex); + // 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.removePersonFromList(currentList, targetIndex); - targetIndex = currentList.length/2; + // 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 person index provided is invalid"); + // invalid index + commandBox.runCommand("del " + 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). + * Runs the delete command to delete the task at specified index and confirms the result is + * correct. + * + * @param targetIndexOneIndexed e.g. to delete the first task in the list, 1 should be given as + * the target index. + * @param currentList A copy of the current list of tasks (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("del " + targetIndexOneIndexed); - commandBox.runCommand("delete " + targetIndexOneIndexed); + // confirm the list now contains all previous tasks except the deleted task + assertTrue(taskListPanel.isListMatching(expectedRemainder)); - //confirm the list now contains all previous persons except the deleted person - assertTrue(personListPanel.isListMatching(expectedRemainder)); + // confirm the result message is correct + assertResultMessage(String.format(MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + taskToDelete + "\n")); - //confirm the result message is correct - assertResultMessage(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); } } diff --git a/src/test/java/guitests/DoUdCommandTest.java b/src/test/java/guitests/DoUdCommandTest.java new file mode 100644 index 000000000000..d09d4d8b5436 --- /dev/null +++ b/src/test/java/guitests/DoUdCommandTest.java @@ -0,0 +1,92 @@ +package guitests; + +import org.junit.Test; +import tars.commons.exceptions.IllegalValueException; +import tars.logic.commands.DoCommand; +import tars.logic.commands.UdCommand; +import tars.model.task.Status; +import tars.testutil.TestTask; +import tars.testutil.TestUtil; + +import static org.junit.Assert.*; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +// @@author A0121533W +/** + * GUI test for done and undone command + */ +public class DoUdCommandTest extends TarsGuiTest { + + @Test + public void doTasks() throws IllegalValueException { + // Initialize Tars list + TestTask[] currentList = td.getTypicalTasks(); + + // Mark tasks as done by indexes + commandBox.runCommand("do 1 2 3"); + + // Confirm the list now contains the specified tasks to be mark as undone + Status done = new Status(true); + int[] indexesToMarkDoneIndexes = {1, 2, 3}; + TestTask[] expectedDoneListIndexes = + TestUtil.markTasks(currentList, indexesToMarkDoneIndexes, done); + assertTrue(taskListPanel.isListMatching(expectedDoneListIndexes)); + + // Mark tasks as done by range + commandBox.runCommand("do 4..7"); + + int[] indexesToMarkDoneRange = {1, 2, 3, 4, 5, 6, 7}; + TestTask[] expectedDoneListRange = + TestUtil.markTasks(currentList, indexesToMarkDoneRange, done); + assertTrue(taskListPanel.isListMatching(expectedDoneListRange)); + + // Mark tasks as undone by indexes + commandBox.runCommand("ud 1 2 3"); + + // Confirm the list now contains the specified tasks to be mark as undone + Status undone = new Status(false); + int[] indexesToMarkUndoneIndexes = {1, 2, 3}; + TestTask[] expectedUndoneListIndexes = TestUtil.markTasks(currentList, + indexesToMarkUndoneIndexes, undone); + assertTrue(taskListPanel.isListMatching(expectedUndoneListIndexes)); + + // Mark tasks as undone by range + commandBox.runCommand("ud 4..7"); + + int[] indexesToMarkUndoneRange = {1, 2, 3, 4, 5, 6, 7}; + TestTask[] expectedUndoneListRange = TestUtil.markTasks(currentList, + indexesToMarkUndoneRange, undone); + assertTrue(taskListPanel.isListMatching(expectedUndoneListRange)); + + // invalid do command + commandBox.runCommand("do abc"); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DoCommand.MESSAGE_USAGE)); + + // invalid do index + commandBox.runCommand("do 8"); + assertResultMessage("The task index provided is invalid"); + + // invalid do range + commandBox.runCommand("do 3..2"); + assertResultMessage( + String.format("Start index should be before end index." + "\n" + + DoCommand.MESSAGE_USAGE)); + + // invalid ud command + commandBox.runCommand("ud abc"); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UdCommand.MESSAGE_USAGE)); + + // invalid ud index + commandBox.runCommand("ud 8"); + assertResultMessage("The task index provided is invalid"); + + // invalid do range + commandBox.runCommand("ud 3..2"); + assertResultMessage( + String.format("Start index should be before end index." + "\n" + + UdCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/test/java/guitests/EditCommandTest.java b/src/test/java/guitests/EditCommandTest.java new file mode 100644 index 000000000000..9083d271888a --- /dev/null +++ b/src/test/java/guitests/EditCommandTest.java @@ -0,0 +1,48 @@ +package guitests; + +import org.junit.Test; + +import tars.commons.exceptions.IllegalValueException; +import tars.logic.commands.EditCommand; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.testutil.TestTask; +import tars.testutil.TestUtil; + +import static org.junit.Assert.*; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +// @@author A0124333U +/** + * GUI test for edit command + */ +public class EditCommandTest extends TarsGuiTest { + + @Test + public void edit() throws IllegalValueException { + // Initialize Tars list + TestTask[] currentList = td.getTypicalTasks(); + + // Edit one task + Name nameToEdit = new Name("Edited Task A"); + Priority priorityToEdit = new Priority("l"); + commandBox.runCommand("edit 1 /n Edited Task A /p l"); + int indexToEdit = 1; + + // confirm the list now contains the edited task + TestTask[] expectedList = TestUtil.editTask(currentList, + indexToEdit - 1, nameToEdit, priorityToEdit); + assertTrue(taskListPanel.isListMatching(expectedList)); + + // invalid command + commandBox.runCommand("edit 1 Johnny"); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditCommand.MESSAGE_USAGE)); + + // invalid index + commandBox.runCommand( + "edit " + (currentList.length + 1) + " /n invalidIndex"); + assertResultMessage("The task index provided is invalid"); + } + +} diff --git a/src/test/java/guitests/FindCommandTest.java b/src/test/java/guitests/FindCommandTest.java index 441a6dbed666..9b128bc5e607 100644 --- a/src/test/java/guitests/FindCommandTest.java +++ b/src/test/java/guitests/FindCommandTest.java @@ -1,39 +1,78 @@ package guitests; import org.junit.Test; -import seedu.address.commons.core.Messages; -import seedu.address.testutil.TestPerson; + +import tars.commons.core.Messages; +import tars.testutil.TestTask; import static org.junit.Assert.assertTrue; -public class FindCommandTest extends AddressBookGuiTest { +import java.util.ArrayList; +import java.util.Arrays; + +// @@author A0124333U +/** + * GUI test for find command + */ +public class FindCommandTest extends TarsGuiTest { @Test - public void find_nonEmptyList() { - assertFindResult("find Mark"); //no results - assertFindResult("find Meier", td.benson, td.daniel); //multiple results + public void find_quickSearch_nonEmptyList() { + assertFindResultForQuickSearch("find Meeting"); // no results + assertFindResultForQuickSearch("find Task B", td.taskB); // single result + assertFindResultForQuickSearch("find Task", td.taskA, td.taskB, + td.taskC, td.taskD, td.taskE, td.taskF, td.taskG); // multiple results - //find after deleting one result - commandBox.runCommand("delete 1"); - assertFindResult("find Meier",td.daniel); + // find after deleting one result + commandBox.runCommand("del 1"); + assertFindResultForQuickSearch("find A"); } @Test - public void find_emptyList(){ + public void find_filterSearch_nonEmptyList() { + assertFindResultForFilterSearch("find /n Task B", td.taskB); // single result + + // find after deleting one result + commandBox.runCommand("del 1"); + assertFindResultForFilterSearch("find /n Task B"); // no results + } + + @Test + public void find_quickSearch_emptyList() { commandBox.runCommand("clear"); - assertFindResult("find Jean"); //no results + assertFindResultForQuickSearch("find No Such Task"); // no results } @Test public void find_invalidCommand_fail() { - commandBox.runCommand("findgeorge"); + commandBox.runCommand("findmeeting"); assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); } - private void assertFindResult(String command, TestPerson... expectedHits ) { + private void assertFindResultForQuickSearch(String command, + TestTask... expectedHits) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + + String[] keywordsArray = command.split("\\s+"); + ArrayList keywordsList = + new ArrayList(Arrays.asList(keywordsArray)); + keywordsList.remove(0); + + assertResultMessage(expectedHits.length + " tasks listed!\n" + + "Quick Search Keywords: " + keywordsList.toString()); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } + + private void assertFindResultForFilterSearch(String command, + TestTask... expectedHits) { commandBox.runCommand(command); assertListSize(expectedHits.length); - assertResultMessage(expectedHits.length + " persons listed!"); - assertTrue(personListPanel.isListMatching(expectedHits)); + + String keywordString = "[Task Name: Task B] "; + + assertResultMessage(expectedHits.length + " tasks listed!\n" + + "Filter Search Keywords: " + keywordString); + assertTrue(taskListPanel.isListMatching(expectedHits)); } } diff --git a/src/test/java/guitests/GuiRobot.java b/src/test/java/guitests/GuiRobot.java index 44aa9edb48aa..5a6155acd5df 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 tars.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/HelpPanelTest.java b/src/test/java/guitests/HelpPanelTest.java new file mode 100644 index 000000000000..a6136d589763 --- /dev/null +++ b/src/test/java/guitests/HelpPanelTest.java @@ -0,0 +1,22 @@ +package guitests; + +import guitests.guihandles.HelpPanelHandle; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +// @@author A0124333U +/** + * GUI test for help command + */ +public class HelpPanelTest extends TarsGuiTest { + + @Test + public void openHelpPanel() { + assertHelpPanelSelected(commandBox.runHelpCommand()); + } + + private void assertHelpPanelSelected(HelpPanelHandle helpPanelHandle) { + assertTrue(helpPanelHandle.isTabSelected()); + } +} diff --git a/src/test/java/guitests/HelpWindowTest.java b/src/test/java/guitests/HelpWindowTest.java deleted file mode 100644 index 258d9d628d80..000000000000 --- a/src/test/java/guitests/HelpWindowTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package guitests; - -import guitests.guihandles.HelpWindowHandle; -import org.junit.Test; - -import static org.junit.Assert.assertTrue; - -public class HelpWindowTest extends AddressBookGuiTest { - - @Test - public void openHelpWindow() { - - personListPanel.clickOnListView(); - - assertHelpWindowOpen(mainMenu.openHelpWindowUsingAccelerator()); - - assertHelpWindowOpen(mainMenu.openHelpWindowUsingMenu()); - - assertHelpWindowOpen(commandBox.runHelpCommand()); - - } - - private void assertHelpWindowOpen(HelpWindowHandle helpWindowHandle) { - assertTrue(helpWindowHandle.isWindowOpen()); - helpWindowHandle.closeWindow(); - } -} diff --git a/src/test/java/guitests/ListCommandTest.java b/src/test/java/guitests/ListCommandTest.java new file mode 100644 index 000000000000..35e4a1e09ee2 --- /dev/null +++ b/src/test/java/guitests/ListCommandTest.java @@ -0,0 +1,75 @@ +package guitests; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tars.testutil.TestTask; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.logic.commands.ListCommand.MESSAGE_SUCCESS; +import static tars.logic.commands.ListCommand.MESSAGE_SUCCESS_DATETIME; +import static tars.logic.commands.ListCommand.MESSAGE_SUCCESS_DATETIME_DESCENDING; +import static tars.logic.commands.ListCommand.MESSAGE_SUCCESS_PRIORITY; +import static tars.logic.commands.ListCommand.MESSAGE_SUCCESS_PRIORITY_DESCENDING; +import static tars.logic.commands.ListCommand.MESSAGE_USAGE; + +// @@author A0140022H +/** + * GUI test for list commands + */ +public class ListCommandTest extends TarsGuiTest { + + private TestTask[] currentList = td.getTypicalTasks();; + + @Test + public void listAllTask() { + TestTask[] expectedList = currentList; + commandBox.runCommand("ls"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS); + } + + @Test + public void listAllTaskByDateTime() { + TestTask[] expectedList = currentList; + commandBox.runCommand("ls /dt"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_DATETIME); + } + + @Test + public void listAllTaskByDateTimeDescending() { + TestTask[] expectedList = {td.taskG, td.taskF, td.taskE, td.taskD, + td.taskC, td.taskB, td.taskA}; + commandBox.runCommand("ls /dt dsc"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_DATETIME_DESCENDING); + } + + @Test + public void listAllTaskByPriority() { + TestTask[] expectedList = {td.taskC, td.taskF, td.taskB, td.taskE, + td.taskA, td.taskD, td.taskG}; + commandBox.runCommand("ls /p"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_PRIORITY); + } + + @Test + public void listAllTaskByPriorityDescending() { + TestTask[] expectedList = {td.taskA, td.taskD, td.taskG, td.taskB, + td.taskE, td.taskC, td.taskF}; + commandBox.runCommand("ls /p dsc"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_SUCCESS_PRIORITY_DESCENDING); + } + + @Test + public void listInvalidCommand() { + TestTask[] expectedList = currentList; + commandBox.runCommand("ls /r"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } +} diff --git a/src/test/java/guitests/RsvCommandTest.java b/src/test/java/guitests/RsvCommandTest.java new file mode 100644 index 000000000000..3617e8443f6d --- /dev/null +++ b/src/test/java/guitests/RsvCommandTest.java @@ -0,0 +1,66 @@ +package guitests; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import guitests.guihandles.RsvTaskCardHandle; +import tars.commons.core.Messages; +import tars.testutil.TestRsvTask; +import tars.testutil.TestUtil; + +// @@author A0124333U +/** + * GUI test for rsv command + */ +public class RsvCommandTest extends TarsGuiTest { + + @Test + public void rsv() { + // reserve one task + TestRsvTask[] currentList = td.getTypicalRsvTasks(); + TestRsvTask taskToRsv = td.rsvTaskC; + assertRsvSuccess(taskToRsv, currentList); + currentList = TestUtil.addRsvTasksToList(currentList, taskToRsv); + + // reserve another task + taskToRsv = td.rsvTaskD; + assertRsvSuccess(taskToRsv, currentList); + currentList = TestUtil.addRsvTasksToList(currentList, taskToRsv); + + // add duplicate task + commandBox.runCommand(td.rsvTaskD.getRsvCommand()); + assertResultMessage(Messages.MESSAGE_DUPLICATE_TASK); + assertTrue(rsvTaskListPanel.isListMatching(currentList)); + + // delete a reserved task + TestRsvTask rsvTaskToDel = td.rsvTaskC; + commandBox.runCommand("rsv /del 3"); + TestRsvTask[] expectedList = TestUtil.delRsvTaskFromList(currentList, rsvTaskToDel); + assertTrue(rsvTaskListPanel.isListMatching(expectedList)); + currentList = TestUtil.delRsvTaskFromList(currentList, taskToRsv); + + // add to empty list + commandBox.runCommand("clear"); + assertRsvSuccess(td.rsvTaskA); + + // invalid command + + commandBox.runCommand("reserves Meeting"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND); + + } + + private void assertRsvSuccess(TestRsvTask taskToRsv, TestRsvTask... currentList) { + commandBox.runCommand(taskToRsv.getRsvCommand()); + + // confirm the new card contains the right data + RsvTaskCardHandle addedCard = rsvTaskListPanel.navigateToRsvTask(taskToRsv.getName().taskName); + assertMatching(taskToRsv, addedCard); + + // confirm the list now contains all previous tasks plus the new task + TestRsvTask[] expectedList = TestUtil.addRsvTasksToList(currentList, taskToRsv); + assertTrue(rsvTaskListPanel.isListMatching(expectedList)); + } + +} diff --git a/src/test/java/guitests/SelectCommandTest.java b/src/test/java/guitests/SelectCommandTest.java deleted file mode 100644 index 5273552056ce..000000000000 --- a/src/test/java/guitests/SelectCommandTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package guitests; - -import org.junit.Test; -import seedu.address.model.person.ReadOnlyPerson; - -import static org.junit.Assert.assertEquals; - -public class SelectCommandTest extends AddressBookGuiTest { - - - @Test - public void selectPerson_nonEmptyList() { - - assertSelectionInvalid(10); //invalid index - assertNoPersonSelected(); - - assertSelectionSuccess(1); //first person in the list - int personCount = td.getTypicalPersons().length; - assertSelectionSuccess(personCount); //last person in the list - int middleIndex = personCount / 2; - assertSelectionSuccess(middleIndex); //a person in the middle of the list - - 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 */ - } - - @Test - public void selectPerson_emptyList(){ - commandBox.runCommand("clear"); - assertListSize(0); - assertSelectionInvalid(1); //invalid index - } - - private void assertSelectionInvalid(int index) { - commandBox.runCommand("select " + index); - assertResultMessage("The person index provided is invalid"); - } - - private void assertSelectionSuccess(int index) { - commandBox.runCommand("select " + index); - assertResultMessage("Selected Person: "+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); - //TODO: confirm the correct page is loaded in the Browser Panel - } - - private void assertNoPersonSelected() { - assertEquals(personListPanel.getSelectedPersons().size(), 0); - } - -} diff --git a/src/test/java/guitests/AddressBookGuiTest.java b/src/test/java/guitests/TarsGuiTest.java similarity index 61% rename from src/test/java/guitests/AddressBookGuiTest.java rename to src/test/java/guitests/TarsGuiTest.java index b9cf9ca092eb..d51d4610c6cf 100644 --- a/src/test/java/guitests/AddressBookGuiTest.java +++ b/src/test/java/guitests/TarsGuiTest.java @@ -8,12 +8,14 @@ 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 tars.TestApp; +import tars.commons.core.EventsCenter; +import tars.model.Tars; +import tars.model.task.ReadOnlyTask; +import tars.model.task.rsv.RsvTask; +import tars.testutil.TestUtil; +import tars.testutil.TypicalTestTasks; import java.util.concurrent.TimeoutException; @@ -21,9 +23,9 @@ import static org.junit.Assert.assertTrue; /** - * A GUI Test class for AddressBook. + * A GUI Test class for Tars. */ -public abstract class AddressBookGuiTest { +public abstract class TarsGuiTest { /* The TestName Rule makes the current test name available inside test methods */ @Rule @@ -31,16 +33,16 @@ 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 * for easy access from child classes. */ protected MainGuiHandle mainGui; - protected MainMenuHandle mainMenu; - protected PersonListPanelHandle personListPanel; + protected TaskListPanelHandle taskListPanel; protected ResultDisplayHandle resultDisplay; + protected RsvTaskListPanelHandle rsvTaskListPanel; protected CommandBoxHandle commandBox; private Stage stage; @@ -58,9 +60,9 @@ public static void setupSpec() { 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(); + rsvTaskListPanel = mainGui.getRsvTaskListPanel(); commandBox = mainGui.getCommandBox(); this.stage = stage; }); @@ -75,9 +77,9 @@ 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); + protected Tars getInitialData() { + Tars ab = TestUtil.generateEmptyTars(); + TypicalTestTasks.loadTarsWithSampleData(ab); return ab; } @@ -95,18 +97,33 @@ public void cleanup() throws TimeoutException { } /** - * Asserts the person shown in the card is same as the given person + * Asserts the task shown in the card is same as the given task + */ + public void assertMatching(ReadOnlyTask task, TaskCardHandle card) { + assertTrue(TestUtil.compareCardAndTask(card, task)); + } + + /** + * Asserts the task shown in the RsvTaskcard is same as the given rsvTask */ - public void assertMatching(ReadOnlyPerson person, PersonCardHandle card) { - assertTrue(TestUtil.compareCardAndPerson(card, person)); + public void assertMatching(RsvTask task, RsvTaskCardHandle card) { + assertTrue(TestUtil.compareCardAndRsvTask(card, task)); } /** - * Asserts the size of the person list is equal to the given number. + * Asserts the size of the task list is equal to the given number. */ protected void assertListSize(int size) { - int numberOfPeople = personListPanel.getNumberOfPeople(); - assertEquals(size, numberOfPeople); + int numberOfTasks = taskListPanel.getNumberOfTasks(); + assertEquals(size, numberOfTasks); + } + + /** + * Asserts the size of the rsv task list is equal to the given number. + */ + protected void assertRsvListSize(int size) { + int numberOfRsvTasks = rsvTaskListPanel.getNumberOfTasks(); + assertEquals(size, numberOfRsvTasks); } /** diff --git a/src/test/java/guitests/UndoCommandTest.java b/src/test/java/guitests/UndoCommandTest.java new file mode 100644 index 000000000000..0f11cdb3226b --- /dev/null +++ b/src/test/java/guitests/UndoCommandTest.java @@ -0,0 +1,46 @@ +package guitests; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import tars.logic.commands.AddCommand; +import tars.logic.commands.DeleteCommand; +import tars.logic.commands.UndoCommand; +import tars.testutil.TestTask; + +// @@author A0139924W +/** + * GUI test for undo command + */ +public class UndoCommandTest extends TarsGuiTest { + + @Test + public void undo_add_successful() { + // setup + TestTask taskToUndo = td.taskH; + commandBox.runCommand(taskToUndo.getAddCommand()); + + commandBox.runCommand("undo"); + TestTask[] expectedList = {td.taskG}; + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(AddCommand.MESSAGE_UNDO, taskToUndo))); + } + + @Test + public void undo_delete_successful() { + // setup + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToUndo = currentList[currentList.length - 1]; + commandBox.runCommand("del " + currentList.length); + + commandBox.runCommand("undo"); + currentList = td.getTypicalTasks(); + assertTrue(taskListPanel.isListMatching(currentList)); + assertResultMessage(String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(DeleteCommand.MESSAGE_UNDO, + "1.\t" + taskToUndo + "\n"))); + + } +} diff --git a/src/test/java/guitests/guihandles/CommandBoxHandle.java b/src/test/java/guitests/guihandles/CommandBoxHandle.java index dcd3155636cd..40e60852e2d6 100644 --- a/src/test/java/guitests/guihandles/CommandBoxHandle.java +++ b/src/test/java/guitests/guihandles/CommandBoxHandle.java @@ -1,16 +1,20 @@ package guitests.guihandles; import guitests.GuiRobot; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; import javafx.stage.Stage; +import tars.commons.core.KeyCombinations; /** * A handle to the Command Box in the GUI. */ -public class CommandBoxHandle extends GuiHandle{ +public class CommandBoxHandle extends GuiHandle { private static final String COMMAND_INPUT_FIELD_ID = "#commandTextField"; - public CommandBoxHandle(GuiRobot guiRobot, Stage primaryStage, String stageTitle) { + public CommandBoxHandle(GuiRobot guiRobot, Stage primaryStage, + String stageTitle) { super(guiRobot, primaryStage, stageTitle); } @@ -28,12 +32,51 @@ public String getCommandInput() { public void runCommand(String command) { enterCommand(command); pressEnter(); - guiRobot.sleep(200); //Give time for the command to take effect + guiRobot.sleep(200); // Give time for the command to take effect } - public HelpWindowHandle runHelpCommand() { + public HelpPanelHandle runHelpCommand() { enterCommand("help"); pressEnter(); - return new HelpWindowHandle(guiRobot, primaryStage); + return getHelpPanelHandle(); } + + public HelpPanelHandle getHelpPanelHandle() { + return new HelpPanelHandle(guiRobot, primaryStage); + } + + public ThisWeekPanelHandle getOverviewPanelHandle() { + return new ThisWeekPanelHandle(guiRobot, primaryStage); + } + + public RsvTaskListPanelHandle getRsvTaskListPanelHandle() { + return new RsvTaskListPanelHandle(guiRobot, primaryStage); + } + + public void pressUpKey() { + guiRobot.type(KeyCode.UP).sleep(500); + } + + public void pressDownKey() { + guiRobot.type(KeyCode.DOWN).sleep(500); + } + + public void pressCtrlRightArrowKeys() { + guiRobot.push( + (KeyCodeCombination) KeyCombinations.KEY_COMB_CTRL_RIGHT_ARROW); + } + + public void pressCtrlLeftArrowKeys() { + guiRobot.push( + (KeyCodeCombination) KeyCombinations.KEY_COMB_CTRL_LEFT_ARROW); + } + + public void pressCtrlZArrowKeys() { + guiRobot.push((KeyCodeCombination) KeyCombinations.KEY_COMB_CTRL_Z); + } + + public void pressCtrlYArrowKeys() { + guiRobot.push((KeyCodeCombination) KeyCombinations.KEY_COMB_CTRL_Y); + } + } diff --git a/src/test/java/guitests/guihandles/GuiHandle.java b/src/test/java/guitests/guihandles/GuiHandle.java index 5e7e0f6de911..38c0197e35e8 100644 --- a/src/test/java/guitests/guihandles/GuiHandle.java +++ b/src/test/java/guitests/guihandles/GuiHandle.java @@ -7,9 +7,10 @@ import javafx.scene.input.KeyCode; import javafx.stage.Stage; import javafx.stage.Window; -import seedu.address.TestApp; -import seedu.address.commons.core.LogsCenter; +import tars.TestApp; +import tars.commons.core.LogsCenter; +import java.util.Optional; import java.util.logging.Logger; /** @@ -31,12 +32,14 @@ public GuiHandle(GuiRobot guiRobot, Stage primaryStage, String stageTitle) { public void focusOnWindow(String stageTitle) { logger.info("Focusing " + stageTitle); - java.util.Optional window = guiRobot.listTargetWindows() - .stream() - .filter(w -> w instanceof Stage && ((Stage) w).getTitle().equals(stageTitle)).findAny(); + Optional window = guiRobot.listTargetWindows().stream() + .filter(w -> w instanceof Stage + && ((Stage) w).getTitle().equals(stageTitle)) + .findAny(); if (!window.isPresent()) { - logger.warning("Can't find stage " + stageTitle + ", Therefore, aborting focusing"); + logger.warning("Can't find stage " + stageTitle + + ", Therefore, aborting focusing"); return; } @@ -55,8 +58,10 @@ protected String getTextFieldText(String filedName) { protected void setTextField(String textFieldId, String newText) { guiRobot.clickOn(textFieldId); - ((TextField)guiRobot.lookup(textFieldId).tryQuery().get()).setText(newText); - guiRobot.sleep(500); // so that the texts stays visible on the GUI for a short period + ((TextField) guiRobot.lookup(textFieldId).tryQuery().get()) + .setText(newText); + guiRobot.sleep(500); // so that the texts stays visible on the GUI for a + // short period } public void pressEnter() { @@ -64,7 +69,8 @@ public void pressEnter() { } protected String getTextFromLabel(String fieldId, Node parentNode) { - return ((Label) guiRobot.from(parentNode).lookup(fieldId).tryQuery().get()).getText(); + return ((Label) guiRobot.from(parentNode).lookup(fieldId).tryQuery() + .get()).getText(); } public void focusOnSelf() { @@ -78,16 +84,18 @@ public void focusOnMainApp() { } public void closeWindow() { - java.util.Optional window = guiRobot.listTargetWindows() - .stream() - .filter(w -> w instanceof Stage && ((Stage) w).getTitle().equals(stageTitle)).findAny(); + Optional window = + guiRobot.listTargetWindows().stream() + .filter(w -> w instanceof Stage + && ((Stage) w).getTitle().equals(stageTitle)) + .findAny(); if (!window.isPresent()) { return; } guiRobot.targetWindow(window.get()); - guiRobot.interact(() -> ((Stage)window.get()).close()); + guiRobot.interact(() -> ((Stage) window.get()).close()); focusOnMainApp(); } } diff --git a/src/test/java/guitests/guihandles/HelpPanelHandle.java b/src/test/java/guitests/guihandles/HelpPanelHandle.java new file mode 100644 index 000000000000..56bb5720db0b --- /dev/null +++ b/src/test/java/guitests/guihandles/HelpPanelHandle.java @@ -0,0 +1,26 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.scene.control.TabPane; +import javafx.stage.Stage; +import tars.ui.MainWindow; + +/** + * Provides a handle to the help window of the app. + */ +public class HelpPanelHandle extends GuiHandle { + + private static final String HELP_PANEL_TITLE = "Help"; + private static final String TAB_PANEL_ROOT_FIELD_ID = "#tabPane"; + + public HelpPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, HELP_PANEL_TITLE); + guiRobot.sleep(1000); + } + + public boolean isTabSelected() { + return ((TabPane) getNode(TAB_PANEL_ROOT_FIELD_ID)).getSelectionModel() + .isSelected(MainWindow.HELP_PANEL_TAB_PANE_INDEX); + } + +} diff --git a/src/test/java/guitests/guihandles/HelpWindowHandle.java b/src/test/java/guitests/guihandles/HelpWindowHandle.java deleted file mode 100644 index 3931c5fb24b7..000000000000 --- a/src/test/java/guitests/guihandles/HelpWindowHandle.java +++ /dev/null @@ -1,28 +0,0 @@ -package guitests.guihandles; - -import guitests.GuiRobot; -import javafx.stage.Stage; - -/** - * Provides a handle to the help window of the app. - */ -public class HelpWindowHandle extends GuiHandle { - - private static final String HELP_WINDOW_TITLE = "Help"; - private static final String HELP_WINDOW_ROOT_FIELD_ID = "#helpWindowRoot"; - - public HelpWindowHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, HELP_WINDOW_TITLE); - guiRobot.sleep(1000); - } - - public boolean isWindowOpen() { - return getNode(HELP_WINDOW_ROOT_FIELD_ID) != null; - } - - public void closeWindow() { - super.closeWindow(); - guiRobot.sleep(500); - } - -} diff --git a/src/test/java/guitests/guihandles/MainGuiHandle.java b/src/test/java/guitests/guihandles/MainGuiHandle.java index 45802c5135c7..c5d809fd9448 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 tars.TestApp; /** * Provides a handle for the main GUI. @@ -13,8 +13,12 @@ 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 RsvTaskListPanelHandle getRsvTaskListPanel() { + return new RsvTaskListPanelHandle(guiRobot, primaryStage); } public ResultDisplayHandle getResultDisplay() { @@ -25,8 +29,4 @@ public CommandBoxHandle getCommandBox() { return new CommandBoxHandle(guiRobot, primaryStage, TestApp.APP_TITLE); } - public MainMenuHandle getMainMenu() { - return new MainMenuHandle(guiRobot, primaryStage); - } - } diff --git a/src/test/java/guitests/guihandles/MainMenuHandle.java b/src/test/java/guitests/guihandles/MainMenuHandle.java deleted file mode 100644 index 0aeb047a0e1d..000000000000 --- a/src/test/java/guitests/guihandles/MainMenuHandle.java +++ /dev/null @@ -1,37 +0,0 @@ -package guitests.guihandles; - -import guitests.GuiRobot; -import javafx.scene.input.KeyCode; -import javafx.stage.Stage; -import seedu.address.TestApp; - -import java.util.Arrays; - -/** - * Provides a handle to the main menu of the app. - */ -public class MainMenuHandle extends GuiHandle { - public MainMenuHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, TestApp.APP_TITLE); - } - - public GuiHandle clickOn(String... menuText) { - Arrays.stream(menuText).forEach((menuItem) -> guiRobot.clickOn(menuItem)); - return this; - } - - public HelpWindowHandle openHelpWindowUsingMenu() { - clickOn("Help", "F1"); - return new HelpWindowHandle(guiRobot, primaryStage); - } - - public HelpWindowHandle openHelpWindowUsingAccelerator() { - useF1Accelerator(); - return new HelpWindowHandle(guiRobot, primaryStage); - } - - private void useF1Accelerator() { - guiRobot.push(KeyCode.F1); - guiRobot.sleep(500); - } -} 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/PersonListPanelHandle.java b/src/test/java/guitests/guihandles/PersonListPanelHandle.java deleted file mode 100644 index 3451992cf735..000000000000 --- a/src/test/java/guitests/guihandles/PersonListPanelHandle.java +++ /dev/null @@ -1,172 +0,0 @@ -package guitests.guihandles; - - -import guitests.GuiRobot; -import javafx.geometry.Point2D; -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 java.util.List; -import java.util.Optional; -import java.util.Set; - -import static org.junit.Assert.assertTrue; - -/** - * Provides a handle for the panel containing the person list. - */ -public class PersonListPanelHandle 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"; - - public PersonListPanelHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, TestApp.APP_TITLE); - } - - public List getSelectedPersons() { - ListView personList = getListView(); - return personList.getSelectionModel().getSelectedItems(); - } - - public ListView getListView() { - return (ListView) getNode(PERSON_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); - } - - /** - * 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, ReadOnlyPerson... persons) { - List personsInList = getListView().getItems(); - - // Return false if the list in panel is too short to contain the given list - if (startPosition + persons.length > personsInList.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)){ - 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, ReadOnlyPerson... persons) throws IllegalArgumentException { - if (persons.length + startPosition != getListView().getItems().size()) { - throw new IllegalArgumentException("List size mismatched\n" + - "Expected " + (getListView().getItems().size() - 1) + " persons"); - } - assertTrue(this.containsInOrder(startPosition, persons)); - for (int i = 0; i < persons.length; i++) { - final int scrollTo = i + startPosition; - guiRobot.interact(() -> getListView().scrollTo(scrollTo)); - guiRobot.sleep(200); - if (!TestUtil.compareCardAndPerson(getPersonCardHandle(startPosition + i), persons[i])) { - return false; - } - } - return true; - } - - - public PersonCardHandle navigateToPerson(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()) { - throw new IllegalStateException("Name not found: " + name); - } - - return navigateToPerson(person.get()); - } - - /** - * Navigates the listview to display and select the person. - */ - public PersonCardHandle navigateToPerson(ReadOnlyPerson person) { - int index = getPersonIndex(person); - - guiRobot.interact(() -> { - getListView().scrollTo(index); - guiRobot.sleep(150); - getListView().getSelectionModel().select(index); - }); - guiRobot.sleep(100); - return getPersonCardHandle(person); - } - - - /** - * 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())){ - return i; - } - } - return NOT_FOUND; - } - - /** - * Gets a person from the list by index - */ - public ReadOnlyPerson getPerson(int index) { - return getListView().getItems().get(index); - } - - public PersonCardHandle getPersonCardHandle(int index) { - return getPersonCardHandle(new Person(getListView().getItems().get(index))); - } - - public PersonCardHandle getPersonCardHandle(ReadOnlyPerson person) { - Set nodes = getAllCardNodes(); - Optional personCardNode = nodes.stream() - .filter(n -> new PersonCardHandle(guiRobot, primaryStage, n).isSamePerson(person)) - .findFirst(); - if (personCardNode.isPresent()) { - return new PersonCardHandle(guiRobot, primaryStage, personCardNode.get()); - } else { - return null; - } - } - - protected Set getAllCardNodes() { - return guiRobot.lookup(CARD_PANE_ID).queryAll(); - } - - public int getNumberOfPeople() { - return getListView().getItems().size(); - } -} diff --git a/src/test/java/guitests/guihandles/ResultDisplayHandle.java b/src/test/java/guitests/guihandles/ResultDisplayHandle.java index 110b4682b184..bb7f2c46b8d2 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 tars.TestApp; /** * A handler for the ResultDisplay of the UI diff --git a/src/test/java/guitests/guihandles/RsvTaskCardHandle.java b/src/test/java/guitests/guihandles/RsvTaskCardHandle.java new file mode 100644 index 000000000000..53ff7f86cae4 --- /dev/null +++ b/src/test/java/guitests/guihandles/RsvTaskCardHandle.java @@ -0,0 +1,46 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.scene.Node; +import javafx.stage.Stage; +import tars.model.task.rsv.RsvTask; + +/** + * Provides a handle to a task card in the task list panel. + */ +public class RsvTaskCardHandle extends GuiHandle { + private static final String NAME_FIELD_ID = "#name"; + + private Node node; + + public RsvTaskCardHandle(GuiRobot guiRobot, Stage primaryStage, Node node) { + super(guiRobot, primaryStage, null); + this.node = node; + } + + protected String getTextFromLabel(String fieldId) { + return getTextFromLabel(fieldId, node); + } + + public String getRsvTaskName() { + return getTextFromLabel(NAME_FIELD_ID); + } + + public boolean isSameRsvTask(RsvTask rsvTask) { + return getRsvTaskName().equals(rsvTask.getName().taskName); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof RsvTaskCardHandle) { + RsvTaskCardHandle handle = (RsvTaskCardHandle) obj; + return getRsvTaskName().equals(handle.getRsvTaskName()); + } + return super.equals(obj); + } + + @Override + public String toString() { + return getRsvTaskName(); + } +} diff --git a/src/test/java/guitests/guihandles/RsvTaskListPanelHandle.java b/src/test/java/guitests/guihandles/RsvTaskListPanelHandle.java new file mode 100644 index 000000000000..3a88704de516 --- /dev/null +++ b/src/test/java/guitests/guihandles/RsvTaskListPanelHandle.java @@ -0,0 +1,177 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.control.ListView; +import javafx.scene.control.TabPane; +import javafx.stage.Stage; +import tars.TestApp; +import tars.model.task.rsv.RsvTask; +import tars.testutil.TestUtil; +import tars.ui.MainWindow; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +/** + * Provides a handle for the panel containing the task list. + */ +public class RsvTaskListPanelHandle extends GuiHandle { + + public static final int NOT_FOUND = -1; + public static final String CARD_PANE_ID = "#cardPane"; + + private static final String RSV_TASK_LIST_VIEW_ID = "#rsvTaskListView"; + private static final String TAB_PANEL_ROOT_FIELD_ID = "#tabPane"; + + public RsvTaskListPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public List getSelectedTasks() { + ListView rsvtaskList = getRsvListView(); + return rsvtaskList.getSelectionModel().getSelectedItems(); + } + + public ListView getRsvListView() { + return (ListView) getNode(RSV_TASK_LIST_VIEW_ID); + } + + /** + * Returns true if the list is showing the task details correctly and in correct order. + * @param tasks A list of task in the correct order. + */ + public boolean isListMatching(RsvTask... tasks) { + return this.isListMatching(0, tasks); + } + + /** + * Clicks on the ListView. + */ + public void clickOnListView() { + Point2D point= TestUtil.getScreenMidPoint(getRsvListView()); + guiRobot.clickOn(point.getX(), point.getY()); + } + + /** + * Returns true if the {@code tasks} appear as the sub list (in that order) at position {@code startPosition}. + */ + public boolean containsInOrder(int startPosition, RsvTask... tasks) { + List tasksInList = getRsvListView().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 tasks doesn't match + for (int i = 0; i < tasks.length; i++) { + if (!tasksInList.get(startPosition + i).getName().taskName.equals(tasks[i].getName().taskName)){ + return false; + } + } + + return true; + } + + /** + * Returns true if the list is showing the RsvTask details correctly and in correct order. + * @param startPosition The starting position of the sub list. + * @param tasks A list of RsvTask in the correct order. + */ + public boolean isListMatching(int startPosition, RsvTask... tasks) throws IllegalArgumentException { + if (tasks.length + startPosition != getRsvListView().getItems().size()) { + throw new IllegalArgumentException("List size mismatched\n" + + "Expected " + (getRsvListView().getItems().size() - 1) + " tasks"); + } + assertTrue(this.containsInOrder(startPosition, tasks)); + for (int i = 0; i < tasks.length; i++) { + final int scrollTo = i + startPosition; + guiRobot.interact(() -> getRsvListView().scrollTo(scrollTo)); + guiRobot.sleep(200); + if (!TestUtil.compareCardAndRsvTask(getRsvTaskCardHandle(startPosition + i), tasks[i])) { + return false; + } + } + return true; + } + + + public RsvTaskCardHandle navigateToRsvTask(String name) { + guiRobot.sleep(500); //Allow a bit of time for the list to be updated + final Optional task = getRsvListView().getItems().stream().filter(p -> p.getName().taskName.equals(name)).findAny(); + if (!task.isPresent()) { + throw new IllegalStateException("Name not found: " + name); + } + + return navigateToRsvTask(task.get()); + } + + /** + * Navigates the listview to display and select the task. + */ + public RsvTaskCardHandle navigateToRsvTask(RsvTask task) { + int index = getRsvTaskIndex(task); + + guiRobot.interact(() -> { + getRsvListView().scrollTo(index); + guiRobot.sleep(150); + getRsvListView().getSelectionModel().select(index); + }); + guiRobot.sleep(100); + return getRsvTaskCardHandle(task); + } + + + /** + * Returns the position of the task given, {@code NOT_FOUND} if not found in the list. + */ + public int getRsvTaskIndex(RsvTask targetTask) { + List tasksInList = getRsvListView().getItems(); + for (int i = 0; i < tasksInList.size(); i++) { + if(tasksInList.get(i).getName().equals(targetTask.getName())){ + return i; + } + } + return NOT_FOUND; + } + + /** + * Gets a rsv task from the list by index + */ + public RsvTask getRsvTask(int index) { + return getRsvListView().getItems().get(index); + } + + public RsvTaskCardHandle getRsvTaskCardHandle(int index) { + return getRsvTaskCardHandle(new RsvTask(getRsvListView().getItems().get(index))); + } + + public RsvTaskCardHandle getRsvTaskCardHandle(RsvTask task) { + Set nodes = getAllCardNodes(); + Optional taskCardNode = nodes.stream() + .filter(n -> new RsvTaskCardHandle(guiRobot, primaryStage, n).isSameRsvTask(task)) + .findFirst(); + if (taskCardNode.isPresent()) { + return new RsvTaskCardHandle(guiRobot, primaryStage, taskCardNode.get()); + } else { + return null; + } + } + + protected Set getAllCardNodes() { + return guiRobot.lookup(CARD_PANE_ID).queryAll(); + } + + public int getNumberOfTasks() { + return getRsvListView().getItems().size(); + } + + public boolean isTabSelected() { + return ((TabPane) getNode(TAB_PANEL_ROOT_FIELD_ID)).getSelectionModel().isSelected(MainWindow.RSV_TASK_LIST_PANEL_TAB_PANE_INDEX); + } +} diff --git a/src/test/java/guitests/guihandles/TaskCardHandle.java b/src/test/java/guitests/guihandles/TaskCardHandle.java new file mode 100644 index 000000000000..8bc8fa3aff08 --- /dev/null +++ b/src/test/java/guitests/guihandles/TaskCardHandle.java @@ -0,0 +1,62 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.scene.Node; +import javafx.stage.Stage; +import tars.commons.util.StringUtil; +import tars.model.task.ReadOnlyTask; + +/** + * Provides a handle to a task card in the task list panel. + */ +public class TaskCardHandle extends GuiHandle { + private static final String NAME_FIELD_ID = "#name"; + private static final String DATE_FIELD_ID = "#date"; + private static final String PRIORITY_FIELD_ID = "#priority"; + private static final String STATUS_FIELD_ID = "#status"; + + 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(NAME_FIELD_ID); + } + + public String getDate() { + return getTextFromLabel(DATE_FIELD_ID); + } + + public String getPriority() { + return getTextFromLabel(PRIORITY_FIELD_ID); + } + + public String getStatus() { + return getTextFromLabel(STATUS_FIELD_ID); + } + + public boolean isSameTask(ReadOnlyTask task) { + return getTaskName().equals(task.getName().taskName); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TaskCardHandle) { + TaskCardHandle handle = (TaskCardHandle) obj; + return getTaskName().equals(handle.getTaskName()); + } + return super.equals(obj); + } + + @Override + public String toString() { + return getTaskName() + StringUtil.STRING_WHITESPACE + getDate(); + } +} diff --git a/src/test/java/guitests/guihandles/TaskListPanelHandle.java b/src/test/java/guitests/guihandles/TaskListPanelHandle.java new file mode 100644 index 000000000000..43c374001fa0 --- /dev/null +++ b/src/test/java/guitests/guihandles/TaskListPanelHandle.java @@ -0,0 +1,172 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.control.ListView; +import javafx.stage.Stage; +import tars.TestApp; +import tars.model.task.Task; +import tars.model.task.ReadOnlyTask; +import tars.testutil.TestUtil; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +/** + * Provides a handle for the panel containing the task 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(); + } + + public ListView getListView() { + return (ListView) getNode(TASK_LIST_VIEW_ID); + } + + /** + * Returns true if the list is showing the task details correctly and in correct order. + * @param tasks A list of task 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 tasks} 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 tasks doesn't match + for (int i = 0; i < tasks.length; i++) { + if (!tasksInList.get(startPosition + i).getName().taskName.equals(tasks[i].getName().taskName)){ + return false; + } + } + + return true; + } + + /** + * Returns true if the list is showing the task details correctly and in correct order. + * + * @param startPosition The starting position of the sub list. + * @param tasks A list of task 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() - 1) + " 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.getName().taskName.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 task. + */ + 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 task 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).getName().equals(targetTask.getName())){ + return i; + } + } + return NOT_FOUND; + } + + /** + * Gets a task 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(); + } +} diff --git a/src/test/java/guitests/guihandles/ThisWeekPanelHandle.java b/src/test/java/guitests/guihandles/ThisWeekPanelHandle.java new file mode 100644 index 000000000000..10276c997b30 --- /dev/null +++ b/src/test/java/guitests/guihandles/ThisWeekPanelHandle.java @@ -0,0 +1,26 @@ +package guitests.guihandles; + +import guitests.GuiRobot; +import javafx.scene.control.TabPane; +import javafx.stage.Stage; +import tars.TestApp; +import tars.ui.MainWindow; + +// @@author A0124333U +/** + * GUI test for this week panel + */ +public class ThisWeekPanelHandle extends GuiHandle { + + private static final String TAB_PANEL_ROOT_FIELD_ID = "#tabPane"; + + public ThisWeekPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public boolean isTabSelected() { + return ((TabPane) getNode(TAB_PANEL_ROOT_FIELD_ID)).getSelectionModel() + .isSelected(MainWindow.OVERVIEW_PANEL_TAB_PANE_INDEX); + } + +} diff --git a/src/test/java/seedu/address/commons/util/FileUtilTest.java b/src/test/java/seedu/address/commons/util/FileUtilTest.java deleted file mode 100644 index 8de2621799cf..000000000000 --- a/src/test/java/seedu/address/commons/util/FileUtilTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.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 java.io.File; -import java.io.IOException; - -import static org.junit.Assert.assertEquals; - -public class FileUtilTest { - private static final File SERIALIZATION_FILE = new File(TestUtil.getFilePathInSandboxFolder("serialize.json")); - - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void getPath(){ - - // valid case - assertEquals("folder" + File.separator + "sub-folder", FileUtil.getPath("folder/sub-folder")); - - // null parameter -> assertion failure - thrown.expect(AssertionError.class); - FileUtil.getPath(null); - - // no forwards slash -> assertion failure - thrown.expect(AssertionError.class); - FileUtil.getPath("folder"); - } - - @Test - public void serializeObjectToJsonFile_noExceptionThrown() throws IOException { - SerializableTestClass serializableTestClass = new SerializableTestClass(); - serializableTestClass.setTestValues(); - - FileUtil.serializeObjectToJsonFile(SERIALIZATION_FILE, serializableTestClass); - - assertEquals(FileUtil.readFromFile(SERIALIZATION_FILE), SerializableTestClass.JSON_STRING_REPRESENTATION); - } - - @Test - public void deserializeObjectFromJsonFile_noExceptionThrown() throws IOException { - FileUtil.writeToFile(SERIALIZATION_FILE, SerializableTestClass.JSON_STRING_REPRESENTATION); - - SerializableTestClass serializableTestClass = FileUtil - .deserializeObjectFromJsonFile(SERIALIZATION_FILE, SerializableTestClass.class); - - assertEquals(serializableTestClass.getName(), SerializableTestClass.getNameTestValue()); - assertEquals(serializableTestClass.getListOfLocalDateTimes(), SerializableTestClass.getListTestValues()); - assertEquals(serializableTestClass.getMapOfIntegerToString(), SerializableTestClass.getHashMapTestValues()); - } -} diff --git a/src/test/java/seedu/address/commons/util/JsonUtilTest.java b/src/test/java/seedu/address/commons/util/JsonUtilTest.java deleted file mode 100644 index fc3902188807..000000000000 --- a/src/test/java/seedu/address/commons/util/JsonUtilTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.address.commons.util; - -/** - * Tests JSON Read and Write - */ -public class JsonUtilTest { - - //TODO: @Test jsonUtil_readJsonStringToObjectInstance_correctObject() - - //TODO: @Test jsonUtil_writeThenReadObjectToJson_correctObject() - -} diff --git a/src/test/java/seedu/address/commons/util/UrlUtilTest.java b/src/test/java/seedu/address/commons/util/UrlUtilTest.java deleted file mode 100644 index 21efd4e1d231..000000000000 --- a/src/test/java/seedu/address/commons/util/UrlUtilTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.commons.util; - -import org.junit.Test; - -import java.net.MalformedURLException; -import java.net.URL; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Tests the UrlUtil methods. - */ -public class UrlUtilTest { - - @Test - public void compareBaseUrls_differentCapital_success() throws MalformedURLException { - URL url1 = new URL("https://www.Google.com/a"); - URL url2 = new URL("https://www.google.com/A"); - assertTrue(UrlUtil.compareBaseUrls(url1, url2)); - } - - @Test - public void compareBaseUrls_testWithAndWithoutWww_success() throws MalformedURLException { - URL url1 = new URL("https://google.com/a"); - URL url2 = new URL("https://www.google.com/a"); - assertTrue(UrlUtil.compareBaseUrls(url1, url2)); - } - - @Test - public void compareBaseUrls_differentSlashes_success() throws MalformedURLException { - URL url1 = new URL("https://www.Google.com/a/acb/"); - URL url2 = new URL("https://www.google.com/A/acb"); - assertTrue(UrlUtil.compareBaseUrls(url1, url2)); - } - - @Test - public void compareBaseUrls_differentUrl_fail() throws MalformedURLException { - URL url1 = new URL("https://www.Google.com/a/ac_b/"); - URL url2 = new URL("https://www.google.com/A/acb"); - assertFalse(UrlUtil.compareBaseUrls(url1, url2)); - } -} diff --git a/src/test/java/seedu/address/commons/util/XmlUtilTest.java b/src/test/java/seedu/address/commons/util/XmlUtilTest.java deleted file mode 100644 index dc4fd886c23e..000000000000 --- a/src/test/java/seedu/address/commons/util/XmlUtilTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package seedu.address.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 javax.xml.bind.JAXBException; -import java.io.File; -import java.io.FileNotFoundException; - -import static org.junit.Assert.assertEquals; - -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")); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void getDataFromFile_nullFile_AssertionError() throws Exception { - thrown.expect(AssertionError.class); - XmlUtil.getDataFromFile(null, AddressBook.class); - } - - @Test - public void getDataFromFile_nullClass_AssertionError() throws Exception { - thrown.expect(AssertionError.class); - XmlUtil.getDataFromFile(VALID_FILE, null); - } - - @Test - public void getDataFromFile_missingFile_FileNotFoundException() throws Exception { - thrown.expect(FileNotFoundException.class); - XmlUtil.getDataFromFile(MISSING_FILE, AddressBook.class); - } - - @Test - public void getDataFromFile_emptyFile_DataFormatMismatchException() throws Exception { - thrown.expect(JAXBException.class); - XmlUtil.getDataFromFile(EMPTY_FILE, AddressBook.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()); - } - - @Test - public void saveDataToFile_nullFile_AssertionError() throws Exception { - thrown.expect(AssertionError.class); - XmlUtil.saveDataToFile(null, new AddressBook()); - } - - @Test - public void saveDataToFile_nullClass_AssertionError() throws Exception { - thrown.expect(AssertionError.class); - XmlUtil.saveDataToFile(VALID_FILE, null); - } - - @Test - public void saveDataToFile_missingFile_FileNotFoundException() throws Exception { - thrown.expect(FileNotFoundException.class); - XmlUtil.saveDataToFile(MISSING_FILE, new AddressBook()); - } - - @Test - public void saveDataToFile_validFile_dataSaved() throws Exception { - TEMP_FILE.createNewFile(); - XmlSerializableAddressBook dataToWrite = new XmlSerializableAddressBook(new AddressBook()); - XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); - XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableAddressBook.class); - assertEquals((new AddressBook(dataToWrite)).toString(),(new AddressBook(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()); - - XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); - dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableAddressBook.class); - assertEquals((new AddressBook(dataToWrite)).toString(),(new AddressBook(dataFromFile)).toString()); - } -} 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 650bd692e50a..000000000000 --- a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java +++ /dev/null @@ -1,99 +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)); - - } - - @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/TestUtil.java b/src/test/java/seedu/address/testutil/TestUtil.java deleted file mode 100644 index 17c92d66398a..000000000000 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ /dev/null @@ -1,354 +0,0 @@ -package seedu.address.testutil; - -import com.google.common.io.Files; -import guitests.guihandles.PersonCardHandle; -import javafx.geometry.Bounds; -import javafx.geometry.Point2D; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; -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 java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; - -/** - * A utility class for test cases. - */ -public class TestUtil { - - public static String LS = System.lineSeparator(); - - public static void assertThrows(Class expected, Runnable executable) { - try { - executable.run(); - } - catch (Throwable actualException) { - if (!actualException.getClass().isAssignableFrom(expected)) { - String message = String.format("Expected thrown: %s, actual: %s", expected.getName(), - actualException.getClass().getName()); - throw new AssertionFailedError(message); - } else return; - } - throw new AssertionFailedError( - 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/"); - - 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; - } - } - - 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); - } - - /** - * Appends the file name to the sandbox folder path. - * Creates the sandbox folder if it doesn't exist. - * @param fileName - * @return - */ - public static String getFilePathInSandboxFolder(String fileName) { - try { - FileUtil.createDirs(new File(SANDBOX_FOLDER)); - } catch (IOException e) { - throw new RuntimeException(e); - } - return SANDBOX_FOLDER + fileName; - } - - public static void createDataFileWithSampleData(String filePath) { - createDataFileWithData(generateSampleStorageAddressBook(), filePath); - } - - public static void createDataFileWithData(T data, String filePath) { - try { - File saveFileForTesting = new File(filePath); - FileUtil.createIfMissing(saveFileForTesting); - XmlUtil.saveDataToFile(saveFileForTesting, data); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - 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 XmlSerializableAddressBook generateSampleStorageAddressBook() { - return new XmlSerializableAddressBook(generateEmptyAddressBook()); - } - - /** - * Tweaks the {@code keyCodeCombination} to resolve the {@code KeyCode.SHORTCUT} to their - * respective platform-specific keycodes - */ - public static KeyCode[] scrub(KeyCodeCombination keyCodeCombination) { - List keys = new ArrayList<>(); - if (keyCodeCombination.getAlt() == KeyCombination.ModifierValue.DOWN) { - keys.add(KeyCode.ALT); - } - if (keyCodeCombination.getShift() == KeyCombination.ModifierValue.DOWN) { - keys.add(KeyCode.SHIFT); - } - if (keyCodeCombination.getMeta() == KeyCombination.ModifierValue.DOWN) { - keys.add(KeyCode.META); - } - if (keyCodeCombination.getControl() == KeyCombination.ModifierValue.DOWN) { - keys.add(KeyCode.CONTROL); - } - keys.add(keyCodeCombination.getCode()); - return keys.toArray(new KeyCode[]{}); - } - - public static boolean isHeadlessEnvironment() { - String headlessProperty = System.getProperty("testfx.headless"); - return headlessProperty != null && headlessProperty.equals("true"); - } - - public static void captureScreenShot(String fileName) { - File file = GuiTest.captureScreenshot(); - try { - Files.copy(file, new File(fileName + ".png")); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static String descOnFail(Object... comparedObjects) { - return "Comparison failed \n" - + Arrays.asList(comparedObjects).stream() - .map(Object::toString) - .collect(Collectors.joining("\n")); - } - - public static void setFinalStatic(Field field, Object newValue) throws NoSuchFieldException, IllegalAccessException{ - field.setAccessible(true); - // remove final modifier from field - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - // ~Modifier.FINAL is used to remove the final modifier from field so that its value is no longer - // final and can be changed - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - field.set(null, newValue); - } - - public static void initRuntime() throws TimeoutException { - FxToolkit.registerPrimaryStage(); - FxToolkit.hideStage(); - } - - public static void tearDownRuntime() throws Exception { - FxToolkit.cleanupStages(); - } - - /** - * Gets private method of a class - * Invoke the method using method.invoke(objectInstance, params...) - * - * Caveat: only find method declared in the current Class, not inherited from supertypes - */ - public static Method getPrivateMethod(Class objectClass, String methodName) throws NoSuchMethodException { - Method method = objectClass.getDeclaredMethod(methodName); - method.setAccessible(true); - return method; - } - - public static void renameFile(File file, String newFileName) { - try { - Files.copy(file, new File(newFileName)); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - - /** - * Gets mid point of a node relative to the screen. - * @param node - * @return - */ - public static Point2D getScreenMidPoint(Node node) { - double x = getScreenPos(node).getMinX() + node.getLayoutBounds().getWidth() / 2; - double y = getScreenPos(node).getMinY() + node.getLayoutBounds().getHeight() / 2; - return new Point2D(x,y); - } - - /** - * Gets mid point of a node relative to its scene. - * @param node - * @return - */ - public static Point2D getSceneMidPoint(Node node) { - double x = getScenePos(node).getMinX() + node.getLayoutBounds().getWidth() / 2; - double y = getScenePos(node).getMinY() + node.getLayoutBounds().getHeight() / 2; - return new Point2D(x,y); - } - - /** - * Gets the bound of the node relative to the parent scene. - * @param node - * @return - */ - public static Bounds getScenePos(Node node) { - return node.localToScene(node.getBoundsInLocal()); - } - - public static Bounds getScreenPos(Node node) { - return node.localToScreen(node.getBoundsInLocal()); - } - - public static double getSceneMaxX(Scene scene) { - return scene.getX() + scene.getWidth(); - } - - public static double getSceneMaxY(Scene scene) { - return scene.getX() + scene.getHeight(); - } - - public static Object getLastElement(List list) { - return list.get(list.size() - 1); - } - - /** - * 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. - */ - public static TestPerson[] removePersonsFromList(final TestPerson[] persons, TestPerson... personsToRemove) { - List listOfPersons = asList(persons); - listOfPersons.removeAll(asList(personsToRemove)); - return listOfPersons.toArray(new TestPerson[listOfPersons.size()]); - } - - - /** - * Returns a copy of the list with the person 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]); - } - - /** - * 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. - * @return - */ - public static TestPerson[] replacePersonFromList(TestPerson[] persons, TestPerson person, int index) { - persons[index] = person; - return persons; - } - - /** - * 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. - */ - public static TestPerson[] addPersonsToList(final TestPerson[] persons, TestPerson... personsToAdd) { - List listOfPersons = asList(persons); - listOfPersons.addAll(asList(personsToAdd)); - return listOfPersons.toArray(new TestPerson[listOfPersons.size()]); - } - - private static List asList(T[] objs) { - List list = new ArrayList<>(); - for(T obj : objs) { - list.add(obj); - } - 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]); - } - -} 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/tars/TestApp.java similarity index 61% rename from src/test/java/seedu/address/TestApp.java rename to src/test/java/tars/TestApp.java index 756642b6c180..277d59dd5599 100644 --- a/src/test/java/seedu/address/TestApp.java +++ b/src/test/java/tars/TestApp.java @@ -1,33 +1,37 @@ -package seedu.address; +package tars; 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 tars.MainApp; +import tars.commons.core.Config; +import tars.commons.core.GuiSettings; +import tars.model.ReadOnlyTars; +import tars.model.UserPrefs; +import tars.storage.XmlSerializableTars; +import tars.testutil.TestUtil; import java.util.function.Supplier; /** - * This class is meant to override some properties of MainApp so that it will be suited for - * testing + * This class is meant to override some properties of MainApp so that it will be + * suited for testing */ public class TestApp extends MainApp { - public static final String SAVE_LOCATION_FOR_TESTING = TestUtil.getFilePathInSandboxFolder("sampleData.xml"); - protected static final String DEFAULT_PREF_FILE_LOCATION_FOR_TESTING = TestUtil.getFilePathInSandboxFolder("pref_testing.json"); + public static final String SAVE_LOCATION_FOR_TESTING = TestUtil + .getFilePathInSandboxFolder("sampleData.xml"); + 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 static final String TARS_NAME = "Test"; + 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 +39,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 XmlSerializableTars(this.initialDataSupplier.get()), this.saveFileLocation); } } @@ -44,9 +48,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.setTarsFilePath(saveFileLocation); config.setUserPrefsFilePath(DEFAULT_PREF_FILE_LOCATION_FOR_TESTING); - config.setAddressBookName(ADDRESS_BOOK_NAME); + config.setTarsName(TARS_NAME); return config; } @@ -55,11 +59,11 @@ 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(600.0, 600.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/VersionTest.java b/src/test/java/tars/commons/core/VersionTest.java similarity index 79% rename from src/test/java/seedu/address/commons/core/VersionTest.java rename to src/test/java/tars/commons/core/VersionTest.java index 87ac01f6c92d..1ae9608d8b79 100644 --- a/src/test/java/seedu/address/commons/core/VersionTest.java +++ b/src/test/java/tars/commons/core/VersionTest.java @@ -1,9 +1,11 @@ -package seedu.address.commons.core; +package tars.commons.core; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import tars.commons.core.Version; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -51,60 +53,63 @@ public void versionToString_validVersion_correctStringRepresentation() { @Test public void versionComparable_validVersion_compareToIsCorrect() { - Version one, another; + Version one; + Version another; // Tests equality one = new Version(0, 0, 0, true); - another = new Version(0, 0, 0, true); + another = new Version(0, 0, 0, true); assertTrue(one.compareTo(another) == 0); one = new Version(11, 12, 13, false); - another = new Version(11, 12, 13, false); + another = new Version(11, 12, 13, false); assertTrue(one.compareTo(another) == 0); // Tests different patch one = new Version(0, 0, 5, false); - another = new Version(0, 0, 0, false); + another = new Version(0, 0, 0, false); assertTrue(one.compareTo(another) > 0); // Tests different minor one = new Version(0, 0, 0, false); - another = new Version(0, 5, 0, false); + another = new Version(0, 5, 0, false); assertTrue(one.compareTo(another) < 0); // Tests different major one = new Version(10, 0, 0, true); - another = new Version(0, 0, 0, true); + another = new Version(0, 0, 0, true); assertTrue(one.compareTo(another) > 0); // Tests high major vs low minor one = new Version(10, 0, 0, true); - another = new Version(0, 1, 0, true); + another = new Version(0, 1, 0, true); assertTrue(one.compareTo(another) > 0); // Tests high patch vs low minor one = new Version(0, 0, 10, false); - another = new Version(0, 1, 0, false); + another = new Version(0, 1, 0, false); assertTrue(one.compareTo(another) < 0); // Tests same major minor different patch one = new Version(2, 15, 0, false); - another = new Version(2, 15, 5, false); + another = new Version(2, 15, 5, false); assertTrue(one.compareTo(another) < 0); // Tests early access vs not early access on same version number one = new Version(2, 15, 0, true); - another = new Version(2, 15, 0, false); + another = new Version(2, 15, 0, false); assertTrue(one.compareTo(another) < 0); - // Tests early access lower version vs not early access higher version compare by version number first + // Tests early access lower version vs not early access higher version + // compare by version number first one = new Version(2, 15, 0, true); - another = new Version(2, 15, 5, false); + another = new Version(2, 15, 5, false); assertTrue(one.compareTo(another) < 0); - // Tests early access higher version vs not early access lower version compare by version number first + // Tests early access higher version vs not early access lower version + // compare by version number first one = new Version(2, 15, 0, false); - another = new Version(2, 15, 5, true); + another = new Version(2, 15, 5, true); assertTrue(one.compareTo(another) < 0); } @@ -119,19 +124,21 @@ public void versionComparable_validVersion_hashCodeIsCorrect() { @Test public void versionComparable_validVersion_equalIsCorrect() { - Version one, another; + Version one; + Version another; one = new Version(0, 0, 0, false); - another = new Version(0, 0, 0, false); + another = new Version(0, 0, 0, false); assertTrue(one.equals(another)); one = new Version(100, 191, 275, true); - another = new Version(100, 191, 275, true); + another = new Version(100, 191, 275, true); assertTrue(one.equals(another)); } - private void verifyVersionParsedCorrectly(String versionString, - int major, int minor, int patch, boolean isEarlyAccess) { - assertEquals(new Version(major, minor, patch, isEarlyAccess), Version.fromString(versionString)); + private void verifyVersionParsedCorrectly(String versionString, int major, + int minor, int patch, boolean isEarlyAccess) { + assertEquals(new Version(major, minor, patch, isEarlyAccess), + Version.fromString(versionString)); } } diff --git a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java b/src/test/java/tars/commons/util/ConfigUtilTest.java similarity index 63% rename from src/test/java/seedu/address/commons/util/ConfigUtilTest.java rename to src/test/java/tars/commons/util/ConfigUtilTest.java index 6699343c4a82..1e39fd89f775 100644 --- a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java +++ b/src/test/java/tars/commons/util/ConfigUtilTest.java @@ -1,12 +1,14 @@ -package seedu.address.commons.util; - +package tars.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 tars.commons.core.Config; +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.ConfigUtil; +import tars.commons.util.FileUtil; import java.io.File; import java.io.IOException; @@ -18,7 +20,8 @@ public class ConfigUtilTest { - private static String TEST_DATA_FOLDER = FileUtil.getPath("./src/test/data/ConfigUtilTest/"); + private static String TEST_DATA_FOLDER = FileUtil + .getPath("./src/test/data/ConfigUtilTest/"); @Rule public ExpectedException thrown = ExpectedException.none(); @@ -38,18 +41,22 @@ public void read_missingFile_emptyResult() throws DataConversionException { } @Test - public void read_notJasonFormat_exceptionThrown() throws DataConversionException { + public void read_notJasonFormat_exceptionThrown() + throws DataConversionException { thrown.expect(DataConversionException.class); read("NotJasonFormatConfig.json"); - /* 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 + /* + * 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 read_fileInOrder_successfullyRead() throws DataConversionException { + public void read_fileInOrder_successfullyRead() + throws DataConversionException { Config expected = getTypicalConfig(); @@ -58,13 +65,15 @@ public void read_fileInOrder_successfullyRead() throws DataConversionException { } @Test - public void read_valuesMissingFromFile_defaultValuesUsed() throws DataConversionException { + public void read_valuesMissingFromFile_defaultValuesUsed() + throws DataConversionException { Config actual = read("EmptyConfig.json").get(); assertEquals(new Config(), actual); } @Test - public void read_extraValuesInFile_extraValuesIgnored() throws DataConversionException { + public void read_extraValuesInFile_extraValuesIgnored() + throws DataConversionException { Config expected = getTypicalConfig(); Config actual = read("ExtraValuesConfig.json").get(); @@ -76,13 +85,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.setTarsFilePath("tars.xml"); + config.setTarsName("TypicalTarsName"); return config; } - private Optional read(String configFileInTestDataFolder) throws DataConversionException { - String configFilePath = addToTestDataPathIfNotNull(configFileInTestDataFolder); + private Optional read(String configFileInTestDataFolder) + throws DataConversionException { + String configFilePath = addToTestDataPathIfNotNull( + configFileInTestDataFolder); return new ConfigUtil().readConfig(configFilePath); } @@ -99,18 +110,20 @@ public void save_nullFile_assertionFailure() throws IOException { } @Test - public void saveConfig_allInOrder_success() throws DataConversionException, IOException { + public void saveConfig_allInOrder_success() + throws DataConversionException, IOException { Config original = getTypicalConfig(); - String configFilePath = testFolder.getRoot() + File.separator + "TempConfig.json"; + String configFilePath = testFolder.getRoot() + File.separator + + "TempConfig.json"; ConfigUtil configStorage = new ConfigUtil(); - //Try writing when the file doesn't exist + // Try writing when the file doesn't exist configStorage.saveConfig(original, configFilePath); Config readBack = configStorage.readConfig(configFilePath).get(); assertEquals(original, readBack); - //Try saving when the file exists + // Try saving when the file exists original.setAppTitle("Updated Title"); original.setLogLevel(Level.FINE); configStorage.saveConfig(original, configFilePath); @@ -118,16 +131,17 @@ public void saveConfig_allInOrder_success() throws DataConversionException, IOEx assertEquals(original, readBack); } - private void save(Config config, String configFileInTestDataFolder) throws IOException { - String configFilePath = addToTestDataPathIfNotNull(configFileInTestDataFolder); + private void save(Config config, String configFileInTestDataFolder) + throws IOException { + String configFilePath = addToTestDataPathIfNotNull( + configFileInTestDataFolder); new ConfigUtil().saveConfig(config, configFilePath); } - private String addToTestDataPathIfNotNull(String configFileInTestDataFolder) { + private String addToTestDataPathIfNotNull( + String configFileInTestDataFolder) { return configFileInTestDataFolder != null - ? TEST_DATA_FOLDER + configFileInTestDataFolder - : null; + ? TEST_DATA_FOLDER + configFileInTestDataFolder : null; } - } diff --git a/src/test/java/tars/commons/util/DateTimeUtilTest.java b/src/test/java/tars/commons/util/DateTimeUtilTest.java new file mode 100644 index 000000000000..a6a667e6f4cc --- /dev/null +++ b/src/test/java/tars/commons/util/DateTimeUtilTest.java @@ -0,0 +1,279 @@ +package tars.commons.util; + +import static org.junit.Assert.*; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import tars.commons.core.Messages; +import tars.model.task.DateTime; +import tars.model.task.DateTime.IllegalDateException; + +/** + * Date time utility test + */ +public class DateTimeUtilTest { + + // @@author A0139924W + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void parseStringToDateTime_invalidDate() { + thrown.expect(DateTimeException.class); + thrown.expectMessage(Messages.MESSAGE_INVALID_DATE); + + DateTimeUtil.parseStringToDateTime("abc"); + DateTimeUtil.parseStringToDateTime("hello world"); + + DateTimeUtil.parseStringToDateTime("+1"); + DateTimeUtil.parseStringToDateTime("-1"); + } + + @Test + public void parseStringToDateTime_emptyArgs() { + String[] expected = + new String[] {StringUtil.EMPTY_STRING, StringUtil.EMPTY_STRING}; + String[] actual = DateTimeUtil + .parseStringToDateTime(StringUtil.STRING_WHITESPACE); + + assertEquals(expected[0], actual[0]); + assertEquals(expected[1], actual[1]); + + actual = DateTimeUtil.parseStringToDateTime(StringUtil.EMPTY_STRING); + assertEquals(expected[0], actual[0]); + assertEquals(expected[1], actual[1]); + } + + @Test + public void parseStringToDateTime_singleDateSuccessful() { + String[] expectedDateTime = + {StringUtil.EMPTY_STRING, "01/01/2016 1500"}; + String[] actualDateTime = + DateTimeUtil.parseStringToDateTime("1/1/2016 1500"); + + assertArrayEquals(expectedDateTime, actualDateTime); + } + + @Test + public void parseStringToDateTime_dateRangeSuccessful() { + String[] expectedDateTime = {"01/01/2016 1500", "02/01/2016 1600"}; + String[] actualDateTime = DateTimeUtil + .parseStringToDateTime("1/1/2016 1500 to 2/1/2016 1600"); + + assertArrayEquals(expectedDateTime, actualDateTime); + } + + // @@author A0140022H + @Test + public void modifyDate() { + String dateToModify = "06/09/2016 2200"; + + String frequencyDay = "day"; + String frequencyWeek = "week"; + String frequencyMonth = "month"; + String frequencyYear = "year"; + + String expectedDay = "07/09/2016 2200"; + String expectedWeek = "13/09/2016 2200"; + String expectedMonth = "06/10/2016 2200"; + String expectedYear = "06/09/2017 2200"; + + String modifiedDay = + DateTimeUtil.modifyDate(dateToModify, frequencyDay); + String modifiedWeek = + DateTimeUtil.modifyDate(dateToModify, frequencyWeek); + String modifiedMonth = + DateTimeUtil.modifyDate(dateToModify, frequencyMonth); + String modifiedYear = + DateTimeUtil.modifyDate(dateToModify, frequencyYear); + + assertEquals(expectedDay, modifiedDay); + assertEquals(expectedWeek, modifiedWeek); + assertEquals(expectedMonth, modifiedMonth); + assertEquals(expectedYear, modifiedYear); + } + + // @@author A0121533W + @Test + public void isWithinWeek_dateTimeNullValue_returnFalse() { + LocalDateTime nullDateTime = null; + assertFalse(DateTimeUtil.isWithinWeek(nullDateTime)); + } + + @Test + public void isWithinWeek_dateTimeNotWithinWeek_returnFalse() { + LocalDateTime nextMonth = + LocalDateTime.now().plus(1, ChronoUnit.MONTHS); + LocalDateTime lastMonth = + LocalDateTime.now().minus(1, ChronoUnit.MONTHS); + assertFalse(DateTimeUtil.isWithinWeek(nextMonth)); + assertFalse(DateTimeUtil.isWithinWeek(lastMonth)); + } + + @Test + public void isOverDue_dateTimeNullValue_returnFalse() { + LocalDateTime nullDateTime = null; + assertFalse(DateTimeUtil.isOverDue(nullDateTime)); + } + + @Test + public void isOverDue_dateTimeOverDue_returnTrue() { + LocalDateTime yesterday = LocalDateTime.now().minus(1, ChronoUnit.DAYS); + assertTrue(DateTimeUtil.isOverDue(yesterday)); + } + + @Test + public void isOverDue_dateTimeNotOverDue_returnFalse() { + LocalDateTime tomorrow = LocalDateTime.now().plus(1, ChronoUnit.DAYS); + assertFalse(DateTimeUtil.isOverDue(tomorrow)); + } + + // @@author A0124333U + @Test + public void isDateTimeWithinRange_emptyDateTimeSource() throws Exception { + DateTime dateTimeSource = + new DateTime(StringUtil.EMPTY_STRING, StringUtil.EMPTY_STRING); + DateTime dateTimeQuery = + new DateTime("17/01/2016 1200", "18/01/2016 1200"); + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQuery)); + } + + @Test + public void isDateTimeWithinRange_dateTimeOutOfRange() throws Exception { + DateTime dateTimeSource = + new DateTime("15/01/2016 1200", "16/01/2016 1200"); + DateTime dateTimeSource2 = + new DateTime("19/01/2016 1200", "20/01/2016 1200"); + DateTime dateTimeQuery = + new DateTime("17/01/2016 1200", "18/01/2016 1200"); + + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQuery)); + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource2, + dateTimeQuery)); + } + + @Test + public void isDateTimeWithinRange_dateTimeWithinRange() throws Exception { + DateTime dateTimeSource = + new DateTime("14/01/2016 1200", "16/01/2016 1200"); + DateTime dateTimeQueryFullyInRange = + new DateTime("14/01/2016 2000", "15/01/2016 1200"); + DateTime dateTimeQueryPartiallyInRange = + new DateTime("13/01/2016 1000", "15/01/2016 1200"); + + assertTrue(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQueryFullyInRange)); + assertTrue(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQueryPartiallyInRange)); + } + + @Test + public void isDateTimeWithinRange_dateTimeWithoutStartDate() + throws Exception { + DateTime dateTimeSource = + new DateTime("15/01/2016 1200", "17/01/2016 1100"); + DateTime dateTimeSourceWithoutStartDate = + new DateTime("", "16/01/2016 1200"); + DateTime dateTimeQuery = + new DateTime("14/01/2016 2000", "17/01/2016 1200"); + DateTime dateTimeQueryWithoutStartDate = + new DateTime("", "16/01/2016 1200"); + DateTime dateTimeQueryWithoutStartDate2 = + new DateTime("", "18/01/2016 1200"); + + assertTrue(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQuery)); + assertFalse(DateTimeUtil.isDateTimeWithinRange(dateTimeSource, + dateTimeQueryWithoutStartDate2)); + assertTrue(DateTimeUtil.isDateTimeWithinRange( + dateTimeSourceWithoutStartDate, dateTimeQuery)); + assertTrue(DateTimeUtil.isDateTimeWithinRange( + dateTimeSourceWithoutStartDate, dateTimeQueryWithoutStartDate)); + assertFalse(DateTimeUtil.isDateTimeWithinRange( + dateTimeSourceWithoutStartDate, + dateTimeQueryWithoutStartDate2)); + } + + @Test + public void isDateTimeConflicting_dateTimeConflicts() throws Exception { + DateTime dateTimeSource = + new DateTime("14/01/2016 1200", "16/01/2016 1200"); + DateTime conflictingDateTimeQuery = + new DateTime("14/01/2016 2000", "15/01/2016 1200"); + DateTime conflictingDateTimeQuery2 = + new DateTime("13/01/2016 1000", "15/01/2016 1200"); + + assertTrue(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + conflictingDateTimeQuery)); + assertTrue(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + conflictingDateTimeQuery2)); + } + + @Test + public void isDateTimeConflicting_dateTimeNotConflicting() + throws Exception { + DateTime dateTimeSource = + new DateTime("14/01/2016 1200", "16/01/2016 1200"); + DateTime dateTimeQueryOutOfRange = + new DateTime("18/01/2016 2000", "19/01/2016 1200"); + DateTime dateTimeAdjacent = + new DateTime("13/01/2016 1000", "14/01/2016 1200"); + + assertFalse(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + dateTimeQueryOutOfRange)); + assertFalse(DateTimeUtil.isDateTimeConflicting(dateTimeSource, + dateTimeAdjacent)); + } + + @Test + public void getListOfFreeTimeSlotsInDate_success() + throws DateTimeException, IllegalDateException { + ArrayList listOfFilledTimeSlots = new ArrayList(); + DateTime dateToCheck = + new DateTime("29/10/2016 0000", "29/10/2016 2359"); + ArrayList currentList = new ArrayList(); + ArrayList expectedList = new ArrayList(); + + // Initialize listOfFilledTimeSlots + listOfFilledTimeSlots + .add(new DateTime("27/10/2016 1200", "29/10/2016 0830")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 0500", "29/10/2016 0630")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 0730", "29/10/2016 0900")); + listOfFilledTimeSlots.add(new DateTime("", "29/10/2016 1300")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 1400", "29/10/2016 1500")); + listOfFilledTimeSlots + .add(new DateTime("29/10/2016 2330", "30/10/2016 0100")); + + // Initialize expectedList + expectedList.add(new DateTime("29/10/2016 0900", "29/10/2016 1400")); + expectedList.add(new DateTime("29/10/2016 1500", "29/10/2016 2330")); + + currentList = DateTimeUtil.getListOfFreeTimeSlotsInDate(dateToCheck, + listOfFilledTimeSlots); + + assertEquals(expectedList, currentList); + } + + @Test + public void getDurationInMinutesBetweenTwoLocalDateTime_success() { + LocalDateTime ldt1 = LocalDateTime.of(2016, 10, 29, 9, 36); + LocalDateTime ldt2 = LocalDateTime.of(2016, 10, 29, 14, 28); + + assertEquals( + DateTimeUtil.getDurationBetweenTwoLocalDateTime(ldt1, ldt2), + "4 hr 52 min"); + } + +} diff --git a/src/test/java/tars/commons/util/FileUtilTest.java b/src/test/java/tars/commons/util/FileUtilTest.java new file mode 100644 index 000000000000..264cfc503f93 --- /dev/null +++ b/src/test/java/tars/commons/util/FileUtilTest.java @@ -0,0 +1,69 @@ +package tars.commons.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import tars.commons.util.FileUtil; +import tars.testutil.SerializableTestClass; +import tars.testutil.TestUtil; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class FileUtilTest { + private static final File SERIALIZATION_FILE = new File( + TestUtil.getFilePathInSandboxFolder("serialize.json")); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void getPath() { + + // valid case + assertEquals("folder" + File.separator + "sub-folder", + FileUtil.getPath("folder/sub-folder")); + + // null parameter -> assertion failure + thrown.expect(AssertionError.class); + FileUtil.getPath(null); + + // no forwards slash -> assertion failure + thrown.expect(AssertionError.class); + FileUtil.getPath("folder"); + } + + @Test + public void serializeObjectToJsonFile_noExceptionThrown() + throws IOException { + SerializableTestClass serializableTestClass = new SerializableTestClass(); + serializableTestClass.setTestValues(); + + FileUtil.serializeObjectToJsonFile(SERIALIZATION_FILE, + serializableTestClass); + + assertEquals(FileUtil.readFromFile(SERIALIZATION_FILE), + SerializableTestClass.JSON_STRING_REPRESENTATION); + } + + @Test + public void deserializeObjectFromJsonFile_noExceptionThrown() + throws IOException { + FileUtil.writeToFile(SERIALIZATION_FILE, + SerializableTestClass.JSON_STRING_REPRESENTATION); + + SerializableTestClass serializableTestClass = FileUtil + .deserializeObjectFromJsonFile(SERIALIZATION_FILE, + SerializableTestClass.class); + + assertEquals(serializableTestClass.getName(), + SerializableTestClass.getNameTestValue()); + assertEquals(serializableTestClass.getListOfLocalDateTimes(), + SerializableTestClass.getListTestValues()); + assertEquals(serializableTestClass.getMapOfIntegerToString(), + SerializableTestClass.getHashMapTestValues()); + } +} diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/tars/commons/util/StringUtilTest.java similarity index 79% rename from src/test/java/seedu/address/commons/util/StringUtilTest.java rename to src/test/java/tars/commons/util/StringUtilTest.java index bd209b1cbcab..19070daa9df2 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/tars/commons/util/StringUtilTest.java @@ -1,7 +1,9 @@ -package seedu.address.commons.util; +package tars.commons.util; import org.junit.Test; +import tars.commons.util.StringUtil; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,5 +28,13 @@ public void isUnsignedPositiveInteger() { assertTrue(StringUtil.isUnsignedInteger("10")); } + @Test + public void containsIgnoreCase() { + String upperCaseString = "TASK ABC"; + String lowerCaseString = "task"; + + assertTrue(StringUtil.containsIgnoreCase(upperCaseString, lowerCaseString)); + } + } diff --git a/src/test/java/tars/commons/util/XmlUtilTest.java b/src/test/java/tars/commons/util/XmlUtilTest.java new file mode 100644 index 000000000000..599071e62320 --- /dev/null +++ b/src/test/java/tars/commons/util/XmlUtilTest.java @@ -0,0 +1,111 @@ +package tars.commons.util; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import tars.commons.util.FileUtil; +import tars.commons.util.XmlUtil; +import tars.model.Tars; +import tars.storage.XmlSerializableTars; +import tars.testutil.TarsBuilder; +import tars.testutil.TestUtil; + +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileNotFoundException; + +import static org.junit.Assert.assertEquals; + +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 + "validTars.xml"); + private static final File TEMP_FILE = new File( + TestUtil.getFilePathInSandboxFolder("tempTars.xml")); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void getDataFromFile_nullFile_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.getDataFromFile(null, Tars.class); + } + + @Test + public void getDataFromFile_nullClass_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.getDataFromFile(VALID_FILE, null); + } + + @Test + public void getDataFromFile_missingFile_FileNotFoundException() + throws Exception { + thrown.expect(FileNotFoundException.class); + XmlUtil.getDataFromFile(MISSING_FILE, Tars.class); + } + + @Test + public void getDataFromFile_emptyFile_DataFormatMismatchException() + throws Exception { + thrown.expect(JAXBException.class); + XmlUtil.getDataFromFile(EMPTY_FILE, Tars.class); + } + + @Test + public void getDataFromFile_validFile_validResult() throws Exception { + XmlSerializableTars dataFromFile = XmlUtil.getDataFromFile(VALID_FILE, + XmlSerializableTars.class); + assertEquals(1, dataFromFile.getTaskList().size()); + assertEquals(0, dataFromFile.getTagList().size()); + } + + @Test + public void saveDataToFile_nullFile_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.saveDataToFile(null, new Tars()); + } + + @Test + public void saveDataToFile_nullClass_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.saveDataToFile(VALID_FILE, null); + } + + @Test + public void saveDataToFile_missingFile_FileNotFoundException() + throws Exception { + thrown.expect(FileNotFoundException.class); + XmlUtil.saveDataToFile(MISSING_FILE, new Tars()); + } + + @Test + public void saveDataToFile_validFile_dataSaved() throws Exception { + TEMP_FILE.createNewFile(); + XmlSerializableTars dataToWrite = new XmlSerializableTars(new Tars()); + XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); + XmlSerializableTars dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, + XmlSerializableTars.class); + assertEquals((new Tars(dataToWrite)).toString(), + (new Tars(dataFromFile)).toString()); + assertEquals((new Tars(dataToWrite)), (new Tars(dataFromFile))); + + TarsBuilder builder = new TarsBuilder(new Tars()); + dataToWrite = new XmlSerializableTars( + builder.withTask(TestUtil.generateSampleTaskData().get(0)) + .withTag("Friends").build()); + + XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); + dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, + XmlSerializableTars.class); + assertEquals((new Tars(dataToWrite)).toString(), + (new Tars(dataFromFile)).toString()); + } +} diff --git a/src/test/java/tars/logic/AddLogicCommandTest.java b/src/test/java/tars/logic/AddLogicCommandTest.java new file mode 100644 index 000000000000..79bd3858bfd1 --- /dev/null +++ b/src/test/java/tars/logic/AddLogicCommandTest.java @@ -0,0 +1,214 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_DUPLICATE_TASK; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_INVALID_DATE; +import static tars.commons.core.Messages.MESSAGE_INVALID_END_DATE; +import static tars.commons.core.Messages.MESSAGE_TASK_CANNOT_BE_FOUND; + +import org.junit.Test; + +import tars.logic.commands.AddCommand; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.UndoCommand; +import tars.model.Tars; +import tars.model.tag.Tag; +import tars.model.task.DateTime; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.model.task.Task; + +// @@author A0139924W +/** + * Logic command test for add + */ +public class AddLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_add_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCommand.MESSAGE_USAGE); + assertCommandBehavior( + "add /dt 22/04/2016 1400 to 23/04/2016 2200 /p h Valid Task Name", + expectedMessage); + assertCommandBehavior("add", expectedMessage); + } + + @Test + public void execute_add_invalidTaskData() throws Exception { + assertCommandBehavior( + "add []\\[;] /dt 05/09/2016 1400 to 06/09/2016 2200 /p m", + Name.MESSAGE_NAME_CONSTRAINTS); + assertCommandBehavior( + "add name - hello world /dt 05/09/2016 1400 to 06/09/2016 2200 /p m", + Name.MESSAGE_NAME_CONSTRAINTS); + assertCommandBehavior( + "add Valid Task Name /dt @@@notAValidDate@@@ -p m", + MESSAGE_INVALID_DATE); + assertCommandBehavior( + "add Valid Task Name /dt 05/09/2016 1400 to 01/09/2016 2200 /p m", + MESSAGE_INVALID_END_DATE); + assertCommandBehavior( + "add Valid Task Name /dt 05/09/2016 1400 to 06/09/2016 2200 /p medium", + Priority.MESSAGE_PRIORITY_CONSTRAINTS); + assertCommandBehavior( + "add Valid Task Name /dt 05/09/2016 1400 to 06/09/2016 2200 /p m /t invalid_-[.tag", + Tag.MESSAGE_TAG_CONSTRAINTS); + + } + + @Test + public void execute_add_successful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_add_endDateSuccessful() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.generateTaskWithEndDateOnly("Jane"); + Tars expectedTars = new Tars(); + expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_add_floatingTaskSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.floatingTask(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_add_emptyTaskNameInvalidFormat() throws Exception { + assertCommandBehavior("add ", + String.format(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCommand.MESSAGE_USAGE))); + } + + // @@author A0140022H + @Test + public void execute_add_recurring() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Task toBeAdded2 = helper.meetAdam(); + toBeAdded2.setDateTime( + new DateTime("08/09/2016 1400", "08/09/2016 1500")); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + expectedTars.addTask(toBeAdded2); + + // execute command and verify result + String expectedMessage = + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"); + expectedMessage += + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded2 + "\n"); + assertCommandBehavior( + helper.generateAddCommand(toBeAdded).concat(" /r 2 every week"), + expectedMessage, expectedTars, expectedTars.getTaskList()); + } + // @@author + + @Test + public void execute_add_duplicateNotAllowed() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // setup starting state + model.addTask(toBeAdded); // task already in internal address book + + // execute command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + MESSAGE_DUPLICATE_TASK, expectedTars, + expectedTars.getTaskList()); + } + + // @@author A0139924W + @Test + public void execute_undoAndRedo_addSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute add command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeAdded); + + // execute undo command and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(AddCommand.MESSAGE_UNDO, toBeAdded)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addTask(toBeAdded); + + // execute redo command and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(AddCommand.MESSAGE_SUCCESS, + toBeAdded + "\n")), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_addUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + // execute add command and verify result + assertCommandBehavior(helper.generateAddCommand(toBeAdded), + String.format(AddCommand.MESSAGE_SUCCESS, toBeAdded + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeAdded); + model.deleteTask(toBeAdded); + + // execute undo command and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + + model.addTask(toBeAdded); + expectedTars.addTask(toBeAdded); + + // execute redo command and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + } +} diff --git a/src/test/java/tars/logic/CdLogicCommandTest.java b/src/test/java/tars/logic/CdLogicCommandTest.java new file mode 100644 index 000000000000..222be25bbc31 --- /dev/null +++ b/src/test/java/tars/logic/CdLogicCommandTest.java @@ -0,0 +1,58 @@ +package tars.logic; + +import java.io.File; + +import org.junit.Test; + +import tars.commons.util.FileUtil; +import tars.logic.commands.CdCommand; +import tars.model.Tars; +import tars.storage.TarsStorage; +import tars.storage.XmlTarsStorage; + +// @@author A0124333U +/** + * Logic command test for cd + */ +public class CdLogicCommandTest extends LogicCommandTest { + @Test + public void execute_cd_incorrectArgsFormatErrorMessageShown() + throws Exception { + assertCommandBehavior("cd ", CdCommand.MESSAGE_INVALID_FILEPATH); + } + + @Test + public void execute_cd_invalidFileTypeErrorMessageShown() throws Exception { + assertCommandBehavior("cd invalidFileType", + CdCommand.MESSAGE_INVALID_FILEPATH); + } + + @Test + public void execute_cd_new_file_success() throws Exception { + String tempTestTarsFilePath = + saveFolder.getRoot().getPath() + "TempTestTars.xml"; + assertCommandBehavior("cd " + tempTestTarsFilePath, String.format( + CdCommand.MESSAGE_SUCCESS_NEW_FILE, tempTestTarsFilePath)); + } + + @Test + public void execute_cd_existing_file_failureToReadExistingFile() + throws Exception { + String existingFilePath = + saveFolder.getRoot().getPath() + "TempTars.xml"; + File existingFile = new File(existingFilePath); + FileUtil.createIfMissing(existingFile); + assertCommandBehavior("cd " + existingFilePath, + CdCommand.MESSAGE_FAILURE_READ_FILE); + } + + @Test + public void execute_cd_existing_file_success() throws Exception { + String existingFilePath = + saveFolder.getRoot().getPath() + "TempTars.xml"; + TarsStorage testStorage = new XmlTarsStorage(existingFilePath); + testStorage.saveTars(Tars.getEmptyTars()); + assertCommandBehavior("cd " + existingFilePath, String.format( + CdCommand.MESSAGE_SUCCESS_EXISTING_FILE, existingFilePath)); + } +} diff --git a/src/test/java/tars/logic/ConfirmLogicCommandTest.java b/src/test/java/tars/logic/ConfirmLogicCommandTest.java new file mode 100644 index 000000000000..490b845a3eb2 --- /dev/null +++ b/src/test/java/tars/logic/ConfirmLogicCommandTest.java @@ -0,0 +1,171 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_DUPLICATE_TASK; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX; +import static tars.commons.core.Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import tars.commons.core.Messages; +import tars.logic.commands.ConfirmCommand; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.UndoCommand; +import tars.model.Tars; +import tars.model.task.Task; +import tars.model.task.rsv.RsvTask; + +// @@author A0124333U +/** + * Logic command test for confirm + */ +public class ConfirmLogicCommandTest extends LogicCommandTest { + @Test + public void execute_confirm_invalidArgsFormatErrorMessageShown() + throws Exception { + + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ConfirmCommand.MESSAGE_USAGE); + assertCommandBehavior("confirm ", expectedMessage); + assertCommandBehavior("confirm /p h 1 2", expectedMessage); + assertCommandBehavior("confirm 1 1 -dt invalidFlag", expectedMessage); + assertCommandBehavior("confirm 1 1 3", expectedMessage); + } + + @Test + public void execute_confirm_invalidRsvTaskIndex_ErrorMessageShown() + throws Exception { + assertCommandBehavior("confirm 2 3", + MESSAGE_INVALID_RSV_TASK_DISPLAYED_INDEX); + } + + @Test + public void execute_confirm_invalidRsvTaskDateTimeIndex_ErrorMessageShown() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = new Tars(); + RsvTask rsvTask = + helper.generateReservedTaskWithOneDateTimeOnly("Test Task"); + expectedTars.addRsvTask(rsvTask); + model.addRsvTask(rsvTask); + + assertCommandBehavior("confirm 1 3", + Messages.MESSAGE_INVALID_DATETIME_DISPLAYED_INDEX, expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_confirm_success() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + + // Create added task + Task addedTask = helper.generateTaskWithName("Test Task"); + + // Create end state taskList with one confirmed task + List taskList = new ArrayList(); + taskList.add(addedTask); + + // Create Empty end state rsvTaskList + List rsvTaskList = new ArrayList(); + + RsvTask rsvTask = + helper.generateReservedTaskWithOneDateTimeOnly("Test Task"); + + Tars expectedTars = new Tars(); + expectedTars.addTask(addedTask); + + // Set Tars start state to 1 reserved task, and 0 tasks. + model.resetData(new Tars()); + model.addRsvTask(rsvTask); + + String expectedMessage = String + .format(ConfirmCommand.MESSAGE_CONFIRM_SUCCESS, addedTask); + assertCommandBehaviorWithRsvTaskList("confirm 1 1 /p h /t tag", + expectedMessage, expectedTars, taskList, rsvTaskList); + + } + + // @@author A0139924W + @Test + public void execute_undoAndRedo_confirmSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Task taskToConfirm = helper.generateTaskWithName("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addTask(taskToConfirm); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "confirm 1 1 /p h /t tag"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(ConfirmCommand.MESSAGE_CONFIRM_SUCCESS, + taskToConfirm), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(taskToConfirm); + expectedTars.addRsvTask(taskToRsv); + + // execute undo command and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + expectedTars.addTask(taskToConfirm); + expectedTars.removeRsvTask(taskToRsv); + + // execute redo command and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_confirmUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Task taskToConfirm = helper.generateTaskWithName("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addTask(taskToConfirm); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "confirm 1 1 /p h /t tag"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(ConfirmCommand.MESSAGE_CONFIRM_SUCCESS, + taskToConfirm), + expectedTars, expectedTars.getTaskList()); + + model.addRsvTask(taskToRsv); + expectedTars.addRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + model.deleteRsvTask(taskToRsv); + expectedTars.removeRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_RSV_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + } + +} diff --git a/src/test/java/tars/logic/DeleteLogicCommandTest.java b/src/test/java/tars/logic/DeleteLogicCommandTest.java new file mode 100644 index 000000000000..a445890259c2 --- /dev/null +++ b/src/test/java/tars/logic/DeleteLogicCommandTest.java @@ -0,0 +1,166 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_DUPLICATE_TASK; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_TASK_CANNOT_BE_FOUND; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import tars.commons.exceptions.InvalidRangeException; +import tars.logic.commands.DeleteCommand; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.UndoCommand; +import tars.model.Tars; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Task; +import tars.ui.formatter.Formatter; + +// @@author A0121533W +/** + * Logic command test for delete + */ +public class DeleteLogicCommandTest extends LogicCommandTest { + private static final int FIRST_TASK_IN_LIST = 0; + private static final int SECOND_TASK_IN_LIST = 1; + private static final int THIRD_TASK_IN_LIST = 2; + private static final int NUM_TASK_TO_DELETE = 3; + + @Test + public void execute_delete_invalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE); + assertIncorrectIndexFormatBehaviorForCommand("del ", expectedMessage); + } + + @Test + public void execute_delete_indexNotFoundErrorMessageShown() + throws Exception { + assertIndexNotFoundBehaviorForCommand("del"); + } + + // @@author A0139924W + @Test + public void execute_delete_invalidRange() throws Exception { + String expectedMessage = InvalidRangeException.MESSAGE_INVALID_RANGE; + assertCommandBehavior("del 2..1", expectedMessage); + } + // @@author + + @Test + public void execute_delete_removesCorrectTask() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + List threeTasks = helper.generateTaskList(NUM_TASK_TO_DELETE); + + Tars expectedTars = helper.generateTars(threeTasks); + expectedTars.removeTask(threeTasks.get(SECOND_TASK_IN_LIST)); + helper.addToModel(model, threeTasks); + + assertCommandBehavior("del 2", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + threeTasks.get(SECOND_TASK_IN_LIST) + "\n"), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_delete_range() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + List threeTasks = helper.generateTaskList(NUM_TASK_TO_DELETE); + + Tars expectedTars = helper.generateTars(threeTasks); + helper.addToModel(model, threeTasks); + + // delete tasks within range + expectedTars.removeTask(threeTasks.get(FIRST_TASK_IN_LIST)); + expectedTars.removeTask(threeTasks.get(SECOND_TASK_IN_LIST)); + expectedTars.removeTask(threeTasks.get(THIRD_TASK_IN_LIST)); + + ArrayList deletedTasks = new ArrayList(); + deletedTasks.add(threeTasks.get(FIRST_TASK_IN_LIST)); + deletedTasks.add(threeTasks.get(SECOND_TASK_IN_LIST)); + deletedTasks.add(threeTasks.get(THIRD_TASK_IN_LIST)); + + String formattedResult = new Formatter().formatTaskList(deletedTasks); + assertCommandBehavior("del 1..3", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, + formattedResult), + expectedTars, expectedTars.getTaskList()); + } + + // @@author A0139924W + @Test + public void execute_undoAndRedo_delSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeRemoved = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeRemoved); + + model.addTask(toBeRemoved); + expectedTars.removeTask(toBeRemoved); + + // execute command and verify result + assertCommandBehavior("del 1", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + toBeRemoved + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addTask(toBeRemoved); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(DeleteCommand.MESSAGE_UNDO, + "1.\t" + toBeRemoved + "\n")), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeRemoved); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(DeleteCommand.MESSAGE_REDO, + "1.\t" + toBeRemoved + "\n")), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_delUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeRemoved = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeRemoved); + + model.addTask(toBeRemoved); + expectedTars.removeTask(toBeRemoved); + + // execute command and verify result + assertCommandBehavior("del 1", + String.format(DeleteCommand.MESSAGE_DELETE_TASK_SUCCESS, + "1.\t" + toBeRemoved + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addTask(toBeRemoved); + model.addTask(toBeRemoved); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeTask(toBeRemoved); + model.deleteTask(toBeRemoved); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + } + +} diff --git a/src/test/java/tars/logic/DoLogicCommandTest.java b/src/test/java/tars/logic/DoLogicCommandTest.java new file mode 100644 index 000000000000..ada58f562dcc --- /dev/null +++ b/src/test/java/tars/logic/DoLogicCommandTest.java @@ -0,0 +1,89 @@ +package tars.logic; + +import java.util.List; + +import org.junit.Test; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.model.Tars; +import tars.model.task.Status; +import tars.model.task.Task; + +// @@author A0121533W +/** + * Logic command test for do + */ +public class DoLogicCommandTest extends LogicCommandTest { + private static final int NUM_TASKS_IN_LIST = 2; + private static final int NUM_TASKS_IN_RANGE = 5; + + @Test + public void execute_mark_allTaskAsDone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, false); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars, true); + + assertCommandBehavior("do 1 2", + "Task: 1, 2 marked done successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_mark_alreadyDone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, true); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars, true); + + assertCommandBehavior("do 1 2", "Task: 1, 2 already marked done.\n", + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_mark_rangeDone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_RANGE, helper, false); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_RANGE, helper, expectedTars, true); + + assertCommandBehavior("do 1..5", + "Task: 1, 2, 3, 4, 5 marked done successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + private void generateTestTars(int numTasks, TypicalTestDataHelper helper, boolean status) + throws Exception { + Status s = new Status(status); + + Task[] taskArray = new Task[numTasks]; + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + taskI.setStatus(s); + taskArray[i-1] = taskI; + } + + List taskList = helper.generateTaskList(taskArray); + + helper.addToModel(model, taskList); + } + + private void generateExpectedTars(int numTasks, TypicalTestDataHelper helper, + Tars expectedTars, boolean status) throws Exception, DuplicateTaskException { + + Status s = new Status(status); + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + taskI.setStatus(s); + expectedTars.addTask(taskI); + } + } +} diff --git a/src/test/java/tars/logic/EditLogicCommandTest.java b/src/test/java/tars/logic/EditLogicCommandTest.java new file mode 100644 index 000000000000..c201c4111e5e --- /dev/null +++ b/src/test/java/tars/logic/EditLogicCommandTest.java @@ -0,0 +1,165 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_INVALID_DATE; + +import java.util.List; + +import org.junit.Test; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.logic.commands.EditCommand; +import tars.logic.commands.RedoCommand; +import tars.logic.commands.UndoCommand; +import tars.model.Tars; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.model.task.DateTime; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.model.task.Status; +import tars.model.task.Task; + +// @@author A0124333U +/** + * Logic command test for edit + */ +public class EditLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_edit_invalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditCommand.MESSAGE_USAGE); + + assertInvalidInputBehaviorForEditCommand("edit ", expectedMessage); + assertInvalidInputBehaviorForEditCommand( + "edit 1 -invalidFlag invalidArg", expectedMessage); + } + + @Test + public void execute_edit_indexNotFoundErrorMessageShown() throws Exception { + assertIndexNotFoundBehaviorForCommand("edit"); + } + + @Test + public void execute_edit_invalidTaskData() throws Exception { + assertInvalidInputBehaviorForEditCommand("edit 1 /n []\\[;]", + Name.MESSAGE_NAME_CONSTRAINTS); + assertInvalidInputBehaviorForEditCommand( + "edit 1 /dt @@@notAValidDate@@@", MESSAGE_INVALID_DATE); + assertInvalidInputBehaviorForEditCommand("edit 1 /p medium", + Priority.MESSAGE_PRIORITY_CONSTRAINTS); + assertInvalidInputBehaviorForEditCommand( + "edit 1 /n validName /dt invalidDate", MESSAGE_INVALID_DATE); + assertInvalidInputBehaviorForEditCommand("edit 1 /tr $#$", + Tag.MESSAGE_TAG_CONSTRAINTS); + } + + @Test + public void execute_edit_editedCorrectTask() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskToAdd = helper.meetAdam(); + model.addTask(taskToAdd); + Tars expectedTars = prepareExpectedTars(); + + String inputCommand = "edit 1 /n Meet Betty Green /dt 20/09/2016 1800 " + + "to 21/09/2016 1800 /p h /tr tag2 /ta tag3"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(EditCommand.MESSAGE_EDIT_TASK_SUCCESS, + expectedTars.getTaskList().get(0)), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_edit_editedDuplicateTask() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskToAdd = helper.meetAdam(); + model.addTask(taskToAdd); + Tars expectedTars = new Tars(); + expectedTars.addTask(taskToAdd); + + String inputCommand = "edit 1 /n Meet Adam Brown"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + new DuplicateTaskException().getMessage().toString(), + expectedTars, expectedTars.getTaskList()); + } + + private void assertInvalidInputBehaviorForEditCommand(String inputCommand, + String expectedMessage) throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + List taskList = helper.generateTaskList(2); + + // set Tars state to 2 tasks + model.resetData(new Tars()); + for (Task p : taskList) { + model.addTask(p); + } + + assertCommandBehavior(inputCommand, expectedMessage, model.getTars(), + taskList); + } + + // @@author A0139924W + @Test + public void execute_undoAndRedo_editSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskToAdd = helper.meetAdam(); + Tars expectedTars = prepareExpectedTars(); + Task editedTask = + expectedTars.getUniqueTaskList().getInternalList().get(0); + + model.addTask(taskToAdd); + + String inputCommand = "edit 1 /n Meet Betty Green /dt 20/09/2016 1800 " + + "to 21/09/2016 1800 /p h /tr tag2 /ta tag3"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(EditCommand.MESSAGE_EDIT_TASK_SUCCESS, + expectedTars.getTaskList().get(0)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.replaceTask(editedTask, taskToAdd); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(EditCommand.MESSAGE_UNDO, taskToAdd)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.replaceTask(taskToAdd, editedTask); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(EditCommand.MESSAGE_REDO, taskToAdd)), + expectedTars, expectedTars.getTaskList()); + } + + private static Tars prepareExpectedTars() throws IllegalValueException { + Tars expectedTars = new Tars(); + Name editedName = new Name("Meet Betty Green"); + DateTime editedDateTime = + new DateTime("20/09/2016 1800", "21/09/2016 1800"); + Priority editedPriority = new Priority("h"); + Status editedStatus = new Status(false); + Tag tag1 = new Tag("tag1"); + Tag tag2 = new Tag("tag3"); + UniqueTagList editedTags = new UniqueTagList(tag1, tag2); + Task editedTask = new Task(editedName, editedDateTime, editedPriority, + editedStatus, editedTags); + expectedTars.addTask(editedTask); + expectedTars.getUniqueTagList().add(new Tag("tag2")); + + return expectedTars; + } +} diff --git a/src/test/java/tars/logic/ExitLogicCommandTest.java b/src/test/java/tars/logic/ExitLogicCommandTest.java new file mode 100644 index 000000000000..93ea871c1312 --- /dev/null +++ b/src/test/java/tars/logic/ExitLogicCommandTest.java @@ -0,0 +1,12 @@ +package tars.logic; + +import org.junit.Test; + +import tars.logic.commands.ExitCommand; + +public class ExitLogicCommandTest extends LogicCommandTest { + @Test + public void execute_exit() throws Exception { + assertCommandBehavior("exit", ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT); + } +} diff --git a/src/test/java/tars/logic/FindLogicCommandTest.java b/src/test/java/tars/logic/FindLogicCommandTest.java new file mode 100644 index 000000000000..625c83c9a29e --- /dev/null +++ b/src/test/java/tars/logic/FindLogicCommandTest.java @@ -0,0 +1,213 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.List; + +import org.junit.Test; + +import tars.logic.commands.Command; +import tars.logic.commands.FindCommand; +import tars.model.Tars; +import tars.model.task.Task; +import tars.model.task.TaskQuery; + +// @@author A0124333U +/** + * Logic command test for find + */ +public class FindLogicCommandTest extends LogicCommandTest { + + @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_quickSearchOnlyMatchesFullWordsInNames() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.generateTaskWithName("bla bla KEY bla"); + Task pTarget2 = helper.generateTaskWithName("bla KEY bla bceofeia"); + Task p1 = helper.generateTaskWithName("KE Y"); + Task p2 = helper.generateTaskWithName("KEYKEYKEY sduauo"); + + List fourTasks = + helper.generateTaskList(p1, pTarget1, p2, pTarget2); + Tars expectedTars = helper.generateTars(fourTasks); + List expectedList = helper.generateTaskList(pTarget1, pTarget2); + helper.addToModel(model, fourTasks); + + String searchKeywords = "\nQuick Search Keywords: [KEY]"; + + assertCommandBehavior("find KEY", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_quickSearchIsNotCaseSensitive() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task p1 = helper.generateTaskWithName("bla bla KEY bla"); + Task p2 = helper.generateTaskWithName("bla KEY bla bceofeia"); + Task p3 = helper.generateTaskWithName("key key"); + Task p4 = helper.generateTaskWithName("KEy sduauo"); + + List fourTasks = helper.generateTaskList(p3, p1, p4, p2); + Tars expectedTars = helper.generateTars(fourTasks); + List expectedList = fourTasks; + helper.addToModel(model, fourTasks); + + String searchKeywords = "\nQuick Search Keywords: [KEY]"; + + assertCommandBehavior("find KEY", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_quickSearchMatchesIfAllKeywordPresent() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task p1 = helper.generateTaskWithName("bla bla KEY bla"); + Task p2 = helper.generateTaskWithName("bla rAnDoM bla bceofeia"); + Task p3 = helper.generateTaskWithName("sduauo"); + Task pTarget1 = helper.generateTaskWithName("key key rAnDoM"); + + List fourTasks = helper.generateTaskList(p1, p2, p3, pTarget1); + Tars expectedTars = helper.generateTars(fourTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, fourTasks); + + String searchKeywords = "\nQuick Search Keywords: [key, rAnDoM]"; + + assertCommandBehavior("find key rAnDoM", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchMatchesIfAllKeywordPresent() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = "\nFilter Search Keywords: [Task Name: adam] " + + "[DateTime: 01/09/2016 1400 to 01/09/2016 1500] [Priority: medium] " + + "[Status: Undone] [Tags: tag1]"; + + assertCommandBehavior( + "find /n adam /dt 01/09/2016 1400 to 01/09/2016 1500 /p medium /ud /t tag1", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchWithoutDateTimeQuery() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = "\nFilter Search Keywords: [Task Name: adam] " + + "[Priority: medium] " + "[Status: Undone] [Tags: tag1]"; + + assertCommandBehavior("find /n adam /p medium /ud /t tag1", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchSingleDateTimeQuery() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = + "\nFilter Search Keywords: [DateTime: 01/09/2016 1400] "; + + assertCommandBehavior("find /dt 01/09/2016 1400", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchTaskNotFound() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(); + helper.addToModel(model, threeTasks); + + String searchKeywords = + "\nFilter Search Keywords: [DateTime: 01/09/2010 1400] "; + + assertCommandBehavior("find /dt 01/09/2010 1400", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } + + @Test + public void execute_find_filterSearchBothDoneAndUndoneSearched() + throws Exception { + + assertCommandBehavior("find /do /ud", + TaskQuery.MESSAGE_BOTH_STATUS_SEARCHED_ERROR); + } + + @Test + public void execute_find_filterSearchMultipleFlagsUsed() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task pTarget1 = helper.meetAdam(); + Task p1 = helper.generateTask(2); + Task p2 = helper.generateTask(3); + + List threeTasks = helper.generateTaskList(pTarget1, p1, p2); + Tars expectedTars = helper.generateTars(threeTasks); + List expectedList = helper.generateTaskList(pTarget1); + helper.addToModel(model, threeTasks); + + String searchKeywords = + "\nFilter Search Keywords: [Task Name: meet adam] " + + "[Priority: medium] " + + "[Status: Undone] [Tags: tag2 tag1]"; + + assertCommandBehavior("find /n meet adam /p medium /ud /t tag1 /t tag2", + Command.getMessageForTaskListShownSummary(expectedList.size()) + + searchKeywords, + expectedTars, expectedList); + } +} diff --git a/src/test/java/tars/logic/FreeLogicCommandTest.java b/src/test/java/tars/logic/FreeLogicCommandTest.java new file mode 100644 index 000000000000..9b2ce8d31645 --- /dev/null +++ b/src/test/java/tars/logic/FreeLogicCommandTest.java @@ -0,0 +1,92 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.List; + +import org.junit.Test; + +import tars.logic.commands.FreeCommand; +import tars.model.Tars; +import tars.model.task.DateTime; +import tars.model.task.Task; + +// @@author A0124333U +/** + * Logic command test for free + */ +public class FreeLogicCommandTest extends LogicCommandTest { + @Test + public void execute_free_incorrectArgsFormat_errorMessageShown() + throws Exception { + assertCommandBehavior("free ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + assertCommandBehavior("free invalidargs", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, FreeCommand.MESSAGE_USAGE)); + assertCommandBehavior("free 29/10/2016 to 30/10/2016", + FreeCommand.MESSAGE_DATE_RANGE_DETECTED); + } + + @Test + public void execute_free_noFreeTimeSlotResult() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.fillModelAndTarsForFreeCommand(model); + + Task task4 = helper.generateTaskWithNameAndDate("Task 4", + new DateTime("10/10/2016 1500", "12/10/2016 1400")); + List expectedShownTaskList = helper.generateTaskList(task4); + + assertCommandBehavior("free 11/10/2016", + String.format(FreeCommand.MESSAGE_NO_FREE_TIMESLOTS, + "Tuesday, 11/10/2016"), + expectedTars, expectedShownTaskList); + + // Case where the user types in a time should still be allowed to pass. Programme will + // extract the date + assertCommandBehavior("free 11/10/2016 0900", + String.format(FreeCommand.MESSAGE_NO_FREE_TIMESLOTS, + "Tuesday, 11/10/2016"), + expectedTars, expectedShownTaskList); + } + + @Test + public void execute_free_freeDayResult() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.fillModelAndTarsForFreeCommand(model); + + // Create expected empty list + List expectedShownTaskList = helper.generateTaskList(); + + assertCommandBehavior("free 01/11/2016", + String.format(FreeCommand.MESSAGE_FREE_DAY, + "Tuesday, 01/11/2016"), + expectedTars, expectedShownTaskList); + } + + @Test + public void execute_free_freeTimeSlotsFound() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.fillModelAndTarsForFreeCommand(model); + + + // Fill up expected shown task list + Task taskWithoutStartDate = helper.generateTaskWithNameAndDate( + "Task without startdate", new DateTime("", "29/10/2016 1500")); + Task task1 = helper.generateTaskWithNameAndDate("Task 1", + new DateTime("28/10/2016 2200", "29/10/2016 0100")); + Task task2 = helper.generateTaskWithNameAndDate("Task 2", + new DateTime("29/10/2016 1430", "29/10/2016 1800")); + List expectedShownTaskList = + helper.generateTaskList(taskWithoutStartDate, task1, task2); + + StringBuilder sb = new StringBuilder(); + + sb.append("Saturday, 29/10/2016").append(": \n") + .append("1. 0100hrs to 1400hrs (13 hr 0 min)\n") + .append("2. 1800hrs to 2359hrs (5 hr 59 min)"); + + assertCommandBehavior("free 29/10/2016", + String.format(FreeCommand.MESSAGE_SUCCESS, sb.toString()), + expectedTars, expectedShownTaskList); + } +} diff --git a/src/test/java/tars/logic/HelpLogicCommandTest.java b/src/test/java/tars/logic/HelpLogicCommandTest.java new file mode 100644 index 000000000000..ccdf2ff7cbc6 --- /dev/null +++ b/src/test/java/tars/logic/HelpLogicCommandTest.java @@ -0,0 +1,33 @@ +package tars.logic; + +import static org.junit.Assert.assertTrue; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import org.junit.Test; + +import tars.logic.commands.HelpCommand; + +// @@author A0139924W +/** + * Logic command test for help + */ +public class HelpLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_help_successful() throws Exception { + assertCommandBehavior("help", HelpCommand.SHOWING_HELP_MESSAGE); + assertTrue(helpShown); + } + + @Test + public void execute_help_invalidArgs() throws Exception { + assertCommandBehavior("help hello world", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_help_showAddUsageSuccessful() throws Exception { + assertCommandBehavior("help add", HelpCommand.SHOWING_HELP_MESSAGE); + assertTrue(helpShown); + } +} diff --git a/src/test/java/tars/logic/ListLogicCommandTest.java b/src/test/java/tars/logic/ListLogicCommandTest.java new file mode 100644 index 000000000000..243f619b9d0e --- /dev/null +++ b/src/test/java/tars/logic/ListLogicCommandTest.java @@ -0,0 +1,132 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.List; + +import org.junit.Test; + +import tars.logic.commands.ListCommand; +import tars.model.Tars; +import tars.model.task.DateTime; +import tars.model.task.Priority; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Task; + +// @@author A0140022H +/** + * Logic command test for list + */ +public class ListLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_list_invalidFlagsErrorMessageShown() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ListCommand.MESSAGE_USAGE); + assertIncorrectIndexFormatBehaviorForCommand("ls -", expectedMessage); + } + + @Test + public void execute_list_showsAllTasks() throws Exception { + // prepare expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Tars expectedTars = helper.generateTars(2); + List expectedList = expectedTars.getTaskList(); + + // prepare tars state + helper.addToModel(model, 2); + + assertCommandBehavior("ls", ListCommand.MESSAGE_SUCCESS, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByPriority() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setPriority(new Priority("l")); + task2.setPriority(new Priority("m")); + task3.setPriority(new Priority("h")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task3); + expectedTars.addTask(task2); + expectedTars.addTask(task1); + List listToSort = helper.generateTaskList(task3, task2, task1); + List expectedList = helper.generateTaskList(task1, task2, task3); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /p", + ListCommand.MESSAGE_SUCCESS_PRIORITY, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByPriorityDescending() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setPriority(new Priority("l")); + task2.setPriority(new Priority("m")); + task3.setPriority(new Priority("h")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task1); + expectedTars.addTask(task2); + expectedTars.addTask(task3); + List listToSort = helper.generateTaskList(task1, task2, task3); + List expectedList = helper.generateTaskList(task3, task2, task1); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /p dsc", + ListCommand.MESSAGE_SUCCESS_PRIORITY_DESCENDING, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByDatetime() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setDateTime(new DateTime("", "01/02/2016 1600")); + task2.setDateTime(new DateTime("", "02/02/2016 1600")); + task3.setDateTime(new DateTime("", "03/02/2016 1600")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task3); + expectedTars.addTask(task2); + expectedTars.addTask(task1); + List listToSort = helper.generateTaskList(task3, task2, task1); + List expectedList = helper.generateTaskList(task1, task2, task3); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /dt", + ListCommand.MESSAGE_SUCCESS_DATETIME, expectedTars, + expectedList); + } + + @Test + public void execute_list_showsAllTasksByDatetimeDescending() + throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task task1 = helper.generateTaskWithName("task1"); + Task task2 = helper.generateTaskWithName("task2"); + Task task3 = helper.generateTaskWithName("task3"); + task1.setDateTime(new DateTime("", "01/02/2016 1600")); + task2.setDateTime(new DateTime("", "02/02/2016 1600")); + task3.setDateTime(new DateTime("", "03/02/2016 1600")); + Tars expectedTars = new Tars(); + expectedTars.addTask(task1); + expectedTars.addTask(task2); + expectedTars.addTask(task3); + List listToSort = helper.generateTaskList(task1, task2, task3); + List expectedList = helper.generateTaskList(task3, task2, task1); + helper.addToModel(model, listToSort); + + assertCommandBehaviorForList("ls /dt dsc", + ListCommand.MESSAGE_SUCCESS_DATETIME_DESCENDING, expectedTars, + expectedList); + } +} diff --git a/src/test/java/tars/logic/LogicCommandTest.java b/src/test/java/tars/logic/LogicCommandTest.java new file mode 100644 index 000000000000..ae5500e5b870 --- /dev/null +++ b/src/test/java/tars/logic/LogicCommandTest.java @@ -0,0 +1,262 @@ +package tars.logic; + +import static org.junit.Assert.assertEquals; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX; +import static tars.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import com.google.common.eventbus.Subscribe; + +import tars.commons.core.Config; +import tars.commons.core.EventsCenter; +import tars.commons.events.model.TarsChangedEvent; +import tars.commons.events.ui.ShowHelpRequestEvent; +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.ConfigUtil; +import tars.logic.commands.ClearCommand; +import tars.logic.commands.CommandResult; +import tars.logic.commands.EditCommand; +import tars.logic.commands.HelpCommand; +import tars.logic.parser.Prefix; +import tars.model.Model; +import tars.model.ModelManager; +import tars.model.ReadOnlyTars; +import tars.model.Tars; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Task; +import tars.model.task.rsv.RsvTask; +import tars.storage.StorageManager; + +public class LogicCommandTest { + /** + * See https://github.com/junit-team/junit4/wiki/rules#temporaryfolder-rule + */ + @Rule + public TemporaryFolder saveFolder = new TemporaryFolder(); + + protected Model model; + protected Logic logic; + private Config originalConfig; + + private static final String configFilePath = "config.json"; + + protected static final Prefix namePrefix = new Prefix("/n"); + protected static final Prefix priorityPrefix = new Prefix("/p"); + protected static final Prefix dateTimePrefix = new Prefix("/dt"); + protected static final Prefix addTagPrefix = new Prefix("/ta"); + protected static final Prefix removeTagPrefix = new Prefix("/tr"); + + // These are for checking the correctness of the events raised + private ReadOnlyTars latestSavedTars; + protected boolean helpShown; + + @Subscribe + private void handleLocalModelChangedEvent(TarsChangedEvent tce) { + latestSavedTars = new Tars(tce.data); + } + + @Subscribe + private void handleShowHelpRequestEvent(ShowHelpRequestEvent she) { + helpShown = true; + } + + @Before + public void setUp() { + try { + originalConfig = ConfigUtil.readConfig(configFilePath).get(); + } catch (DataConversionException e) { + e.printStackTrace(); + } + model = new ModelManager(); + String tempTarsFile = saveFolder.getRoot().getPath() + "TempTars.xml"; + String tempPreferencesFile = + saveFolder.getRoot().getPath() + "TempPreferences.json"; + logic = new LogicManager(model, + new StorageManager(tempTarsFile, tempPreferencesFile)); + EventsCenter.getInstance().registerHandler(this); + + latestSavedTars = new Tars(model.getTars()); // last saved assumed to be up to date before. + helpShown = false; + } + + @After + public void tearDown() throws IOException { + undoChangeInTarsFilePath(); + EventsCenter.clearSubscribers(); + } + + /* + * A method to undo any changes to the Tars File Path during tests + */ + protected void undoChangeInTarsFilePath() throws IOException { + ConfigUtil.saveConfig(originalConfig, configFilePath); + } + + /** + * Executes the command and confirms that the result message is correct. Both the 'tars' and the + * 'last shown list' are expected to be empty. + * + * @see #assertCommandBehavior(String, String, ReadOnlyTars, List) + */ + protected void assertCommandBehavior(String inputCommand, + String expectedMessage) throws Exception { + assertCommandBehavior(inputCommand, expectedMessage, new Tars(), + 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 tars data are same as those in the {@code expectedTars}
+ * - the backing list shown by UI matches the {@code shownList}
+ * - {@code expectedTars} was saved to the storage file.
+ */ + protected void assertCommandBehavior(String inputCommand, + String expectedMessage, ReadOnlyTars expectedTars, + 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.getFilteredTaskList()); + + // Confirm the state of data (saved and in-memory) is as expected + assertEquals(expectedTars, model.getTars()); + assertEquals(expectedTars, latestSavedTars); + } + + // @@author A0140022H + protected void assertCommandBehaviorForList(String inputCommand, + String expectedMessage, ReadOnlyTars expectedTars, + 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.getFilteredTaskList()); + + // Confirm the state of data (saved and in-memory) is as expected + assertEquals(expectedTars, latestSavedTars); + } + // @@author + + protected void assertCommandBehaviorWithRsvTaskList(String inputCommand, + String expectedMessage, ReadOnlyTars expectedTars, + List expectedShownTaskList, + List expectedShownRsvTaskList) 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(expectedShownTaskList, model.getFilteredTaskList()); + assertEquals(expectedShownRsvTaskList, model.getFilteredRsvTaskList()); + + // Confirm the state of data (saved and in-memory) is as expected + assertEquals(expectedTars, model.getTars()); + assertEquals(expectedTars, latestSavedTars); + } + + /** + * Confirms the 'invalid argument index number behaviour' for the given command targeting a + * single task in the shown list, using visible index. + * + * @param commandWord to test assuming it targets a single task in the last shown list based on + * visible index. + */ + protected 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 task in the shown list, using visible index. + * + * @param commandWord to test assuming it targets a single task in the last shown list based on + * visible index. + */ + protected void assertIndexNotFoundBehaviorForCommand(String commandWord) + throws Exception { + String expectedMessage = MESSAGE_INVALID_TASK_DISPLAYED_INDEX; + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + List taskList = helper.generateTaskList(2); + + // set TARS state to 2 tasks + model.resetData(new Tars()); + for (Task p : taskList) { + model.addTask(p); + } + + if (EditCommand.COMMAND_WORD.equals(commandWord)) { // Only For Edit Command + assertCommandBehavior(commandWord + " 3 /n changeTaskName", + expectedMessage, model.getTars(), taskList); + } else { // For Select & Delete Commands + assertCommandBehavior(commandWord + " 3", expectedMessage, + model.getTars(), taskList); + } + } + + @Test + public void execute_invalid() throws Exception { + String invalidCommand = " "; + assertCommandBehavior(invalidCommand, String.format( + MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_unknownCommandWord() throws Exception { + String unknownCommand = "uicfhmowqewca"; + assertCommandBehavior(unknownCommand, MESSAGE_UNKNOWN_COMMAND); + } + + @Test + public void execute_clear() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + model.addTask(helper.generateTask(1)); + model.addTask(helper.generateTask(2)); + model.addTask(helper.generateTask(3)); + + assertCommandBehavior("clear", ClearCommand.MESSAGE_SUCCESS, new Tars(), + Collections.emptyList()); + } + + @Test + public void check_task_equals() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskA = helper.meetAdam(); + Task taskB = taskA; + + assertEquals(taskA, taskB); + assertEquals(taskA.hashCode(), taskB.hashCode()); + } + + @Test + public void check_name_equals() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task taskA = helper.meetAdam(); + Task taskB = taskA; + + assertEquals(taskA.getName(), taskB.getName()); + assertEquals(taskA.getName().hashCode(), taskB.getName().hashCode()); + } +} diff --git a/src/test/java/tars/logic/RedoLogicCommandTest.java b/src/test/java/tars/logic/RedoLogicCommandTest.java new file mode 100644 index 000000000000..4b28c485cb37 --- /dev/null +++ b/src/test/java/tars/logic/RedoLogicCommandTest.java @@ -0,0 +1,28 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import org.junit.Test; + +import tars.logic.commands.RedoCommand; + +// @@author A0139924W +/** + * Logic command test for redo + */ +public class RedoLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_redo_emptyCmdHistStack() throws Exception { + assertCommandBehavior(RedoCommand.COMMAND_WORD, + RedoCommand.MESSAGE_EMPTY_REDO_CMD_HIST); + } + + @Test + public void execute_redo_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RedoCommand.MESSAGE_USAGE); + assertCommandBehavior("redo EXTRA ARGUMENTS", expectedMessage); + assertCommandBehavior("redo 123", expectedMessage); + } +} diff --git a/src/test/java/tars/logic/RsvLogicCommandTest.java b/src/test/java/tars/logic/RsvLogicCommandTest.java new file mode 100644 index 000000000000..34b9e466bb1b --- /dev/null +++ b/src/test/java/tars/logic/RsvLogicCommandTest.java @@ -0,0 +1,278 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_CONFLICTING_TASKS_WARNING; +import static tars.commons.core.Messages.MESSAGE_DUPLICATE_TASK; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_INVALID_DATE; +import static tars.commons.core.Messages.MESSAGE_RSV_TASK_CANNOT_BE_FOUND; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import tars.logic.commands.RedoCommand; +import tars.logic.commands.RsvCommand; +import tars.logic.commands.UndoCommand; +import tars.model.Tars; +import tars.model.task.DateTime; +import tars.model.task.Task; +import tars.model.task.rsv.RsvTask; + +// @@author A0124333U +/** + * Logic command test for rsv + */ +public class RsvLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_rsvInvalidArgsFormatErrorMessageShown() + throws Exception { + + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE); + assertCommandBehavior("rsv ", expectedMessage); + } + + @Test + public void execute_rsvAddInvalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessageForNullDate = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_DATETIME_NOT_FOUND); + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE); + assertCommandBehavior("rsv Rsv Task Without Date", + expectedMessageForNullDate); + assertCommandBehavior("rsv Rsv Task with flags other than date -p h", + expectedMessageForNullDate); + assertCommandBehavior("rsv /dt tomorrow", expectedMessage); + assertCommandBehavior("rsv Rsv Task with invalid Date /dt invalidDate", + MESSAGE_INVALID_DATE); + } + + @Test + public void execute_rsvDelInvalidArgsFormatErrorMessageShown() + throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RsvCommand.MESSAGE_USAGE_DEL); + assertCommandBehavior("rsv invalidArgs /del 1", expectedMessage); + assertCommandBehavior("rsv /del invalidValue", expectedMessage); + } + + @Test + public void execute_rsv_del_successful() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + + // Create a reserved task + RsvTask rsvTask = + helper.generateReservedTaskWithOneDateTimeOnly("Test Task"); + + // Create empty taskList + List taskList = new ArrayList(); + + // Create empty end state rsvTaskList + List rsvTaskList = new ArrayList(); + + // Create empty end state Tars + Tars expectedTars = new Tars(); + + // Set Tars start state to 1 reserved task, and 0 tasks. + model.resetData(new Tars()); + model.addRsvTask(rsvTask); + + String expectedMessage = String.format(RsvCommand.MESSAGE_SUCCESS_DEL, + "1.\t" + rsvTask + "\n"); + assertCommandBehaviorWithRsvTaskList("rsv /del 1", expectedMessage, + expectedTars, taskList, rsvTaskList); + } + + @Test + public void execute_rsv_conflictingTaskShowWarning() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask rsvTaskA = + helper.generateReservedTaskWithNameAndDate("Rsv Task A", + new DateTime("14/10/2016 0900", "16/10/2016 0900")); + RsvTask taskToRsv = + helper.generateReservedTaskWithNameAndDate("Task To Rsv", + new DateTime("13/10/2016 1000", "15/10/2016 1000")); + + // Create empty taskList + List taskList = new ArrayList(); + + List rsvTaskList = new ArrayList(); + rsvTaskList.add(rsvTaskA); + rsvTaskList.add(taskToRsv); + + Tars expectedTars = new Tars(); + String expectedMessage = + String.format(RsvCommand.MESSAGE_SUCCESS, taskToRsv.toString()) + + "\n" + MESSAGE_CONFLICTING_TASKS_WARNING + + "\nConflicts for " + + taskToRsv.getDateTimeList().get(0).toString() + ": " + + "\nRsvTask 1: " + rsvTaskA.toString(); + + expectedTars.addRsvTask(rsvTaskA); + expectedTars.addRsvTask(taskToRsv); + + model.resetData(new Tars()); + model.addRsvTask(rsvTaskA); + + assertCommandBehaviorWithRsvTaskList( + "rsv Task To Rsv /dt 13/10/2016 1000 to 15/10/2016 1000", + expectedMessage, expectedTars, taskList, rsvTaskList); + + } + + //@@author A0139924W + @Test + public void execute_undoAndRedo_rsvSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addRsvTask(taskToRsv); + + String inputCommand = + "rsv Reserved Task /dt 05/09/2016 1400 to 06/09/2016 2200"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS, taskToRsv), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_UNDO_DELETE, + taskToRsv)), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_REDO_ADD, taskToRsv)), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_rsvUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + expectedTars.addRsvTask(taskToRsv); + + String inputCommand = + "rsv Reserved Task /dt 05/09/2016 1400 to 06/09/2016 2200"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS, taskToRsv), + expectedTars, expectedTars.getTaskList()); + + model.deleteRsvTask(taskToRsv); + expectedTars.removeRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_RSV_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + + model.addRsvTask(taskToRsv); + expectedTars.addRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_rsvDelSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "rsv /del 1"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS_DEL, + "1.\t" + taskToRsv + "\n"), + expectedTars, expectedTars.getTaskList()); + + expectedTars.addRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_UNDO_ADD, + "1.\t" + taskToRsv + "\n")), + expectedTars, expectedTars.getTaskList()); + + expectedTars.removeRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, + String.format(RsvCommand.MESSAGE_REDO_DELETE, + "1.\t" + taskToRsv + "\n")), + expectedTars, expectedTars.getTaskList()); + + } + + @Test + public void execute_undoAndRedo_rsvDelUnsuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + RsvTask taskToRsv = + helper.generateReservedTaskWithOneDateTimeOnly("Reserved Task"); + Tars expectedTars = new Tars(); + + // setup model + model.addRsvTask(taskToRsv); + + String inputCommand = "rsv /del 1"; + + // execute command and verify result + assertCommandBehavior(inputCommand, + String.format(RsvCommand.MESSAGE_SUCCESS_DEL, + "1.\t" + taskToRsv + "\n"), + expectedTars, expectedTars.getTaskList()); + + model.addRsvTask(taskToRsv); + expectedTars.addRsvTask(taskToRsv); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_UNSUCCESS, + MESSAGE_DUPLICATE_TASK), + expectedTars, expectedTars.getTaskList()); + + model.deleteRsvTask(taskToRsv); + expectedTars.removeRsvTask(taskToRsv); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_UNSUCCESS, + MESSAGE_RSV_TASK_CANNOT_BE_FOUND), + expectedTars, expectedTars.getTaskList()); + + } +} diff --git a/src/test/java/tars/logic/TagLogicCommandTest.java b/src/test/java/tars/logic/TagLogicCommandTest.java new file mode 100644 index 000000000000..142ef2938d9b --- /dev/null +++ b/src/test/java/tars/logic/TagLogicCommandTest.java @@ -0,0 +1,309 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_DUPLICATE_TAG; +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tars.commons.core.Messages.MESSAGE_INVALID_TAG_DISPLAYED_INDEX; + +import java.util.ArrayList; + +import org.junit.Test; + +import tars.logic.commands.RedoCommand; +import tars.logic.commands.TagCommand; +import tars.logic.commands.UndoCommand; +import tars.model.Tars; +import tars.model.tag.ReadOnlyTag; +import tars.model.tag.Tag; +import tars.model.task.ReadOnlyTask; +import tars.model.task.Task; +import tars.ui.formatter.Formatter; + +// @@author A0139924W +/** + * Logic command test for tag + */ +public class TagLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_tag_invalidPrefix() throws Exception { + assertCommandBehavior("tag /gg", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tag_invalidFormat() throws Exception { + assertCommandBehavior("tag ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag RANDOM_TEXT", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tag_invalidIndex() throws Exception { + // EP: negative number + assertCommandBehavior("tag /e -1 VALIDTASKNAME", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /del -1", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + + // EP: zero + assertCommandBehavior("tag /e 0 VALIDTASKNAME", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + assertCommandBehavior("tag /del 0", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + + // EP: signed number + assertCommandBehavior("tag /e +1 VALIDTASKNAME", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /e -2 VALIDTASKNAME", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /del +1", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + assertCommandBehavior("tag /del -1", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX); + + // EP: invalid number + assertCommandBehavior("tag /del aaa", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag /del bbb", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tag_emptyParameters() throws Exception { + assertCommandBehavior("tag", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -e", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -e ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -del", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + assertCommandBehavior("tag -del ", String.format( + MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE)); + } + + @Test + public void execute_tagList_emptyListSuccessful() throws Exception { + // execute command and verify result + assertCommandBehavior("tag /ls", + new Formatter().formatTags(model.getUniqueTagList())); + } + + @Test + public void execute_tagList_filledListSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /ls", + new Formatter().formatTags(model.getUniqueTagList()), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_successful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeRenamed = + expectedTars.getUniqueTagList().getInternalList().get(0); + Tag newTag = new Tag("tag3"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + // execute command and verify result + assertCommandBehavior("tag /e 1 tag3", + String.format(String.format( + TagCommand.MESSAGE_RENAME_TAG_SUCCESS, "tag1", "tag3")), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_duplicate() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /e 1 tag2", MESSAGE_DUPLICATE_TAG, + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_invalidIndex() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /e 3 VALIDTAGNAME", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX, expectedTars, + expectedTars.getTaskList()); + + assertCommandBehavior("tag /e 4 VALIDTAGNAME", + MESSAGE_INVALID_TAG_DISPLAYED_INDEX, expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_tagRename_invalidTagName() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /e 1 INVALID_TAG_NAME", + Tag.MESSAGE_TAG_CONSTRAINTS, expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_tagDel_successful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeDeleted = + expectedTars.getUniqueTagList().getInternalList().get(0); + + expectedTars.getUniqueTagList().remove(new Tag(toBeDeleted)); + expectedTars.removeTagFromAllTasks(toBeDeleted); + + // execute command and verify result + assertCommandBehavior("tag /del 1", String + .format(TagCommand.MESSAGE_DELETE_TAG_SUCCESS, toBeDeleted), + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_tagDel_invalidIndex() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + // execute command and verify result + assertCommandBehavior("tag /del 3", MESSAGE_INVALID_TAG_DISPLAYED_INDEX, + expectedTars, expectedTars.getTaskList()); + + assertCommandBehavior("tag /del 4", MESSAGE_INVALID_TAG_DISPLAYED_INDEX, + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_tagEditSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeRenamed = + expectedTars.getUniqueTagList().getInternalList().get(0); + Tag newTag = new Tag("tag3"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + assertCommandBehavior("tag /e 1 tag3", + String.format(String.format( + TagCommand.MESSAGE_RENAME_TAG_SUCCESS, "tag1", "tag3")), + expectedTars, expectedTars.getTaskList()); + + toBeRenamed = expectedTars.getUniqueTagList().getInternalList().get(0); + newTag = new Tag("tag1"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + toBeRenamed = expectedTars.getUniqueTagList().getInternalList().get(0); + newTag = new Tag("tag3"); + + expectedTars.getUniqueTagList().update(toBeRenamed, newTag); + expectedTars.renameTasksWithNewTag(toBeRenamed, newTag); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_undoAndRedo_tagDelSuccessful() throws Exception { + // setup expectations + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + Task toBeAdded = helper.meetAdam(); + Tars expectedTars = new Tars(); + expectedTars.addTask(toBeAdded); + + model.addTask(toBeAdded); + + ReadOnlyTag toBeDeleted = + expectedTars.getUniqueTagList().getInternalList().get(0); + + expectedTars.getUniqueTagList().remove(new Tag(toBeDeleted)); + ArrayList editedTaskList = + expectedTars.removeTagFromAllTasks(toBeDeleted); + + // execute command and verify result + assertCommandBehavior("tag /del 1", String + .format(TagCommand.MESSAGE_DELETE_TAG_SUCCESS, toBeDeleted), + expectedTars, expectedTars.getTaskList()); + + expectedTars.getUniqueTagList().add(new Tag(toBeDeleted)); + expectedTars.addTagToAllTasks(toBeDeleted, editedTaskList); + + // execute undo and verify result + assertCommandBehavior(UndoCommand.COMMAND_WORD, + String.format(UndoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + + expectedTars.getUniqueTagList().remove(new Tag(toBeDeleted)); + expectedTars.removeTagFromAllTasks(toBeDeleted); + + // execute redo and verify result + assertCommandBehavior(RedoCommand.COMMAND_WORD, + String.format(RedoCommand.MESSAGE_SUCCESS, ""), expectedTars, + expectedTars.getTaskList()); + } +} diff --git a/src/test/java/tars/logic/TypicalTestDataHelper.java b/src/test/java/tars/logic/TypicalTestDataHelper.java new file mode 100644 index 000000000000..7b4292e1e7b1 --- /dev/null +++ b/src/test/java/tars/logic/TypicalTestDataHelper.java @@ -0,0 +1,236 @@ +package tars.logic; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import tars.commons.util.StringUtil; +import tars.model.Model; +import tars.model.Tars; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.model.task.DateTime; +import tars.model.task.Name; +import tars.model.task.Priority; +import tars.model.task.Status; +import tars.model.task.Task; +import tars.model.task.rsv.RsvTask; + +/** + * Test data helper with typical test data + */ +public class TypicalTestDataHelper { + + protected Task meetAdam() throws Exception { + Name name = new Name("Meet Adam Brown"); + DateTime dateTime = new DateTime("01/09/2016 1400", "01/09/2016 1500"); + Priority priority = new Priority("m"); + Status status = new Status(false); + Tag tag1 = new Tag("tag1"); + Tag tag2 = new Tag("tag2"); + UniqueTagList tags = new UniqueTagList(tag1, tag2); + return new Task(name, dateTime, priority, status, tags); + } + + protected Task floatingTask() throws Exception { + Name name = new Name("Do homework"); + DateTime dateTime = new DateTime(StringUtil.EMPTY_STRING, StringUtil.EMPTY_STRING); + Priority priority = new Priority(StringUtil.EMPTY_STRING); + Status status = new Status(false); + UniqueTagList tags = new UniqueTagList(); + return new Task(name, dateTime, priority, status, tags); + } + + /** + * Generates a valid task using the given seed. Running this function with the same parameter + * values guarantees the returned task will have the same state. Each unique seed will generate + * a unique Task object. + * + * @param seed used to generate the task data field values + */ + protected Task generateTask(int seed) throws Exception { + int seed2 = (seed + 1) % 31 + 1; // Generate 2nd seed for DateTime value + return new Task(new Name("Task " + seed), + new DateTime(seed + "/01/2016 1400", seed2 + "/01/2016 2200"), + new Priority("h"), new Status(false), + new UniqueTagList(new Tag("tag" + Math.abs(seed)), + new Tag("tag" + Math.abs(seed + 1)))); + } + + /** + * Generates the correct add command based on the task given + */ + protected String generateAddCommand(Task p) { + StringBuffer cmd = new StringBuffer(); + cmd.append("add ").append(p.getName().toString()); + + if (p.getDateTime().toString().length() > 0) { + cmd.append(" /dt ").append(p.getDateTime().toString()); + } + + if (p.getPriority().toString().length() > 0) { + cmd.append(" /p ").append(p.getPriority().toString()); + } + + UniqueTagList tags = p.getTags(); + for (Tag t : tags) { + cmd.append(" /t ").append(t.tagName); + } + + return cmd.toString(); + } + + /** + * Generates an Tars with auto-generated undone tasks. + */ + protected Tars generateTars(int numGenerated) throws Exception { + Tars tars = new Tars(); + addToTars(tars, numGenerated); + return tars; + } + + /** + * Generates an Tars based on the list of Tasks given. + */ + protected Tars generateTars(List tasks) throws Exception { + Tars tars = new Tars(); + addToTars(tars, tasks); + return tars; + } + + /** + * Adds auto-generated Task objects to the given Tars + * + * @param tars The Tars to which the Tasks will be added + */ + protected void addToTars(Tars tars, int numGenerated) throws Exception { + addToTars(tars, generateTaskList(numGenerated)); + } + + /** + * Adds the given list of Tasks to the given Tars + */ + protected void addToTars(Tars tars, List tasksToAdd) throws Exception { + for (Task p : tasksToAdd) { + tars.addTask(p); + } + } + + /** + * Adds auto-generated Task objects to the given model + * + * @param model The model to which the Tasks will be added + */ + protected void addToModel(Model model, int numGenerated) throws Exception { + addToModel(model, generateTaskList(numGenerated)); + } + + /** + * Adds the given list of Tasks to the given model + */ + protected void addToModel(Model model, List tasksToAdd) throws Exception { + for (Task p : tasksToAdd) { + model.addTask(p); + } + } + + /** + * Generates a list of Tasks based on the flags. + */ + protected List generateTaskList(int numGenerated) throws Exception { + List tasks = new ArrayList<>(); + for (int i = 1; i <= numGenerated; i++) { + tasks.add(generateTask(i)); + } + return tasks; + } + + protected List generateTaskList(Task... tasks) { + return Arrays.asList(tasks); + } + + /** + * Generates a Task object with given name. Other fields will have some + * dummy values. + */ + protected Task generateTaskWithName(String name) throws Exception { + return new Task(new Name(name), new DateTime("05/09/2016 1400", "06/09/2016 2200"), new Priority("h"), + new Status(false), new UniqueTagList(new Tag("tag"))); + } + + /** + * Generates a Task object with given name. Other fields will have some + * dummy values. + */ + protected Task generateTaskWithEndDateOnly(String name) throws Exception { + return new Task(new Name(name), new DateTime(null, "06/09/2016 2200"), new Priority("h"), new Status(false), + new UniqueTagList(new Tag("tag"))); + } + + /** + * Generates a Task object with given name and date time + * + * @@author A0124333U + */ + protected Task generateTaskWithNameAndDate(String name, DateTime dateTime) throws Exception { + assert (dateTime != null && name != null); + return new Task(new Name(name), dateTime, new Priority("h"), new Status(false), + new UniqueTagList(new Tag("tag"))); + } + + /** + * Generates a RsvTask object with given name and datetime(s) + */ + protected RsvTask generateReservedTaskWithNameAndDate(String name, DateTime... dateTimes) throws Exception { + ArrayList dateTimeList = new ArrayList(); + for (DateTime dt : dateTimes) { + dateTimeList.add(dt); + } + return new RsvTask(new Name(name), dateTimeList); + } + + /** + * Generates a RsvTask object with given name and a dummy dateTime + */ + protected RsvTask generateReservedTaskWithOneDateTimeOnly(String name) throws Exception { + ArrayList dateTimeList = new ArrayList(); + dateTimeList.add(new DateTime("05/09/2016 1400", "06/09/2016 2200")); + return new RsvTask(new Name(name), dateTimeList); + } + + protected Tars fillModelAndTarsForFreeCommand(Model model) throws Exception { + RsvTask rsvTask1 = generateReservedTaskWithNameAndDate("rsvTask1", + new DateTime("29/10/2016 1400", "29/10/2016 1500"), + new DateTime("30/10/2016 1400", "30/10/2016 1500")); + RsvTask rsvTask2 = generateReservedTaskWithNameAndDate("rsvTask2", + new DateTime("28/10/2016 0900", "28/10/2016 1400")); + Task floatingTask = generateTaskWithNameAndDate("Floating Task", new DateTime("", "")); + Task taskWithoutStartDate = generateTaskWithNameAndDate("Task without startdate", + new DateTime("", "29/10/2016 1500")); + Task task1 = generateTaskWithNameAndDate("Task 1", new DateTime("28/10/2016 2200", "29/10/2016 0100")); + Task task2 = generateTaskWithNameAndDate("Task 2", new DateTime("29/10/2016 1430", "29/10/2016 1800")); + Task task3 = generateTaskWithNameAndDate("Task 3", new DateTime("01/10/2016 1400", "01/10/2016 1500")); + Task task4 = generateTaskWithNameAndDate("Task 4", new DateTime("10/10/2016 1500", "12/10/2016 1400")); + + Tars tars = new Tars(); + tars.addRsvTask(rsvTask1); + tars.addRsvTask(rsvTask2); + tars.addTask(floatingTask); + tars.addTask(taskWithoutStartDate); + tars.addTask(task1); + tars.addTask(task2); + tars.addTask(task3); + tars.addTask(task4); + + model.addRsvTask(rsvTask1); + model.addRsvTask(rsvTask2); + model.addTask(floatingTask); + model.addTask(taskWithoutStartDate); + model.addTask(task1); + model.addTask(task2); + model.addTask(task3); + model.addTask(task4); + + return tars; + } +} diff --git a/src/test/java/tars/logic/UdLogicCommandTest.java b/src/test/java/tars/logic/UdLogicCommandTest.java new file mode 100644 index 000000000000..d05a65264c3c --- /dev/null +++ b/src/test/java/tars/logic/UdLogicCommandTest.java @@ -0,0 +1,88 @@ +package tars.logic; + +import java.util.List; + +import org.junit.Test; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.model.Tars; +import tars.model.task.Status; +import tars.model.task.Task; + +// @@author A0121533W +/** + * Logic command test for ud + */ +public class UdLogicCommandTest extends LogicCommandTest { + + private static final int NUM_TASKS_IN_LIST = 2; + private static final int NUM_TASKS_IN_RANGE = 5; + + @Test + public void execute_mark_allTaskAsUndone() throws Exception { + + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, true); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars); + + assertCommandBehavior("ud 1 2", + "Task: 1, 2 marked undone successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + @Test + public void execute_mark_alreadyUndone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_LIST, helper, false); + + Tars expectedTars = new Tars(); + generateExpectedTars(NUM_TASKS_IN_LIST, helper, expectedTars); + + assertCommandBehavior("ud 1 2", "Task: 1, 2 already marked undone.\n", + expectedTars, expectedTars.getTaskList()); + } + + @Test + public void execute_mark_rangeUndone() throws Exception { + TypicalTestDataHelper helper = new TypicalTestDataHelper(); + generateTestTars(NUM_TASKS_IN_RANGE, helper, true); + + Tars expectedTars = new Tars(); + + generateExpectedTars(NUM_TASKS_IN_RANGE, helper, expectedTars); + + assertCommandBehavior("ud 1..5", + "Task: 1, 2, 3, 4, 5 marked undone successfully.\n", expectedTars, + expectedTars.getTaskList()); + } + + private void generateTestTars(int numTasks, TypicalTestDataHelper helper, boolean status) + throws Exception { + Status s = new Status(status); + + Task[] taskArray = new Task[numTasks]; + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + taskI.setStatus(s); + taskArray[i-1] = taskI; + } + + List taskList = helper.generateTaskList(taskArray); + + helper.addToModel(model, taskList); + } + + private void generateExpectedTars(int numTasks, TypicalTestDataHelper helper, + Tars expectedTars) throws Exception, DuplicateTaskException { + + for (int i = 1; i < numTasks + 1; i++) { + String name = "task " + String.valueOf(i); + Task taskI = helper.generateTaskWithName(name); + expectedTars.addTask(taskI); + } + } +} diff --git a/src/test/java/tars/logic/UndoLogicCommandTest.java b/src/test/java/tars/logic/UndoLogicCommandTest.java new file mode 100644 index 000000000000..969d0b66b260 --- /dev/null +++ b/src/test/java/tars/logic/UndoLogicCommandTest.java @@ -0,0 +1,29 @@ +package tars.logic; + +import static tars.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import org.junit.Test; + +import tars.logic.commands.UndoCommand; + +// @@author A0139924W +/** + * Logic command test for undo + */ +public class UndoLogicCommandTest extends LogicCommandTest { + + @Test + public void execute_undo_emptyCmdHistStack() throws Exception { + assertCommandBehavior(UndoCommand.COMMAND_WORD, + UndoCommand.MESSAGE_EMPTY_UNDO_CMD_HIST); + } + + @Test + public void execute_undo_invalidArgsFormat() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UndoCommand.MESSAGE_USAGE); + assertCommandBehavior("undo EXTRA ARGUMENTS", expectedMessage); + assertCommandBehavior("undo 123", expectedMessage); + } + +} diff --git a/src/test/java/tars/logic/parser/ArgumentTokenizerTest.java b/src/test/java/tars/logic/parser/ArgumentTokenizerTest.java new file mode 100644 index 000000000000..8caa95e84ae4 --- /dev/null +++ b/src/test/java/tars/logic/parser/ArgumentTokenizerTest.java @@ -0,0 +1,134 @@ +package tars.logic.parser; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.concurrent.atomic.AtomicInteger; + +// @@author A0139924W +/** + * Argument tokenizer test. + * + * Credit: Test adapted from nus-cs2103-AY1617S1/addressbook-level4 + */ +public class ArgumentTokenizerTest { + private static final Prefix unknownPrefix = new Prefix("/uuuuuu"); + private static final Prefix tagPrefix = new Prefix("/t"); + private static final Prefix dateTimePrefix = new Prefix("/dt"); + private static final Prefix namePrefix = new Prefix("/n"); + + @Test + public void accessors_notTokenizedYet() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix); + + assertPreambleAbsent(tokenizer); + assertArgumentAbsent(tokenizer, tagPrefix); + } + + @Test + public void tokenize_emptyArgsString_noValues() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix); + String argsString = " "; + tokenizer.tokenize(argsString); + + assertPreambleAbsent(tokenizer); + assertArgumentAbsent(tokenizer, tagPrefix); + } + + @Test + public void tokenize_noPrefixes_allTakenAsPreamble() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(); + String argsString = " some random string /t tag with leading and trailing spaces "; + tokenizer.tokenize(argsString); + + // Same string expected as preamble, but leading/trailing spaces should be trimmed + assertPreamblePresent(tokenizer, argsString.trim()); + + } + + @Test + public void tokenize_oneArgument() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix); + + // Preamble present + tokenizer.tokenize(" Some preamble string /t Argument value "); + assertPreamblePresent(tokenizer, "Some preamble string"); + assertArgumentPresent(tokenizer, tagPrefix, "Argument value"); + + // No preamble + tokenizer.tokenize(" /t Argument value "); + assertPreambleAbsent(tokenizer); + assertArgumentPresent(tokenizer, tagPrefix, "Argument value"); + + } + + @Test + public void tokenize_multipleArguments() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix, dateTimePrefix, namePrefix); + + // Only two arguments are present + tokenizer.tokenize("SomePreambleString /t dashT-Value /n slashP value"); + assertPreamblePresent(tokenizer, "SomePreambleString"); + assertArgumentPresent(tokenizer, namePrefix, "slashP value"); + assertArgumentPresent(tokenizer, tagPrefix, "dashT-Value"); + assertArgumentAbsent(tokenizer, dateTimePrefix); + + /* Also covers: Reusing of the tokenizer multiple times */ + + // Reuse tokenizer on an empty string to ensure state is correctly reset + // (i.e. no stale values from the previous tokenizing remain in the state) + tokenizer.tokenize(""); + assertPreambleAbsent(tokenizer); + assertArgumentAbsent(tokenizer, tagPrefix); + + /** Also covers: testing for prefixes not specified as a prefix **/ + + // Prefixes not previously given to the tokenizer should not return any values + String stringWithUnknownPrefix = unknownPrefix.value + "some value"; + tokenizer.tokenize(stringWithUnknownPrefix); + assertArgumentAbsent(tokenizer, unknownPrefix); + assertPreamblePresent(tokenizer, stringWithUnknownPrefix); // Unknown prefix is taken as + // part of preamble + } + + @Test + public void tokenize_multipleArgumentsWithRepeats() { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(tagPrefix, namePrefix); + + tokenizer.tokenize("SomePreambleString /n /t dashT-Value /t another"); + assertPreamblePresent(tokenizer, "SomePreambleString"); + assertArgumentPresent(tokenizer, tagPrefix, "dashT-Value", "another"); + assertArgumentPresent(tokenizer, namePrefix, "/n"); + } + + private void assertPreamblePresent(ArgumentTokenizer argsTokenizer, String expectedPreamble) { + assertEquals(expectedPreamble, argsTokenizer.getPreamble().get()); + } + + private void assertPreambleAbsent(ArgumentTokenizer argsTokenizer) { + assertFalse(argsTokenizer.getPreamble().isPresent()); + } + + private void assertArgumentAbsent(ArgumentTokenizer argsTokenizer, Prefix prefix) { + assertFalse(argsTokenizer.getValue(prefix).isPresent()); + } + + private void assertArgumentPresent(ArgumentTokenizer argsTokenizer, + Prefix prefix, String... expectedValues) { + + // Verify the first value is returned + assertEquals(expectedValues[0], argsTokenizer.getValue(prefix).get()); + + // Verify the number of values returned is as expected + assertEquals(expectedValues.length, + argsTokenizer.getMultipleValues(prefix).get().size()); + + // Verify all values returned are as expected and in order + final AtomicInteger count = new AtomicInteger(); + argsTokenizer.getMultipleValues(prefix).get().forEach((v) -> { + assertEquals(expectedValues[count.getAndIncrement()], v); + }); + + } +} diff --git a/src/test/java/tars/logic/parser/PrefixTest.java b/src/test/java/tars/logic/parser/PrefixTest.java new file mode 100644 index 000000000000..c554149a0d22 --- /dev/null +++ b/src/test/java/tars/logic/parser/PrefixTest.java @@ -0,0 +1,24 @@ +package tars.logic.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +// @@author A0139924W +/** + * Prefix test + */ +public class PrefixTest { + + @Test + public void equalsMethod() { + Prefix tagPrefix = new Prefix("/tag"); + + assertEquals(tagPrefix, tagPrefix); + assertEquals(tagPrefix, new Prefix("/tag")); + + assertNotEquals(tagPrefix, "/tag"); + assertNotEquals(tagPrefix, new Prefix("/nTag")); + } +} diff --git a/src/test/java/seedu/address/model/UnmodifiableObservableListTest.java b/src/test/java/tars/model/UnmodifiableObservableListTest.java similarity index 89% rename from src/test/java/seedu/address/model/UnmodifiableObservableListTest.java rename to src/test/java/tars/model/UnmodifiableObservableListTest.java index 0334d7e42073..68479c176a89 100644 --- a/src/test/java/seedu/address/model/UnmodifiableObservableListTest.java +++ b/src/test/java/tars/model/UnmodifiableObservableListTest.java @@ -1,27 +1,28 @@ -package seedu.address.model; +package tars.model; import javafx.collections.FXCollections; +import tars.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 tars.testutil.TestUtil.assertThrows; public class UnmodifiableObservableListTest { @Rule public ExpectedException thrown = ExpectedException.none(); - List backing; - UnmodifiableObservableList list; + private List backing; + private UnmodifiableObservableList list; @Before - public void setup() { + public void setUp() { backing = new ArrayList<>(); backing.add(10); list = new UnmodifiableObservableList<>(FXCollections.observableList(backing)); diff --git a/src/test/java/seedu/address/storage/JsonUserPrefStorageTest.java b/src/test/java/tars/storage/JsonUserPrefStorageTest.java similarity index 95% rename from src/test/java/seedu/address/storage/JsonUserPrefStorageTest.java rename to src/test/java/tars/storage/JsonUserPrefStorageTest.java index de6d2348e0b7..681b22a65036 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefStorageTest.java +++ b/src/test/java/tars/storage/JsonUserPrefStorageTest.java @@ -1,13 +1,14 @@ -package seedu.address.storage; - +package tars.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 tars.commons.exceptions.DataConversionException; +import tars.commons.util.FileUtil; +import tars.model.UserPrefs; +import tars.storage.JsonUserPrefStorage; import java.io.File; import java.io.IOException; diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/tars/storage/StorageManagerTest.java similarity index 63% rename from src/test/java/seedu/address/storage/StorageManagerTest.java rename to src/test/java/tars/storage/StorageManagerTest.java index 0e919872665b..00c72c1c6693 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/tars/storage/StorageManagerTest.java @@ -1,14 +1,15 @@ -package seedu.address.storage; - +package tars.storage; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.testutil.TypicalTestPersons; + +import tars.model.Tars; +import tars.model.ReadOnlyTars; +import tars.model.UserPrefs; +import tars.storage.StorageManager; +import tars.testutil.TypicalTestTasks; import static org.junit.Assert.assertEquals; @@ -21,7 +22,7 @@ public class StorageManagerTest { @Before - public void setup() { + public void setUp() { storageManager = new StorageManager(getTempFilePath("ab"), getTempFilePath("prefs")); } @@ -47,12 +48,12 @@ 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)); - //More extensive testing of AddressBook saving/reading is done in XmlAddressBookStorageTest + public void tarsReadSave() throws Exception { + Tars original = new TypicalTestTasks().getTypicalTars(); + storageManager.saveTars(original); + ReadOnlyTars retrieved = storageManager.readTars().get(); + assertEquals(original, new Tars(retrieved)); + //More extensive testing of Tars saving/reading is done in XmlTarsStorageTest } diff --git a/src/test/java/tars/storage/XmlTarsStorageTest.java b/src/test/java/tars/storage/XmlTarsStorageTest.java new file mode 100644 index 000000000000..eec97298580e --- /dev/null +++ b/src/test/java/tars/storage/XmlTarsStorageTest.java @@ -0,0 +1,100 @@ +package tars.storage; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +import tars.commons.exceptions.DataConversionException; +import tars.commons.util.FileUtil; +import tars.model.Tars; +import tars.model.ReadOnlyTars; +import tars.model.task.Task; +import tars.storage.XmlTarsStorage; +import tars.testutil.TypicalTestTasks; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class XmlTarsStorageTest { + private static String TEST_DATA_FOLDER = FileUtil.getPath("./src/test/data/XmlTarsStorageTest/"); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Test + public void readTars_nullFilePath_assertionFailure() throws Exception { + thrown.expect(AssertionError.class); + readTars(null); + } + + private java.util.Optional readTars(String filePath) throws Exception { + return new XmlTarsStorage(filePath).readTars(addToTestDataPathIfNotNull(filePath)); + } + + private String addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { + return prefsFileInTestDataFolder != null + ? TEST_DATA_FOLDER + prefsFileInTestDataFolder + : null; + } + + @Test + public void read_missingFile_emptyResult() throws Exception { + assertFalse(readTars("NonExistentFile.xml").isPresent()); + } + + @Test + public void read_notXmlFormat_exceptionThrown() throws Exception { + + thrown.expect(DataConversionException.class); + readTars("NotXmlFormatTars.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 readAndSaveTars_allInOrder_success() throws Exception { + String filePath = testFolder.getRoot().getPath() + "TempTars.xml"; + TypicalTestTasks td = new TypicalTestTasks(); + Tars original = td.getTypicalTars(); + XmlTarsStorage xmlTarsStorage = new XmlTarsStorage(filePath); + + //Save in new file and read back + xmlTarsStorage.saveTars(original, filePath); + ReadOnlyTars readBack = xmlTarsStorage.readTars(filePath).get(); + assertEquals(original, new Tars(readBack)); + + //Modify data, overwrite exiting file, and read back + original.addTask(new Task(TypicalTestTasks.taskH)); + original.removeTask(new Task(TypicalTestTasks.taskA)); + xmlTarsStorage.saveTars(original, filePath); + readBack = xmlTarsStorage.readTars(filePath).get(); + assertEquals(original, new Tars(readBack)); + + } + + @Test + public void saveTars_nullTars_assertionFailure() throws IOException { + thrown.expect(AssertionError.class); + saveTars(null, "SomeFile.xml"); + } + + private void saveTars(ReadOnlyTars tars, String filePath) throws IOException { + new XmlTarsStorage(filePath).saveTars(tars, addToTestDataPathIfNotNull(filePath)); + } + + @Test + public void saveTars_nullFilePath_assertionFailure() throws IOException { + thrown.expect(AssertionError.class); + saveTars(new Tars(), null); + } + + +} diff --git a/src/test/java/tars/testutil/RsvTaskBuilder.java b/src/test/java/tars/testutil/RsvTaskBuilder.java new file mode 100644 index 000000000000..dfe08f46d5ed --- /dev/null +++ b/src/test/java/tars/testutil/RsvTaskBuilder.java @@ -0,0 +1,34 @@ +package tars.testutil; + +import tars.commons.exceptions.IllegalValueException; +import tars.model.task.DateTime; +import tars.model.task.Name; + +// @@author A0124333U +/** + * A utility class to help with building reserve task objects. + */ +public class RsvTaskBuilder { + + private TestRsvTask rsvTask; + + public RsvTaskBuilder() { + this.rsvTask = new TestRsvTask(); + } + + public RsvTaskBuilder withName(String name) throws IllegalValueException { + this.rsvTask.setName(new Name(name)); + return this; + } + + public RsvTaskBuilder withDateTime(String dateTime1, String dateTime2) + throws IllegalValueException { + this.rsvTask.setDateTimeList(new DateTime(dateTime1, dateTime2)); + return this; + } + + public TestRsvTask build() { + return this.rsvTask; + } + +} diff --git a/src/test/java/seedu/address/testutil/SerializableTestClass.java b/src/test/java/tars/testutil/SerializableTestClass.java similarity index 96% rename from src/test/java/seedu/address/testutil/SerializableTestClass.java rename to src/test/java/tars/testutil/SerializableTestClass.java index ef58ef857179..3a61f1df9877 100644 --- a/src/test/java/seedu/address/testutil/SerializableTestClass.java +++ b/src/test/java/tars/testutil/SerializableTestClass.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package tars.testutil; import java.time.LocalDateTime; import java.util.ArrayList; @@ -27,8 +27,6 @@ public class SerializableTestClass { private List listOfLocalDateTimes; private HashMap mapOfIntegerToString; - public SerializableTestClass() {} - public static String getNameTestValue() { return NAME_TEST_VALUE; } diff --git a/src/test/java/tars/testutil/TarsBuilder.java b/src/test/java/tars/testutil/TarsBuilder.java new file mode 100644 index 000000000000..711bc532a3cd --- /dev/null +++ b/src/test/java/tars/testutil/TarsBuilder.java @@ -0,0 +1,35 @@ +package tars.testutil; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.model.Tars; +import tars.model.task.Task; +import tars.model.tag.Tag; + +/** + * A utility class to help with building Tars objects. + * Example usage:
+ * {@code Tars ab = new TarsBuilder().withTask("John", "Doe").withTag("Friend").build();} + */ +public class TarsBuilder { + + private Tars tars; + + public TarsBuilder(Tars tars){ + this.tars = tars; + } + + public TarsBuilder withTask(Task task) throws DuplicateTaskException { + tars.addTask(task); + return this; + } + + public TarsBuilder withTag(String tagName) throws IllegalValueException { + tars.addTag(new Tag(tagName)); + return this; + } + + public Tars build(){ + return tars; + } +} diff --git a/src/test/java/tars/testutil/TaskBuilder.java b/src/test/java/tars/testutil/TaskBuilder.java new file mode 100644 index 000000000000..9fe8da5b61a4 --- /dev/null +++ b/src/test/java/tars/testutil/TaskBuilder.java @@ -0,0 +1,47 @@ +package tars.testutil; + +import tars.commons.exceptions.IllegalValueException; +import tars.model.task.*; +import tars.model.tag.Tag; + +public class TaskBuilder { + + private TestTask task; + + public TaskBuilder() { + this.task = new TestTask(); + } + + public TaskBuilder withName(String name) throws IllegalValueException { + this.task.setName(new Name(name)); + return this; + } + + public TaskBuilder withTags(String ... tags) throws IllegalValueException { + for (String tag: tags) { + task.getTags().add(new Tag(tag)); + } + return this; + } + + public TaskBuilder withDateTime(String dateTime1, String dateTime2) throws IllegalValueException { + this.task.setDateTime(new DateTime(dateTime1, dateTime2)); + return this; + } + + public TaskBuilder withPriority(String priority) throws IllegalValueException { + this.task.setPriority(new Priority(priority)); + return this; + } + + public TestTask build() { + return this.task; + } + + public TaskBuilder withStatus() { + Status done = new Status(Status.DONE); + this.task.setStatus(done); + return this; + } + +} diff --git a/src/test/java/tars/testutil/TestRsvTask.java b/src/test/java/tars/testutil/TestRsvTask.java new file mode 100644 index 000000000000..87e919724bb5 --- /dev/null +++ b/src/test/java/tars/testutil/TestRsvTask.java @@ -0,0 +1,28 @@ +package tars.testutil; + +import tars.model.task.DateTime; +import tars.model.task.rsv.RsvTask; + +// @@author A0124333U +/** + * A mutable reserve task object. For testing only + */ +public class TestRsvTask extends RsvTask { + + public void setDateTimeList(DateTime...dateTimes) { + for (DateTime dt : dateTimes) { + dateTimeList.add(dt); + } + } + + public String getRsvCommand() { + StringBuilder sb = new StringBuilder(); + sb.append("rsv " + this.getName().taskName + " "); + for (DateTime dt : dateTimeList) { + sb.append("/dt " + dt.toString() + " "); + } + + return sb.toString(); + } + +} diff --git a/src/test/java/tars/testutil/TestTask.java b/src/test/java/tars/testutil/TestTask.java new file mode 100644 index 000000000000..ee59fbfdca56 --- /dev/null +++ b/src/test/java/tars/testutil/TestTask.java @@ -0,0 +1,76 @@ +package tars.testutil; + +import tars.model.task.*; +import tars.model.tag.UniqueTagList; + +/** + * A mutable task object. For testing only. + */ +public class TestTask implements ReadOnlyTask { + + private Name name; + private UniqueTagList tags; + private DateTime dateTime; + private Priority priority; + private Status status = new Status(); + + public TestTask() { + tags = new UniqueTagList(); + } + + public void setName(Name name) { + this.name = name; + } + + public void setDateTime(DateTime dateTime) { + this.dateTime = dateTime; + } + + + public void setPriority(Priority priority) { + this.priority = priority; + } + + @Override + public Name getName() { + return name; + } + + @Override + public DateTime getDateTime() { + return dateTime; + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public Priority getPriority() { + return priority; + } + + @Override + public UniqueTagList getTags() { + return tags; + } + + public void setStatus(Status status) { + this.status = status; + } + + @Override + public String toString() { + return getAsText(); + } + + public String getAddCommand() { + StringBuilder sb = new StringBuilder(); + sb.append("add " + this.getName().taskName + " "); + sb.append("/dt " + this.getDateTime().toString() + " "); + sb.append("/p " + this.getPriority().toString() + " "); + this.getTags().getInternalList().stream().forEach(s -> sb.append("/t " + s.tagName + " ")); + return sb.toString(); + } +} diff --git a/src/test/java/tars/testutil/TestUtil.java b/src/test/java/tars/testutil/TestUtil.java new file mode 100644 index 000000000000..8acb924d5bc8 --- /dev/null +++ b/src/test/java/tars/testutil/TestUtil.java @@ -0,0 +1,418 @@ +package tars.testutil; + +import com.google.common.io.Files; + +import guitests.guihandles.RsvTaskCardHandle; +import guitests.guihandles.TaskCardHandle; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import junit.framework.AssertionFailedError; +import org.loadui.testfx.GuiTest; +import org.testfx.api.FxToolkit; + +import tars.TestApp; +import tars.commons.exceptions.IllegalValueException; +import tars.commons.util.FileUtil; +import tars.commons.util.XmlUtil; +import tars.model.Tars; +import tars.model.task.*; +import tars.model.task.rsv.RsvTask; +import tars.model.task.rsv.UniqueRsvTaskList; +import tars.model.tag.Tag; +import tars.model.tag.UniqueTagList; +import tars.storage.XmlSerializableTars; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +/** + * A utility class for test cases. + */ +public class TestUtil { + + public static String LS = System.lineSeparator(); + + /** + * 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 final Tag[] sampleTagData = getSampleTagData(); + + public static void assertThrows(Class expected, Runnable executable) { + try { + executable.run(); + } catch (Throwable actualException) { + if (!actualException.getClass().isAssignableFrom(expected)) { + String message = String.format("Expected thrown: %s, actual: %s", expected.getName(), + actualException.getClass().getName()); + throw new AssertionFailedError(message); + } else + return; + } + throw new AssertionFailedError( + String.format("Expected %s to be thrown, but nothing was thrown.", expected.getName())); + } + + private static Task[] getSampleTaskData() { + try { + return new Task[] { + new Task(new Name("Task 1"), new DateTime("01/09/2016 1400", "02/09/2016 1400"), new Priority("h"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 2"), new DateTime("02/09/2016 1400", "03/09/2016 1400"), new Priority("m"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 3"), new DateTime("03/09/2016 1400", "04/09/2016 1400"), new Priority("l"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 4"), new DateTime("04/09/2016 1400", "05/09/2016 1400"), new Priority("h"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 5"), new DateTime("05/09/2016 1400", "06/09/2016 1400"), new Priority("m"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 6"), new DateTime("06/09/2016 1400", "07/09/2016 1400"), new Priority("l"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 7"), new DateTime("07/09/2016 1400", "08/09/2016 1400"), new Priority("h"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 8"), new DateTime("08/09/2016 1400", "09/09/2016 1400"), new Priority("m"), + new Status(false), new UniqueTagList()), + new Task(new Name("Task 9"), new DateTime("09/09/2016 1400", "10/09/2016 1400"), new Priority("l"), + new Status(false), new UniqueTagList()) }; + } catch (IllegalValueException e) { + assert false; + // not possible + return null; + } + } + + 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 generateSampleTaskData() { + return Arrays.asList(sampleTaskData); + } + + /** + * Appends the file name to the sandbox folder path. Creates the sandbox + * folder if it doesn't exist. + * + * @param fileName + * @return + */ + public static String getFilePathInSandboxFolder(String fileName) { + try { + FileUtil.createDirs(new File(SANDBOX_FOLDER)); + } catch (IOException e) { + throw new RuntimeException(e); + } + return SANDBOX_FOLDER + fileName; + } + + public static void createDataFileWithSampleData(String filePath) { + createDataFileWithData(generateSampleStorageTars(), filePath); + } + + public static void createDataFileWithData(T data, String filePath) { + try { + File saveFileForTesting = new File(filePath); + FileUtil.createIfMissing(saveFileForTesting); + XmlUtil.saveDataToFile(saveFileForTesting, data); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String... s) { + createDataFileWithSampleData(TestApp.SAVE_LOCATION_FOR_TESTING); + } + + public static Tars generateEmptyTars() { + return new Tars(new UniqueTaskList(), new UniqueTagList(), new UniqueRsvTaskList()); + } + + public static XmlSerializableTars generateSampleStorageTars() { + return new XmlSerializableTars(generateEmptyTars()); + } + + /** + * Tweaks the {@code keyCodeCombination} to resolve the + * {@code KeyCode.SHORTCUT} to their respective platform-specific keycodes + */ + public static KeyCode[] scrub(KeyCodeCombination keyCodeCombination) { + List keys = new ArrayList<>(); + if (keyCodeCombination.getAlt() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.ALT); + } + if (keyCodeCombination.getShift() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.SHIFT); + } + if (keyCodeCombination.getMeta() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.META); + } + if (keyCodeCombination.getControl() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.CONTROL); + } + keys.add(keyCodeCombination.getCode()); + return keys.toArray(new KeyCode[] {}); + } + + public static boolean isHeadlessEnvironment() { + String headlessProperty = System.getProperty("testfx.headless"); + return headlessProperty != null && headlessProperty.equals("true"); + } + + public static void captureScreenShot(String fileName) { + File file = GuiTest.captureScreenshot(); + try { + Files.copy(file, new File(fileName + ".png")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String descOnFail(Object... comparedObjects) { + return "Comparison failed \n" + + Arrays.asList(comparedObjects).stream().map(Object::toString).collect(Collectors.joining("\n")); + } + + public static void setFinalStatic(Field field, Object newValue) + throws NoSuchFieldException, IllegalAccessException { + field.setAccessible(true); + // remove final modifier from field + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + // ~Modifier.FINAL is used to remove the final modifier from field so + // that its value is no longer + // final and can be changed + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.set(null, newValue); + } + + public static void initRuntime() throws TimeoutException { + FxToolkit.registerPrimaryStage(); + FxToolkit.hideStage(); + } + + public static void tearDownRuntime() throws Exception { + FxToolkit.cleanupStages(); + } + + /** + * Gets private method of a class Invoke the method using + * method.invoke(objectInstance, params...) + * + * Caveat: only find method declared in the current Class, not inherited + * from supertypes + */ + public static Method getPrivateMethod(Class objectClass, String methodName) throws NoSuchMethodException { + Method method = objectClass.getDeclaredMethod(methodName); + method.setAccessible(true); + return method; + } + + public static void renameFile(File file, String newFileName) { + try { + Files.copy(file, new File(newFileName)); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + /** + * Gets mid point of a node relative to the screen. + * + * @param node + * @return + */ + public static Point2D getScreenMidPoint(Node node) { + double x = getScreenPos(node).getMinX() + node.getLayoutBounds().getWidth() / 2; + double y = getScreenPos(node).getMinY() + node.getLayoutBounds().getHeight() / 2; + return new Point2D(x, y); + } + + /** + * Gets mid point of a node relative to its scene. + * + * @param node + * @return + */ + public static Point2D getSceneMidPoint(Node node) { + double x = getScenePos(node).getMinX() + node.getLayoutBounds().getWidth() / 2; + double y = getScenePos(node).getMinY() + node.getLayoutBounds().getHeight() / 2; + return new Point2D(x, y); + } + + /** + * Gets the bound of the node relative to the parent scene. + * + * @param node + * @return + */ + public static Bounds getScenePos(Node node) { + return node.localToScene(node.getBoundsInLocal()); + } + + public static Bounds getScreenPos(Node node) { + return node.localToScreen(node.getBoundsInLocal()); + } + + public static double getSceneMaxX(Scene scene) { + return scene.getX() + scene.getWidth(); + } + + public static double getSceneMaxY(Scene scene) { + return scene.getX() + scene.getHeight(); + } + + public static Object getLastElement(List list) { + return list.get(list.size() - 1); + } + + /** + * Removes a subset from the list of tasks. + * + * @param tasks The list of tasks + * @param tasksToRemove The subset of tasks. + * @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, int index) { + tasks[index] = task; + return tasks; + } + + /** + * Appends tasks to the array of tasks. + * + * @param tasks An 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()]); + } + + /** + * Appends Reserved Tasks to the array of rsvTasks + * @param rsvTasks + * @param rsvTasksToAdd + * @return The modified array of rsv tasks + */ + public static TestRsvTask[] addRsvTasksToList(final TestRsvTask[] rsvTasks, TestRsvTask... rsvTasksToAdd) { + List listOfRsvTasks = asList(rsvTasks); + listOfRsvTasks.addAll(asList(rsvTasksToAdd)); + return listOfRsvTasks.toArray(new TestRsvTask[listOfRsvTasks.size()]); + } + + /** + * Removes a reserved task from the array of rsvTasks + * @param rsvTasks + * @param rsvTaskToDel + * @return The modified array of rsv tasks + */ + + public static TestRsvTask[] delRsvTaskFromList(final TestRsvTask[] rsvTasks, TestRsvTask rsvTaskToDel) { + List listOfRsvTasks = asList(rsvTasks); + listOfRsvTasks.remove(rsvTaskToDel); + return listOfRsvTasks.toArray(new TestRsvTask[listOfRsvTasks.size()]); + } + + // @@author A0124333U + /** + * Edits the task with index 1 on the list of tasks. + * + * @param tasks An array of tasks. + * @param indexToEdit Index of the task to edit. + * @param nameToEdit Name of the task to edit. + * @param priorityToEdit Priority of the task to edit. + * @return The modified array of tasks. + */ + public static TestTask[] editTask(final TestTask[] tasks, int indexToEdit, Name nameToEdit, + Priority priorityToEdit) { + List listOfTasks = asList(tasks); + listOfTasks.get(indexToEdit).setName(nameToEdit); + listOfTasks.get(indexToEdit).setPriority(priorityToEdit); + + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); + } + + // @@author A0121533W + /** + * Marks the task as done with index 1 in the list of tasks. + * + * @param tasks An array of tasks. + * @param indexes An array of indexes to mark + * @return The modified array of marked tasks + */ + public static TestTask[] markTasks(final TestTask[] tasks, int[] indexesToMark, Status status) { + List listOfTasks = asList(tasks); + for (int i = 0; i < indexesToMark.length; i++) { + listOfTasks.get(i).setStatus(status); + } + + return listOfTasks.toArray(new TestTask[listOfTasks.size()]); + } + // @@author + + private static List asList(T[] objs) { + List list = new ArrayList<>(); + for (T obj : objs) { + list.add(obj); + } + return list; + } + + public static boolean compareCardAndTask(TaskCardHandle card, ReadOnlyTask task) { + return card.isSameTask(task); + } + + public static boolean compareCardAndRsvTask(RsvTaskCardHandle card, RsvTask tasks) { + return card.isSameRsvTask(tasks); + } + +} diff --git a/src/test/java/tars/testutil/TypicalTestTasks.java b/src/test/java/tars/testutil/TypicalTestTasks.java new file mode 100644 index 000000000000..fd02c84770eb --- /dev/null +++ b/src/test/java/tars/testutil/TypicalTestTasks.java @@ -0,0 +1,109 @@ +package tars.testutil; + +import tars.commons.exceptions.DuplicateTaskException; +import tars.commons.exceptions.IllegalValueException; +import tars.model.Tars; +import tars.model.task.*; +import tars.model.task.rsv.RsvTask; + +public class TypicalTestTasks { + + public static TestTask taskA, taskB, taskC, taskD, taskE, taskF, taskG, taskH, taskI, cfmTaskA; + public static TestRsvTask rsvTaskA, rsvTaskB, rsvTaskC, rsvTaskD; + + public TypicalTestTasks() { + try { + taskA = new TaskBuilder().withName("Task A") + .withDateTime("01/09/2016 1400" , "02/09/2016 1400") + .withPriority("h") + .withTags("test").build(); + taskB = new TaskBuilder().withName("Task B") + .withDateTime("02/09/2016 1400" , "03/09/2016 1400") + .withPriority("m") + .withTags("tars", "test").build(); + taskC = new TaskBuilder().withName("Task C") + .withDateTime("03/09/2016 1400" , "04/09/2016 1400") + .withPriority("l") + .build(); + taskD = new TaskBuilder().withName("Task D") + .withDateTime("04/09/2016 1400" , "05/09/2016 1400") + .withPriority("h") + .build(); + taskE = new TaskBuilder().withName("Task E") + .withDateTime("05/09/2016 1400" , "06/09/2016 1400") + .withPriority("m") + .build(); + taskF = new TaskBuilder().withName("Task F") + .withDateTime("06/09/2016 1400" , "07/09/2016 1400") + .withPriority("l") + .build(); + taskG = new TaskBuilder().withName("Task G") + .withDateTime("07/09/2016 1400" , "08/09/2016 1400") + .withPriority("h") + .withStatus() + .build(); + rsvTaskA = new RsvTaskBuilder().withName("Rsv Task A") + .withDateTime("01/10/2016 1400", "02/10/2016 1400") + .withDateTime("03/10/2016 1400", "04/10/2016 1400") + .build(); + rsvTaskB = new RsvTaskBuilder().withName("Rsv Task B") + .withDateTime("05/10/2016 1400", "06/10/2016 1400") + .build(); + + //Manually added + taskH = new TaskBuilder().withName("Task H") + .withDateTime("08/09/2016 1400" , "09/09/2016 1400") + .withPriority("m") + .build(); + taskI = new TaskBuilder().withName("Task I") + .withDateTime("09/09/2016 1400" , "10/09/2016 1400") + .withPriority("l") + .build(); + cfmTaskA = new TaskBuilder().withName("Rsv Task A") + .withDateTime("03/10/2016 1400", "04/10/2016 1400") + .withPriority("h") + .build(); + rsvTaskC = new RsvTaskBuilder().withName("Rsv Task C") + .withDateTime("07/10/2016 1400", "08/10/2016 1400") + .withDateTime("09/10/2016 1400", "10/10/2016 1400") + .build(); + rsvTaskD = new RsvTaskBuilder().withName("Rsv Task D") + .withDateTime("11/10/2016 1400", "12/10/2016 1400") + .build(); + } catch (IllegalValueException e) { + e.printStackTrace(); + assert false : "not possible"; + } + } + + public static void loadTarsWithSampleData(Tars tars) { + + try { + tars.addTask(new Task(taskA)); + tars.addTask(new Task(taskB)); + tars.addTask(new Task(taskC)); + tars.addTask(new Task(taskD)); + tars.addTask(new Task(taskE)); + tars.addTask(new Task(taskF)); + tars.addTask(new Task(taskG)); + tars.addRsvTask(new RsvTask(rsvTaskA)); + tars.addRsvTask(new RsvTask(rsvTaskB)); + } catch (DuplicateTaskException e) { + assert false : "not possible"; + } + } + + public TestTask[] getTypicalTasks() { + return new TestTask[]{taskA, taskB, taskC, taskD, taskE, taskF, taskG}; + } + + public TestRsvTask[] getTypicalRsvTasks() { + return new TestRsvTask[]{rsvTaskA, rsvTaskB}; + } + + public Tars getTypicalTars(){ + Tars tars = new Tars(); + loadTarsWithSampleData(tars); + return tars; + } +} diff --git a/src/test/java/tars/ui/formatter/DateFormatterTest.java b/src/test/java/tars/ui/formatter/DateFormatterTest.java new file mode 100644 index 000000000000..65fcc9755379 --- /dev/null +++ b/src/test/java/tars/ui/formatter/DateFormatterTest.java @@ -0,0 +1,86 @@ +package tars.ui.formatter; + +import static org.junit.Assert.*; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import org.junit.Test; + +import tars.model.task.DateTime; +import tars.model.task.DateTime.IllegalDateException; + +// @@author A0139924W +/** + * Date formatter test + */ +public class DateFormatterTest { + + @Test + public void generateSingleDateFormat_todayCorrectFormat() { + LocalDate testDate = LocalDate.now(); + LocalTime testTime = LocalTime.of(0, 0, 0); + LocalDateTime testDateTime = LocalDateTime.of(testDate, testTime); + + assertEquals("Today at 12:00 AM", + DateFormatter.generateSingleDateFormat(testDateTime)); + } + + @Test + public void generateSingleDateFormat_tomorrowCorrectFormat() { + LocalDate testDate = LocalDate.now().plusDays(1); + LocalTime testTime = LocalTime.of(0, 0, 0); + LocalDateTime testDateTime = LocalDateTime.of(testDate, testTime); + + assertEquals("Tomorrow at 12:00 AM", + DateFormatter.generateSingleDateFormat(testDateTime)); + } + + @Test + public void generateSingleDateFormat_otherDayCorrectFormat() { + LocalDate testDate = LocalDate.of(2010, 10, 10); + LocalTime testTime = LocalTime.of(0, 0, 0); + LocalDateTime testDateTime = LocalDateTime.of(testDate, testTime); + + assertEquals("Sun, Oct 10 2010 12:00 AM", + DateFormatter.generateSingleDateFormat(testDateTime)); + } + + @Test + public void generateDateRangeFormat_sameDayCorrectFormat() { + LocalDate testDateA = LocalDate.of(2010, 10, 10); + LocalTime testTimeA = LocalTime.of(0, 0, 0); + LocalDate testDateB = LocalDate.of(2010, 10, 10); + LocalTime testTimeB = LocalTime.of(1, 0, 0); + LocalDateTime testDateTimeA = LocalDateTime.of(testDateA, testTimeA); + LocalDateTime testDateTimeB = LocalDateTime.of(testDateB, testTimeB); + + assertEquals("Sun, Oct 10 2010 12:00 AM - 01:00 AM", DateFormatter + .generateDateRangeFormat(testDateTimeA, testDateTimeB)); + } + + @Test + public void generateDateRangeFormat_diffDayCorrectFormat() { + LocalDate testDateA = LocalDate.of(2010, 10, 10); + LocalTime testTimeA = LocalTime.of(0, 0, 0); + LocalDate testDateB = LocalDate.of(2010, 11, 10); + LocalTime testTimeB = LocalTime.of(1, 0, 0); + LocalDateTime testDateTimeA = LocalDateTime.of(testDateA, testTimeA); + LocalDateTime testDateTimeB = LocalDateTime.of(testDateB, testTimeB); + + assertEquals("Sun, Oct 10 2010 12:00 AM - Wed, Nov 10 2010 01:00 AM", + DateFormatter.generateDateRangeFormat(testDateTimeA, + testDateTimeB)); + } + + @Test + public void formatDate_singleStartDateCorrectFormat() + throws DateTimeException, IllegalDateException { + DateTime dateTime = new DateTime("", "10/10/2010 1200"); + + assertEquals("Sun, Oct 10 2010 12:00 PM", + DateFormatter.formatDate(dateTime)); + } +} diff --git a/src/test/java/tars/ui/formatter/FormatterTest.java b/src/test/java/tars/ui/formatter/FormatterTest.java new file mode 100644 index 000000000000..db44ad485158 --- /dev/null +++ b/src/test/java/tars/ui/formatter/FormatterTest.java @@ -0,0 +1,34 @@ +package tars.ui.formatter; + +import static org.junit.Assert.assertEquals; +import java.util.ArrayList; +import org.junit.Test; +import tars.model.task.ReadOnlyTask; +import tars.model.task.rsv.RsvTask; + +// @@author A0139924W +/** + * Formatter test + */ +public class FormatterTest { + + @Test + public void formatTaskList_emptyList() { + String actualFormattedText = + new Formatter().formatTaskList(new ArrayList()); + String expectedFormmatedText = + String.format(Formatter.EMPTY_LIST_MESSAGE, "tasks"); + + assertEquals(expectedFormmatedText, actualFormattedText); + } + + @Test + public void formatRsvTaskList_emptyList() { + String actualFormattedText = + new Formatter().formatRsvTaskList(new ArrayList()); + String expectedFormmatedText = + String.format(Formatter.EMPTY_LIST_MESSAGE, "tasks"); + + assertEquals(expectedFormmatedText, actualFormattedText); + } +}