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/collate.bat b/collate.bat new file mode 100644 index 000000000000..741fde7c69e2 --- /dev/null +++ b/collate.bat @@ -0,0 +1,3 @@ +java -jar Collate-TUI.jar collate from src/main to collated/main include java, fxml, css +java -jar Collate-TUI.jar collate from src/test to collated/test include java +java -jar Collate-TUI.jar collate from docs to collated/docs include md, html diff --git a/collated/docs/A0097627N.md b/collated/docs/A0097627N.md index a6220770e2f9..0ca6bea6988a 100644 --- a/collated/docs/A0097627N.md +++ b/collated/docs/A0097627N.md @@ -5,7 +5,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -Priority | As a ... | I want to ... | So that I can... +Priority | As a(n) ... | I want to ... | So that I can... -------- | :-------- | :--------- | :----------- `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App `* * *` | new user | view more information about a particular command | learn how to use various commands @@ -21,9 +21,9 @@ Priority | As a ... | I want to ... | So that I can... `*` | user | sort tasks by priority level | see the most important tasks and prioritize accordingly `*` | user | undo most recent command | undo the most recent operation `*` | user | redo most recent undo command | redo the operation done by the most recent undo action +`*` | user | change storage location | choose where my tasks are saved at `*` | advanced user | alias keywords with shorter versions | type a command faster `*` | advanced user | remove alias of keywords with shorter versions | get rid of shorter version of certain keywords -{More to be added} ## Appendix B : Use Cases @@ -33,7 +33,7 @@ Priority | As a ... | I want to ... | So that I can... (For all use cases below, the **System** is the `Savvy Tasker` and the **Actor** is the `user`, unless specified otherwise) -#### Use case: Add task +### Use case: Add task **MSS** @@ -63,7 +63,7 @@ Use case ends. > 2d1. Savvy Tasker shows an error message
> Use case resumes at step 1 -#### Use case: List tasks +### Use case: List tasks **MSS** @@ -79,7 +79,7 @@ Use case ends. > 3a1. Savvy Tasker shows an error message
Use case ends -#### Use case: Find task +### Use case: Find task **MSS** @@ -100,7 +100,7 @@ Use case ends. > Use case ends -#### Use case: Modify task +### Use case: Modify task **MSS** @@ -125,7 +125,30 @@ Use case ends. > 2b1. Savvy Tasker shows an error message and display the expected format
Use case resumes at step 3 -#### Use case: Mark task as done + +### Use case: Change storage location + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to change the storage location of Savvy Tasker +3. Savvy Tasker changes the storage location, saving all existing data in the new location
+Use case ends. + +**Extensions** + +2a. The given path is invalid + +> 2a1. Savvy Tasker shows an error message
+ Use case ends + +2b. The given path is is not accessible (read/write) by Savvy Tasker + +> 2b1. Savvy Tasker shows an error message
+ Use case ends + + +### Use case: Mark task as done **MSS** @@ -148,7 +171,7 @@ Use case ends. > 3b1. Savvy Tasker shows a 'task already marked' error message.
> Use case resumes at step 1 -###Use case: Unmark marked task +### Use case: Unmark marked task **MSS** @@ -169,7 +192,7 @@ Use case ends. > Use case resumes at step 1 -#### Use case: Delete task +### Use case: Delete task **MSS** @@ -191,7 +214,7 @@ Use case ends. > 4a1. Savvy Tasker shows an error message > Use case resumes at step 3
-###Use case: Alias keyword and use shorten keyword +### Use case: Alias keyword and use shorten keyword **MSS** @@ -213,7 +236,7 @@ Use case ends. > 2b1. Savvy Tasker shows a error message and the shorten keyword's original associated keyword > Use case resumes at step 1
-###Use case: Unalias keyword +### Use case: Unalias keyword **MSS** @@ -228,7 +251,7 @@ Use case ends. > 2a1. Savvy Tasker shows a 'not found' error message > Use case resumes at step 1
-###Use case: Undo previous command +### Use case: Undo previous command **MSS** @@ -243,7 +266,7 @@ Use case ends. > 2a1. Savvy Tasker shows a 'cannot undo' error message
> Use case ends -###Use case: Redo most recently undone command +### Use case: Redo most recently undone command **MSS** @@ -269,7 +292,6 @@ Use case ends. 8. Should store data in text file. 9. Should work without requiring an installer. -{More to be added} ## Appendix D : Glossary diff --git a/collated/docs/A0139915W.md b/collated/docs/A0139915W.md index 5ab0becbc7be..d3102f52e3e7 100644 --- a/collated/docs/A0139915W.md +++ b/collated/docs/A0139915W.md @@ -79,7 +79,7 @@ Format: `add TASK_NAME [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVE > LOCATION | `Optional` Specifies the location where the task happens. > PRIORITY_LEVEL | `Optional` Specifies the priority level of the task.
`Accepts` values `low`, `medium`, `high`
`Defaults` to `???` > RECURRING_TYPE | `Optional` Specifies the recurring type of the task.
`Accepts` values `none`, `daily`, `weekly`, `monthly`, `yearly`
`Defaults` to `none` -> NUMBER_OF_RECURRENCE | `Optional` Specifies the number of times the task recurrs. A value of 0 specifies a never-ending recurrence.
`Defaults` to `0`
`Ignored` if RECURRING_TYPE is `none` +> NUMBER_OF_RECURRENCE | `Optional` Specifies the number of times the task recurrs.
`Defaults` to `1`
`Ignored` if RECURRING_TYPE is `none` > CATEGORY | `Optional` Specifies a custom category for the task. This can be used for keeping track of similar tasks. > DESCRIPTION | `Optional` Describes the task. @@ -95,11 +95,11 @@ Examples: #### Listing all tasks: `list` Shows a list of all tasks in Savvy Tasker
-Format: `list [t/LIST_TYPE]` +Format: `list [LIST_TYPE]` > Parameters | Description > -------- | :-------- -> LIST_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `DueDate`, `PriorityLevel`, `Archived`
`Defaults` to `DueDate` +> LIST_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `DueDate`, `PriorityLevel`, `Archived`, `Alias`
`Defaults` to `DueDate` `LIST_TYPE` Explanation: * `DueDate`
@@ -108,7 +108,9 @@ Format: `list [t/LIST_TYPE]` * `PriorityLevel`
Tasks are sorted according to priority level beginning with the highest.
* `Archived`
- Tasks that have been [marked](#mark-a-task-as-done--mark) are listed. They are sorted according to the time of creation of the task.
+ Tasks that have been [marked](#mark-a-task-as-done--mark) are listed. They are sorted according to the time of creation of the task. +* `Alias`
+ [Aliases](#alias-a-keyword--alias) that have been registered are listed.
#### Finding all task containing any keyword in its name: `find` Finds tasks whose names contain any of the given keywords.
@@ -168,6 +170,16 @@ Format: `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/ >
> Overwrites any of the specified fields ('LOCATION', 'DESCRIPTION'...) with the new values +#### Change storage location : `storage` +Changes the storage location of Savvy Tasker.
+Format: `storage PATH` + +> Parameters | Description +> -------- | :-------- +> PATH | `Mandatory` Specifies the path where Savvy Tasker's task list is saved at. +>
+> If the new storage location specified by `PATH` is not accessible by Savvy Tasker, no change will be made to the existing path. + ``` ###### \UserGuide.md ``` md @@ -182,10 +194,11 @@ Command | Format [Delete](#deleting-a-task--delete) | `delete INDEX [MORE_INDEX]`
Example: `delete 1 2 3` [Exit](#exiting-the-program--exit) | `exit` [Find](#finding-all-task-containing-any-keyword-in-its-name-find) | `find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]`
Example: `find t/exact CS2103 Meeting` -[List](#listing-all-tasks-list) | `list [t/LIST_TYPE]`
Example: `list t/archived` +[List](#listing-all-tasks-list) | `list [LIST_TYPE]`
Example: `list archived` [Help](#viewing-help--help) | `help` [Modify](#modifies-a-task--modify) | `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]`
Example: `modify 2 t/Wednesday Weekly Milestone s/wed d/Project Meeting and Finalization` [Mark](#mark-a-task-as-done--mark) | `mark INDEX [MORE_INDEX]`
Example: `mark 1 2 3` +[Storage](#change-storage-location--storage) | `storage PATH`
Example: `storage data/savvytasker.xml` [Unmark](#unmark-a-task-as-done--unmark) | `unmark INDEX [MORE_INDEX]`
Example: `unmark 1 2 3` [Undo](#undo-the-most-recent-operation--undo) | `undo` [Redo](#redo-the-most-recent-undo-operation--redo) | `redo` diff --git a/collated/main/A0097627N.md b/collated/main/A0097627N.md index 8e35367693b4..58bc1d6cc7f0 100644 --- a/collated/main/A0097627N.md +++ b/collated/main/A0097627N.md @@ -17,7 +17,7 @@ @Override public boolean redo() { execute(); - return false; + return true; } /** @@ -26,22 +26,84 @@ */ @Override public boolean undo() { - - UnmodifiableObservableList lastShownList = model.getFilteredTaskListTask(); - - for (int i = 0; i < lastShownList.size(); i++) { - if (lastShownList.get(i) == toAdd){ - ReadOnlyTask taskToDelete = lastShownList.get(i); - try { - model.deleteTask(taskToDelete); - } catch (TaskNotFoundException e) { - e.printStackTrace(); - } + Iterator itr = tasksAdded.iterator(); + while (itr.hasNext()) { + try { + model.deleteTask(itr.next()); + } catch (TaskNotFoundException e) { + // do nothing. } - } + } + // clears the list of tasks added. + // if redo is performed, the list will be populated again. + tasksAdded.clear(); + return true; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { return false; } + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` +###### \java\seedu\savvytasker\logic\commands\AliasCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return true; + } + + /** + * Redo the alias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + execute(); + return true; + } + /** + * Undo the alias command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // TODO Auto-generated method stub + + assert model != null; + + AliasSymbol toRemove = null; + for(AliasSymbol symbol : model.getSavvyTasker().getReadOnlyListOfAliasSymbols()) { + if (symbol.getKeyword().equals(this.keyword)) { + toRemove = symbol; + break; + } + } + try { + model.removeAliasSymbol(toRemove); + } catch (SymbolKeywordNotFoundException e) { + e.printStackTrace(); + } + + return true; + } + /** * Check if command is an undo command * @return true if the command is an undo operation, false otherwise @@ -59,6 +121,7 @@ public boolean isRedo(){ return false; } +} ``` ###### \java\seedu\savvytasker\logic\commands\ClearCommand.java ``` java @@ -492,8 +555,7 @@ for(Task taskToMark : tasksToMark) { if (!taskToMark.isArchived()){ taskToMark.setArchived(true); - model.deleteTask(taskToMark); - model.addTask(taskToMark); + model.modifyTask(taskToMark, taskToMark); resultSb.append(String.format(MESSAGE_MARK_TASK_SUCCESS, taskToMark)); } else { resultSb.append(String.format(MESSAGE_MARK_TASK_FAIL, taskToMark)); @@ -501,8 +563,6 @@ } } catch (TaskNotFoundException pnfe) { assert false : "The target task cannot be missing"; - } catch (DuplicateTaskException e) { - e.printStackTrace(); } catch (InvalidDateException e) { assert false : "The target task should be valid, only the archived flag is set"; } @@ -730,6 +790,55 @@ return false; } ``` +###### \java\seedu\savvytasker\logic\commands\StorageCommand.java +``` java + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } +``` ###### \java\seedu\savvytasker\logic\commands\UnaliasCommand.java ``` java /** @@ -885,8 +994,7 @@ for(Task taskToUnmark : tasksToUnmark) { if (taskToUnmark.isArchived()){ taskToUnmark.setArchived(false); - model.deleteTask(taskToUnmark); - model.addTask(taskToUnmark); + model.modifyTask(taskToUnmark, taskToUnmark); model.updateFilteredListToShowArchived(); resultSb.append(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark)); } else { @@ -895,9 +1003,7 @@ } } catch (TaskNotFoundException pnfe) { assert false : "The target task cannot be missing"; - } catch (DuplicateTaskException e) { - e.printStackTrace(); - }catch (InvalidDateException e) { + } catch (InvalidDateException e) { assert false : "The target task should be valid, only the archived flag is set"; } return new CommandResult(resultSb.toString()); diff --git a/collated/main/A0139915W.md b/collated/main/A0139915W.md index 0942cfeac070..150582cad2c3 100644 --- a/collated/main/A0139915W.md +++ b/collated/main/A0139915W.md @@ -1,4 +1,27 @@ # A0139915W +###### \java\seedu\savvytasker\commons\events\storage\DataSavingLocationChangedEvent.java +``` java +/** + * Indicates a change in location of the storage + */ +public class DataSavingLocationChangedEvent extends BaseEvent { + + public final ReadOnlySavvyTasker data; + public final String newPath; + + public DataSavingLocationChangedEvent(ReadOnlySavvyTasker data, String newPath) { + this.data = data; + this.newPath = newPath; + } + + @Override + public String toString() { + return "number of tasks " + data.getReadOnlyListOfTasks().size() + + " new path " + this.newPath; + } + +} +``` ###### \java\seedu\savvytasker\commons\events\ui\TaskPanelSelectionChangedEvent.java ``` java @@ -24,6 +47,12 @@ public class TaskPanelSelectionChangedEvent extends BaseEvent { } } ``` +###### \java\seedu\savvytasker\commons\util\FileUtil.java +``` java + public static boolean createIfMissing(File file) throws IOException { + return createFile(file); + } +``` ###### \java\seedu\savvytasker\commons\util\SmartDefaultDates.java ``` java /** @@ -86,6 +115,7 @@ public class SmartDefaultDates { calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); } return calendar.getTime(); } @@ -105,7 +135,13 @@ public class SmartDefaultDates { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); this.startDateTime = calendar.getTime(); + + if (this.startDateTime.compareTo(this.endDateTime) > 0) { + // end date is before today, leave start date as null + this.startDateTime = null; + } } @@ -134,6 +170,7 @@ public class SmartDefaultDates { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); } return calendar.getTime(); } @@ -154,6 +191,7 @@ public class SmartDefaultDates { calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); this.endDateTime = calendar.getTime(); } @@ -169,11 +207,6 @@ public class SmartDefaultDates { Date end = getEnd(endDateTime); this.startDateTime = start; this.endDateTime = end; - if (this.startDateTime.compareTo(this.endDateTime) > 0) { - calendar.setTime(this.endDateTime); - calendar.add(Calendar.DATE, 7); - this.endDateTime = calendar.getTime(); - } } public Date getStartDate() { @@ -185,6 +218,22 @@ public class SmartDefaultDates { } } ``` +###### \java\seedu\savvytasker\commons\util\StringUtil.java +``` java + // reused original implementation of 'containsIgnoreCase' to find exact matches + public static boolean containsExactIgnoreCase(String source, String query) { + List strings = Arrays.asList(source); + return strings.stream().filter(s -> s.equalsIgnoreCase(query)).count() > 0; + } + + // reused original implementation of 'containsIgnoreCase' to find partial matches + public static boolean containsPartialIgnoreCase(String source, String query) { + if (source == null) return false; + String[] split = source.toLowerCase().split("\\s+"); + List strings = Arrays.asList(split); + return strings.stream().filter(s -> s.contains(query.toLowerCase())).count() > 0; + } +``` ###### \java\seedu\savvytasker\logic\commands\AddCommand.java ``` java /** @@ -202,16 +251,24 @@ public class SmartDefaultDates { this.numberOfRecurrence = numberOfRecurrence; this.category = category; this.description = description; + tasksAdded = new LinkedList(); } private void createTask() { final boolean isArchived = false; // all tasks are first added as active tasks final int taskId = 0; // taskId to be assigned by ModelManager, leave as 0 + final int groupId = 0; // groupId to be assigned by ModelManager, leave as 0 - this.toAdd = new Task(taskId, taskName, startDateTime, endDateTime, + this.toAdd = new Task(taskId, groupId, taskName, startDateTime, endDateTime, location, priority, recurringType, numberOfRecurrence, category, description, isArchived); } + + private void addToListOfTasksAdded(Task... tasks) { + for (Task t : tasks) { + tasksAdded.add(t); + } + } @Override public CommandResult execute() { @@ -219,15 +276,41 @@ public class SmartDefaultDates { createTask(); try { - model.addTask(toAdd); + Task taskAdded = null; + if (toAdd.getRecurringType() == RecurrenceType.None) { + // not a recurring task, add a single task + taskAdded = model.addTask(toAdd); + addToListOfTasksAdded(taskAdded); + } else { + // a recurring task, add a group of recurring tasks + LinkedList tasksAdded = model.addRecurringTask(toAdd); + taskAdded = tasksAdded.peekFirst(); + addToListOfTasksAdded(tasksAdded.toArray(new Task[tasksAdded.size()])); + } + + int targetIndex = getIndexOfTask(taskAdded); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } else { + // GUI should never ever get here + } return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } catch (DuplicateTaskException e) { - return new CommandResult(MESSAGE_DUPLICATE_TASK); } catch (InvalidDateException ex) { return new CommandResult(MESSAGE_INVALID_START_END); } } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + model.updateFilteredListToShowActive(); //because newly added tasks are all active. + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } ``` ###### \java\seedu\savvytasker\logic\commands\DeleteCommand.java ``` java @@ -258,7 +341,7 @@ public class SmartDefaultDates { //tasksToUndo.add((Task)taskToDelete); resultSb.append(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); } - } catch (TaskNotFoundException pnfe) { + } catch (TaskNotFoundException tnfe) { assert false : "The target task cannot be missing"; } @@ -301,6 +384,9 @@ public class SmartDefaultDates { // use default, sort by due date _listType = ListType.DueDate; } + + // shows the task list by default, unless user + // specifies to show the alias switch (_listType) { case DueDate: @@ -312,10 +398,19 @@ public class SmartDefaultDates { case Archived: model.updateFilteredListToShowArchived(); break; + case Alias: + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Alias)); + break; default: - assert false; // should not reach here + // nothing to do + break; + } + if (_listType != ListType.Alias) { + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Task)); + return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } else { + return new CommandResult(getMessageForAliasListShownSummary(model.getAliasSymbolCount())); } - return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); } ``` ###### \java\seedu\savvytasker\logic\commands\ModifyCommand.java @@ -358,7 +453,13 @@ public class SmartDefaultDates { try { originalTask = (Task)taskToModify; - model.modifyTask(taskToModify, replacement); + Task taskModified = model.modifyTask(taskToModify, replacement); + int targetIndex = getIndexOfTask(taskModified); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } else { + // GUI should never ever get here + } } catch (TaskNotFoundException e) { assert false : "The target task cannot be missing"; } catch (InvalidDateException ex) { @@ -367,6 +468,48 @@ public class SmartDefaultDates { return new CommandResult(String.format(MESSAGE_SUCCESS, replacement)); } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } +``` +###### \java\seedu\savvytasker\logic\commands\StorageAndModelRequiringCommand.java +``` java +/** + * Represents a command which requires the Storage class as a dependency. + * Commands should inherit this class if they only require dependency for + * storage and model components +*/ +public abstract class StorageAndModelRequiringCommand extends Command { + protected Storage storage; + protected Model model; + + public void setStorage(Storage storage) { + this.storage = storage; + } + + public void setModel(Model model) { + this.model = model; + } +} +``` +###### \java\seedu\savvytasker\logic\LogicManager.java +``` java + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + + @Override + public ObservableList getAliasSymbolList() { + return parser.getAliasSymbolList(); + } ``` ###### \java\seedu\savvytasker\logic\parser\FindCommandParser.java ``` java @@ -384,24 +527,55 @@ public class SmartDefaultDates { throw new ParseException(commandText, String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); } ``` +###### \java\seedu\savvytasker\MainApp.java +``` java + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + try { + String configPath = getApplicationParameter("config"); + if(configPath == null) { + configPath = Config.DEFAULT_CONFIG_FILE; + } + config.setSavvyTaskerFilePath(dslce.newPath); + ConfigUtil.saveConfig(config, configPath); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + } +``` ###### \java\seedu\savvytasker\model\Model.java ``` java - /** Deletes the given Task. */ - void deleteTask(ReadOnlyTask target) throws TaskNotFoundException; + /** + * Deletes the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. + * */ + Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException; - /** Modifies the given Task. */ - void modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException; + /** + * Modifies the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the modify operation is successful, an exception is thrown otherwise. + * */ + Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException; /** Adds the given Task. * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the add operation is successful, an exception is thrown otherwise. + * */ + Task addTask(Task task) throws InvalidDateException; + + /** Adds the given Task as a recurring task. The task's recurrence type must not be null. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of Tasks added if the add operation is successful, an exception is thrown otherwise. * */ - void addTask(Task task) throws DuplicateTaskException, InvalidDateException; + LinkedList addRecurringTask(Task task) throws InvalidDateException; /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ UnmodifiableObservableList getFilteredTaskList(); - - /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ - UnmodifiableObservableList getFilteredTaskListTask(); /** Updates the filter of the filtered task list to show all active tasks sorted by due date */ void updateFilteredListToShowActiveSortedByDueDate(); @@ -436,7 +610,7 @@ public class SmartDefaultDates { savvyTasker = new SavvyTasker(src); filteredTasks = new FilteredList<>(savvyTasker.getTasks()); - sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDueDate()); updateFilteredListToShowActive(); // shows only active tasks on start } @@ -447,30 +621,40 @@ public class SmartDefaultDates { public ModelManager(ReadOnlySavvyTasker initialData) { savvyTasker = new SavvyTasker(initialData); filteredTasks = new FilteredList<>(savvyTasker.getTasks()); - sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDueDate()); updateFilteredListToShowActive(); // shows only active tasks on start } ``` ###### \java\seedu\savvytasker\model\ModelManager.java ``` java @Override - public synchronized void deleteTask(ReadOnlyTask target) throws TaskNotFoundException { - savvyTasker.removeTask(target); + public synchronized Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException { + Task taskDeleted = savvyTasker.removeTask(target); indicateSavvyTaskerChanged(); + return taskDeleted; } @Override - public void modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException { - savvyTasker.replaceTask(target, replacement); + public synchronized Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException { + Task taskModified = savvyTasker.replaceTask(target, replacement); indicateSavvyTaskerChanged(); + return taskModified; } @Override - public synchronized void addTask(Task t) throws DuplicateTaskException, InvalidDateException { - t.setId(savvyTasker.getNextTaskId()); - savvyTasker.addTask(t); + public synchronized Task addTask(Task t) throws InvalidDateException { + Task taskAdded = savvyTasker.addTask(t); updateFilteredListToShowActive(); indicateSavvyTaskerChanged(); + return taskAdded; + } + + @Override + public synchronized LinkedList addRecurringTask(Task recurringTask) throws InvalidDateException { + LinkedList recurringTasks = savvyTasker.addRecurringTasks(recurringTask); + updateFilteredListToShowActive(); + indicateSavvyTaskerChanged(); + return recurringTasks; } ``` ###### \java\seedu\savvytasker\model\ModelManager.java @@ -479,11 +663,6 @@ public class SmartDefaultDates { public UnmodifiableObservableList getFilteredTaskList() { return new UnmodifiableObservableList(sortedAndFilteredTasks); } - - @Override - public UnmodifiableObservableList getFilteredTaskListTask() { - return new UnmodifiableObservableList(sortedAndFilteredTasks); - } @Override public void updateFilteredListToShowActiveSortedByDueDate() { @@ -762,18 +941,7 @@ public class SmartDefaultDates { else if (task1 == null) return 1; else if (task2 == null) return -1; else { - // Priority Level can be nulls - // Check for existence of priorityLevel before comparing - if (task1.getPriority() == null && - task2.getPriority() == null) { - return 0; - } else if (task1.getPriority() == null) { - return 1; - } else if (task2.getPriority() == null) { - return -1; - } else { - return task2.getPriority().compareTo(task1.getPriority()); - } + return task2.getPriority().compareTo(task1.getPriority()); } } @@ -795,40 +963,162 @@ public class SmartDefaultDates { ``` ###### \java\seedu\savvytasker\model\SavvyTasker.java ``` java + /** - * Returns the next available id for use to uniquely identify a task. - * @author A0139915W - * @return The next available id. + * Adds a task to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the task added if the operation succeeds, an exception is thrown otherwise. */ - public int getNextTaskId() { - return tasks.getNextId(); + public Task addTask(Task t) throws InvalidDateException { + // guarantees unique ID + t.setId(tasks.getNextId()); + try { + return tasks.add(t); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } } /** - * Adds a task to savvy tasker. - * @throws TaskList.DuplicateTaskException if an equivalent task already exists. + * Adds a group of recurring tasks to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of recurring tasks if the operation succeeds, an exception is thrown otherwise + */ + public LinkedList addRecurringTasks(Task recurringTask) throws InvalidDateException { + LinkedList tasksToAdd = + createRecurringTasks(recurringTask, recurringTask.getRecurringType(), + recurringTask.getNumberOfRecurrence()); + Iterator itr = tasksToAdd.iterator(); + + while(itr.hasNext()) { + // this will be an atomic operation + // guaranteed no duplicates + // if the start/end dates are invalid, + // the first task to be added will fail immediately, + // subsequent tasks will not be added + try { + tasks.add(itr.next()); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } + } + return tasksToAdd; + } + + /** + * Creates a list of recurring tasks to be added into savvy tasker. + * @param recurringTask the task that recurs + * @param recurringType the type of recurrence + * @param numberOfRecurrences the number of recurrences + * @return A list of tasks that represents a recurring task. */ - public void addTask(Task t) throws DuplicateTaskException, InvalidDateException { - tasks.add(t); + private LinkedList createRecurringTasks(Task recurringTask, RecurrenceType recurringType, int numberOfRecurrences) { + assert recurringTask.getRecurringType() != null; + assert recurringTask.getNumberOfRecurrence() > 0; + + LinkedList listOfTasks = new LinkedList(); + recurringTask.setGroupId(tasks.getNextGroupId()); + + for (int i = 0; i < numberOfRecurrences; ++i) { + Task t = recurringTask.clone(); + // guarantees uniqueness + t.setId(tasks.getNextId()); + listOfTasks.add(setDatesForRecurringType(t, recurringType, i)); + } + + return listOfTasks; + } + + /** + * Helper function for createRecurringTasks(). Sets the respective start/end datetime for the + * i-th recurring task to be added + * @param t The first recurring task + * @param recurringType the type of recurrence + * @param index the index of the loop + * @return A task with its respective datetime set. + */ + private Task setDatesForRecurringType(Task t, RecurrenceType recurringType, int index) { + Date startDate = t.getStartDateTime(); + Date endDate = t.getEndDateTime(); + Calendar calendar = Calendar.getInstance(); + switch (recurringType) { + case Daily: // add one day to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 1 * index); + endDate = calendar.getTime(); + } + break; + case Weekly: // add 7 days to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 7 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 7 * index); + endDate = calendar.getTime(); + } + break; + case Monthly: // add 1 month to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.MONTH, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.MONTH, 1 * index); + endDate = calendar.getTime(); + } + break; + case Yearly: // add 1 year to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.YEAR, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.YEAR, 1 * index); + endDate = calendar.getTime(); + } + break; + case None: + default: + assert false; // should not come here + } + t.setStartDateTime(startDate); + t.setEndDateTime(endDate); + return t; } /** * Removes a task from savvy tasker. * @param key the task to be removed - * @return true if the task is removed successfully - * @throws TaskNotFoundException if the task to be removed does not exist + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the remove operation is successful, an exception is thrown otherwise. */ - public boolean removeTask(ReadOnlyTask key) throws TaskNotFoundException { + public Task removeTask(ReadOnlyTask key) throws TaskNotFoundException { return tasks.remove(key); } /** * Replaces a task from savvy tasker. * @param key the task to be replaced + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date * @return true if the task is removed successfully - * @throws TaskNotFoundException if the task to be removed does not exist */ - public boolean replaceTask(ReadOnlyTask key, Task replacement) throws TaskNotFoundException, InvalidDateException { + public Task replaceTask(ReadOnlyTask key, Task replacement) throws TaskNotFoundException, InvalidDateException { return tasks.replace(key, replacement); } ``` @@ -871,6 +1161,7 @@ public class SmartDefaultDates { public interface ReadOnlyTask { int getId(); + int getGroupId(); boolean isMarked(); boolean isArchived(); String getTaskName(); @@ -900,23 +1191,68 @@ public interface ReadOnlyTask { .append(" Task Name: ") .append(getTaskName()) .append(" Archived: ") - .append(isArchived()) - .append(" Start: ") - .append(getStartDateTime()) - .append(" End: ") - .append(getEndDateTime()) - .append(" Location: ") - .append(getLocation()) - .append(" Priority: ") + .append(isArchived()); + if (getStartDateTime() != null) { + builder.append(" Start: ") + .append(getStartDateTime()); + } + if (getEndDateTime() != null) { + builder.append(" End: ") + .append(getEndDateTime()); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()); + } + builder.append(" Priority: ") + .append(getPriority()); + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + . append(getDescription()); + } + return builder.toString(); + } + + + /** + * Formats the task as text, showing all task details, formatted for the UI. + */ + default String getTextForUi() { + final StringBuilder builder = new StringBuilder(); + if (getStartDateTime() != null) { + builder.append(" Start: ") + .append(getStartDateTime()) + .append("\n"); + } + if (getEndDateTime() != null) { + builder.append(" End: ") + .append(getEndDateTime()) + .append("\n"); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()) + .append("\n"); + } + builder.append(" Priority: ") .append(getPriority()) - .append(" Recurring Type: ") - .append(getRecurringType()) - .append(" Nr. Recurrence: ") - .append(getNumberOfRecurrence()) - .append(" Category: ") - .append(getCategory()) - .append(" Description: ") - .append(getDescription()); + .append("\n"); + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()) + .append("\n"); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + .append(getDescription()) + .append("\n"); + } + builder.append(" Archived: ") + .append(isArchived()); return builder.toString(); } @@ -930,6 +1266,7 @@ public interface ReadOnlyTask { public class Task implements ReadOnlyTask { private int id; + private int groupId; private String taskName; private Date startDateTime; private Date endDateTime; @@ -944,12 +1281,13 @@ public class Task implements ReadOnlyTask { /** * Constructor with smart defaults */ - public Task(int id, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + public Task(int id, int groupId, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, String description, boolean isArchived) { SmartDefaultDates sdd = new SmartDefaultDates(startDateTime, endDateTime); this.id = id; + this.groupId = groupId; this.taskName = taskName; this.startDateTime = sdd.getStartDate(); this.endDateTime = sdd.getEndDate(); @@ -965,7 +1303,7 @@ public class Task implements ReadOnlyTask { this.recurringType = recurringType; } if (numberOfRecurrence == null) { - this.numberOfRecurrence = 0; + this.numberOfRecurrence = 1; } else { this.numberOfRecurrence = numberOfRecurrence.intValue(); } @@ -977,11 +1315,12 @@ public class Task implements ReadOnlyTask { /** * Constructor without smart defaults */ - public Task(int id, String taskName, Date startDateTime, Date endDateTime, String location, + public Task(int id, int groupId, String taskName, Date startDateTime, Date endDateTime, String location, PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, String description, boolean isArchived) { this.id = id; + this.groupId = groupId; this.taskName = taskName; this.startDateTime = startDateTime; this.endDateTime = endDateTime; @@ -997,7 +1336,7 @@ public class Task implements ReadOnlyTask { this.recurringType = recurringType; } if (numberOfRecurrence == null) { - this.numberOfRecurrence = 0; + this.numberOfRecurrence = 1; } else { this.numberOfRecurrence = numberOfRecurrence.intValue(); } @@ -1010,15 +1349,13 @@ public class Task implements ReadOnlyTask { this.taskName = taskName; // sets initial default values this.priority = PriorityLevel.Medium; - this.recurringType = RecurrenceType.None; - this.numberOfRecurrence = 0; } /** * Copy constructor. */ public Task(ReadOnlyTask source) { - this(source.getId(), source.getTaskName(), source.getStartDateTime(), + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), source.getEndDateTime(), source.getLocation(), source.getPriority(), source.getRecurringType(), source.getNumberOfRecurrence(), source.getCategory(), source.getDescription(), source.isArchived()); @@ -1030,12 +1367,13 @@ public class Task implements ReadOnlyTask { public Task(ReadOnlyTask source, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, String description) { - this(source.getId(), source.getTaskName(), source.getStartDateTime(), + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), source.getEndDateTime(), source.getLocation(), source.getPriority(), source.getRecurringType(), source.getNumberOfRecurrence(), source.getCategory(), source.getDescription(), source.isArchived()); //this.id should follow that of the source. + //this.groupId should follow that of the source. //this.isArchived should follow that of the source. this.taskName = taskName == null ? this.taskName : taskName; setStartDate(startDateTime); @@ -1089,6 +1427,11 @@ public class Task implements ReadOnlyTask { return this.id; } + @Override + public int getGroupId() { + return this.groupId; + } + @Override public boolean isMarked() { return isArchived(); // all marked tasks are archived @@ -1148,6 +1491,10 @@ public class Task implements ReadOnlyTask { this.id = id; } + public void setGroupId(int groupId) { + this.groupId = groupId; + } + public void setTaskName(String taskName) { this.taskName = taskName; } @@ -1226,6 +1573,17 @@ public class Task implements ReadOnlyTask { public String toString() { return getAsText(); } + + /** + * Creates a deep copy of this object. + */ + public Task clone() { + Task t = new Task(id, groupId, taskName, + (Date)startDateTime.clone(), (Date)endDateTime.clone(), + location, priority, recurringType, numberOfRecurrence, + category, description, isArchived); + return t; + } } ``` @@ -1285,6 +1643,8 @@ public class TaskList implements Iterable { private final ObservableList internalList = FXCollections.observableArrayList(); private int nextId = 0; private boolean isNextIdInitialized = false; + private int nextGroupId = 0; + private boolean isNextGroupIdInitialized = false; /** * Constructs empty TaskList. @@ -1298,10 +1658,8 @@ public class TaskList implements Iterable { */ public int getNextId() { if (!isNextIdInitialized) { - int nextLowest = -1; // first id to be used is 0. Start finding with -1 - LinkedList usedIds = new LinkedList(); + int nextLowest = 0; // first id to be used is 1. Start finding with 0 for (Task t : internalList) { - usedIds.add(t.getId()); if (t.getId() > nextLowest) { nextLowest = t.getId(); } @@ -1315,6 +1673,29 @@ public class TaskList implements Iterable { nextId++; return nextId; } + + /** + * Gets the next available group id for uniquely identifying a group of recurring tasks in + * Savvy Tasker. + * @return The next available group id; + */ + public int getNextGroupId() { + if (!isNextGroupIdInitialized) { + int nextLowest = 0; // first id to be used is 1. Start finding with 0 + for (Task t : internalList) { + if (t.getId() > nextLowest) { + nextLowest = t.getGroupId(); + } + } + // assumption that the number of tasks < 2^31 + // implementation will be buggy if nextId exceeds 2^31 + nextGroupId = nextLowest; + assert nextGroupId < Integer.MAX_VALUE; + isNextGroupIdInitialized = true; + } + nextGroupId++; + return nextGroupId; + } /** * Returns true if the list contains an equivalent task as the given argument. @@ -1338,10 +1719,11 @@ public class TaskList implements Iterable { /** * Adds a task to the list. - * - * @throws DuplicateTaskException if the person to add is a duplicate of an existing task in the list. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task added if the add is successful, an exception is thrown otherwise. */ - public void add(Task toAdd) throws DuplicateTaskException, InvalidDateException { + public Task add(Task toAdd) throws DuplicateTaskException, InvalidDateException { assert toAdd != null; if (contains(toAdd)) { throw new DuplicateTaskException(); @@ -1350,28 +1732,36 @@ public class TaskList implements Iterable { throw new InvalidDateException(); } internalList.add(toAdd); + return toAdd; } /** * Removes the equivalent task from the list. - * - * @throws TaskNotFoundException if no such task could be found in the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. */ - public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + public Task remove(ReadOnlyTask toRemove) throws TaskNotFoundException { assert toRemove != null; - final boolean taskFoundAndDeleted = internalList.remove(toRemove); - if (!taskFoundAndDeleted) { + int index = internalList.indexOf(toRemove); + if (index >= 0) { + final Task taskToDelete = internalList.get(index); + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskToDelete; + } else { throw new TaskNotFoundException(); } - return taskFoundAndDeleted; } /** * Replaces the equivalent task from the list. - * - * @throws TaskNotFoundException if no such task could be found in the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task replaced if the replace is successful, an exception is thrown otherwise. */ - public boolean replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotFoundException, InvalidDateException { + public Task replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotFoundException, InvalidDateException { assert toReplace != null; assert replacement != null; if (internalList.contains(toReplace)) { @@ -1379,7 +1769,7 @@ public class TaskList implements Iterable { throw new InvalidDateException(); } internalList.set(internalList.indexOf(toReplace), replacement); - return true; + return replacement; } else { throw new TaskNotFoundException(); @@ -1408,6 +1798,33 @@ public class TaskList implements Iterable { } } ``` +###### \java\seedu\savvytasker\storage\SavvyTaskerStorage.java +``` java + /** + * Sets the file path of the data file. Returns true if successful, false otherwise + */ + boolean setSavvyTaskerFilePath(String path); +``` +###### \java\seedu\savvytasker\storage\StorageManager.java +``` java + @Override + public boolean setSavvyTaskerFilePath(String path) { + return savvyTaskerStorage.setSavvyTaskerFilePath(path); + } +``` +###### \java\seedu\savvytasker\storage\StorageManager.java +``` java + @Override + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + logger.info(LogsCenter.getEventHandlingLogMessage(dslce, "Local storage location changed.")); + try { + saveSavvyTasker(dslce.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } +``` ###### \java\seedu\savvytasker\storage\XmlAdaptedTask.java ``` java /** @@ -1417,6 +1834,8 @@ public class XmlAdaptedTask { @XmlElement(required = true) private int id; + @XmlElement(required = false) + private int groupId; @XmlElement(required = true) private String taskName; @XmlElement(required = false) @@ -1428,10 +1847,6 @@ public class XmlAdaptedTask { @XmlElement(required = false) private PriorityLevel priority; @XmlElement(required = false) - private RecurrenceType recurringType; - @XmlElement(required = false) - private int numberOfRecurrence; - @XmlElement(required = false) private String category; @XmlElement(required = false) private String description; @@ -1451,13 +1866,12 @@ public class XmlAdaptedTask { */ public XmlAdaptedTask(ReadOnlyTask source) { id = source.getId(); + groupId = source.getGroupId(); taskName = source.getTaskName(); startDateTime = source.getStartDateTime(); endDateTime = source.getEndDateTime(); location = source.getLocation(); priority = source.getPriority(); - recurringType = source.getRecurringType(); - numberOfRecurrence = source.getNumberOfRecurrence(); category = source.getCategory(); description = source.getDescription(); isArchived = source.isArchived(); @@ -1475,13 +1889,39 @@ public class XmlAdaptedTask { final Date endDateTime = this.endDateTime; final String location = this.location; final PriorityLevel priority = this.priority; - final RecurrenceType recurringType = this.recurringType; - final int numberOfRecurrence = this.numberOfRecurrence; final String category = this.category; final String description = this.description; final boolean isArchived = this.isArchived; - return new Task(id, taskName, startDateTime, endDateTime, location, priority, - recurringType, numberOfRecurrence, category, description, isArchived); + return new Task(id, groupId, taskName, startDateTime, endDateTime, location, priority, + null, null, category, description, isArchived); } } ``` +###### \java\seedu\savvytasker\storage\XmlSavvyTaskerStorage.java +``` java + @Override + /** + * Changes the path of the storage location if the file at newPath can be successfully created. + */ + public boolean setSavvyTaskerFilePath(String newPath) { + try { + File file = new File(newPath); + if (FileUtil.createIfMissing(file)) { + this.filePath = newPath; + return true; + } else { + return false; + } + } catch (IOException e) { + // do nothing, can't set the path to new path + } + return false; + } +``` +###### \java\seedu\savvytasker\ui\StatusBarFooter.java +``` java + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + setSaveLocation(dslce.newPath); + } +``` diff --git a/collated/main/A0139915Wreused.md b/collated/main/A0139915Wreused.md deleted file mode 100644 index 34a4b0d4c6d1..000000000000 --- a/collated/main/A0139915Wreused.md +++ /dev/null @@ -1,17 +0,0 @@ -# A0139915Wreused -###### \java\seedu\savvytasker\commons\util\StringUtil.java -``` java - // reused original implementation of 'containsIgnoreCase' to find exact matches - public static boolean containsExactIgnoreCase(String source, String query) { - List strings = Arrays.asList(source); - return strings.stream().filter(s -> s.equalsIgnoreCase(query)).count() > 0; - } - - // reused original implementation of 'containsIgnoreCase' to find partial matches - public static boolean containsPartialIgnoreCase(String source, String query) { - if (source == null) return false; - String[] split = source.toLowerCase().split("\\s+"); - List strings = Arrays.asList(split); - return strings.stream().filter(s -> s.contains(query.toLowerCase())).count() > 0; - } -``` diff --git a/collated/main/A0139916U.md b/collated/main/A0139916U.md index 085b04221fa3..466daa605bbd 100644 --- a/collated/main/A0139916U.md +++ b/collated/main/A0139916U.md @@ -18,8 +18,8 @@ public class AliasSymbolChangedEvent extends BaseEvent { Removed; } - public final AliasSymbol symbol; - public final Action action; + private final AliasSymbol symbol; + private final Action action; public AliasSymbolChangedEvent(AliasSymbol symbol, Action action) { assert symbol != null; @@ -33,6 +33,14 @@ public class AliasSymbolChangedEvent extends BaseEvent { public String toString() { return "Alias symbol " + action.toString() + ": " + symbol.toString(); } + + public AliasSymbol getSymbol() { + return this.symbol; + } + + public Action getAction() { + return this.action; + } } ``` @@ -101,71 +109,6 @@ public class AliasCommand extends ModelRequiringCommand { this.logic = logic; } -``` -###### \java\seedu\savvytasker\logic\commands\AliasCommand.java -``` java - /** - * Checks if a command can perform undo operations - * @return true if the command supports undo, false otherwise - */ - @Override - public boolean canUndo() { - return true; - } - - /** - * Redo the alias command - * @return true if the operation completed successfully, false otherwise - */ - @Override - public boolean redo() { - execute(); - return true; - } - /** - * Undo the alias command - * @return true if the operation completed successfully, false otherwise - */ - @Override - public boolean undo() { - // TODO Auto-generated method stub - - assert model != null; - - AliasSymbol toRemove = null; - for(AliasSymbol symbol : model.getSavvyTasker().getReadOnlyListOfAliasSymbols()) { - if (symbol.getKeyword().equals(this.keyword)) { - toRemove = symbol; - break; - } - } - try { - model.removeAliasSymbol(toRemove); - } catch (SymbolKeywordNotFoundException e) { - e.printStackTrace(); - } - - return true; - } - - /** - * Check if command is an undo command - * @return true if the command is an undo operation, false otherwise - */ - @Override - public boolean isUndo() { - return false; - } - - /** - * Check if command is a redo command - * @return true if the command is a redo operation, false otherwise - */ - @Override - public boolean isRedo(){ - return false; - } -} ``` ###### \java\seedu\savvytasker\logic\commands\Command.java ``` java @@ -238,8 +181,10 @@ public class UnaliasCommand extends ModelRequiringCommand { } try { - if (toRemove == null) + if (toRemove == null) { return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); + } + toUndo = toRemove; model.removeAliasSymbol(toRemove); return new CommandResult(String.format(MESSAGE_SUCCESS, toRemove)); @@ -247,6 +192,7 @@ public class UnaliasCommand extends ModelRequiringCommand { return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); } } + ``` ###### \java\seedu\savvytasker\logic\LogicManager.java ``` java @@ -265,6 +211,7 @@ public class UnaliasCommand extends ModelRequiringCommand { parser.registerCommandParser(new RedoCommandParser()); parser.registerCommandParser(new AliasCommandParser()); parser.registerCommandParser(new UnaliasCommandParser()); + parser.registerCommandParser(new StorageCommandParser()); } private void loadAllAliasSymbols() { @@ -300,24 +247,42 @@ public class UnaliasCommand extends ModelRequiringCommand { return redone; } + /** + * Log the result of adding/removing a symbol in the parser. + * + * @param success if the operation succeeded + * @param changedSymbol the symbol that was involved in the operation + * @param successMsgFormat the message to print if the operation succeeded. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + * @param failureMsgFormat the message to print if the operation failed. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + */ + private void logParserSymbolChange(boolean success, AliasSymbol changedSymbol, + String successMsgFormat, String failureMsgFormat) { + if (success) { + logger.info(String.format(successMsgFormat, changedSymbol)); + } else { + logger.warning(String.format(failureMsgFormat, changedSymbol)); + } + } + @Subscribe public void handleAliasSymbolChangedEvent(AliasSymbolChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage( - event, "Alias symbol " + event.action.toString().toLowerCase())); - if (event.action.equals(AliasSymbolChangedEvent.Action.Added)) { - boolean success = parser.addAliasSymbol(event.symbol); - if (success) { - logger.info("Added alias symbol '"+event.symbol.getKeyword()+"' to parser"); - } else { - logger.warning("Failed to add alias symbol '"+event.symbol.getKeyword()+" to parser"); - } - } else { - boolean success = parser.removeAliasSymbol(event.symbol.getKeyword()); - if (success) { - logger.info("Removed alias symbol '"+event.symbol.getKeyword()+"' from parser"); - } else { - logger.warning("Failed to remove alias symbol '"+event.symbol.getKeyword()+" from parser"); - } + event, "Alias symbol " + event.getAction().toString().toLowerCase())); + + if (event.getAction().equals(AliasSymbolChangedEvent.Action.Added)) { + logParserSymbolChange( + parser.addAliasSymbol(event.getSymbol()), + event.getSymbol(), + "Added alias symbol '%s' to parser", + "Failed to add alias symbol '%s' to parser"); + } else if (event.getAction().equals(AliasSymbolChangedEvent.Action.Removed)) { + logParserSymbolChange( + parser.removeAliasSymbol(event.getSymbol().getKeyword()), + event.getSymbol(), + "Removed alias symbol '%s' from parser", + "Failed to remove alias symbol '%s' from parser"); } } @@ -340,22 +305,12 @@ import seedu.savvytasker.logic.parser.DateParser.InferredDate; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.RecurrenceType; -public class AddCommandParser implements CommandParser { +public class AddCommandParser extends TaskFieldParser { private static final String HEADER = "add"; private static final String READABLE_FORMAT = HEADER+" TASK_NAME [s/START_DATE] " + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; - private static final String REGEX_REF_TASK_NAME = "TaskName"; - private static final String REGEX_REF_START_DATE = "StartDate"; - private static final String REGEX_REF_END_DATE = "EndDate"; - private static final String REGEX_REF_LOCATION = "Location"; - private static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; - private static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; - private static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; - private static final String REGEX_REF_CATEGORY = "Category"; - private static final String REGEX_REF_DESCRIPTION = "Description"; - private static final Pattern REGEX_PATTERN = Pattern.compile( HEADER+"\\s+(?<"+REGEX_REF_TASK_NAME+">([^/]+?(\\s+|$))+)((?<=\\s)(" + "(s/(?<"+REGEX_REF_START_DATE+">[^/]+)(?!.*\\ss/))|" + @@ -367,8 +322,6 @@ public class AddCommandParser implements CommandParser { "(c/(?<"+REGEX_REF_CATEGORY+">[^/]+)(?!.*\\sc/))|" + "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]+)(?!.*\\sd/))" + ")(\\s|$)){0,10}", Pattern.CASE_INSENSITIVE); - - private static final TaskFieldParser TASK_PARSER = new TaskFieldParser(); @Override public String getHeader() { @@ -384,15 +337,15 @@ public class AddCommandParser implements CommandParser { public AddCommand parse(String commandText) throws ParseException { Matcher matcher = REGEX_PATTERN.matcher(commandText); if (matcher.matches()) { - InferredDate startDate = TASK_PARSER.parseStartDate(matcher.group(REGEX_REF_START_DATE)); - InferredDate endDate = TASK_PARSER.parseEndDate(matcher.group(REGEX_REF_END_DATE)); - String taskName = TASK_PARSER.parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); - String location = TASK_PARSER.parseLocation(matcher.group(REGEX_REF_LOCATION)); - PriorityLevel priority = TASK_PARSER.parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); - RecurrenceType recurrence = TASK_PARSER.parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); - Integer nrOfRecurrence = TASK_PARSER.parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); - String category = TASK_PARSER.parseCategory(matcher.group(REGEX_REF_CATEGORY)); - String description = TASK_PARSER.parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + InferredDate startDate = parseStartDate(matcher.group(REGEX_REF_START_DATE)); + InferredDate endDate = parseEndDate(matcher.group(REGEX_REF_END_DATE)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); return new AddCommand(taskName, startDate, endDate, location, priority, @@ -427,7 +380,8 @@ public class AliasCommandParser implements CommandParser { HEADER+"\\s+((?<=\\s)(" + "(r/(?<"+REGEX_REF_REPRESENTATION+">[^/]+)(?!.*\\sr/))|" + "(k/(?<"+REGEX_REF_KEYWORD+">[^/]+)(?!.*\\sk/))" + - ")(\\s|$)){2}" + ")(\\s|$)){2}", + Pattern.CASE_INSENSITIVE ); @Override @@ -920,16 +874,16 @@ import java.util.regex.Pattern; import seedu.savvytasker.commons.core.Messages; import seedu.savvytasker.logic.commands.ListCommand; -import seedu.savvytasker.model.task.ListType; +import seedu.savvytasker.model.ListType; public class ListCommandParser implements CommandParser { private static final String HEADER = "list"; - private static final String READABLE_FORMAT = HEADER+" [t/LIST_TYPE]"; + private static final String READABLE_FORMAT = HEADER+" [LIST_TYPE]"; private static final String REGEX_REF_LIST_TYPE = "ListType"; private static final Pattern REGEX_PATTERN = Pattern.compile( - HEADER+"\\s*((?<=\\s)t/(?<"+REGEX_REF_LIST_TYPE+">[^/]+))?", + HEADER+"\\s*((?<=\\s)(?<"+REGEX_REF_LIST_TYPE+">[^/]+))?", Pattern.CASE_INSENSITIVE); @Override @@ -1033,45 +987,86 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import seedu.savvytasker.logic.commands.Command; import seedu.savvytasker.logic.commands.HelpCommand; import seedu.savvytasker.logic.commands.IncorrectCommand; import seedu.savvytasker.model.alias.AliasSymbol; +/** + * Represents the master parser that is used by Logic. This is a parser containing + * all subparsers that will do the actual parsing to produce Command objects. This + * parser replaces keywords of the input with aliased representations before selecting + * a subparser to do the parsing. The selection of the subparser is based on the first + * word of the input, called the header, and matching it to the subparser that declares + * that it parses it. + */ public class MasterParser { private static final Pattern KEYWORD_PATTERN = - Pattern.compile("(\\S+)(\\s+|$)"); + Pattern.compile("([^\\s/]+)([\\s/]+|$)"); private final Map> commandParsers; private final Map aliasingSymbols; + private final ObservableList aliasList = FXCollections.observableArrayList(); + public MasterParser() { this.commandParsers = new HashMap>(); this.aliasingSymbols = new HashMap(); } + /** + * Parses the input text, selecting an appropriate registered parser to parse it. + * The parser selected is based on the first header word of the input text. The text + * is preprocessed, replacing any of its tokens that are keywords to an alias, before + * being passed to the parser. + * + * @param userInput the text to be parse + * @return the command that was parsed if successful, or IncorrectCommand if there is no + * parser that can parse the text or if there is a format error with the text. + */ public Command parse(String userInput) { String[] pieces = preprocessInitial(userInput.trim()); - if (pieces == null) - return new IncorrectCommand(userInput, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + if (pieces == null) { + return new IncorrectCommand(userInput, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } String header = pieces[0]; String body = pieces[1]; - String trueHeader = extractHeader(header); - CommandParser parser = commandParsers.get(trueHeader); - if (parser == null) - return new IncorrectCommand(header + body, String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); - if (parser.shouldPreprocess()) - body = preprocessBody(body); + CommandParser parser = selectParser(extractTrueHeader(header)); + if (parser == null) { + return new IncorrectCommand(header + body, + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } - String combined = header + body; + if (parser.shouldPreprocess()) { + return makeParserParse(parser, header + preprocessBody(body)); + } else { + return makeParserParse(parser, header + body); + } + } + + /** + * Makes the parser parse the preprocessed text. + * + * @param parser the parser to use to parse + * @param preprocessedText the text to parse + * @return the output Command from the parsing, or IncorrectCommand object if the parse failed + */ + private Command makeParserParse(CommandParser parser, String preprocessedText) { try { - return parser.parse(combined); + return parser.parse(preprocessedText); } catch (ParseException pe) { - return new IncorrectCommand(combined, String.format(pe.getFailureDetails())); + return new IncorrectCommand(preprocessedText, String.format(pe.getFailureDetails())); } } + private CommandParser selectParser(String header) { + return commandParsers.get(header); + } + /** * Does an initial preprocessing of a command text in case the header is aliased. * Returns a string array with 2 elements: the first is the header which is possibly aliased, @@ -1102,13 +1097,13 @@ public class MasterParser { } /** - * Gets the header from the preprocessed header as a preprocessed header may contain + * Gets the true header from the preprocessed header as a preprocessed header may contain * several tokens. * * @param preprocessedHeader the preprocessed header * @return the true header */ - private String extractHeader(String preprocessedHeader) { + private String extractTrueHeader(String preprocessedHeader) { Matcher matcher = KEYWORD_PATTERN.matcher(preprocessedHeader); if (matcher.find()) { @@ -1206,7 +1201,8 @@ public class MasterParser { return false; if (isCommandParserRegistered(symbol.getKeyword())) return false; - + + aliasList.add(symbol); aliasingSymbols.put(symbol.getKeyword(), symbol); return true; } @@ -1221,7 +1217,12 @@ public class MasterParser { public boolean removeAliasSymbol(String symbolKeyword) { assert symbolKeyword != null; - return aliasingSymbols.remove(symbolKeyword) != null; + AliasSymbol symbol = aliasingSymbols.remove(symbolKeyword); + if (symbol != null) { + return aliasList.remove(symbol); + } else { + return false; + } } /** @@ -1240,8 +1241,13 @@ public class MasterParser { * @see #removeAliasSymbol */ public void clearAllAliasSymbols() { + aliasList.clear(); aliasingSymbols.clear(); } + + public ObservableList getAliasSymbolList() { + return aliasList; + } } ``` ###### \java\seedu\savvytasker\logic\parser\ModifyCommandParser.java @@ -1258,22 +1264,13 @@ import seedu.savvytasker.logic.parser.DateParser.InferredDate; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.RecurrenceType; -public class ModifyCommandParser implements CommandParser { +public class ModifyCommandParser extends TaskFieldParser { private static final String HEADER = "modify"; private static final String READABLE_FORMAT = HEADER+" INDEX [t/TASK_NAME] [s/START_DATE] " + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; private static final String REGEX_REF_INDEX = "Index"; - private static final String REGEX_REF_TASK_NAME = "TaskName"; - private static final String REGEX_REF_START_DATE = "StartDate"; - private static final String REGEX_REF_END_DATE = "EndDate"; - private static final String REGEX_REF_LOCATION = "Location"; - private static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; - private static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; - private static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; - private static final String REGEX_REF_CATEGORY = "Category"; - private static final String REGEX_REF_DESCRIPTION = "Description"; private static final Pattern REGEX_PATTERN = Pattern.compile( HEADER+"\\s+(?<"+REGEX_REF_INDEX+">([^/]+?(\\s+|$))+)((?<=\\s)(" + @@ -1288,7 +1285,6 @@ public class ModifyCommandParser implements CommandParser { "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]*)(?!.*\\sd/))" + ")(\\s|$)){0,11}", Pattern.CASE_INSENSITIVE); - private static final TaskFieldParser TASK_PARSER = new TaskFieldParser(); private static final IndexParser INDEX_PARSER = new IndexParser(); @Override @@ -1309,13 +1305,13 @@ public class ModifyCommandParser implements CommandParser { int index = parseIndex(matcher.group(REGEX_REF_INDEX)); InferredDate startDate = parseDate(matcher.group(REGEX_REF_START_DATE)); InferredDate endDate = parseDate(matcher.group(REGEX_REF_END_DATE)); - String taskName = TASK_PARSER.parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); - String location = TASK_PARSER.parseLocation(matcher.group(REGEX_REF_LOCATION)); - PriorityLevel priority = TASK_PARSER.parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); - RecurrenceType recurrence = TASK_PARSER.parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); - Integer nrOfRecurrence = TASK_PARSER.parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); - String category = TASK_PARSER.parseCategory(matcher.group(REGEX_REF_CATEGORY)); - String description = TASK_PARSER.parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); return new ModifyCommand(index, taskName, startDate, endDate, location, priority, @@ -1337,10 +1333,10 @@ public class ModifyCommandParser implements CommandParser { private InferredDate parseDate(String dateText) throws ParseException { if (dateText != null && dateText.trim().isEmpty()) { - return TASK_PARSER.dateParser.new InferredDate(new Date(), true, true); + return dateParser.new InferredDate(new Date(), true, true); } - return TASK_PARSER.parseStartDate(dateText); + return parseStartDate(dateText); } } ``` @@ -1422,37 +1418,100 @@ public class RedoCommandParser implements CommandParser { Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); } +} +``` +###### \java\seedu\savvytasker\logic\parser\StorageCommandParser.java +``` java +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.StorageCommand; + +public class StorageCommandParser implements CommandParser { + private static final String HEADER = "storage"; + private static final String READABLE_FORMAT = HEADER+" NEW_PATH"; + + private static final String REGEX_REF_PATH = "Path"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_PATH+">.*)", + Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public StorageCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String path = matcher.group(REGEX_REF_PATH).trim(); + + if (!path.isEmpty()) { + return new StorageCommand(path); + } + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + } ``` ###### \java\seedu\savvytasker\logic\parser\TaskFieldParser.java ``` java package seedu.savvytasker.logic.parser; +import seedu.savvytasker.logic.commands.Command; import seedu.savvytasker.logic.parser.DateParser.InferredDate; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.RecurrenceType; /** * This class contains common parsing methods for parsing Task fields. + * Each of the parse method takes in a string which can be null, and return + * the respective parsed object. */ -public class TaskFieldParser { +public abstract class TaskFieldParser implements CommandParser { + /* + * Provides standard regex ref names for various task fields that might be used by subclasses. + */ + protected static final String REGEX_REF_TASK_NAME = "TaskName"; + protected static final String REGEX_REF_START_DATE = "StartDate"; + protected static final String REGEX_REF_END_DATE = "EndDate"; + protected static final String REGEX_REF_LOCATION = "Location"; + protected static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; + protected static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; + protected static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; + protected static final String REGEX_REF_CATEGORY = "Category"; + protected static final String REGEX_REF_DESCRIPTION = "Description"; + protected final DateParser dateParser; - public TaskFieldParser() { + protected TaskFieldParser() { this.dateParser = new DateParser(); } - public String parseTaskName(String taskNameText) throws ParseException { + protected String parseTaskName(String taskNameText) throws ParseException { if (taskNameText == null) return null; return taskNameText.trim(); } - public InferredDate parseStartDate(String dateText) throws ParseException { + protected InferredDate parseStartDate(String dateText) throws ParseException { return parseDate(dateText, "START_DATE: "); } - public InferredDate parseEndDate(String dateText) throws ParseException { + protected InferredDate parseEndDate(String dateText) throws ParseException { return parseDate(dateText, "END_DATE: "); } @@ -1468,13 +1527,13 @@ public class TaskFieldParser { } } - public String parseLocation(String locationText) throws ParseException { + protected String parseLocation(String locationText) throws ParseException { if (locationText == null) return null; return locationText.trim(); } - public PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseException { + protected PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseException { if (priorityLevelText == null) return null; @@ -1486,7 +1545,7 @@ public class TaskFieldParser { } } - public RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws ParseException { + protected RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws ParseException { if (recurrenceTypeText == null) return null; @@ -1498,7 +1557,7 @@ public class TaskFieldParser { } } - public Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseException { + protected Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseException { if (numRecurrenceText == null) return null; @@ -1520,13 +1579,13 @@ public class TaskFieldParser { return numRecurrence; } - public String parseCategory(String categoryText) throws ParseException { + protected String parseCategory(String categoryText) throws ParseException { if (categoryText == null) return null; return categoryText.trim(); } - public String parseDescription(String descriptionText) throws ParseException { + protected String parseDescription(String descriptionText) throws ParseException { if (descriptionText == null) return null; return descriptionText.trim(); @@ -1550,7 +1609,8 @@ public class UnaliasCommandParser implements CommandParser { private static final String REGEX_REF_KEYWORD = "Keyword"; private static final Pattern REGEX_PATTERN = Pattern.compile( - HEADER+"\\s+(?<"+REGEX_REF_KEYWORD+">[^/]+)" + HEADER+"\\s+(?<"+REGEX_REF_KEYWORD+">[^/]+)", + Pattern.CASE_INSENSITIVE ); @Override @@ -1705,7 +1765,7 @@ public class AliasSymbol { assert keyword != null && !keyword.matches(".*\\s+.*"); assert representation != null && !representation.isEmpty(); - this.keyword = keyword; + this.keyword = keyword.toLowerCase(); this.representation = representation; } @@ -1892,6 +1952,49 @@ public class SymbolKeywordNotFoundException extends IllegalValueException { } } ``` +###### \java\seedu\savvytasker\model\ListType.java +``` java +package seedu.savvytasker.model; + +/** + * This enum represents what and how to list tasks/symbols. + */ +public enum ListType { + /** + * List tasks by due date. + */ + DueDate, + + /** + * List tasks by priority level. + */ + PriorityLevel, + + /** + * List archived tasks. + */ + Archived, + + /** + * List aliases. + */ + Alias; + + /** + * Gets a ListType enum object from its enum name, ignoring cases. + * @param name the name of the ListType enum object + * @return the corresponding ListType enum object + */ + public static ListType valueOfIgnoreCase(String name) { + for (ListType type : ListType.values()) { + if (type.toString().equalsIgnoreCase(name)) + return type; + } + + throw new IllegalArgumentException("Unknown list type: " + name); + } +} +``` ###### \java\seedu\savvytasker\model\ModelManager.java ``` java @@ -1918,6 +2021,11 @@ public class SymbolKeywordNotFoundException extends IllegalValueException { indicateSavvyTaskerChanged(); indicateAliasSymbolRemoved(symbol); } + + @Override + public int getAliasSymbolCount() { + return savvyTasker.getAliasSymbolCount(); + } ``` ###### \java\seedu\savvytasker\model\ReadOnlySavvyTasker.java ``` java @@ -2018,44 +2126,6 @@ public enum FindType { } } ``` -###### \java\seedu\savvytasker\model\task\ListType.java -``` java -package seedu.savvytasker.model.task; - -/** - * This enum represents the different ways to list tasks. - */ -public enum ListType { - /** - * List tasks by due date. - */ - DueDate, - - /** - * List tasks by priority level. - */ - PriorityLevel, - - /** - * List archived tasks. - */ - Archived; - - /** - * Gets a ListType enum object from its enum name, ignoring cases. - * @param name the name of the ListType enum object - * @return the corresponding ListType enum object - */ - public static ListType valueOfIgnoreCase(String name) { - for (ListType type : ListType.values()) { - if (type.toString().equalsIgnoreCase(name)) - return type; - } - - throw new IllegalArgumentException("Unknown list type: " + name); - } -} -``` ###### \java\seedu\savvytasker\model\task\PriorityLevel.java ``` java package seedu.savvytasker.model.task; diff --git a/collated/test/A0139915W.md b/collated/test/A0139915W.md index aaae7a2d2735..3a51dbc567e6 100644 --- a/collated/test/A0139915W.md +++ b/collated/test/A0139915W.md @@ -22,7 +22,15 @@ public class AddCommandTest extends SavvyTaskerGuiTest { //invalid command commandBox.runCommand("adds Bad Command Task"); - assertResultMessage(String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertResultMessage("Input: adds Bad Command Task\n" + + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + + //invalid start end date + Date start = getDate("31/12/2015"); + Date end = getDate("30/12/2015"); + commandBox.runCommand("add bad start-end pair s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(AddCommand.MESSAGE_INVALID_START_END)); } private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { @@ -32,11 +40,30 @@ public class AddCommandTest extends SavvyTaskerGuiTest { TaskCardHandle addedCard = taskListPanel.navigateToTask(taskToAdd.getTaskName()); assertMatching(taskToAdd, addedCard); - //confirm the list now contains all previous persons plus the new person + //confirm the list now contains all previous tasks plus the new task TestTask[] expectedList = TestUtil.addTasksToList(currentList, taskToAdd); assertTrue(taskListPanel.isListMatching(expectedList)); } + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } } ``` ###### \java\guitests\DeleteCommandTest.java @@ -118,6 +145,11 @@ public class FindCommandTest extends SavvyTaskerGuiTest { public void find_nonEmptyList_byExactMatch() { assertFindResult("find t/exact Nearer Due Task", td.nearerDue); // one matching result only } + + @Test + public void find_nonEmptyList_byCategory() { + assertFindResult("find t/category priority", td.highPriority, td.medPriority, td.lowPriority); // matching 3 results + } @Test public void find_emptyList(){ @@ -128,7 +160,7 @@ public class FindCommandTest extends SavvyTaskerGuiTest { @Test public void find_invalidCommand_fail() { commandBox.runCommand("findmyring"); - assertResultMessage(String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertResultMessage("Input: findmyring\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); } private void assertFindResult(String command, TestTask... expectedHits) { @@ -146,6 +178,7 @@ public class FindCommandTest extends SavvyTaskerGuiTest { */ public class TaskCardHandle extends GuiHandle { private static final String TASKNAME_FIELD_ID = "#taskName"; + private static final String DETAILS_FIELD_ID = "#details"; private Node node; @@ -161,16 +194,21 @@ public class TaskCardHandle extends GuiHandle { public String getTaskName() { return getTextFromLabel(TASKNAME_FIELD_ID); } + + public String getDetails() { + return getTextFromLabel(DETAILS_FIELD_ID); + } public boolean isSameTask(ReadOnlyTask task) { - return getTaskName().equals(task.getTaskName()); + return getTaskName().equals(task.getTaskName()) && getDetails().equals(task.getTextForUi()); } @Override public boolean equals(Object obj) { if(obj instanceof TaskCardHandle) { TaskCardHandle handle = (TaskCardHandle) obj; - return getTaskName().equals(handle.getTaskName()); //TODO: compare the rest + return getTaskName().equals(handle.getTaskName()) && + getDetails().equals(handle.getDetails()); } return super.equals(obj); } @@ -354,6 +392,12 @@ public class ListCommandTest extends SavvyTaskerGuiTest { td.highPriority, td.medPriority, td.lowPriority); } + @Test + public void list_nonEmptyList_byInvalidSwitch() { + commandBox.runCommand("list badswitch"); + assertResultMessage("Input: list badswitch\nLIST_TYPE: Unknown type \'badswitch\'"); + } + @Test public void list_nonEmptyList_byDueDate() { // covered by list_nonEmptyList() @@ -361,13 +405,13 @@ public class ListCommandTest extends SavvyTaskerGuiTest { @Test public void list_nonEmptyList_byPriority() { - assertListResult("list t/PriorityLevel", td.highPriority, td.medPriority, + assertListResult("list PriorityLevel", td.highPriority, td.medPriority, td.furthestDue, td.nearerDue, td.notSoNearerDue, td.earliestDue, td.lowPriority); //multiple results } @Test public void list_nonEmptyList_byArchived() { - assertListResult("list t/Archived", td.longDue); // one matching result only + assertListResult("list Archived", td.longDue); // one matching result only } @Test @@ -379,7 +423,7 @@ public class ListCommandTest extends SavvyTaskerGuiTest { @Test public void find_invalidCommand_fail() { commandBox.runCommand("listmytasks"); - assertResultMessage(String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertResultMessage("Input: listmytasks\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); } private void assertListResult(String command, TestTask... expectedHits ) { @@ -390,6 +434,383 @@ public class ListCommandTest extends SavvyTaskerGuiTest { } } ``` +###### \java\guitests\ModifyCommandTest.java +``` java +public class ModifyCommandTest extends SavvyTaskerGuiTest { + + @Test + public void add() { + //modify task + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToModify = currentList[0]; + Date start = getDate("30/12/2016"); + Date end = getDate("31/12/2016"); + taskToModify.setStartDateTime(start); + taskToModify.setEndDateTime(end); + assertModifySuccess("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end), taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + taskToModify.setStartDateTime(null); + taskToModify.setEndDateTime(null); + assertModifySuccess("modify 1 s/ e/", taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + //modify invalid index + commandBox.runCommand("modify " + currentList.length + "1" + " s/sat"); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX)); + + //modify with invalid end date + start = getDate("31/12/2016"); + end = getDate("30/12/2016"); + commandBox.runCommand("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_START_END)); + } + + private void assertModifySuccess(String command, TestTask taskToModify, TestTask... currentList) { + commandBox.runCommand(command); + + //confirm the new card contains the right data + TaskCardHandle modifiedCard = taskListPanel.navigateToTask(taskToModify.getTaskName()); + assertMatching(taskToModify, modifiedCard); + + //confirm the list now contains all previous persons plus the new person + TestTask[] expectedList = TestUtil.replaceTaskFromList(currentList, taskToModify); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + +} +``` +###### \java\seedu\savvytasker\commons\util\SmartDefaultDatesTest.java +``` java +public class SmartDefaultDatesTest { + + @Test + public void smartDefaultDates_parseStart() { + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredEnd = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start date, assumed to on the given date at 12am + // and to end on the given date at 2359:59 + Date expectedStartTime = getDate("31/12/2016 000000"); + Date expectedEndTime = getDate("31/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start time, assumed to start today at the given time + // and to end today 2359:59 + expectedStartTime = getDate(sdf.format(today) + " 150000"); + expectedEndTime = getDate(sdf.format(today) + " 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseEnd() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredStart = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date, assumed to start today at 12am + // and to end on the given date at 2359:59 + Date expectedStartTime = getDate(sdf.format(today) + " 000000"); + Date expectedEndTime = getDate("31/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end time, assumed to start today at 12am + // and to end at the given time today + expectedStartTime = getDate(sdf.format(today) + " 000000"); + expectedEndTime = getDate(sdf.format(today) + " 150000"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2000"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date in the past, start date will be null + // and to end on the given date at 2359:59 + expectedStartTime = null; + expectedEndTime = getDate("31/12/2000 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseStartEnd() { + // START_TIME + // date not supplied -> today + // time not supplied -> 0000 + // END_TIME + // date not supplied -> today + // time not supplied -> 2359 + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end + // start defaults to 0000 + // end defaults to 2359:59 + Date expectedStartTime = getDate("31/12/2016 000000"); + Date expectedEndTime = getDate("31/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/30/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end, end date earlier than start + // start defaults to 0000 + // end defaults to 2359:59 + // no restrictions imposed on end time earlier than start time + expectedStartTime = getDate("31/12/2016 000000"); + expectedEndTime = getDate("30/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10am"); + inferredEnd = dateParser.parseSingle("10pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end + // start and end defaults to the given time today + expectedStartTime = getDate(sdf.format(today) + " 100000"); + expectedEndTime = getDate(sdf.format(today) + " 220000"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10pm"); + inferredEnd = dateParser.parseSingle("10am"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end, end time ends before start time + // start and end defaults to the given time today + // no restrictions imposed on end time being earlier + expectedStartTime = getDate(sdf.format(today) + " 220000"); + expectedEndTime = getDate(sdf.format(today) + " 100000"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_defaultParse() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + Date actualStart = sdd.getStart(dateParser.new InferredDate(new Date(), true, true)); + Date actualEnd = sdd.getEnd(dateParser.new InferredDate(new Date(), true, true)); + Date expectedStart = null; + Date expectedEnd = null; + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("10pm")); + actualEnd = sdd.getEnd(dateParser.parseSingle("10am")); + } catch (ParseException e) { + assert false; //won't get here + } + expectedStart = getDate(sdf.format(today) + " 220000"); + expectedEnd = getDate(sdf.format(today) + " 100000"); + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("12/31/2016")); + actualEnd = sdd.getEnd(dateParser.parseSingle("12/31/2016")); + } catch (ParseException e) { + assert false; //won't get here + } + expectedStart = getDate("31/12/2016 000000"); + expectedEnd = getDate("31/12/2016 235959"); + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HHmmss"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date today(int hours_24, int minute_60) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.set(Calendar.HOUR_OF_DAY, hours_24); + calendar.set(Calendar.MINUTE, minute_60); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } +} +``` +###### \java\seedu\savvytasker\model\task\TaskListTest.java +``` java +public class TaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void taskList_addDuplicate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(DuplicateTaskException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + tasks.add(t); // passes + assertEquals(1, tasks.getInternalList().size()); + tasks.add(t); // fails + } + + @Test + public void taskList_addInvalidDate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.add(t); // fails, end date earlier than start date + } + + @Test + public void taskList_removeNonExistent() throws TaskNotFoundException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.remove(t); // fails + } + + @Test + public void taskList_replaceNonExistent() throws TaskNotFoundException, InvalidDateException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.replace(t, t); // fails + } + + @Test + public void taskList_replaceInvalidDate() throws TaskNotFoundException, InvalidDateException, DuplicateTaskException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("30/12/2016")); + t.setEndDateTime(getDate("31/12/2016")); + tasks.add(t); + assertEquals(1, tasks.getInternalList().size()); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.replace(t, t); // fails, end date earlier than start date + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +``` ###### \java\seedu\savvytasker\testutil\SavvyTaskerBuilder.java ``` java /** @@ -497,6 +918,7 @@ public class TaskBuilder { public class TestTask implements ReadOnlyTask { private int id; + private int groupId; private String taskName; private Date startDateTime; private Date endDateTime; @@ -515,10 +937,16 @@ public class TestTask implements ReadOnlyTask { this.numberOfRecurrence = 0; } + @Override public int getId() { return id; } + @Override + public int getGroupId() { + return groupId; + } + @Override public String getTaskName() { return taskName; @@ -577,6 +1005,10 @@ public class TestTask implements ReadOnlyTask { public void setId(int id) { this.id = id; } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } public void setTaskName(String taskName) { this.taskName = taskName; @@ -624,8 +1056,32 @@ public class TestTask implements ReadOnlyTask { } public String getAddCommand() { + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HHmm"); StringBuilder sb = new StringBuilder(); sb.append("add " + this.getTaskName()); + if (startDateTime != null) { + sb.append(" s/ ").append(sdf.format(startDateTime)); + } + if (endDateTime != null) { + sb.append(" e/ ").append(sdf.format(endDateTime)); + } + if (location != null && !location.isEmpty()) { + sb.append(" l/ ").append(location); + } + if (priority != null && priority != PriorityLevel.Medium) { + // p/ defaults to medium, if set to medium, take as non-existent + sb.append(" p/ ").append(priority.toString()); + } + if (recurringType != null && recurringType != RecurrenceType.None) { + // r/ defaults to none, if set to none, take as non-existent + sb.append(" r/ ").append(recurringType.toString()); + } + if (category != null && !category.isEmpty()) { + sb.append(" c/ ").append(category); + } + if (description != null && !description.isEmpty()) { + sb.append(" d/ ").append(description); + } return sb.toString(); } } @@ -674,8 +1130,13 @@ public class TestTask implements ReadOnlyTask { * @param index The index of the task to be replaced. * @return */ - public static TestTask[] replaceTaskFromList(TestTask[] tasks, TestTask task, int index) { - tasks[index] = task; + public static TestTask[] replaceTaskFromList(TestTask[] tasks, TestTask task) { + for (int i = 0; i < tasks.length; ++i) { + if (tasks[i].getId() == task.getId()) { + tasks[i] = task; + break; + } + } return tasks; } @@ -710,26 +1171,26 @@ public class TypicalTestTasks { public TypicalTestTasks() { try { - highPriority = new TaskBuilder().withId(0).withTaskName("High Priority Task") - .withPriority(PriorityLevel.High).build(); - medPriority = new TaskBuilder().withId(1).withTaskName("Medium Priority Task") - .withPriority(PriorityLevel.Medium).build(); - lowPriority = new TaskBuilder().withId(2).withTaskName("Low Priority Task") - .withPriority(PriorityLevel.Low).build(); - furthestDue = new TaskBuilder().withId(3).withTaskName("Furthest Due Task") + highPriority = new TaskBuilder().withId(1).withTaskName("High Priority Task") + .withPriority(PriorityLevel.High).withCategory("priority").build(); + medPriority = new TaskBuilder().withId(2).withTaskName("Medium Priority Task") + .withPriority(PriorityLevel.Medium).withCategory("priority").build(); + lowPriority = new TaskBuilder().withId(3).withTaskName("Low Priority Task") + .withPriority(PriorityLevel.Low).withCategory("priority").build(); + furthestDue = new TaskBuilder().withId(4).withTaskName("Furthest Due Task") .withEndDateTime(getDate("01/12/2016")).build(); - nearerDue = new TaskBuilder().withId(4).withTaskName("Nearer Due Task") + nearerDue = new TaskBuilder().withId(5).withTaskName("Nearer Due Task") .withEndDateTime(getDate("01/11/2016")).build(); - notSoNearerDue = new TaskBuilder().withId(5).withTaskName("Not So Nearer Due Task") + notSoNearerDue = new TaskBuilder().withId(6).withTaskName("Not So Nearer Due Task") .withEndDateTime(getDate("02/11/2016")).build(); - earliestDue = new TaskBuilder().withId(6).withTaskName("Earliest Due Task") + earliestDue = new TaskBuilder().withId(7).withTaskName("Earliest Due Task") .withEndDateTime(getDate("01/10/2016")).build(); - longDue = new TaskBuilder().withId(7).withTaskName("Long Due Task") + longDue = new TaskBuilder().withId(8).withTaskName("Long Due Task") .withEndDateTime(getDate("01/1/2016")).withArchived(true).build(); //Manually added - happy = new TaskBuilder().withId(8).withTaskName("Happy Task").build(); - haloween = new TaskBuilder().withId(9).withTaskName("Haloween Task").build(); + happy = new TaskBuilder().withId(9).withTaskName("Happy Task").build(); + haloween = new TaskBuilder().withId(10).withTaskName("Haloween Task").build(); } catch (IllegalValueException e) { e.printStackTrace(); assert false : "not possible"; @@ -747,8 +1208,6 @@ public class TypicalTestTasks { st.addTask(new Task(td.notSoNearerDue)); st.addTask(new Task(td.earliestDue)); st.addTask(new Task(td.longDue)); - } catch (DuplicateTaskException e) { - assert false : "not possible"; } catch (InvalidDateException e) { assert false : "not possible"; } diff --git a/collated/test/A0139916U.md b/collated/test/A0139916U.md index aef3ff61c827..8870b3a9d0e2 100644 --- a/collated/test/A0139916U.md +++ b/collated/test/A0139916U.md @@ -34,6 +34,7 @@ public class ParserTest { private RedoCommandParser redoParser; private AliasCommandParser aliasParser; private UnaliasCommandParser unaliasParser; + private StorageCommandParser storageParser; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -54,49 +55,50 @@ public class ParserTest { redoParser = new RedoCommandParser(); aliasParser = new AliasCommandParser(); unaliasParser = new UnaliasCommandParser(); + storageParser = new StorageCommandParser(); } @Test - public void parse_add_reorder() throws ParseException { + public void parseAdd_reorder() throws ParseException { assertNotNull(addParser.parse("add task l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); } @Test - public void parse_add_multipleSpaces() throws ParseException { + public void parseAdd_multipleSpaces() throws ParseException { assertNotNull(addParser.parse("add Multiple Spaces s/ 2pm")); } @Test - public void parse_add_sameOptionMultipleTimes() throws ParseException { + public void parseAdd_sameOptionMultipleTimes_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task s/ tomorrow 3pm s/ tomorrow 10pm"); } @Test - public void parse_add_missingTaskName() throws ParseException { + public void parseAdd_missingTaskName_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add s/ tomorrow 3pm"); } @Test - public void parse_add_arbitrarySlash() throws ParseException { + public void parseAdd_arbitrarySlash_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task s/ tomorrow 2pm/3pm e/ sunday"); } @Test - public void parse_add_fullValid() throws ParseException { + public void parseAdd_fullValid() throws ParseException { assertNotNull(addParser.parse("add task s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); } @Test - public void parse_add_invalidRecurrenceType() throws ParseException { + public void parseAdd_invalidRecurrenceType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task r/ Error "); } @Test - public void parse_add_invalidPriorityLevel() throws ParseException { + public void parseAdd_invalidPriorityLevel_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task p/ Error "); } @@ -104,34 +106,34 @@ public class ParserTest { //================================================================================== @Test - public void parse_delete_noIndexSpecified() throws ParseException { + public void parseDelete_noIndexSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); deleteParser.parse("delete"); } @Test - public void parse_delete_oneIndex() throws ParseException { + public void parseDelete_oneIndex() throws ParseException { assertNotNull(deleteParser.parse("delete 1")); } @Test - public void parse_delete_multipleIndices() throws ParseException { + public void parseDelete_multipleIndices() throws ParseException { assertNotNull(deleteParser.parse("delete 1 2 3")); } @Test - public void parse_delete_multipleSpacesIndices() throws ParseException { + public void parseDelete_multipleSpacesIndices() throws ParseException { assertNotNull(deleteParser.parse("delete 1 2 3")); } @Test - public void parse_delete_negativeIndex() throws ParseException { + public void parseDelete_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); deleteParser.parse("delete -1"); } @Test - public void parse_delete_zeroIndex() throws ParseException { + public void parseDelete_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); deleteParser.parse("delete 0"); } @@ -139,198 +141,198 @@ public class ParserTest { //================================================================================== @Test - public void parse_modify_noIndex() throws ParseException { + public void parseModify_noIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify t/ newtask"); } @Test - public void parse_modify_multipleIndex() throws ParseException { + public void parseModify_multipleIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify 1 2 3 t/ newtask"); } @Test - public void parse_modify_negativeIndex() throws ParseException { + public void parseModify_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify -1 t/ newtask"); } @Test - public void parse_modify_zeroIndex() throws ParseException { + public void parseModify_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify 0 t/ newtask"); } @Test - public void parse_modify_onlySpecifyIndex() throws ParseException { + public void parseModify_onlySpecifyIndex() throws ParseException { assertNotNull(modifyParser.parse("modify 1")); } @Test - public void parse_modify_fullValid() throws ParseException { + public void parseModify_fullValid() throws ParseException { assertNotNull(modifyParser.parse("modify 3 t/ newtask s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); } @Test - public void parse_modify_reorder() throws ParseException { + public void parseModify_reorder() throws ParseException { assertNotNull(modifyParser.parse("modify 1 l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); } @Test - public void parse_modify_multipleSpaces() throws ParseException { + public void parseModify_multipleSpaces() throws ParseException { assertNotNull(modifyParser.parse("modify 1 t/ Multiple Spaces s/ 2pm")); } //================================================================================== @Test - public void parse_clear_spaces() throws ParseException { + public void parseClear_spaces() throws ParseException { assertNotNull(clearParser.parse("clear ")); } @Test - public void parse_clear_invalid() throws ParseException { + public void parseClear_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); clearParser.parse("clear 1"); } @Test - public void parse_clear_valid() throws ParseException { + public void parseClear_valid() throws ParseException { assertNotNull(clearParser.parse("clear")); } //================================================================================== @Test - public void parse_list_noParameters() throws ParseException { + public void parseList_noParameters() throws ParseException { assertNotNull(listParser.parse("list")); } @Test - public void parse_list_noParametersSpaces() throws ParseException { + public void parseList_noParametersSpaces() throws ParseException { assertNotNull(listParser.parse("list ")); } @Test public void parse_list_valid() throws ParseException { - assertNotNull(listParser.parse("list t/ Priority Level ")); + assertNotNull(listParser.parse("list Priority Level ")); } @Test - public void parse_list_invalidType() throws ParseException { + public void parseList_invalidType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); - listParser.parse("list t/ Error "); + listParser.parse("list Error "); } //================================================================================== @Test - public void parse_find_noKeywords() throws ParseException { + public void parseFind_noKeywords_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find"); } @Test - public void parse_find_noKeywordsSpaces() throws ParseException { + public void parseFind_noKeywordsSpaces_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find "); } @Test - public void parse_find_noKeywordsButWithType() throws ParseException { + public void parseFind_noKeywordsButWithType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find t/ Exact "); } @Test - public void parse_find_invalidType() throws ParseException { + public void parseFind_invalidType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find t/ Error some words"); } @Test - public void parse_find_validAfter() throws ParseException { + public void parseFind_validAfter() throws ParseException { assertNotNull(findParser.parse("find t/ Exact this word ")); } @Test - public void parse_find_validBefore() throws ParseException { + public void parseFind_validBefore() throws ParseException { assertNotNull(findParser.parse("find some words t/ Partial ")); } @Test - public void parse_find_validBeforeAndAfter() throws ParseException { + public void parseFind_validBeforeAndAfter() throws ParseException { assertNotNull(findParser.parse("find some words t/ Full some words after ")); } //================================================================================== @Test - public void parse_help_spaces() throws ParseException { + public void parseHelp_spaces() throws ParseException { assertNotNull(helpParser.parse("help ")); } @Test - public void parse_help_invalid() throws ParseException { + public void parseHelp_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("help 1"); } @Test - public void parse_help_valid() throws ParseException { + public void parseHelp_valid() throws ParseException { assertNotNull(helpParser.parse("help")); } //================================================================================== @Test - public void parse_exit_spaces() throws ParseException { + public void parseExit_spaces() throws ParseException { assertNotNull(exitParser.parse("exit ")); } @Test - public void parse_exit_invalid() throws ParseException { + public void parseExit_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("exit 1"); } @Test - public void parse_exit_valid() throws ParseException { + public void parseExit_valid() throws ParseException { assertNotNull(exitParser.parse("exit")); } //================================================================================== @Test - public void parse_mark_noIndexSpecified() throws ParseException { + public void parseMark_noIndexSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); markParser.parse("mark"); } @Test - public void parse_mark_oneIndex() throws ParseException { + public void parseMark_oneIndex() throws ParseException { assertNotNull(markParser.parse("mark 1")); } @Test - public void parse_mark_multipleIndices() throws ParseException { + public void parseMark_multipleIndices() throws ParseException { assertNotNull(markParser.parse("mark 1 2 3")); } @Test - public void parse_mark_multipleSpacesIndices() throws ParseException { + public void parseMark_multipleSpacesIndices() throws ParseException { assertNotNull(markParser.parse("mark 1 2 3")); } @Test - public void parse_mark_negativeIndex() throws ParseException { + public void parseMark_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); markParser.parse("mark -1"); } @Test - public void parse_mark_zeroIndex() throws ParseException { + public void parseMark_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); markParser.parse("mark 0"); } @@ -338,34 +340,34 @@ public class ParserTest { //================================================================================== @Test - public void parse_unmark_noIndexSpecified() throws ParseException { + public void parseUnmark_noIndexSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unmarkParser.parse("unmark"); } @Test - public void parse_unmark_oneIndex() throws ParseException { + public void parseUnmark_oneIndex() throws ParseException { assertNotNull(unmarkParser.parse("unmark 1")); } @Test - public void parse_unmark_multipleIndices() throws ParseException { + public void parseUnmark_multipleIndices() throws ParseException { assertNotNull(unmarkParser.parse("unmark 1 2 3")); } @Test - public void parse_unmark_multipleSpacesIndices() throws ParseException { + public void parseUnmark_multipleSpacesIndices() throws ParseException { assertNotNull(unmarkParser.parse("unmark 1 2 3")); } @Test - public void parse_unmark_negativeIndex() throws ParseException { + public void parseUnmark_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unmarkParser.parse("unmark -1"); } @Test - public void parse_unmark_zeroIndex() throws ParseException { + public void parseUnmark_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unmarkParser.parse("unmark 0"); } @@ -373,106 +375,118 @@ public class ParserTest { //================================================================================== @Test - public void parse_undo_spaces() throws ParseException { + public void parseUndo_spaces() throws ParseException { assertNotNull(undoParser.parse("undo ")); } @Test - public void parse_undo_invalid() throws ParseException { + public void parseUndo_invalid() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("undo 1"); } @Test - public void parse_undo_valid() throws ParseException { + public void parseUndo_valid() throws ParseException { assertNotNull(undoParser.parse("undo")); } //================================================================================== @Test - public void parse_redo_spaces() throws ParseException { + public void parseRedo_spaces() throws ParseException { assertNotNull(redoParser.parse("redo ")); } @Test - public void parse_redo_invalid() throws ParseException { + public void parseRedo_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("redo 1"); } @Test - public void parse_redo_valid() throws ParseException { + public void parseRedo_valid() throws ParseException { assertNotNull(redoParser.parse("redo")); } //================================================================================== @Test - public void parse_alias_keywordUnspecified() throws ParseException { + public void parseAlias_keywordUnspecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias r/ a string of things"); } @Test - public void parse_alias_textUnspecified() throws ParseException { + public void parseAlias_textUnspecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ xyz"); } @Test - public void parse_alias_noSwitchesSpecified() throws ParseException { + public void parseAlias_noSwitchesSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias power overwhelming"); } @Test - public void parse_alias_keywordTooLong() throws ParseException { + public void parseAlias_keywordNotSingleWord_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ not a single word r/ project management"); } @Test - public void parse_alias_keywordEmpty() throws ParseException { + public void parseAlias_keywordEmpty_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ r/ project management"); } @Test - public void parse_alias_textEmpty() throws ParseException { + public void parseAlias_textEmpty_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ pjm r/ "); } @Test - public void parse_alias_fullValid() throws ParseException { + public void parseAlias_fullValid() throws ParseException { assertNotNull(aliasParser.parse("alias k/ pjm r/ project management ")); } //================================================================================== @Test - public void parse_unalias_emptyKeyword() throws ParseException { + public void parseUnalias_emptyKeyword_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unaliasParser.parse("unalias "); } @Test - public void parse_unalias_valid() throws ParseException { + public void parseUnalias_valid() throws ParseException { assertNotNull(unaliasParser.parse("unalias something ")); } //================================================================================== - + + @Test + public void parseStorage_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + storageParser.parse("storage "); + } + + @Test + public void parseStorage_valid() throws ParseException { + assertNotNull(storageParser.parse("storage C:/Users/Brown/Desktop/file.xml ")); + } + + //================================================================================== @Test - public void parse_master_subparser() throws ParseException { + public void masterParser_subparserParsing_returnParsedCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); assertTrue(parser.parse(" add A New Task s/ tomorrow e/ the day after tomorrow, l/ SR10 ") instanceof AddCommand); } @Test - public void parse_master_subparserRemoved() throws ParseException { + public void masterParser_subparserRemoved_returnIncorrectCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); parser.unregisterCommandParser("add"); @@ -480,7 +494,7 @@ public class ParserTest { } @Test - public void parse_master_alias() throws ParseException { + public void masterParser_alias_returnParsedCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); @@ -489,14 +503,14 @@ public class ParserTest { } @Test - public void parse_master_invalidAlias() throws ParseException { + public void masterParser_invalidAlias_returnFalse() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); assertFalse(parser.addAliasSymbol(new AliasSymbol("add", "add A New Task"))); } @Test - public void parse_master_removedAlias() throws ParseException { + public void masterParser_removedAlias_returnIncorrectCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index d985b50905ac..16f7b1f58831 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -293,7 +293,7 @@ b. Require developers to download those libraries manually (this creates extra w Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -Priority | As a ... | I want to ... | So that I can... +Priority | As a(n) ... | I want to ... | So that I can... -------- | :-------- | :--------- | :----------- `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App `* * *` | new user | view more information about a particular command | learn how to use various commands @@ -309,9 +309,9 @@ Priority | As a ... | I want to ... | So that I can... `*` | user | sort tasks by priority level | see the most important tasks and prioritize accordingly `*` | user | undo most recent command | undo the most recent operation `*` | user | redo most recent undo command | redo the operation done by the most recent undo action +`*` | user | change storage location | choose where my tasks are saved at `*` | advanced user | alias keywords with shorter versions | type a command faster `*` | advanced user | remove alias of keywords with shorter versions | get rid of shorter version of certain keywords -{More to be added} ## Appendix B : Use Cases @@ -319,7 +319,7 @@ Priority | As a ... | I want to ... | So that I can... (For all use cases below, the **System** is the `Savvy Tasker` and the **Actor** is the `user`, unless specified otherwise) -#### Use case: Add task +### Use case: Add task **MSS** @@ -349,7 +349,7 @@ Use case ends. > 2d1. Savvy Tasker shows an error message
> Use case resumes at step 1 -#### Use case: List tasks +### Use case: List tasks **MSS** @@ -365,7 +365,7 @@ Use case ends. > 3a1. Savvy Tasker shows an error message
Use case ends -#### Use case: Find task +### Use case: Find task **MSS** @@ -386,7 +386,7 @@ Use case ends. > Use case ends -#### Use case: Modify task +### Use case: Modify task **MSS** @@ -411,7 +411,30 @@ Use case ends. > 2b1. Savvy Tasker shows an error message and display the expected format
Use case resumes at step 3 -#### Use case: Mark task as done + +### Use case: Change storage location + +**MSS** + +1. Savvy Tasker waits for user command +2. User requests to change the storage location of Savvy Tasker +3. Savvy Tasker changes the storage location, saving all existing data in the new location
+Use case ends. + +**Extensions** + +2a. The given path is invalid + +> 2a1. Savvy Tasker shows an error message
+ Use case ends + +2b. The given path is is not accessible (read/write) by Savvy Tasker + +> 2b1. Savvy Tasker shows an error message
+ Use case ends + + +### Use case: Mark task as done **MSS** @@ -434,7 +457,7 @@ Use case ends. > 3b1. Savvy Tasker shows a 'task already marked' error message.
> Use case resumes at step 1 -###Use case: Unmark marked task +### Use case: Unmark marked task **MSS** @@ -455,7 +478,7 @@ Use case ends. > Use case resumes at step 1 -#### Use case: Delete task +### Use case: Delete task **MSS** @@ -477,7 +500,7 @@ Use case ends. > 4a1. Savvy Tasker shows an error message > Use case resumes at step 3
-###Use case: Alias keyword and use shorten keyword +### Use case: Alias keyword and use shorten keyword **MSS** @@ -499,7 +522,7 @@ Use case ends. > 2b1. Savvy Tasker shows a error message and the shorten keyword's original associated keyword > Use case resumes at step 1
-###Use case: Unalias keyword +### Use case: Unalias keyword **MSS** @@ -514,7 +537,7 @@ Use case ends. > 2a1. Savvy Tasker shows a 'not found' error message > Use case resumes at step 1
-###Use case: Undo previous command +### Use case: Undo previous command **MSS** @@ -529,7 +552,7 @@ Use case ends. > 2a1. Savvy Tasker shows a 'cannot undo' error message
> Use case ends -###Use case: Redo most recently undone command +### Use case: Redo most recently undone command **MSS** @@ -555,7 +578,6 @@ Use case ends. 8. Should store data in text file. 9. Should work without requiring an installer. -{More to be added} ## Appendix D : Glossary diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 7aaa71e72095..eb6fc76c9f68 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -62,7 +62,7 @@ Format: `add TASK_NAME [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVE > LOCATION | `Optional` Specifies the location where the task happens. > PRIORITY_LEVEL | `Optional` Specifies the priority level of the task.
`Accepts` values `low`, `medium`, `high`
`Defaults` to `???` > RECURRING_TYPE | `Optional` Specifies the recurring type of the task.
`Accepts` values `none`, `daily`, `weekly`, `monthly`, `yearly`
`Defaults` to `none` -> NUMBER_OF_RECURRENCE | `Optional` Specifies the number of times the task recurrs. A value of 0 specifies a never-ending recurrence.
`Defaults` to `0`
`Ignored` if RECURRING_TYPE is `none` +> NUMBER_OF_RECURRENCE | `Optional` Specifies the number of times the task recurrs.
`Defaults` to `1`
`Ignored` if RECURRING_TYPE is `none` > CATEGORY | `Optional` Specifies a custom category for the task. This can be used for keeping track of similar tasks. > DESCRIPTION | `Optional` Describes the task. @@ -78,11 +78,11 @@ Examples: #### Listing all tasks: `list` Shows a list of all tasks in Savvy Tasker
-Format: `list [t/LIST_TYPE]` +Format: `list [LIST_TYPE]` > Parameters | Description > -------- | :-------- -> LIST_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `DueDate`, `PriorityLevel`, `Archived`
`Defaults` to `DueDate` +> LIST_TYPE | `Optional` Specifies the name of the task.
`Accepts` values `DueDate`, `PriorityLevel`, `Archived`, `Alias`
`Defaults` to `DueDate` `LIST_TYPE` Explanation: * `DueDate`
@@ -91,7 +91,9 @@ Format: `list [t/LIST_TYPE]` * `PriorityLevel`
Tasks are sorted according to priority level beginning with the highest.
* `Archived`
- Tasks that have been [marked](#mark-a-task-as-done--mark) are listed. They are sorted according to the time of creation of the task.
+ Tasks that have been [marked](#mark-a-task-as-done--mark) are listed. They are sorted according to the time of creation of the task. +* `Alias`
+ [Aliases](#alias-a-keyword--alias) that have been registered are listed.
#### Finding all task containing any keyword in its name: `find` Finds tasks whose names contain any of the given keywords.
@@ -151,6 +153,16 @@ Format: `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/ >
> Overwrites any of the specified fields ('LOCATION', 'DESCRIPTION'...) with the new values +#### Change storage location : `storage` +Changes the storage location of Savvy Tasker.
+Format: `storage PATH` + +> Parameters | Description +> -------- | :-------- +> PATH | `Mandatory` Specifies the path where Savvy Tasker's task list is saved at. +>
+> If the new storage location specified by `PATH` is not accessible by Savvy Tasker, no change will be made to the existing path. + [//]: # (@@author A0097627N) #### Mark a task as done : `mark` @@ -256,10 +268,11 @@ Command | Format [Delete](#deleting-a-task--delete) | `delete INDEX [MORE_INDEX]`
Example: `delete 1 2 3` [Exit](#exiting-the-program--exit) | `exit` [Find](#finding-all-task-containing-any-keyword-in-its-name-find) | `find [t/FIND_TYPE] KEYWORD [MORE_KEYWORDS]`
Example: `find t/exact CS2103 Meeting` -[List](#listing-all-tasks-list) | `list [t/LIST_TYPE]`
Example: `list t/archived` +[List](#listing-all-tasks-list) | `list [LIST_TYPE]`
Example: `list archived` [Help](#viewing-help--help) | `help` [Modify](#modifies-a-task--modify) | `modify INDEX [t/TASK_NAME] [s/START_DATE] [e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] [n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]`
Example: `modify 2 t/Wednesday Weekly Milestone s/wed d/Project Meeting and Finalization` [Mark](#mark-a-task-as-done--mark) | `mark INDEX [MORE_INDEX]`
Example: `mark 1 2 3` +[Storage](#change-storage-location--storage) | `storage PATH`
Example: `storage data/savvytasker.xml` [Unmark](#unmark-a-task-as-done--unmark) | `unmark INDEX [MORE_INDEX]`
Example: `unmark 1 2 3` [Undo](#undo-the-most-recent-operation--undo) | `undo` [Redo](#redo-the-most-recent-undo-operation--redo) | `redo` diff --git a/src/main/java/seedu/savvytasker/MainApp.java b/src/main/java/seedu/savvytasker/MainApp.java index 26073d7271ad..787ff282cca2 100644 --- a/src/main/java/seedu/savvytasker/MainApp.java +++ b/src/main/java/seedu/savvytasker/MainApp.java @@ -14,6 +14,7 @@ import seedu.savvytasker.commons.core.EventsCenter; import seedu.savvytasker.commons.core.LogsCenter; import seedu.savvytasker.commons.core.Version; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; import seedu.savvytasker.commons.events.ui.ExitAppRequestEvent; import seedu.savvytasker.commons.exceptions.DataConversionException; import seedu.savvytasker.commons.util.ConfigUtil; @@ -44,6 +45,7 @@ public class MainApp extends Application { protected Model model; protected Config config; protected UserPrefs userPrefs; + protected static MainApp instance; public MainApp() {} @@ -51,9 +53,10 @@ public MainApp() {} public void init() throws Exception { logger.info("=============================[ Initializing Savvy Tasker ]==========================="); super.init(); - + instance = this; + config = initConfig(getApplicationParameter("config")); - storage = new StorageManager(config.getAddressBookFilePath(), config.getUserPrefsFilePath()); + storage = new StorageManager(config.getSavvyTaskerFilePath(), config.getUserPrefsFilePath()); userPrefs = initPrefs(config); @@ -179,6 +182,22 @@ public void stop() { Platform.exit(); System.exit(0); } + + //@@author A0139915W + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + try { + String configPath = getApplicationParameter("config"); + if(configPath == null) { + configPath = Config.DEFAULT_CONFIG_FILE; + } + config.setSavvyTaskerFilePath(dslce.newPath); + ConfigUtil.saveConfig(config, configPath); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + } + //@@author @Subscribe public void handleExitAppRequestEvent(ExitAppRequestEvent event) { diff --git a/src/main/java/seedu/savvytasker/commons/core/Config.java b/src/main/java/seedu/savvytasker/commons/core/Config.java index 3f360ba2bedf..249cc84d3a8a 100644 --- a/src/main/java/seedu/savvytasker/commons/core/Config.java +++ b/src/main/java/seedu/savvytasker/commons/core/Config.java @@ -14,8 +14,8 @@ public class Config { private String appTitle = "Savvy Tasker"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; - private String addressBookFilePath = "data/savvytasker.xml"; - private String addressBookName = "MyTaskList"; + private String savvyTaskerFilePath = "data/savvytasker.xml"; + private String savvyTaskerListName = "MyTaskList"; public Config() { @@ -45,20 +45,20 @@ public void setUserPrefsFilePath(String userPrefsFilePath) { this.userPrefsFilePath = userPrefsFilePath; } - public String getAddressBookFilePath() { - return addressBookFilePath; + public String getSavvyTaskerFilePath() { + return savvyTaskerFilePath; } - public void setAddressBookFilePath(String addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setSavvyTaskerFilePath(String savvyTaskerFilePath) { + this.savvyTaskerFilePath = savvyTaskerFilePath; } - public String getAddressBookName() { - return addressBookName; + public String getSavvyTaskerListName() { + return savvyTaskerListName; } - public void setAddressBookName(String addressBookName) { - this.addressBookName = addressBookName; + public void setSavvyTaskerName(String savvyTaskerName) { + this.savvyTaskerListName = savvyTaskerName; } @@ -76,13 +76,13 @@ public boolean equals(Object other) { return Objects.equals(appTitle, o.appTitle) && Objects.equals(logLevel, o.logLevel) && Objects.equals(userPrefsFilePath, o.userPrefsFilePath) - && Objects.equals(addressBookFilePath, o.addressBookFilePath) - && Objects.equals(addressBookName, o.addressBookName); + && Objects.equals(savvyTaskerFilePath, o.savvyTaskerFilePath) + && Objects.equals(savvyTaskerListName, o.savvyTaskerListName); } @Override public int hashCode() { - return Objects.hash(appTitle, logLevel, userPrefsFilePath, addressBookFilePath, addressBookName); + return Objects.hash(appTitle, logLevel, userPrefsFilePath, savvyTaskerFilePath, savvyTaskerListName); } @Override @@ -91,8 +91,8 @@ public String toString(){ sb.append("App title : " + appTitle); sb.append("\nCurrent log level : " + logLevel); sb.append("\nPreference file Location : " + userPrefsFilePath); - sb.append("\nLocal data file location : " + addressBookFilePath); - sb.append("\nAddressBook name : " + addressBookName); + sb.append("\nLocal data file location : " + savvyTaskerFilePath); + sb.append("\nSavvy Tasker List name : " + savvyTaskerListName); return sb.toString(); } diff --git a/src/main/java/seedu/savvytasker/commons/core/Messages.java b/src/main/java/seedu/savvytasker/commons/core/Messages.java index 134dcd37d6b9..bb130b8ebb7e 100644 --- a/src/main/java/seedu/savvytasker/commons/core/Messages.java +++ b/src/main/java/seedu/savvytasker/commons/core/Messages.java @@ -9,6 +9,7 @@ public class Messages { public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_ALIASES_LISTED_OVERVIEW = "%1$d aliases listed!"; public static final String MESSAGE_INVALID_START_END = "The end time cannot be earlier than the start time"; } diff --git a/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java index 3db6aaa27df1..c3c2ff8a129b 100644 --- a/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java +++ b/src/main/java/seedu/savvytasker/commons/events/model/AliasSymbolChangedEvent.java @@ -16,8 +16,8 @@ public enum Action { Removed; } - public final AliasSymbol symbol; - public final Action action; + private final AliasSymbol symbol; + private final Action action; public AliasSymbolChangedEvent(AliasSymbol symbol, Action action) { assert symbol != null; @@ -31,5 +31,13 @@ public AliasSymbolChangedEvent(AliasSymbol symbol, Action action) { public String toString() { return "Alias symbol " + action.toString() + ": " + symbol.toString(); } + + public AliasSymbol getSymbol() { + return this.symbol; + } + + public Action getAction() { + return this.action; + } } diff --git a/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingLocationChangedEvent.java b/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingLocationChangedEvent.java new file mode 100644 index 000000000000..6e92df0cf402 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/storage/DataSavingLocationChangedEvent.java @@ -0,0 +1,27 @@ +package seedu.savvytasker.commons.events.storage; + +import seedu.savvytasker.commons.events.BaseEvent; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + +//@@author A0139915W +/** + * Indicates a change in location of the storage + */ +public class DataSavingLocationChangedEvent extends BaseEvent { + + public final ReadOnlySavvyTasker data; + public final String newPath; + + public DataSavingLocationChangedEvent(ReadOnlySavvyTasker data, String newPath) { + this.data = data; + this.newPath = newPath; + } + + @Override + public String toString() { + return "number of tasks " + data.getReadOnlyListOfTasks().size() + + " new path " + this.newPath; + } + +} +//@@author diff --git a/src/main/java/seedu/savvytasker/commons/events/ui/ChangeListRequestEvent.java b/src/main/java/seedu/savvytasker/commons/events/ui/ChangeListRequestEvent.java new file mode 100644 index 000000000000..8f1f4f7fef28 --- /dev/null +++ b/src/main/java/seedu/savvytasker/commons/events/ui/ChangeListRequestEvent.java @@ -0,0 +1,26 @@ +package seedu.savvytasker.commons.events.ui; + +import seedu.savvytasker.commons.events.BaseEvent; + +/** + * Indicates a request to jump to the list of tasks + */ +public class ChangeListRequestEvent extends BaseEvent { + + public enum DisplayedList { + Task, + Alias + } + + public final DisplayedList displayedList; + + public ChangeListRequestEvent(DisplayedList displayedList) { + this.displayedList = displayedList; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/savvytasker/commons/util/FileUtil.java b/src/main/java/seedu/savvytasker/commons/util/FileUtil.java index f642a058f70a..f5eb599f2cfe 100644 --- a/src/main/java/seedu/savvytasker/commons/util/FileUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/FileUtil.java @@ -14,11 +14,11 @@ public static boolean isFileExists(File file) { return file.exists() && file.isFile(); } - public static void createIfMissing(File file) throws IOException { - if (!isFileExists(file)) { - createFile(file); - } + //@@author A0139915W + public static boolean createIfMissing(File file) throws IOException { + return createFile(file); } + //@@author /** * Creates a file if it does not exist along with its missing parent directories diff --git a/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java b/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java index cf6133f1c763..c7206ebdf7c7 100644 --- a/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java +++ b/src/main/java/seedu/savvytasker/commons/util/SmartDefaultDates.java @@ -66,6 +66,7 @@ public Date getEnd(InferredDate endDateTime) { calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); } return calendar.getTime(); } @@ -85,7 +86,13 @@ private void parseEnd(InferredDate endDateTime) { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); this.startDateTime = calendar.getTime(); + + if (this.startDateTime.compareTo(this.endDateTime) > 0) { + // end date is before today, leave start date as null + this.startDateTime = null; + } } @@ -114,6 +121,7 @@ public Date getStart(InferredDate startDateTime) { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); } return calendar.getTime(); } @@ -134,6 +142,7 @@ private void parseStart(InferredDate startDateTime) { calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 0); this.endDateTime = calendar.getTime(); } @@ -149,11 +158,6 @@ private void parseStartAndEnd(InferredDate startDateTime, InferredDate endDateTi Date end = getEnd(endDateTime); this.startDateTime = start; this.endDateTime = end; - if (this.startDateTime.compareTo(this.endDateTime) > 0) { - calendar.setTime(this.endDateTime); - calendar.add(Calendar.DATE, 7); - this.endDateTime = calendar.getTime(); - } } public Date getStartDate() { diff --git a/src/main/java/seedu/savvytasker/commons/util/StringUtil.java b/src/main/java/seedu/savvytasker/commons/util/StringUtil.java index 3c0fc9ea185e..115b96e7d353 100644 --- a/src/main/java/seedu/savvytasker/commons/util/StringUtil.java +++ b/src/main/java/seedu/savvytasker/commons/util/StringUtil.java @@ -9,7 +9,7 @@ * Helper functions for handling strings. */ public class StringUtil { - //@@author A0139915W-reused + //@@author A0139915W // reused original implementation of 'containsIgnoreCase' to find exact matches public static boolean containsExactIgnoreCase(String source, String query) { List strings = Arrays.asList(source); diff --git a/src/main/java/seedu/savvytasker/logic/Logic.java b/src/main/java/seedu/savvytasker/logic/Logic.java index db33308d66c6..bc55816f290a 100644 --- a/src/main/java/seedu/savvytasker/logic/Logic.java +++ b/src/main/java/seedu/savvytasker/logic/Logic.java @@ -2,6 +2,7 @@ import javafx.collections.ObservableList; import seedu.savvytasker.logic.commands.CommandResult; +import seedu.savvytasker.model.alias.AliasSymbol; import seedu.savvytasker.model.task.ReadOnlyTask; /** @@ -17,6 +18,9 @@ public interface Logic { /** Returns the filtered list of tasks */ ObservableList getFilteredTaskList(); + + /** Returns the filtered list of alias symbol */ + ObservableList getAliasSymbolList(); /** */ boolean canParseHeader(String keyword); diff --git a/src/main/java/seedu/savvytasker/logic/LogicManager.java b/src/main/java/seedu/savvytasker/logic/LogicManager.java index 732bc14daebc..0225052f898d 100644 --- a/src/main/java/seedu/savvytasker/logic/LogicManager.java +++ b/src/main/java/seedu/savvytasker/logic/LogicManager.java @@ -7,11 +7,16 @@ import com.google.common.eventbus.Subscribe; import javafx.collections.ObservableList; +import seedu.savvytasker.MainApp; import seedu.savvytasker.commons.core.ComponentManager; +import seedu.savvytasker.commons.core.EventsCenter; import seedu.savvytasker.commons.core.LogsCenter; import seedu.savvytasker.commons.events.model.AliasSymbolChangedEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent.DisplayedList; import seedu.savvytasker.logic.commands.Command; import seedu.savvytasker.logic.commands.CommandResult; +import seedu.savvytasker.logic.commands.ListCommand; import seedu.savvytasker.logic.parser.AddCommandParser; import seedu.savvytasker.logic.parser.AliasCommandParser; import seedu.savvytasker.logic.parser.ClearCommandParser; @@ -24,6 +29,7 @@ import seedu.savvytasker.logic.parser.MasterParser; import seedu.savvytasker.logic.parser.ModifyCommandParser; import seedu.savvytasker.logic.parser.RedoCommandParser; +import seedu.savvytasker.logic.parser.StorageCommandParser; import seedu.savvytasker.logic.parser.UnaliasCommandParser; import seedu.savvytasker.logic.parser.UndoCommandParser; import seedu.savvytasker.logic.parser.UnmarkCommandParser; @@ -31,6 +37,7 @@ import seedu.savvytasker.model.alias.AliasSymbol; import seedu.savvytasker.model.task.ReadOnlyTask; import seedu.savvytasker.storage.Storage; +import seedu.savvytasker.ui.Ui; /** * The main LogicManager of the app. @@ -39,12 +46,14 @@ public class LogicManager extends ComponentManager implements Logic { private final Logger logger = LogsCenter.getLogger(LogicManager.class); private final Model model; + private final Storage storage; private final MasterParser parser; private final Stack undoStack; private final Stack redoStack; public LogicManager(Model model, Storage storage) { this.model = model; + this.storage = storage; this.parser = new MasterParser(); this.undoStack = new Stack(); this.redoStack = new Stack(); @@ -59,9 +68,15 @@ public CommandResult execute(String commandText) { Command command = parser.parse(commandText); command.setModel(model); command.setLogic(this); + command.setStorage(storage); CommandResult result = command.execute(); + if (!(command instanceof ListCommand)) { + // forcefully show the task list instead + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Task)); + } + //@@author A0097627N if (command.isUndo()){ if (!undo()) { @@ -82,10 +97,17 @@ else if (command.canUndo()){ return result; } + //@@author A0139915W @Override public ObservableList getFilteredTaskList() { return model.getFilteredTaskList(); } + + @Override + public ObservableList getAliasSymbolList() { + return parser.getAliasSymbolList(); + } + //@@author //@@author A0139916U private void registerAllDefaultCommandParsers() { @@ -103,6 +125,7 @@ private void registerAllDefaultCommandParsers() { parser.registerCommandParser(new RedoCommandParser()); parser.registerCommandParser(new AliasCommandParser()); parser.registerCommandParser(new UnaliasCommandParser()); + parser.registerCommandParser(new StorageCommandParser()); } private void loadAllAliasSymbols() { @@ -138,24 +161,42 @@ private boolean redo() { return redone; } + /** + * Log the result of adding/removing a symbol in the parser. + * + * @param success if the operation succeeded + * @param changedSymbol the symbol that was involved in the operation + * @param successMsgFormat the message to print if the operation succeeded. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + * @param failureMsgFormat the message to print if the operation failed. It should contain a single + * %s string format specifier, which will be replaced by the symbol's string representation. + */ + private void logParserSymbolChange(boolean success, AliasSymbol changedSymbol, + String successMsgFormat, String failureMsgFormat) { + if (success) { + logger.info(String.format(successMsgFormat, changedSymbol)); + } else { + logger.warning(String.format(failureMsgFormat, changedSymbol)); + } + } + @Subscribe public void handleAliasSymbolChangedEvent(AliasSymbolChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage( - event, "Alias symbol " + event.action.toString().toLowerCase())); - if (event.action.equals(AliasSymbolChangedEvent.Action.Added)) { - boolean success = parser.addAliasSymbol(event.symbol); - if (success) { - logger.info("Added alias symbol '"+event.symbol.getKeyword()+"' to parser"); - } else { - logger.warning("Failed to add alias symbol '"+event.symbol.getKeyword()+" to parser"); - } - } else { - boolean success = parser.removeAliasSymbol(event.symbol.getKeyword()); - if (success) { - logger.info("Removed alias symbol '"+event.symbol.getKeyword()+"' from parser"); - } else { - logger.warning("Failed to remove alias symbol '"+event.symbol.getKeyword()+" from parser"); - } + event, "Alias symbol " + event.getAction().toString().toLowerCase())); + + if (event.getAction().equals(AliasSymbolChangedEvent.Action.Added)) { + logParserSymbolChange( + parser.addAliasSymbol(event.getSymbol()), + event.getSymbol(), + "Added alias symbol '%s' to parser", + "Failed to add alias symbol '%s' to parser"); + } else if (event.getAction().equals(AliasSymbolChangedEvent.Action.Removed)) { + logParserSymbolChange( + parser.removeAliasSymbol(event.getSymbol().getKeyword()), + event.getSymbol(), + "Removed alias symbol '%s' from parser", + "Failed to remove alias symbol '%s' from parser"); } } diff --git a/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java b/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java index 52343ea376a9..83f6fc639954 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/AddCommand.java @@ -1,12 +1,16 @@ package seedu.savvytasker.logic.commands; +import java.util.Iterator; +import java.util.LinkedList; + +import seedu.savvytasker.commons.core.EventsCenter; import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; import seedu.savvytasker.logic.parser.DateParser.InferredDate; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.ReadOnlyTask; import seedu.savvytasker.model.task.RecurrenceType; import seedu.savvytasker.model.task.Task; -import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; import seedu.savvytasker.model.task.TaskList.InvalidDateException; import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; @@ -38,6 +42,7 @@ public class AddCommand extends ModelRequiringCommand { private final String description; private Task toAdd; + private LinkedList tasksAdded; //@@author A0139915W /** @@ -55,16 +60,24 @@ public AddCommand(String taskName, InferredDate startDateTime, InferredDate endD this.numberOfRecurrence = numberOfRecurrence; this.category = category; this.description = description; + tasksAdded = new LinkedList(); } private void createTask() { final boolean isArchived = false; // all tasks are first added as active tasks final int taskId = 0; // taskId to be assigned by ModelManager, leave as 0 + final int groupId = 0; // groupId to be assigned by ModelManager, leave as 0 - this.toAdd = new Task(taskId, taskName, startDateTime, endDateTime, + this.toAdd = new Task(taskId, groupId, taskName, startDateTime, endDateTime, location, priority, recurringType, numberOfRecurrence, category, description, isArchived); } + + private void addToListOfTasksAdded(Task... tasks) { + for (Task t : tasks) { + tasksAdded.add(t); + } + } @Override public CommandResult execute() { @@ -72,15 +85,41 @@ public CommandResult execute() { createTask(); try { - model.addTask(toAdd); + Task taskAdded = null; + if (toAdd.getRecurringType() == RecurrenceType.None) { + // not a recurring task, add a single task + taskAdded = model.addTask(toAdd); + addToListOfTasksAdded(taskAdded); + } else { + // a recurring task, add a group of recurring tasks + LinkedList tasksAdded = model.addRecurringTask(toAdd); + taskAdded = tasksAdded.peekFirst(); + addToListOfTasksAdded(tasksAdded.toArray(new Task[tasksAdded.size()])); + } + + int targetIndex = getIndexOfTask(taskAdded); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } else { + // GUI should never ever get here + } return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } catch (DuplicateTaskException e) { - return new CommandResult(MESSAGE_DUPLICATE_TASK); } catch (InvalidDateException ex) { return new CommandResult(MESSAGE_INVALID_START_END); } } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + model.updateFilteredListToShowActive(); //because newly added tasks are all active. + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } //@@author //@@author A0097627N @@ -100,7 +139,7 @@ public boolean canUndo() { @Override public boolean redo() { execute(); - return false; + return true; } /** @@ -109,20 +148,18 @@ public boolean redo() { */ @Override public boolean undo() { - - UnmodifiableObservableList lastShownList = model.getFilteredTaskListTask(); - - for (int i = 0; i < lastShownList.size(); i++) { - if (lastShownList.get(i) == toAdd){ - ReadOnlyTask taskToDelete = lastShownList.get(i); - try { - model.deleteTask(taskToDelete); - } catch (TaskNotFoundException e) { - e.printStackTrace(); - } + Iterator itr = tasksAdded.iterator(); + while (itr.hasNext()) { + try { + model.deleteTask(itr.next()); + } catch (TaskNotFoundException e) { + // do nothing. } - } - return false; + } + // clears the list of tasks added. + // if redo is performed, the list will be populated again. + tasksAdded.clear(); + return true; } /** diff --git a/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java b/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java index f5666df04465..fe751a23a821 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/AliasCommand.java @@ -62,7 +62,7 @@ public void setLogic(Logic logic) { this.logic = logic; } - //@@author A0139916U + //@@author A0097627N /** * Checks if a command can perform undo operations * @return true if the command supports undo, false otherwise diff --git a/src/main/java/seedu/savvytasker/logic/commands/Command.java b/src/main/java/seedu/savvytasker/logic/commands/Command.java index 9ef43c3dc2fa..db06165bb9b5 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/Command.java +++ b/src/main/java/seedu/savvytasker/logic/commands/Command.java @@ -5,6 +5,7 @@ import seedu.savvytasker.commons.events.ui.IncorrectCommandAttemptedEvent; import seedu.savvytasker.logic.Logic; import seedu.savvytasker.model.Model; +import seedu.savvytasker.storage.Storage; /** * Represents a command with hidden internal logic and the ability to be executed. @@ -19,6 +20,15 @@ public abstract class Command { public static String getMessageForTaskListShownSummary(int displaySize) { return String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, displaySize); } + /** + * Constructs a feedback message to summarise an operation that displayed a listing of aliases. + * + * @param displaySize used to generate summary + * @return summary message for tasks displayed + */ + public static String getMessageForAliasListShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_ALIASES_LISTED_OVERVIEW, displaySize); + } /** * Executes the command and returns the result message. @@ -40,6 +50,13 @@ public void setModel(Model model) { /* Intentionally does nothing */ } * access to the dependencies. */ public void setLogic(Logic logic) { /* Intentionally does nothing */ } + + /** + * Provides any storage related dependencies to the command. + * Commands making use of any of these should override this method to gain + * access to the dependencies. + */ + public void setStorage(Storage storage) { /* Intentionally does nothing */ } /** * Raises an event to indicate an attempt to execute an incorrect command diff --git a/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java b/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java index 418a864dfce2..40a64c594447 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/DeleteCommand.java @@ -58,7 +58,7 @@ public CommandResult execute() { //tasksToUndo.add((Task)taskToDelete); resultSb.append(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); } - } catch (TaskNotFoundException pnfe) { + } catch (TaskNotFoundException tnfe) { assert false : "The target task cannot be missing"; } diff --git a/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java b/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java index 71d5b895be83..b369336ba941 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/IncorrectCommand.java @@ -6,17 +6,17 @@ */ public class IncorrectCommand extends Command { public final String resolvedText; - public final String feedbackToUser; + public final String errorFeedback; - public IncorrectCommand(String resolvedText, String feedbackToUser){ + public IncorrectCommand(String resolvedText, String errorFeedback){ this.resolvedText = resolvedText; - this.feedbackToUser = feedbackToUser; + this.errorFeedback = errorFeedback; } @Override public CommandResult execute() { indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(feedbackToUser); + return new CommandResult("Input: " + resolvedText + "\n" + errorFeedback); } //@@author A0097627N diff --git a/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java index 9f297bb218f3..f8c39d9032af 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/ListCommand.java @@ -1,6 +1,9 @@ package seedu.savvytasker.logic.commands; -import seedu.savvytasker.model.task.ListType; +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent.DisplayedList; +import seedu.savvytasker.model.ListType; /** * Lists all tasks in the savvy tasker to the user. @@ -35,6 +38,9 @@ public CommandResult execute() { // use default, sort by due date _listType = ListType.DueDate; } + + // shows the task list by default, unless user + // specifies to show the alias switch (_listType) { case DueDate: @@ -46,10 +52,19 @@ public CommandResult execute() { case Archived: model.updateFilteredListToShowArchived(); break; + case Alias: + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Alias)); + break; default: - assert false; // should not reach here + // nothing to do + break; + } + if (_listType != ListType.Alias) { + EventsCenter.getInstance().post(new ChangeListRequestEvent(DisplayedList.Task)); + return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } else { + return new CommandResult(getMessageForAliasListShownSummary(model.getAliasSymbolCount())); } - return new CommandResult(getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); } //@@author diff --git a/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java b/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java index d20314e38d9f..88f2bac1d142 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/MarkCommand.java @@ -8,7 +8,6 @@ import seedu.savvytasker.model.SavvyTasker; import seedu.savvytasker.model.task.ReadOnlyTask; import seedu.savvytasker.model.task.Task; -import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; import seedu.savvytasker.model.task.TaskList.InvalidDateException; import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; @@ -54,8 +53,7 @@ public CommandResult execute() { for(Task taskToMark : tasksToMark) { if (!taskToMark.isArchived()){ taskToMark.setArchived(true); - model.deleteTask(taskToMark); - model.addTask(taskToMark); + model.modifyTask(taskToMark, taskToMark); resultSb.append(String.format(MESSAGE_MARK_TASK_SUCCESS, taskToMark)); } else { resultSb.append(String.format(MESSAGE_MARK_TASK_FAIL, taskToMark)); @@ -63,8 +61,6 @@ public CommandResult execute() { } } catch (TaskNotFoundException pnfe) { assert false : "The target task cannot be missing"; - } catch (DuplicateTaskException e) { - e.printStackTrace(); } catch (InvalidDateException e) { assert false : "The target task should be valid, only the archived flag is set"; } diff --git a/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java b/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java index 87c09d0ca87f..02438849d7a4 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/ModifyCommand.java @@ -1,7 +1,9 @@ package seedu.savvytasker.logic.commands; +import seedu.savvytasker.commons.core.EventsCenter; import seedu.savvytasker.commons.core.Messages; import seedu.savvytasker.commons.core.UnmodifiableObservableList; +import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; import seedu.savvytasker.logic.parser.DateParser.InferredDate; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.ReadOnlyTask; @@ -78,7 +80,13 @@ public CommandResult execute() { try { originalTask = (Task)taskToModify; - model.modifyTask(taskToModify, replacement); + Task taskModified = model.modifyTask(taskToModify, replacement); + int targetIndex = getIndexOfTask(taskModified); + if (targetIndex >= 0) { + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + } else { + // GUI should never ever get here + } } catch (TaskNotFoundException e) { assert false : "The target task cannot be missing"; } catch (InvalidDateException ex) { @@ -87,6 +95,16 @@ public CommandResult execute() { return new CommandResult(String.format(MESSAGE_SUCCESS, replacement)); } + + /** + * Helper method to retrieve the index of the task in the tasklist that was added. + * @param task The task to find + * @return Returns the index of the task in the list, -1 if not found. + */ + private int getIndexOfTask(Task task) { + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + return lastShownList.indexOf(task); + } //@@author //@@author A0097627N diff --git a/src/main/java/seedu/savvytasker/logic/commands/StorageAndModelRequiringCommand.java b/src/main/java/seedu/savvytasker/logic/commands/StorageAndModelRequiringCommand.java new file mode 100644 index 000000000000..fd18f92fa260 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/StorageAndModelRequiringCommand.java @@ -0,0 +1,23 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.model.Model; +import seedu.savvytasker.storage.Storage; + +//@@author A0139915W +/** + * Represents a command which requires the Storage class as a dependency. + * Commands should inherit this class if they only require dependency for + * storage and model components +*/ +public abstract class StorageAndModelRequiringCommand extends Command { + protected Storage storage; + protected Model model; + + public void setStorage(Storage storage) { + this.storage = storage; + } + + public void setModel(Model model) { + this.model = model; + } +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/StorageCommand.java b/src/main/java/seedu/savvytasker/logic/commands/StorageCommand.java new file mode 100644 index 000000000000..45df445e3f37 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/commands/StorageCommand.java @@ -0,0 +1,87 @@ +package seedu.savvytasker.logic.commands; + +import seedu.savvytasker.commons.core.EventsCenter; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; +import seedu.savvytasker.model.ReadOnlySavvyTasker; + +/** + * Changes the storage location of Savvy Tasker + */ +public class StorageCommand extends StorageAndModelRequiringCommand { + + public final String path; + + public static final String COMMAND_WORD = "storage"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sets the storage path to the path specified.\n" + + "Parameters: PATH\n" + + "Example: " + COMMAND_WORD + " data/savvytasker.xml"; + + public static final String MESSAGE_CHANGE_LOCATION_SUCCESS = "Changed storage location to: %1$s"; + public static final String MESSAGE_CHANGE_LOCATION_FAILED = "Failed to change storage location to: %1$s"; + + public StorageCommand(String path) { + this.path = path; + } + + @Override + public CommandResult execute() { + if (storage.setSavvyTaskerFilePath(path)) { + ReadOnlySavvyTasker savvyTasker = model.getSavvyTasker(); + EventsCenter.getInstance().post(new DataSavingLocationChangedEvent(savvyTasker, path)); + return new CommandResult(String.format(MESSAGE_CHANGE_LOCATION_SUCCESS, path)); + } else { + return new CommandResult(String.format(MESSAGE_CHANGE_LOCATION_FAILED, path)); + } + } + + //@@author A0097627N + /** + * Checks if a command can perform undo operations + * @return true if the command supports undo, false otherwise + */ + @Override + public boolean canUndo() { + return false; + } + + /** + * Redo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean redo() { + // nothing required to be done + return false; + } + + /** + * Undo the select command + * @return true if the operation completed successfully, false otherwise + */ + @Override + public boolean undo() { + // nothing required to be done + return false; + } + + /** + * Check if command is an undo command + * @return true if the command is an undo operation, false otherwise + */ + @Override + public boolean isUndo() { + return false; + } + + /** + * Check if command is a redo command + * @return true if the command is a redo operation, false otherwise + */ + @Override + public boolean isRedo(){ + return false; + } + //@@author +} diff --git a/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java b/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java index e8ed11152db5..380fc236cfdf 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/UnaliasCommand.java @@ -47,8 +47,10 @@ public CommandResult execute() { } try { - if (toRemove == null) + if (toRemove == null) { return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); + } + toUndo = toRemove; model.removeAliasSymbol(toRemove); return new CommandResult(String.format(MESSAGE_SUCCESS, toRemove)); @@ -56,7 +58,6 @@ public CommandResult execute() { return new CommandResult(MESSAGE_UNREGOGNIZED_ALIAS); } } - //@@author //@@author A0097627N /** diff --git a/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java b/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java index f3d360b01793..cee98003b600 100644 --- a/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java +++ b/src/main/java/seedu/savvytasker/logic/commands/UnmarkCommand.java @@ -8,7 +8,6 @@ import seedu.savvytasker.model.SavvyTasker; import seedu.savvytasker.model.task.ReadOnlyTask; import seedu.savvytasker.model.task.Task; -import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; import seedu.savvytasker.model.task.TaskList.InvalidDateException; import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; @@ -54,8 +53,7 @@ public CommandResult execute() { for(Task taskToUnmark : tasksToUnmark) { if (taskToUnmark.isArchived()){ taskToUnmark.setArchived(false); - model.deleteTask(taskToUnmark); - model.addTask(taskToUnmark); + model.modifyTask(taskToUnmark, taskToUnmark); model.updateFilteredListToShowArchived(); resultSb.append(String.format(MESSAGE_UNMARK_TASK_SUCCESS, taskToUnmark)); } else { @@ -64,9 +62,7 @@ public CommandResult execute() { } } catch (TaskNotFoundException pnfe) { assert false : "The target task cannot be missing"; - } catch (DuplicateTaskException e) { - e.printStackTrace(); - }catch (InvalidDateException e) { + } catch (InvalidDateException e) { assert false : "The target task should be valid, only the archived flag is set"; } return new CommandResult(resultSb.toString()); diff --git a/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java index 7301ce70e1e9..66dc288eeedf 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/AddCommandParser.java @@ -10,22 +10,12 @@ import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.RecurrenceType; -public class AddCommandParser implements CommandParser { +public class AddCommandParser extends TaskFieldParser { private static final String HEADER = "add"; private static final String READABLE_FORMAT = HEADER+" TASK_NAME [s/START_DATE] " + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; - private static final String REGEX_REF_TASK_NAME = "TaskName"; - private static final String REGEX_REF_START_DATE = "StartDate"; - private static final String REGEX_REF_END_DATE = "EndDate"; - private static final String REGEX_REF_LOCATION = "Location"; - private static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; - private static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; - private static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; - private static final String REGEX_REF_CATEGORY = "Category"; - private static final String REGEX_REF_DESCRIPTION = "Description"; - private static final Pattern REGEX_PATTERN = Pattern.compile( HEADER+"\\s+(?<"+REGEX_REF_TASK_NAME+">([^/]+?(\\s+|$))+)((?<=\\s)(" + "(s/(?<"+REGEX_REF_START_DATE+">[^/]+)(?!.*\\ss/))|" + @@ -37,8 +27,6 @@ public class AddCommandParser implements CommandParser { "(c/(?<"+REGEX_REF_CATEGORY+">[^/]+)(?!.*\\sc/))|" + "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]+)(?!.*\\sd/))" + ")(\\s|$)){0,10}", Pattern.CASE_INSENSITIVE); - - private static final TaskFieldParser TASK_PARSER = new TaskFieldParser(); @Override public String getHeader() { @@ -54,15 +42,15 @@ public String getRequiredFormat() { public AddCommand parse(String commandText) throws ParseException { Matcher matcher = REGEX_PATTERN.matcher(commandText); if (matcher.matches()) { - InferredDate startDate = TASK_PARSER.parseStartDate(matcher.group(REGEX_REF_START_DATE)); - InferredDate endDate = TASK_PARSER.parseEndDate(matcher.group(REGEX_REF_END_DATE)); - String taskName = TASK_PARSER.parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); - String location = TASK_PARSER.parseLocation(matcher.group(REGEX_REF_LOCATION)); - PriorityLevel priority = TASK_PARSER.parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); - RecurrenceType recurrence = TASK_PARSER.parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); - Integer nrOfRecurrence = TASK_PARSER.parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); - String category = TASK_PARSER.parseCategory(matcher.group(REGEX_REF_CATEGORY)); - String description = TASK_PARSER.parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + InferredDate startDate = parseStartDate(matcher.group(REGEX_REF_START_DATE)); + InferredDate endDate = parseEndDate(matcher.group(REGEX_REF_END_DATE)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); return new AddCommand(taskName, startDate, endDate, location, priority, diff --git a/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java index c54280d6ed9c..2d5240d3534d 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/AliasCommandParser.java @@ -18,7 +18,8 @@ public class AliasCommandParser implements CommandParser { HEADER+"\\s+((?<=\\s)(" + "(r/(?<"+REGEX_REF_REPRESENTATION+">[^/]+)(?!.*\\sr/))|" + "(k/(?<"+REGEX_REF_KEYWORD+">[^/]+)(?!.*\\sk/))" + - ")(\\s|$)){2}" + ")(\\s|$)){2}", + Pattern.CASE_INSENSITIVE ); @Override diff --git a/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java index af95bce78bff..ad2457b96f2f 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/ListCommandParser.java @@ -6,16 +6,16 @@ import seedu.savvytasker.commons.core.Messages; import seedu.savvytasker.logic.commands.ListCommand; -import seedu.savvytasker.model.task.ListType; +import seedu.savvytasker.model.ListType; public class ListCommandParser implements CommandParser { private static final String HEADER = "list"; - private static final String READABLE_FORMAT = HEADER+" [t/LIST_TYPE]"; + private static final String READABLE_FORMAT = HEADER+" [LIST_TYPE]"; private static final String REGEX_REF_LIST_TYPE = "ListType"; private static final Pattern REGEX_PATTERN = Pattern.compile( - HEADER+"\\s*((?<=\\s)t/(?<"+REGEX_REF_LIST_TYPE+">[^/]+))?", + HEADER+"\\s*((?<=\\s)(?<"+REGEX_REF_LIST_TYPE+">[^/]+))?", Pattern.CASE_INSENSITIVE); @Override diff --git a/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java b/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java index 753b62808fe3..271cb564acd5 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/MasterParser.java @@ -9,45 +9,86 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import seedu.savvytasker.logic.commands.Command; import seedu.savvytasker.logic.commands.HelpCommand; import seedu.savvytasker.logic.commands.IncorrectCommand; import seedu.savvytasker.model.alias.AliasSymbol; +/** + * Represents the master parser that is used by Logic. This is a parser containing + * all subparsers that will do the actual parsing to produce Command objects. This + * parser replaces keywords of the input with aliased representations before selecting + * a subparser to do the parsing. The selection of the subparser is based on the first + * word of the input, called the header, and matching it to the subparser that declares + * that it parses it. + */ public class MasterParser { private static final Pattern KEYWORD_PATTERN = - Pattern.compile("(\\S+)(\\s+|$)"); + Pattern.compile("([^\\s/]+)([\\s/]+|$)"); private final Map> commandParsers; private final Map aliasingSymbols; + private final ObservableList aliasList = FXCollections.observableArrayList(); + public MasterParser() { this.commandParsers = new HashMap>(); this.aliasingSymbols = new HashMap(); } + /** + * Parses the input text, selecting an appropriate registered parser to parse it. + * The parser selected is based on the first header word of the input text. The text + * is preprocessed, replacing any of its tokens that are keywords to an alias, before + * being passed to the parser. + * + * @param userInput the text to be parse + * @return the command that was parsed if successful, or IncorrectCommand if there is no + * parser that can parse the text or if there is a format error with the text. + */ public Command parse(String userInput) { String[] pieces = preprocessInitial(userInput.trim()); - if (pieces == null) - return new IncorrectCommand(userInput, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + if (pieces == null) { + return new IncorrectCommand(userInput, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } String header = pieces[0]; String body = pieces[1]; - String trueHeader = extractHeader(header); - CommandParser parser = commandParsers.get(trueHeader); - if (parser == null) - return new IncorrectCommand(header + body, String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); - if (parser.shouldPreprocess()) - body = preprocessBody(body); + CommandParser parser = selectParser(extractTrueHeader(header)); + if (parser == null) { + return new IncorrectCommand(header + body, + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + } - String combined = header + body; + if (parser.shouldPreprocess()) { + return makeParserParse(parser, header + preprocessBody(body)); + } else { + return makeParserParse(parser, header + body); + } + } + + /** + * Makes the parser parse the preprocessed text. + * + * @param parser the parser to use to parse + * @param preprocessedText the text to parse + * @return the output Command from the parsing, or IncorrectCommand object if the parse failed + */ + private Command makeParserParse(CommandParser parser, String preprocessedText) { try { - return parser.parse(combined); + return parser.parse(preprocessedText); } catch (ParseException pe) { - return new IncorrectCommand(combined, String.format(pe.getFailureDetails())); + return new IncorrectCommand(preprocessedText, String.format(pe.getFailureDetails())); } } + private CommandParser selectParser(String header) { + return commandParsers.get(header); + } + /** * Does an initial preprocessing of a command text in case the header is aliased. * Returns a string array with 2 elements: the first is the header which is possibly aliased, @@ -78,13 +119,13 @@ private String[] preprocessInitial(String commandText) { } /** - * Gets the header from the preprocessed header as a preprocessed header may contain + * Gets the true header from the preprocessed header as a preprocessed header may contain * several tokens. * * @param preprocessedHeader the preprocessed header * @return the true header */ - private String extractHeader(String preprocessedHeader) { + private String extractTrueHeader(String preprocessedHeader) { Matcher matcher = KEYWORD_PATTERN.matcher(preprocessedHeader); if (matcher.find()) { @@ -182,7 +223,8 @@ public boolean addAliasSymbol(AliasSymbol symbol) { return false; if (isCommandParserRegistered(symbol.getKeyword())) return false; - + + aliasList.add(symbol); aliasingSymbols.put(symbol.getKeyword(), symbol); return true; } @@ -197,7 +239,12 @@ public boolean addAliasSymbol(AliasSymbol symbol) { public boolean removeAliasSymbol(String symbolKeyword) { assert symbolKeyword != null; - return aliasingSymbols.remove(symbolKeyword) != null; + AliasSymbol symbol = aliasingSymbols.remove(symbolKeyword); + if (symbol != null) { + return aliasList.remove(symbol); + } else { + return false; + } } /** @@ -216,6 +263,11 @@ public boolean doesAliasSymbolExist(String symbolKeyword) { * @see #removeAliasSymbol */ public void clearAllAliasSymbols() { + aliasList.clear(); aliasingSymbols.clear(); } + + public ObservableList getAliasSymbolList() { + return aliasList; + } } diff --git a/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java index 4bb317bdbde2..6592d486ee0b 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/ModifyCommandParser.java @@ -11,22 +11,13 @@ import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.RecurrenceType; -public class ModifyCommandParser implements CommandParser { +public class ModifyCommandParser extends TaskFieldParser { private static final String HEADER = "modify"; private static final String READABLE_FORMAT = HEADER+" INDEX [t/TASK_NAME] [s/START_DATE] " + "[e/END_DATE] [l/LOCATION] [p/PRIORITY_LEVEL] [r/RECURRING_TYPE] " + "[n/NUMBER_OF_RECURRENCE] [c/CATEGORY] [d/DESCRIPTION]"; private static final String REGEX_REF_INDEX = "Index"; - private static final String REGEX_REF_TASK_NAME = "TaskName"; - private static final String REGEX_REF_START_DATE = "StartDate"; - private static final String REGEX_REF_END_DATE = "EndDate"; - private static final String REGEX_REF_LOCATION = "Location"; - private static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; - private static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; - private static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; - private static final String REGEX_REF_CATEGORY = "Category"; - private static final String REGEX_REF_DESCRIPTION = "Description"; private static final Pattern REGEX_PATTERN = Pattern.compile( HEADER+"\\s+(?<"+REGEX_REF_INDEX+">([^/]+?(\\s+|$))+)((?<=\\s)(" + @@ -41,7 +32,6 @@ public class ModifyCommandParser implements CommandParser { "(d/(?<"+REGEX_REF_DESCRIPTION+">[^/]*)(?!.*\\sd/))" + ")(\\s|$)){0,11}", Pattern.CASE_INSENSITIVE); - private static final TaskFieldParser TASK_PARSER = new TaskFieldParser(); private static final IndexParser INDEX_PARSER = new IndexParser(); @Override @@ -62,13 +52,13 @@ public ModifyCommand parse(String commandText) throws ParseException { int index = parseIndex(matcher.group(REGEX_REF_INDEX)); InferredDate startDate = parseDate(matcher.group(REGEX_REF_START_DATE)); InferredDate endDate = parseDate(matcher.group(REGEX_REF_END_DATE)); - String taskName = TASK_PARSER.parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); - String location = TASK_PARSER.parseLocation(matcher.group(REGEX_REF_LOCATION)); - PriorityLevel priority = TASK_PARSER.parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); - RecurrenceType recurrence = TASK_PARSER.parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); - Integer nrOfRecurrence = TASK_PARSER.parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); - String category = TASK_PARSER.parseCategory(matcher.group(REGEX_REF_CATEGORY)); - String description = TASK_PARSER.parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); + String taskName = parseTaskName(matcher.group(REGEX_REF_TASK_NAME)); + String location = parseLocation(matcher.group(REGEX_REF_LOCATION)); + PriorityLevel priority = parsePriorityLevel(matcher.group(REGEX_REF_PRIORITY_LEVEL)); + RecurrenceType recurrence = parseRecurrenceType(matcher.group(REGEX_REF_RECURRING_TYPE)); + Integer nrOfRecurrence = parseNumberOfRecurrence(matcher.group(REGEX_REF_NUMBER_OF_RECURRENCE)); + String category = parseCategory(matcher.group(REGEX_REF_CATEGORY)); + String description = parseDescription(matcher.group(REGEX_REF_DESCRIPTION)); return new ModifyCommand(index, taskName, startDate, endDate, location, priority, @@ -90,9 +80,9 @@ private int parseIndex(String indexText) throws ParseException { private InferredDate parseDate(String dateText) throws ParseException { if (dateText != null && dateText.trim().isEmpty()) { - return TASK_PARSER.dateParser.new InferredDate(new Date(), true, true); + return dateParser.new InferredDate(new Date(), true, true); } - return TASK_PARSER.parseStartDate(dateText); + return parseStartDate(dateText); } } diff --git a/src/main/java/seedu/savvytasker/logic/parser/StorageCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/StorageCommandParser.java new file mode 100644 index 000000000000..c97a391c1431 --- /dev/null +++ b/src/main/java/seedu/savvytasker/logic/parser/StorageCommandParser.java @@ -0,0 +1,45 @@ +//@@author A0139916U +package seedu.savvytasker.logic.parser; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.StorageCommand; + +public class StorageCommandParser implements CommandParser { + private static final String HEADER = "storage"; + private static final String READABLE_FORMAT = HEADER+" NEW_PATH"; + + private static final String REGEX_REF_PATH = "Path"; + + private static final Pattern REGEX_PATTERN = Pattern.compile( + HEADER+"\\s+(?<"+REGEX_REF_PATH+">.*)", + Pattern.CASE_INSENSITIVE); + + @Override + public String getHeader() { + return HEADER; + } + + @Override + public String getRequiredFormat() { + return READABLE_FORMAT; + } + + @Override + public StorageCommand parse(String commandText) throws ParseException { + Matcher matcher = REGEX_PATTERN.matcher(commandText); + if (matcher.matches()) { + String path = matcher.group(REGEX_REF_PATH).trim(); + + if (!path.isEmpty()) { + return new StorageCommand(path); + } + } + + throw new ParseException(commandText, String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, getRequiredFormat())); + } + +} diff --git a/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java b/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java index 4e981b3dd4ba..361ad6944576 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/TaskFieldParser.java @@ -1,31 +1,47 @@ //@@author A0139916U package seedu.savvytasker.logic.parser; +import seedu.savvytasker.logic.commands.Command; import seedu.savvytasker.logic.parser.DateParser.InferredDate; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.RecurrenceType; /** * This class contains common parsing methods for parsing Task fields. + * Each of the parse method takes in a string which can be null, and return + * the respective parsed object. */ -public class TaskFieldParser { +public abstract class TaskFieldParser implements CommandParser { + /* + * Provides standard regex ref names for various task fields that might be used by subclasses. + */ + protected static final String REGEX_REF_TASK_NAME = "TaskName"; + protected static final String REGEX_REF_START_DATE = "StartDate"; + protected static final String REGEX_REF_END_DATE = "EndDate"; + protected static final String REGEX_REF_LOCATION = "Location"; + protected static final String REGEX_REF_PRIORITY_LEVEL = "Priority"; + protected static final String REGEX_REF_RECURRING_TYPE = "RecurringType"; + protected static final String REGEX_REF_NUMBER_OF_RECURRENCE = "RecurringNumber"; + protected static final String REGEX_REF_CATEGORY = "Category"; + protected static final String REGEX_REF_DESCRIPTION = "Description"; + protected final DateParser dateParser; - public TaskFieldParser() { + protected TaskFieldParser() { this.dateParser = new DateParser(); } - public String parseTaskName(String taskNameText) throws ParseException { + protected String parseTaskName(String taskNameText) throws ParseException { if (taskNameText == null) return null; return taskNameText.trim(); } - public InferredDate parseStartDate(String dateText) throws ParseException { + protected InferredDate parseStartDate(String dateText) throws ParseException { return parseDate(dateText, "START_DATE: "); } - public InferredDate parseEndDate(String dateText) throws ParseException { + protected InferredDate parseEndDate(String dateText) throws ParseException { return parseDate(dateText, "END_DATE: "); } @@ -41,13 +57,13 @@ private InferredDate parseDate(String dateText, String errorField) throws ParseE } } - public String parseLocation(String locationText) throws ParseException { + protected String parseLocation(String locationText) throws ParseException { if (locationText == null) return null; return locationText.trim(); } - public PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseException { + protected PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseException { if (priorityLevelText == null) return null; @@ -59,7 +75,7 @@ public PriorityLevel parsePriorityLevel(String priorityLevelText) throws ParseEx } } - public RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws ParseException { + protected RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws ParseException { if (recurrenceTypeText == null) return null; @@ -71,7 +87,7 @@ public RecurrenceType parseRecurrenceType(String recurrenceTypeText) throws Pars } } - public Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseException { + protected Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseException { if (numRecurrenceText == null) return null; @@ -93,13 +109,13 @@ public Integer parseNumberOfRecurrence(String numRecurrenceText) throws ParseExc return numRecurrence; } - public String parseCategory(String categoryText) throws ParseException { + protected String parseCategory(String categoryText) throws ParseException { if (categoryText == null) return null; return categoryText.trim(); } - public String parseDescription(String descriptionText) throws ParseException { + protected String parseDescription(String descriptionText) throws ParseException { if (descriptionText == null) return null; return descriptionText.trim(); diff --git a/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java b/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java index 96e63e82adf2..809f57552b6e 100644 --- a/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java +++ b/src/main/java/seedu/savvytasker/logic/parser/UnaliasCommandParser.java @@ -14,7 +14,8 @@ public class UnaliasCommandParser implements CommandParser { private static final String REGEX_REF_KEYWORD = "Keyword"; private static final Pattern REGEX_PATTERN = Pattern.compile( - HEADER+"\\s+(?<"+REGEX_REF_KEYWORD+">[^/]+)" + HEADER+"\\s+(?<"+REGEX_REF_KEYWORD+">[^/]+)", + Pattern.CASE_INSENSITIVE ); @Override diff --git a/src/main/java/seedu/savvytasker/model/task/ListType.java b/src/main/java/seedu/savvytasker/model/ListType.java similarity index 82% rename from src/main/java/seedu/savvytasker/model/task/ListType.java rename to src/main/java/seedu/savvytasker/model/ListType.java index cca1a5d58472..2b7c9f244679 100644 --- a/src/main/java/seedu/savvytasker/model/task/ListType.java +++ b/src/main/java/seedu/savvytasker/model/ListType.java @@ -1,8 +1,8 @@ //@@author A0139916U -package seedu.savvytasker.model.task; +package seedu.savvytasker.model; /** - * This enum represents the different ways to list tasks. + * This enum represents what and how to list tasks/symbols. */ public enum ListType { /** @@ -18,7 +18,12 @@ public enum ListType { /** * List archived tasks. */ - Archived; + Archived, + + /** + * List aliases. + */ + Alias; /** * Gets a ListType enum object from its enum name, ignoring cases. diff --git a/src/main/java/seedu/savvytasker/model/Model.java b/src/main/java/seedu/savvytasker/model/Model.java index eaff64142432..4b081eb8ba34 100644 --- a/src/main/java/seedu/savvytasker/model/Model.java +++ b/src/main/java/seedu/savvytasker/model/Model.java @@ -1,5 +1,7 @@ package seedu.savvytasker.model; +import java.util.LinkedList; + import seedu.savvytasker.commons.core.UnmodifiableObservableList; import seedu.savvytasker.model.alias.AliasSymbol; import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; @@ -22,22 +24,37 @@ public interface Model { ReadOnlySavvyTasker getSavvyTasker(); //@@author A0139915W - /** Deletes the given Task. */ - void deleteTask(ReadOnlyTask target) throws TaskNotFoundException; + /** + * Deletes the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. + * */ + Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException; - /** Modifies the given Task. */ - void modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException; + /** + * Modifies the given Task. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the modify operation is successful, an exception is thrown otherwise. + * */ + Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException; /** Adds the given Task. * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns a Task if the add operation is successful, an exception is thrown otherwise. + * */ + Task addTask(Task task) throws InvalidDateException; + + /** Adds the given Task as a recurring task. The task's recurrence type must not be null. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of Tasks added if the add operation is successful, an exception is thrown otherwise. * */ - void addTask(Task task) throws DuplicateTaskException, InvalidDateException; + LinkedList addRecurringTask(Task task) throws InvalidDateException; /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ UnmodifiableObservableList getFilteredTaskList(); - - /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ - UnmodifiableObservableList getFilteredTaskListTask(); /** Updates the filter of the filtered task list to show all active tasks sorted by due date */ void updateFilteredListToShowActiveSortedByDueDate(); @@ -60,4 +77,7 @@ public interface Model { /** Removes an the given AliasSymbol. */ void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundException; + + /** Gets the number of aliases */ + int getAliasSymbolCount(); } diff --git a/src/main/java/seedu/savvytasker/model/ModelManager.java b/src/main/java/seedu/savvytasker/model/ModelManager.java index 98c5fbb87b84..1551a6608316 100644 --- a/src/main/java/seedu/savvytasker/model/ModelManager.java +++ b/src/main/java/seedu/savvytasker/model/ModelManager.java @@ -4,6 +4,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.logging.Logger; @@ -22,7 +23,6 @@ import seedu.savvytasker.model.task.FindType; import seedu.savvytasker.model.task.ReadOnlyTask; import seedu.savvytasker.model.task.Task; -import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; import seedu.savvytasker.model.task.TaskList.InvalidDateException; import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; @@ -50,7 +50,7 @@ public ModelManager(SavvyTasker src) { savvyTasker = new SavvyTasker(src); filteredTasks = new FilteredList<>(savvyTasker.getTasks()); - sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDueDate()); updateFilteredListToShowActive(); // shows only active tasks on start } @@ -61,7 +61,7 @@ public ModelManager() { public ModelManager(ReadOnlySavvyTasker initialData) { savvyTasker = new SavvyTasker(initialData); filteredTasks = new FilteredList<>(savvyTasker.getTasks()); - sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDefault()); + sortedAndFilteredTasks = new SortedList<>(filteredTasks, new TaskSortedByDueDate()); updateFilteredListToShowActive(); // shows only active tasks on start } //@@author @@ -95,23 +95,33 @@ private void indicateAliasSymbolRemoved(AliasSymbol symbol) { //@@author A0139915W @Override - public synchronized void deleteTask(ReadOnlyTask target) throws TaskNotFoundException { - savvyTasker.removeTask(target); + public synchronized Task deleteTask(ReadOnlyTask target) throws TaskNotFoundException { + Task taskDeleted = savvyTasker.removeTask(target); indicateSavvyTaskerChanged(); + return taskDeleted; } @Override - public void modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException { - savvyTasker.replaceTask(target, replacement); + public synchronized Task modifyTask(ReadOnlyTask target, Task replacement) throws TaskNotFoundException, InvalidDateException { + Task taskModified = savvyTasker.replaceTask(target, replacement); indicateSavvyTaskerChanged(); + return taskModified; } @Override - public synchronized void addTask(Task t) throws DuplicateTaskException, InvalidDateException { - t.setId(savvyTasker.getNextTaskId()); - savvyTasker.addTask(t); + public synchronized Task addTask(Task t) throws InvalidDateException { + Task taskAdded = savvyTasker.addTask(t); updateFilteredListToShowActive(); indicateSavvyTaskerChanged(); + return taskAdded; + } + + @Override + public synchronized LinkedList addRecurringTask(Task recurringTask) throws InvalidDateException { + LinkedList recurringTasks = savvyTasker.addRecurringTasks(recurringTask); + updateFilteredListToShowActive(); + indicateSavvyTaskerChanged(); + return recurringTasks; } //@@author @@ -129,6 +139,11 @@ public synchronized void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeyw indicateSavvyTaskerChanged(); indicateAliasSymbolRemoved(symbol); } + + @Override + public int getAliasSymbolCount() { + return savvyTasker.getAliasSymbolCount(); + } //@@author //=========== Filtered/Sorted Task List Accessors =============================================================== @@ -138,11 +153,6 @@ public synchronized void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeyw public UnmodifiableObservableList getFilteredTaskList() { return new UnmodifiableObservableList(sortedAndFilteredTasks); } - - @Override - public UnmodifiableObservableList getFilteredTaskListTask() { - return new UnmodifiableObservableList(sortedAndFilteredTasks); - } @Override public void updateFilteredListToShowActiveSortedByDueDate() { @@ -464,18 +474,7 @@ public int compare(Task task1, Task task2) { else if (task1 == null) return 1; else if (task2 == null) return -1; else { - // Priority Level can be nulls - // Check for existence of priorityLevel before comparing - if (task1.getPriority() == null && - task2.getPriority() == null) { - return 0; - } else if (task1.getPriority() == null) { - return 1; - } else if (task2.getPriority() == null) { - return -1; - } else { - return task2.getPriority().compareTo(task1.getPriority()); - } + return task2.getPriority().compareTo(task1.getPriority()); } } diff --git a/src/main/java/seedu/savvytasker/model/SavvyTasker.java b/src/main/java/seedu/savvytasker/model/SavvyTasker.java index a58381cf4ab5..7b32afb8635f 100644 --- a/src/main/java/seedu/savvytasker/model/SavvyTasker.java +++ b/src/main/java/seedu/savvytasker/model/SavvyTasker.java @@ -1,7 +1,11 @@ package seedu.savvytasker.model; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -12,6 +16,7 @@ import seedu.savvytasker.model.alias.DuplicateSymbolKeywordException; import seedu.savvytasker.model.alias.SymbolKeywordNotFoundException; import seedu.savvytasker.model.task.ReadOnlyTask; +import seedu.savvytasker.model.task.RecurrenceType; import seedu.savvytasker.model.task.Task; import seedu.savvytasker.model.task.TaskList; import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; @@ -67,40 +72,162 @@ public void resetData(ReadOnlySavvyTasker newData) { //// symbol/task-level operations //@@author A0139915W + /** - * Returns the next available id for use to uniquely identify a task. - * @author A0139915W - * @return The next available id. + * Adds a task to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the task added if the operation succeeds, an exception is thrown otherwise. */ - public int getNextTaskId() { - return tasks.getNextId(); + public Task addTask(Task t) throws InvalidDateException { + // guarantees unique ID + t.setId(tasks.getNextId()); + try { + return tasks.add(t); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } } /** - * Adds a task to savvy tasker. - * @throws TaskList.DuplicateTaskException if an equivalent task already exists. + * Adds a group of recurring tasks to savvy tasker. + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the list of recurring tasks if the operation succeeds, an exception is thrown otherwise + */ + public LinkedList addRecurringTasks(Task recurringTask) throws InvalidDateException { + LinkedList tasksToAdd = + createRecurringTasks(recurringTask, recurringTask.getRecurringType(), + recurringTask.getNumberOfRecurrence()); + Iterator itr = tasksToAdd.iterator(); + + while(itr.hasNext()) { + // this will be an atomic operation + // guaranteed no duplicates + // if the start/end dates are invalid, + // the first task to be added will fail immediately, + // subsequent tasks will not be added + try { + tasks.add(itr.next()); + } catch (DuplicateTaskException e) { + // should never get here. + return null; + } + } + return tasksToAdd; + } + + /** + * Creates a list of recurring tasks to be added into savvy tasker. + * @param recurringTask the task that recurs + * @param recurringType the type of recurrence + * @param numberOfRecurrences the number of recurrences + * @return A list of tasks that represents a recurring task. */ - public void addTask(Task t) throws DuplicateTaskException, InvalidDateException { - tasks.add(t); + private LinkedList createRecurringTasks(Task recurringTask, RecurrenceType recurringType, int numberOfRecurrences) { + assert recurringTask.getRecurringType() != null; + assert recurringTask.getNumberOfRecurrence() > 0; + + LinkedList listOfTasks = new LinkedList(); + recurringTask.setGroupId(tasks.getNextGroupId()); + + for (int i = 0; i < numberOfRecurrences; ++i) { + Task t = recurringTask.clone(); + // guarantees uniqueness + t.setId(tasks.getNextId()); + listOfTasks.add(setDatesForRecurringType(t, recurringType, i)); + } + + return listOfTasks; + } + + /** + * Helper function for createRecurringTasks(). Sets the respective start/end datetime for the + * i-th recurring task to be added + * @param t The first recurring task + * @param recurringType the type of recurrence + * @param index the index of the loop + * @return A task with its respective datetime set. + */ + private Task setDatesForRecurringType(Task t, RecurrenceType recurringType, int index) { + Date startDate = t.getStartDateTime(); + Date endDate = t.getEndDateTime(); + Calendar calendar = Calendar.getInstance(); + switch (recurringType) { + case Daily: // add one day to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 1 * index); + endDate = calendar.getTime(); + } + break; + case Weekly: // add 7 days to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.DATE, 7 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.DATE, 7 * index); + endDate = calendar.getTime(); + } + break; + case Monthly: // add 1 month to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.MONTH, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.MONTH, 1 * index); + endDate = calendar.getTime(); + } + break; + case Yearly: // add 1 year to the i-th task + if (startDate != null) { + calendar.setTime(startDate); + calendar.add(Calendar.YEAR, 1 * index); + startDate = calendar.getTime(); + } + if (endDate != null) { + calendar.setTime(endDate); + calendar.add(Calendar.YEAR, 1 * index); + endDate = calendar.getTime(); + } + break; + case None: + default: + assert false; // should not come here + } + t.setStartDateTime(startDate); + t.setEndDateTime(endDate); + return t; } /** * Removes a task from savvy tasker. * @param key the task to be removed - * @return true if the task is removed successfully - * @throws TaskNotFoundException if the task to be removed does not exist + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the remove operation is successful, an exception is thrown otherwise. */ - public boolean removeTask(ReadOnlyTask key) throws TaskNotFoundException { + public Task removeTask(ReadOnlyTask key) throws TaskNotFoundException { return tasks.remove(key); } /** * Replaces a task from savvy tasker. * @param key the task to be replaced + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date * @return true if the task is removed successfully - * @throws TaskNotFoundException if the task to be removed does not exist */ - public boolean replaceTask(ReadOnlyTask key, Task replacement) throws TaskNotFoundException, InvalidDateException { + public Task replaceTask(ReadOnlyTask key, Task replacement) throws TaskNotFoundException, InvalidDateException { return tasks.replace(key, replacement); } //@@author @@ -125,6 +252,10 @@ public void removeAliasSymbol(AliasSymbol symbol) throws SymbolKeywordNotFoundEx } //@@author + public int getAliasSymbolCount() { + return symbols.size(); + } + //// util methods @Override diff --git a/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java b/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java index afd67350f3b1..52fbaddfc592 100644 --- a/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java +++ b/src/main/java/seedu/savvytasker/model/alias/AliasSymbol.java @@ -24,7 +24,7 @@ public AliasSymbol(String keyword, String representation) { assert keyword != null && !keyword.matches(".*\\s+.*"); assert representation != null && !representation.isEmpty(); - this.keyword = keyword; + this.keyword = keyword.toLowerCase(); this.representation = representation; } diff --git a/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java b/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java index 7b0c9d7b3444..9b4df673008c 100644 --- a/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java +++ b/src/main/java/seedu/savvytasker/model/task/ReadOnlyTask.java @@ -10,6 +10,7 @@ public interface ReadOnlyTask { int getId(); + int getGroupId(); boolean isMarked(); boolean isArchived(); String getTaskName(); @@ -39,23 +40,68 @@ default String getAsText() { .append(" Task Name: ") .append(getTaskName()) .append(" Archived: ") - .append(isArchived()) - .append(" Start: ") - .append(getStartDateTime()) - .append(" End: ") - .append(getEndDateTime()) - .append(" Location: ") - .append(getLocation()) - .append(" Priority: ") + .append(isArchived()); + if (getStartDateTime() != null) { + builder.append(" Start: ") + .append(getStartDateTime()); + } + if (getEndDateTime() != null) { + builder.append(" End: ") + .append(getEndDateTime()); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()); + } + builder.append(" Priority: ") + .append(getPriority()); + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + . append(getDescription()); + } + return builder.toString(); + } + + + /** + * Formats the task as text, showing all task details, formatted for the UI. + */ + default String getTextForUi() { + final StringBuilder builder = new StringBuilder(); + if (getStartDateTime() != null) { + builder.append(" Start: ") + .append(getStartDateTime()) + .append("\n"); + } + if (getEndDateTime() != null) { + builder.append(" End: ") + .append(getEndDateTime()) + .append("\n"); + } + if (getLocation() != null && !getLocation().isEmpty()) { + builder.append(" Location: ") + .append(getLocation()) + .append("\n"); + } + builder.append(" Priority: ") .append(getPriority()) - .append(" Recurring Type: ") - .append(getRecurringType()) - .append(" Nr. Recurrence: ") - .append(getNumberOfRecurrence()) - .append(" Category: ") - .append(getCategory()) - .append(" Description: ") - .append(getDescription()); + .append("\n"); + if (getCategory() != null && !getCategory().isEmpty()) { + builder.append(" Category: ") + .append(getCategory()) + .append("\n"); + } + if (getDescription() != null && !getDescription().isEmpty()) { + builder.append(" Description: ") + .append(getDescription()) + .append("\n"); + } + builder.append(" Archived: ") + .append(isArchived()); return builder.toString(); } diff --git a/src/main/java/seedu/savvytasker/model/task/Task.java b/src/main/java/seedu/savvytasker/model/task/Task.java index e70d8763336c..593822c9bf31 100644 --- a/src/main/java/seedu/savvytasker/model/task/Task.java +++ b/src/main/java/seedu/savvytasker/model/task/Task.java @@ -13,6 +13,7 @@ public class Task implements ReadOnlyTask { private int id; + private int groupId; private String taskName; private Date startDateTime; private Date endDateTime; @@ -27,12 +28,13 @@ public class Task implements ReadOnlyTask { /** * Constructor with smart defaults */ - public Task(int id, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, + public Task(int id, int groupId, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, String description, boolean isArchived) { SmartDefaultDates sdd = new SmartDefaultDates(startDateTime, endDateTime); this.id = id; + this.groupId = groupId; this.taskName = taskName; this.startDateTime = sdd.getStartDate(); this.endDateTime = sdd.getEndDate(); @@ -48,7 +50,7 @@ public Task(int id, String taskName, InferredDate startDateTime, InferredDate en this.recurringType = recurringType; } if (numberOfRecurrence == null) { - this.numberOfRecurrence = 0; + this.numberOfRecurrence = 1; } else { this.numberOfRecurrence = numberOfRecurrence.intValue(); } @@ -60,11 +62,12 @@ public Task(int id, String taskName, InferredDate startDateTime, InferredDate en /** * Constructor without smart defaults */ - public Task(int id, String taskName, Date startDateTime, Date endDateTime, String location, + public Task(int id, int groupId, String taskName, Date startDateTime, Date endDateTime, String location, PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, String description, boolean isArchived) { this.id = id; + this.groupId = groupId; this.taskName = taskName; this.startDateTime = startDateTime; this.endDateTime = endDateTime; @@ -80,7 +83,7 @@ public Task(int id, String taskName, Date startDateTime, Date endDateTime, Strin this.recurringType = recurringType; } if (numberOfRecurrence == null) { - this.numberOfRecurrence = 0; + this.numberOfRecurrence = 1; } else { this.numberOfRecurrence = numberOfRecurrence.intValue(); } @@ -93,15 +96,13 @@ public Task(String taskName) { this.taskName = taskName; // sets initial default values this.priority = PriorityLevel.Medium; - this.recurringType = RecurrenceType.None; - this.numberOfRecurrence = 0; } /** * Copy constructor. */ public Task(ReadOnlyTask source) { - this(source.getId(), source.getTaskName(), source.getStartDateTime(), + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), source.getEndDateTime(), source.getLocation(), source.getPriority(), source.getRecurringType(), source.getNumberOfRecurrence(), source.getCategory(), source.getDescription(), source.isArchived()); @@ -113,12 +114,13 @@ public Task(ReadOnlyTask source) { public Task(ReadOnlyTask source, String taskName, InferredDate startDateTime, InferredDate endDateTime, String location, PriorityLevel priority, RecurrenceType recurringType, Integer numberOfRecurrence, String category, String description) { - this(source.getId(), source.getTaskName(), source.getStartDateTime(), + this(source.getId(), source.getGroupId(), source.getTaskName(), source.getStartDateTime(), source.getEndDateTime(), source.getLocation(), source.getPriority(), source.getRecurringType(), source.getNumberOfRecurrence(), source.getCategory(), source.getDescription(), source.isArchived()); //this.id should follow that of the source. + //this.groupId should follow that of the source. //this.isArchived should follow that of the source. this.taskName = taskName == null ? this.taskName : taskName; setStartDate(startDateTime); @@ -172,6 +174,11 @@ public int getId() { return this.id; } + @Override + public int getGroupId() { + return this.groupId; + } + @Override public boolean isMarked() { return isArchived(); // all marked tasks are archived @@ -231,6 +238,10 @@ public void setId(int id) { this.id = id; } + public void setGroupId(int groupId) { + this.groupId = groupId; + } + public void setTaskName(String taskName) { this.taskName = taskName; } @@ -309,6 +320,17 @@ public int hashCode() { public String toString() { return getAsText(); } + + /** + * Creates a deep copy of this object. + */ + public Task clone() { + Task t = new Task(id, groupId, taskName, + (Date)startDateTime.clone(), (Date)endDateTime.clone(), + location, priority, recurringType, numberOfRecurrence, + category, description, isArchived); + return t; + } } //@@author diff --git a/src/main/java/seedu/savvytasker/model/task/TaskList.java b/src/main/java/seedu/savvytasker/model/task/TaskList.java index e56a2a0e2502..6c68eab70ae7 100644 --- a/src/main/java/seedu/savvytasker/model/task/TaskList.java +++ b/src/main/java/seedu/savvytasker/model/task/TaskList.java @@ -1,7 +1,6 @@ package seedu.savvytasker.model.task; import java.util.Iterator; -import java.util.LinkedList; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -63,6 +62,8 @@ public static class TaskNotFoundException extends Exception { private final ObservableList internalList = FXCollections.observableArrayList(); private int nextId = 0; private boolean isNextIdInitialized = false; + private int nextGroupId = 0; + private boolean isNextGroupIdInitialized = false; /** * Constructs empty TaskList. @@ -76,10 +77,8 @@ public TaskList() {} */ public int getNextId() { if (!isNextIdInitialized) { - int nextLowest = -1; // first id to be used is 0. Start finding with -1 - LinkedList usedIds = new LinkedList(); + int nextLowest = 0; // first id to be used is 1. Start finding with 0 for (Task t : internalList) { - usedIds.add(t.getId()); if (t.getId() > nextLowest) { nextLowest = t.getId(); } @@ -93,6 +92,29 @@ public int getNextId() { nextId++; return nextId; } + + /** + * Gets the next available group id for uniquely identifying a group of recurring tasks in + * Savvy Tasker. + * @return The next available group id; + */ + public int getNextGroupId() { + if (!isNextGroupIdInitialized) { + int nextLowest = 0; // first id to be used is 1. Start finding with 0 + for (Task t : internalList) { + if (t.getId() > nextLowest) { + nextLowest = t.getGroupId(); + } + } + // assumption that the number of tasks < 2^31 + // implementation will be buggy if nextId exceeds 2^31 + nextGroupId = nextLowest; + assert nextGroupId < Integer.MAX_VALUE; + isNextGroupIdInitialized = true; + } + nextGroupId++; + return nextGroupId; + } /** * Returns true if the list contains an equivalent task as the given argument. @@ -116,10 +138,11 @@ public boolean isValidStartEnd(ReadOnlyTask toCheck) { /** * Adds a task to the list. - * - * @throws DuplicateTaskException if the person to add is a duplicate of an existing task in the list. + * @throws {@link DuplicateTaskException} if a duplicate is found + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task added if the add is successful, an exception is thrown otherwise. */ - public void add(Task toAdd) throws DuplicateTaskException, InvalidDateException { + public Task add(Task toAdd) throws DuplicateTaskException, InvalidDateException { assert toAdd != null; if (contains(toAdd)) { throw new DuplicateTaskException(); @@ -128,28 +151,36 @@ public void add(Task toAdd) throws DuplicateTaskException, InvalidDateException throw new InvalidDateException(); } internalList.add(toAdd); + return toAdd; } /** * Removes the equivalent task from the list. - * - * @throws TaskNotFoundException if no such task could be found in the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @return Returns a Task if the delete operation is successful, an exception is thrown otherwise. */ - public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + public Task remove(ReadOnlyTask toRemove) throws TaskNotFoundException { assert toRemove != null; - final boolean taskFoundAndDeleted = internalList.remove(toRemove); - if (!taskFoundAndDeleted) { + int index = internalList.indexOf(toRemove); + if (index >= 0) { + final Task taskToDelete = internalList.get(index); + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskToDelete; + } else { throw new TaskNotFoundException(); } - return taskFoundAndDeleted; } /** * Replaces the equivalent task from the list. - * - * @throws TaskNotFoundException if no such task could be found in the list. + * @throws {@link TaskNotFoundException} if the task does not exist + * @throws {@link InvalidDateException} if the end date is earlier than the start date + * @return Returns the Task replaced if the replace is successful, an exception is thrown otherwise. */ - public boolean replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotFoundException, InvalidDateException { + public Task replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotFoundException, InvalidDateException { assert toReplace != null; assert replacement != null; if (internalList.contains(toReplace)) { @@ -157,7 +188,7 @@ public boolean replace(ReadOnlyTask toReplace, Task replacement) throws TaskNotF throw new InvalidDateException(); } internalList.set(internalList.indexOf(toReplace), replacement); - return true; + return replacement; } else { throw new TaskNotFoundException(); diff --git a/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java b/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java index 2346201925f4..60a25ac03ea6 100644 --- a/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java +++ b/src/main/java/seedu/savvytasker/storage/SavvyTaskerStorage.java @@ -16,6 +16,13 @@ public interface SavvyTaskerStorage { */ String getSavvyTaskerFilePath(); + //@@author A0139915W + /** + * Sets the file path of the data file. Returns true if successful, false otherwise + */ + boolean setSavvyTaskerFilePath(String path); + //@@author + /** * Returns SavvyTasker data as a {@link ReadOnlySavvyTasker}. * Returns {@code Optional.empty()} if storage file is not found. diff --git a/src/main/java/seedu/savvytasker/storage/Storage.java b/src/main/java/seedu/savvytasker/storage/Storage.java index b52ea243a253..4ccbb1b5e76a 100644 --- a/src/main/java/seedu/savvytasker/storage/Storage.java +++ b/src/main/java/seedu/savvytasker/storage/Storage.java @@ -5,6 +5,7 @@ import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; import seedu.savvytasker.commons.exceptions.DataConversionException; import seedu.savvytasker.model.ReadOnlySavvyTasker; import seedu.savvytasker.model.UserPrefs; @@ -22,15 +23,25 @@ public interface Storage extends SavvyTaskerStorage, UserPrefsStorage { @Override String getSavvyTaskerFilePath(); + + @Override + boolean setSavvyTaskerFilePath(String path); @Override Optional readSavvyTasker() throws DataConversionException, IOException; @Override void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker) throws IOException; + + /** + * Changes the storage location as requested + * Creates the data file at the new location. + * Raises {@link DataSavingExceptionEvent} if there was an error during saving. + */ + void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce); /** - * Saves the current version of the Address Book to the hard disk. + * Saves the current version of the Savvy Tasker to the hard disk. * Creates the data file if it is missing. * Raises {@link DataSavingExceptionEvent} if there was an error during saving. */ diff --git a/src/main/java/seedu/savvytasker/storage/StorageManager.java b/src/main/java/seedu/savvytasker/storage/StorageManager.java index 5117af6e555e..a9d463a690b4 100644 --- a/src/main/java/seedu/savvytasker/storage/StorageManager.java +++ b/src/main/java/seedu/savvytasker/storage/StorageManager.java @@ -10,6 +10,7 @@ import seedu.savvytasker.commons.core.LogsCenter; import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; import seedu.savvytasker.commons.exceptions.DataConversionException; import seedu.savvytasker.model.ReadOnlySavvyTasker; import seedu.savvytasker.model.UserPrefs; @@ -47,13 +48,20 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ SavvyTasker methods ============================== @Override public String getSavvyTaskerFilePath() { return savvyTaskerStorage.getSavvyTaskerFilePath(); } + //@@author A0139915W + @Override + public boolean setSavvyTaskerFilePath(String path) { + return savvyTaskerStorage.setSavvyTaskerFilePath(path); + } + //@@author + @Override public Optional readSavvyTasker() throws DataConversionException, IOException { return readSavvyTasker(savvyTaskerStorage.getSavvyTaskerFilePath()); @@ -76,6 +84,18 @@ public void saveSavvyTasker(ReadOnlySavvyTasker savvyTasker, String filePath) th savvyTaskerStorage.saveSavvyTasker(savvyTasker, filePath); } + //@@author A0139915W + @Override + @Subscribe + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + logger.info(LogsCenter.getEventHandlingLogMessage(dslce, "Local storage location changed.")); + try { + saveSavvyTasker(dslce.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + //@@author @Override @Subscribe diff --git a/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java b/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java index 2598075ab531..7df5aab8cf31 100644 --- a/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java +++ b/src/main/java/seedu/savvytasker/storage/XmlAdaptedTask.java @@ -7,7 +7,6 @@ import seedu.savvytasker.commons.exceptions.IllegalValueException; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.ReadOnlyTask; -import seedu.savvytasker.model.task.RecurrenceType; import seedu.savvytasker.model.task.Task; //@@author A0139915W @@ -18,6 +17,8 @@ public class XmlAdaptedTask { @XmlElement(required = true) private int id; + @XmlElement(required = false) + private int groupId; @XmlElement(required = true) private String taskName; @XmlElement(required = false) @@ -29,10 +30,6 @@ public class XmlAdaptedTask { @XmlElement(required = false) private PriorityLevel priority; @XmlElement(required = false) - private RecurrenceType recurringType; - @XmlElement(required = false) - private int numberOfRecurrence; - @XmlElement(required = false) private String category; @XmlElement(required = false) private String description; @@ -52,13 +49,12 @@ public XmlAdaptedTask() {} */ public XmlAdaptedTask(ReadOnlyTask source) { id = source.getId(); + groupId = source.getGroupId(); taskName = source.getTaskName(); startDateTime = source.getStartDateTime(); endDateTime = source.getEndDateTime(); location = source.getLocation(); priority = source.getPriority(); - recurringType = source.getRecurringType(); - numberOfRecurrence = source.getNumberOfRecurrence(); category = source.getCategory(); description = source.getDescription(); isArchived = source.isArchived(); @@ -76,13 +72,11 @@ public Task toModelType() throws IllegalValueException { final Date endDateTime = this.endDateTime; final String location = this.location; final PriorityLevel priority = this.priority; - final RecurrenceType recurringType = this.recurringType; - final int numberOfRecurrence = this.numberOfRecurrence; final String category = this.category; final String description = this.description; final boolean isArchived = this.isArchived; - return new Task(id, taskName, startDateTime, endDateTime, location, priority, - recurringType, numberOfRecurrence, category, description, isArchived); + return new Task(id, groupId, taskName, startDateTime, endDateTime, location, priority, + null, null, category, description, isArchived); } } //@@author diff --git a/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java b/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java index 2afdb822eb47..29092ceb7cc6 100644 --- a/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java +++ b/src/main/java/seedu/savvytasker/storage/XmlSavvyTaskerStorage.java @@ -29,6 +29,27 @@ public String getSavvyTaskerFilePath() { return filePath; } + //@@author A0139915W + @Override + /** + * Changes the path of the storage location if the file at newPath can be successfully created. + */ + public boolean setSavvyTaskerFilePath(String newPath) { + try { + File file = new File(newPath); + if (FileUtil.createIfMissing(file)) { + this.filePath = newPath; + return true; + } else { + return false; + } + } catch (IOException e) { + // do nothing, can't set the path to new path + } + return false; + } + //@@author + /** * Similar to {@link #readSavvyTasker()} * @param filePath location of the data. Cannot be null diff --git a/src/main/java/seedu/savvytasker/ui/AliasSymbolCard.java b/src/main/java/seedu/savvytasker/ui/AliasSymbolCard.java new file mode 100644 index 000000000000..32d272f2be63 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/AliasSymbolCard.java @@ -0,0 +1,56 @@ +package seedu.savvytasker.ui; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import seedu.savvytasker.model.alias.AliasSymbol; + +public class AliasSymbolCard extends UiPart{ + + private static final String FXML = "AliasSymbolListCard.fxml"; + + @FXML + private HBox cardPane; + @FXML + private Label aliasName; + @FXML + private Label id; + @FXML + private Label details; + + private AliasSymbol symbol; + private int displayedIndex; + + public AliasSymbolCard(){ + + } + + public static AliasSymbolCard load(AliasSymbol symbol, int displayedIndex){ + AliasSymbolCard card = new AliasSymbolCard(); + card.symbol = symbol; + card.displayedIndex = displayedIndex; + return UiPartLoader.loadUiPart(card); + } + + @FXML + public void initialize() { + aliasName.setText(symbol.getKeyword()); + id.setText(displayedIndex + ". "); + details.setText(symbol.getRepresentation()); + } + + public HBox getLayout() { + return cardPane; + } + + @Override + public void setNode(Node node) { + cardPane = (HBox)node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} diff --git a/src/main/java/seedu/savvytasker/ui/AliasSymbolListPanel.java b/src/main/java/seedu/savvytasker/ui/AliasSymbolListPanel.java new file mode 100644 index 000000000000..001aa10b1c50 --- /dev/null +++ b/src/main/java/seedu/savvytasker/ui/AliasSymbolListPanel.java @@ -0,0 +1,111 @@ +package seedu.savvytasker.ui; + +import java.util.logging.Logger; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import seedu.savvytasker.commons.core.LogsCenter; +import seedu.savvytasker.model.alias.AliasSymbol; + +/** + * Panel containing the list of persons. + */ +public class AliasSymbolListPanel extends UiPart { + private final Logger logger = LogsCenter.getLogger(AliasSymbolListPanel.class); + private static final String FXML = "AliasSymbolListPanel.fxml"; + private VBox panel; + private AnchorPane placeHolderPane; + + @FXML + private ListView aliasSymbolListView; + + public AliasSymbolListPanel() { + super(); + } + + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + public Node getNode() { + return panel; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane pane) { + this.placeHolderPane = pane; + } + + public static AliasSymbolListPanel load(Stage primaryStage, AnchorPane aliasSymbolListPlaceholder, + ObservableList aliasList) { + AliasSymbolListPanel aliasListPanel = + UiPartLoader.loadUiPart(primaryStage, aliasSymbolListPlaceholder, new AliasSymbolListPanel()); + aliasListPanel.configure(aliasList); + return aliasListPanel; + } + + private void configure(ObservableList aliasList) { + setConnections(aliasList); + addToPlaceholder(); + } + + private void setConnections(ObservableList aliasList) { + aliasSymbolListView.setItems(aliasList); + aliasSymbolListView.setCellFactory(listView -> new AliasSymbolListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void addToPlaceholder() { + SplitPane.setResizableWithParent(placeHolderPane, false); + placeHolderPane.getChildren().add(panel); + } + + private void setEventHandlerForSelectionChangeEvent() { + aliasSymbolListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in alias list panel changed to : '" + newValue + "'"); + //raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + public void scrollTo(int index) { + Platform.runLater(() -> { + aliasSymbolListView.scrollTo(index); + aliasSymbolListView.getSelectionModel().clearAndSelect(index); + }); + } + + class AliasSymbolListViewCell extends ListCell { + + public AliasSymbolListViewCell() { + } + + @Override + protected void updateItem(AliasSymbol symbol, boolean empty) { + super.updateItem(symbol, empty); + + if (empty || symbol == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(AliasSymbolCard.load(symbol, getIndex() + 1).getLayout()); + } + } + } + +} diff --git a/src/main/java/seedu/savvytasker/ui/MainWindow.java b/src/main/java/seedu/savvytasker/ui/MainWindow.java index 426f6a7e0bf5..1974651a4ecc 100644 --- a/src/main/java/seedu/savvytasker/ui/MainWindow.java +++ b/src/main/java/seedu/savvytasker/ui/MainWindow.java @@ -31,6 +31,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private BrowserPanel browserPanel; private TaskListPanel taskListPanel; + private AliasSymbolListPanel aliasSymbolListPanel; private ResultDisplay resultDisplay; private StatusBarFooter statusBarFooter; private CommandBox commandBox; @@ -53,13 +54,19 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private AnchorPane personListPanelPlaceholder; + private AnchorPane taskListPanelPlaceholder; + + @FXML + private AnchorPane aliasSymbolListPanelPlaceholder; @FXML private AnchorPane resultDisplayPlaceholder; @FXML private AnchorPane statusbarPlaceholder; + + @FXML + private VBox listPanel; public MainWindow() { @@ -79,7 +86,7 @@ public String getFxmlPath() { 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); + mainWindow.configure(config.getAppTitle(), config.getSavvyTaskerListName(), config, prefs, logic); return mainWindow; } @@ -109,11 +116,41 @@ private void setAccelerators() { void fillInnerParts() { browserPanel = BrowserPanel.load(browserPlaceholder); - taskListPanel = TaskListPanel.load(primaryStage, getPersonListPlaceholder(), logic.getFilteredTaskList()); + taskListPanel = TaskListPanel.load(primaryStage, getTaskListPlaceholder(), logic.getFilteredTaskList()); + aliasSymbolListPanel = AliasSymbolListPanel.load(primaryStage, getAliasSymbolPlaceholder(), logic.getAliasSymbolList()); + setDefaultView(); resultDisplay = ResultDisplay.load(primaryStage, getResultDisplayPlaceholder()); - statusBarFooter = StatusBarFooter.load(primaryStage, getStatusbarPlaceholder(), config.getAddressBookFilePath()); + statusBarFooter = StatusBarFooter.load(primaryStage, getStatusbarPlaceholder(), config.getSavvyTaskerFilePath()); commandBox = CommandBox.load(primaryStage, getCommandBoxPlaceholder(), resultDisplay, logic); } + + /** + * Removes all the children in the taskPanel VBox + * Shows the default list, which is the list of tasks + */ + private void setDefaultView() { + getListPanel().getChildren().remove(getAliasSymbolPlaceholder()); + getListPanel().getChildren().remove(getTaskListPlaceholder()); + getListPanel().getChildren().add(getTaskListPlaceholder()); + } + + /** + * Set to true to show the list of tasks. Set to false to show the list of alias + * @param isShown + */ + public void showTaskList(boolean isShown) { + getListPanel().getChildren().remove(getAliasSymbolPlaceholder()); + getListPanel().getChildren().remove(getTaskListPlaceholder()); + if (isShown) { + getListPanel().getChildren().add(getTaskListPlaceholder()); + } else { + getListPanel().getChildren().add(getAliasSymbolPlaceholder()); + } + } + + private VBox getListPanel() { + return listPanel; + } private AnchorPane getCommandBoxPlaceholder() { return commandBoxPlaceholder; @@ -127,8 +164,12 @@ private AnchorPane getResultDisplayPlaceholder() { return resultDisplayPlaceholder; } - public AnchorPane getPersonListPlaceholder() { - return personListPanelPlaceholder; + public AnchorPane getTaskListPlaceholder() { + return taskListPanelPlaceholder; + } + + public AnchorPane getAliasSymbolPlaceholder() { + return aliasSymbolListPanelPlaceholder; } public void hide() { @@ -181,8 +222,12 @@ public void show() { private void handleExit() { raise(new ExitAppRequestEvent()); } + + public AliasSymbolListPanel getAliasSymbolListPanel() { + return this.aliasSymbolListPanel; + } - public TaskListPanel getPersonListPanel() { + public TaskListPanel getTaskListPanel() { return this.taskListPanel; } diff --git a/src/main/java/seedu/savvytasker/ui/StatusBarFooter.java b/src/main/java/seedu/savvytasker/ui/StatusBarFooter.java index 4d927f84f101..482171f30520 100644 --- a/src/main/java/seedu/savvytasker/ui/StatusBarFooter.java +++ b/src/main/java/seedu/savvytasker/ui/StatusBarFooter.java @@ -14,6 +14,7 @@ import javafx.stage.Stage; import seedu.savvytasker.commons.core.LogsCenter; import seedu.savvytasker.commons.events.model.SavvyTaskerChangedEvent; +import seedu.savvytasker.commons.events.storage.DataSavingLocationChangedEvent; import seedu.savvytasker.commons.util.FxViewUtil; /** @@ -47,7 +48,7 @@ public void configure(String saveLocation) { addSyncStatus(); setSyncStatus("Not updated yet in this session"); addSaveLocation(); - setSaveLocation("./" + saveLocation); + setSaveLocation(saveLocation); registerAsAnEventHandler(this); } @@ -91,8 +92,15 @@ public String getFxmlPath() { return FXML; } + //@@author A0139915W @Subscribe - public void handleAddressBookChangedEvent(SavvyTaskerChangedEvent stce) { + public void handleSavvyTaskerSaveLocationChangedEvent(DataSavingLocationChangedEvent dslce) { + setSaveLocation(dslce.newPath); + } + //@@author + + @Subscribe + public void handleSavvyTaskerChangedEvent(SavvyTaskerChangedEvent stce) { String lastUpdated = (new Date()).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(stce, "Setting last updated status to " + lastUpdated)); setSyncStatus("Last Updated: " + lastUpdated); diff --git a/src/main/java/seedu/savvytasker/ui/TaskCard.java b/src/main/java/seedu/savvytasker/ui/TaskCard.java index b3cc0f963fce..5d3512c8b5de 100644 --- a/src/main/java/seedu/savvytasker/ui/TaskCard.java +++ b/src/main/java/seedu/savvytasker/ui/TaskCard.java @@ -1,7 +1,5 @@ package seedu.savvytasker.ui; -import java.util.Date; - import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.Label; @@ -10,8 +8,7 @@ public class TaskCard extends UiPart{ - private static final String FXML = "PersonListCard.fxml"; - private static final String EMPTY_FIELD = " - "; + private static final String FXML = "TaskListCard.fxml"; @FXML private HBox cardPane; @@ -20,13 +17,7 @@ public class TaskCard extends UiPart{ @FXML private Label id; @FXML - private Label startDate; - @FXML - private Label endDate; - @FXML - private Label description; - @FXML - private Label tags; + private Label details; private ReadOnlyTask task; private int displayedIndex; @@ -45,25 +36,8 @@ public static TaskCard load(ReadOnlyTask task, int displayedIndex){ @FXML public void initialize() { taskName.setText(task.getTaskName()); - Date startDate = task.getStartDateTime(); - if (startDate != null) { - this.startDate.setText(startDate.toString()); - } else { - this.startDate.setText(EMPTY_FIELD); - } - Date endDate = task.getEndDateTime(); - if (endDate != null) { - this.endDate.setText(endDate.toString()); - } else { - this.endDate.setText(EMPTY_FIELD); - } - String description = task.getDescription(); - if (description != null) { - this.description.setText(description); - } else { - this.description.setText(EMPTY_FIELD); - } id.setText(displayedIndex + ". "); + details.setText(task.getTextForUi()); } public HBox getLayout() { diff --git a/src/main/java/seedu/savvytasker/ui/TaskListPanel.java b/src/main/java/seedu/savvytasker/ui/TaskListPanel.java index 9b10bf105c42..f90ea46f78fd 100644 --- a/src/main/java/seedu/savvytasker/ui/TaskListPanel.java +++ b/src/main/java/seedu/savvytasker/ui/TaskListPanel.java @@ -17,11 +17,11 @@ import seedu.savvytasker.model.task.ReadOnlyTask; /** - * Panel containing the list of persons. + * Panel containing the list of tasks. */ public class TaskListPanel extends UiPart { private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); - private static final String FXML = "PersonListPanel.fxml"; + private static final String FXML = "TaskListPanel.fxml"; private VBox panel; private AnchorPane placeHolderPane; @@ -36,6 +36,10 @@ public TaskListPanel() { public void setNode(Node node) { panel = (VBox) node; } + + public Node getNode() { + return panel; + } @Override public String getFxmlPath() { @@ -74,7 +78,7 @@ private void addToPlaceholder() { private void setEventHandlerForSelectionChangeEvent() { taskListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); + logger.fine("Selection in task list panel changed to : '" + newValue + "'"); raise(new TaskPanelSelectionChangedEvent(newValue)); } }); diff --git a/src/main/java/seedu/savvytasker/ui/UiManager.java b/src/main/java/seedu/savvytasker/ui/UiManager.java index cd41d3a82aa1..f5b5835b2868 100644 --- a/src/main/java/seedu/savvytasker/ui/UiManager.java +++ b/src/main/java/seedu/savvytasker/ui/UiManager.java @@ -14,6 +14,8 @@ import seedu.savvytasker.commons.core.Config; import seedu.savvytasker.commons.core.LogsCenter; import seedu.savvytasker.commons.events.storage.DataSavingExceptionEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent; +import seedu.savvytasker.commons.events.ui.ChangeListRequestEvent.DisplayedList; import seedu.savvytasker.commons.events.ui.JumpToListRequestEvent; import seedu.savvytasker.commons.events.ui.ShowHelpRequestEvent; import seedu.savvytasker.commons.events.ui.TaskPanelSelectionChangedEvent; @@ -112,10 +114,20 @@ private void handleShowHelpEvent(ShowHelpRequestEvent event) { mainWindow.handleHelp(); } + @Subscribe + private void handleChangeListRequestEvent(ChangeListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + if (event.displayedList == DisplayedList.Task) { + mainWindow.showTaskList(true); + } else { + mainWindow.showTaskList(false); + } + } + @Subscribe private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getPersonListPanel().scrollTo(event.targetIndex); + mainWindow.getTaskListPanel().scrollTo(event.targetIndex); } @Subscribe diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/AliasSymbolListCard.fxml similarity index 74% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/AliasSymbolListCard.fxml index 9953ce588beb..7f205ec4b5b1 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/AliasSymbolListCard.fxml @@ -12,7 +12,7 @@ - + @@ -26,14 +26,14 @@ - - diff --git a/src/main/resources/view/AliasSymbolListPanel.fxml b/src/main/resources/view/AliasSymbolListPanel.fxml new file mode 100644 index 000000000000..9149a98c6ab4 --- /dev/null +++ b/src/main/resources/view/AliasSymbolListPanel.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 4eeb2d392bab..e2567ee9ad65 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -36,12 +36,13 @@ - + - + + diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 000000000000..2df104257f29 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml similarity index 100% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/TaskListPanel.fxml diff --git a/src/test/java/guitests/AddCommandTest.java b/src/test/java/guitests/AddCommandTest.java index 41984bea99f6..becd062d5121 100644 --- a/src/test/java/guitests/AddCommandTest.java +++ b/src/test/java/guitests/AddCommandTest.java @@ -4,7 +4,7 @@ import org.junit.Test; -import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.logic.commands.AddCommand; import seedu.savvytasker.logic.commands.HelpCommand; import seedu.savvytasker.testutil.TestTask; import seedu.savvytasker.testutil.TestUtil; @@ -12,6 +12,11 @@ import static org.junit.Assert.assertTrue; import static seedu.savvytasker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + //@@author A0139915W public class AddCommandTest extends SavvyTaskerGuiTest { @@ -34,7 +39,15 @@ public void add() { //invalid command commandBox.runCommand("adds Bad Command Task"); - assertResultMessage(String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertResultMessage("Input: adds Bad Command Task\n" + + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + + //invalid start end date + Date start = getDate("31/12/2015"); + Date end = getDate("30/12/2015"); + commandBox.runCommand("add bad start-end pair s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(AddCommand.MESSAGE_INVALID_START_END)); } private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { @@ -44,10 +57,29 @@ private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { TaskCardHandle addedCard = taskListPanel.navigateToTask(taskToAdd.getTaskName()); assertMatching(taskToAdd, addedCard); - //confirm the list now contains all previous persons plus the new person + //confirm the list now contains all previous tasks plus the new task TestTask[] expectedList = TestUtil.addTasksToList(currentList, taskToAdd); assertTrue(taskListPanel.isListMatching(expectedList)); } + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } } //@@author diff --git a/src/test/java/guitests/FindCommandTest.java b/src/test/java/guitests/FindCommandTest.java index fc884bb9e436..8183ef85bc17 100644 --- a/src/test/java/guitests/FindCommandTest.java +++ b/src/test/java/guitests/FindCommandTest.java @@ -2,7 +2,6 @@ import org.junit.Test; -import seedu.savvytasker.commons.core.Messages; import seedu.savvytasker.logic.commands.HelpCommand; import seedu.savvytasker.testutil.TestTask; @@ -37,6 +36,11 @@ public void find_nonEmptyList_byFullMatch() { public void find_nonEmptyList_byExactMatch() { assertFindResult("find t/exact Nearer Due Task", td.nearerDue); // one matching result only } + + @Test + public void find_nonEmptyList_byCategory() { + assertFindResult("find t/category priority", td.highPriority, td.medPriority, td.lowPriority); // matching 3 results + } @Test public void find_emptyList(){ @@ -47,7 +51,7 @@ public void find_emptyList(){ @Test public void find_invalidCommand_fail() { commandBox.runCommand("findmyring"); - assertResultMessage(String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertResultMessage("Input: findmyring\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); } private void assertFindResult(String command, TestTask... expectedHits) { diff --git a/src/test/java/guitests/ListCommandTest.java b/src/test/java/guitests/ListCommandTest.java index 990f475711c9..c8989ee4d9f6 100644 --- a/src/test/java/guitests/ListCommandTest.java +++ b/src/test/java/guitests/ListCommandTest.java @@ -2,7 +2,6 @@ import org.junit.Test; -import seedu.savvytasker.commons.core.Messages; import seedu.savvytasker.logic.commands.HelpCommand; import seedu.savvytasker.testutil.TestTask; @@ -23,6 +22,12 @@ public void list_nonEmptyList() { td.highPriority, td.medPriority, td.lowPriority); } + @Test + public void list_nonEmptyList_byInvalidSwitch() { + commandBox.runCommand("list badswitch"); + assertResultMessage("Input: list badswitch\nLIST_TYPE: Unknown type \'badswitch\'"); + } + @Test public void list_nonEmptyList_byDueDate() { // covered by list_nonEmptyList() @@ -30,13 +35,13 @@ public void list_nonEmptyList_byDueDate() { @Test public void list_nonEmptyList_byPriority() { - assertListResult("list t/PriorityLevel", td.highPriority, td.medPriority, + assertListResult("list PriorityLevel", td.highPriority, td.medPriority, td.furthestDue, td.nearerDue, td.notSoNearerDue, td.earliestDue, td.lowPriority); //multiple results } @Test public void list_nonEmptyList_byArchived() { - assertListResult("list t/Archived", td.longDue); // one matching result only + assertListResult("list Archived", td.longDue); // one matching result only } @Test @@ -48,7 +53,7 @@ public void list_emptyList(){ @Test public void find_invalidCommand_fail() { commandBox.runCommand("listmytasks"); - assertResultMessage(String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertResultMessage("Input: listmytasks\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); } private void assertListResult(String command, TestTask... expectedHits ) { diff --git a/src/test/java/guitests/ModifyCommandTest.java b/src/test/java/guitests/ModifyCommandTest.java new file mode 100644 index 000000000000..fddc833fe95e --- /dev/null +++ b/src/test/java/guitests/ModifyCommandTest.java @@ -0,0 +1,84 @@ +package guitests; + +import guitests.guihandles.TaskCardHandle; + +import org.junit.Test; + +import seedu.savvytasker.commons.core.Messages; +import seedu.savvytasker.testutil.TestTask; +import seedu.savvytasker.testutil.TestUtil; + +import static org.junit.Assert.assertTrue; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +//@@author A0139915W +public class ModifyCommandTest extends SavvyTaskerGuiTest { + + @Test + public void add() { + //modify task + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToModify = currentList[0]; + Date start = getDate("30/12/2016"); + Date end = getDate("31/12/2016"); + taskToModify.setStartDateTime(start); + taskToModify.setEndDateTime(end); + assertModifySuccess("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end), taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + taskToModify.setStartDateTime(null); + taskToModify.setEndDateTime(null); + assertModifySuccess("modify 1 s/ e/", taskToModify, currentList); + currentList = TestUtil.replaceTaskFromList(currentList, taskToModify); + + //modify invalid index + commandBox.runCommand("modify " + currentList.length + "1" + " s/sat"); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX)); + + //modify with invalid end date + start = getDate("31/12/2016"); + end = getDate("30/12/2016"); + commandBox.runCommand("modify 1 s/" + getLocaleDateString(start) + + " e/" + getLocaleDateString(end)); + assertResultMessage(String.format(Messages.MESSAGE_INVALID_START_END)); + } + + private void assertModifySuccess(String command, TestTask taskToModify, TestTask... currentList) { + commandBox.runCommand(command); + + //confirm the new card contains the right data + TaskCardHandle modifiedCard = taskListPanel.navigateToTask(taskToModify.getTaskName()); + assertMatching(taskToModify, modifiedCard); + + //confirm the list now contains all previous persons plus the new person + TestTask[] expectedList = TestUtil.replaceTaskFromList(currentList, taskToModify); + assertTrue(taskListPanel.isListMatching(expectedList)); + } + + private DateFormat formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); + private String getLocaleDateString(Date date) { + try { + return formatter.format(date); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + +} +//@@author diff --git a/src/test/java/guitests/guihandles/TaskCardHandle.java b/src/test/java/guitests/guihandles/TaskCardHandle.java index fe32021d1b5f..58771f3d2091 100644 --- a/src/test/java/guitests/guihandles/TaskCardHandle.java +++ b/src/test/java/guitests/guihandles/TaskCardHandle.java @@ -11,6 +11,7 @@ */ public class TaskCardHandle extends GuiHandle { private static final String TASKNAME_FIELD_ID = "#taskName"; + private static final String DETAILS_FIELD_ID = "#details"; private Node node; @@ -26,16 +27,21 @@ protected String getTextFromLabel(String fieldId) { public String getTaskName() { return getTextFromLabel(TASKNAME_FIELD_ID); } + + public String getDetails() { + return getTextFromLabel(DETAILS_FIELD_ID); + } public boolean isSameTask(ReadOnlyTask task) { - return getTaskName().equals(task.getTaskName()); + return getTaskName().equals(task.getTaskName()) && getDetails().equals(task.getTextForUi()); } @Override public boolean equals(Object obj) { if(obj instanceof TaskCardHandle) { TaskCardHandle handle = (TaskCardHandle) obj; - return getTaskName().equals(handle.getTaskName()); //TODO: compare the rest + return getTaskName().equals(handle.getTaskName()) && + getDetails().equals(handle.getDetails()); } return super.equals(obj); } diff --git a/src/test/java/seedu/savvytasker/TestApp.java b/src/test/java/seedu/savvytasker/TestApp.java index f29428ea4944..ad08171828b1 100644 --- a/src/test/java/seedu/savvytasker/TestApp.java +++ b/src/test/java/seedu/savvytasker/TestApp.java @@ -45,9 +45,9 @@ public TestApp(Supplier initialDataSupplier, String saveFil protected Config initConfig(String configFilePath) { Config config = super.initConfig(configFilePath); config.setAppTitle(APP_TITLE); - config.setAddressBookFilePath(saveFileLocation); + config.setSavvyTaskerFilePath(saveFileLocation); config.setUserPrefsFilePath(DEFAULT_PREF_FILE_LOCATION_FOR_TESTING); - config.setAddressBookName(ADDRESS_BOOK_NAME); + config.setSavvyTaskerName(ADDRESS_BOOK_NAME); return config; } diff --git a/src/test/java/seedu/savvytasker/commons/core/ConfigTest.java b/src/test/java/seedu/savvytasker/commons/core/ConfigTest.java index f1c820ab9b2c..3f0c0642d746 100644 --- a/src/test/java/seedu/savvytasker/commons/core/ConfigTest.java +++ b/src/test/java/seedu/savvytasker/commons/core/ConfigTest.java @@ -20,7 +20,7 @@ public void toString_defaultObject_stringReturned() { "Current log level : INFO\n" + "Preference file Location : preferences.json\n" + "Local data file location : data/savvytasker.xml\n" + - "AddressBook name : MyTaskList"; + "Savvy Tasker List name : MyTaskList"; assertEquals(defaultConfigAsString, new Config().toString()); } diff --git a/src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java b/src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java index f3fa7414459e..0e853b22d958 100644 --- a/src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java +++ b/src/test/java/seedu/savvytasker/commons/util/ConfigUtilTest.java @@ -79,8 +79,8 @@ private Config getTypicalConfig() { config.setAppTitle("Typical App Title"); config.setLogLevel(Level.INFO); config.setUserPrefsFilePath("C:\\preferences.json"); - config.setAddressBookFilePath("addressbook.xml"); - config.setAddressBookName("TypicalAddressBookName"); + config.setSavvyTaskerFilePath("data/savvytasker.xml"); + config.setSavvyTaskerName("MyTaskList"); return config; } diff --git a/src/test/java/seedu/savvytasker/commons/util/SmartDefaultDatesTest.java b/src/test/java/seedu/savvytasker/commons/util/SmartDefaultDatesTest.java new file mode 100644 index 000000000000..6fe7ca6fc358 --- /dev/null +++ b/src/test/java/seedu/savvytasker/commons/util/SmartDefaultDatesTest.java @@ -0,0 +1,247 @@ +package seedu.savvytasker.commons.util; + +import org.junit.Test; + +import seedu.savvytasker.logic.parser.DateParser; +import seedu.savvytasker.logic.parser.DateParser.InferredDate; +import seedu.savvytasker.logic.parser.ParseException; + +import static org.junit.Assert.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +//@@author A0139915W +public class SmartDefaultDatesTest { + + @Test + public void smartDefaultDates_parseStart() { + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredEnd = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start date, assumed to on the given date at 12am + // and to end on the given date at 2359:59 + Date expectedStartTime = getDate("31/12/2016 000000"); + Date expectedEndTime = getDate("31/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specifying only start time, assumed to start today at the given time + // and to end today 2359:59 + expectedStartTime = getDate(sdf.format(today) + " 150000"); + expectedEndTime = getDate(sdf.format(today) + " 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseEnd() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + InferredDate inferredStart = null; + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date, assumed to start today at 12am + // and to end on the given date at 2359:59 + Date expectedStartTime = getDate(sdf.format(today) + " 000000"); + Date expectedEndTime = getDate("31/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("3pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end time, assumed to start today at 12am + // and to end at the given time today + expectedStartTime = getDate(sdf.format(today) + " 000000"); + expectedEndTime = getDate(sdf.format(today) + " 150000"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + + try { + //use MM-dd-yyyy + inferredEnd = dateParser.parseSingle("12/31/2000"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // specified only the end date in the past, start date will be null + // and to end on the given date at 2359:59 + expectedStartTime = null; + expectedEndTime = getDate("31/12/2000 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_parseStartEnd() { + // START_TIME + // date not supplied -> today + // time not supplied -> 0000 + // END_TIME + // date not supplied -> today + // time not supplied -> 2359 + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + InferredDate inferredStart = null; + InferredDate inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/31/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + SmartDefaultDates sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end + // start defaults to 0000 + // end defaults to 2359:59 + Date expectedStartTime = getDate("31/12/2016 000000"); + Date expectedEndTime = getDate("31/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("12/31/2016"); + inferredEnd = dateParser.parseSingle("12/30/2016"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no time supplied for start and end, end date earlier than start + // start defaults to 0000 + // end defaults to 2359:59 + // no restrictions imposed on end time earlier than start time + expectedStartTime = getDate("31/12/2016 000000"); + expectedEndTime = getDate("30/12/2016 235959"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10am"); + inferredEnd = dateParser.parseSingle("10pm"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end + // start and end defaults to the given time today + expectedStartTime = getDate(sdf.format(today) + " 100000"); + expectedEndTime = getDate(sdf.format(today) + " 220000"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + + inferredStart = null; + inferredEnd = null; + try { + //use MM-dd-yyyy + inferredStart = dateParser.parseSingle("10pm"); + inferredEnd = dateParser.parseSingle("10am"); + } catch (ParseException e) { + assert false; //won't get here + } + sdd = new SmartDefaultDates(inferredStart, inferredEnd); + // no date supplied for start and end, end time ends before start time + // start and end defaults to the given time today + // no restrictions imposed on end time being earlier + expectedStartTime = getDate(sdf.format(today) + " 220000"); + expectedEndTime = getDate(sdf.format(today) + " 100000"); + assertEquals(expectedStartTime, sdd.getStartDate()); + assertEquals(expectedEndTime, sdd.getEndDate()); + } + + @Test + public void smartDefaultDates_defaultParse() { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Date today = today(0, 0); + DateParser dateParser = new DateParser(); + SmartDefaultDates sdd = new SmartDefaultDates(null, null); + Date actualStart = sdd.getStart(dateParser.new InferredDate(new Date(), true, true)); + Date actualEnd = sdd.getEnd(dateParser.new InferredDate(new Date(), true, true)); + Date expectedStart = null; + Date expectedEnd = null; + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("10pm")); + actualEnd = sdd.getEnd(dateParser.parseSingle("10am")); + } catch (ParseException e) { + assert false; //won't get here + } + expectedStart = getDate(sdf.format(today) + " 220000"); + expectedEnd = getDate(sdf.format(today) + " 100000"); + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + + try { + //use MM-dd-yyyy + actualStart = sdd.getStart(dateParser.parseSingle("12/31/2016")); + actualEnd = sdd.getEnd(dateParser.parseSingle("12/31/2016")); + } catch (ParseException e) { + assert false; //won't get here + } + expectedStart = getDate("31/12/2016 000000"); + expectedEnd = getDate("31/12/2016 235959"); + assertEquals(expectedStart, actualStart); + assertEquals(expectedEnd, actualEnd); + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HHmmss"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } + + private Date today(int hours_24, int minute_60) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.set(Calendar.HOUR_OF_DAY, hours_24); + calendar.set(Calendar.MINUTE, minute_60); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } +} +//@@author diff --git a/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java b/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java index eec2e4ca6fd3..9b59af423a5e 100644 --- a/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java +++ b/src/test/java/seedu/savvytasker/logic/LogicManagerTest.java @@ -88,6 +88,7 @@ public void teardown() { public void execute_invalid() throws Exception { String invalidCommand = " "; assertCommandBehavior(invalidCommand, + "Input: \n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); } @@ -128,7 +129,7 @@ private void assertCommandBehavior(String inputCommand, String expectedMessage, @Test public void execute_unknownCommandWord() throws Exception { String unknownCommand = "uicfhmowqewca"; - assertCommandBehavior(unknownCommand, String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); + assertCommandBehavior(unknownCommand, "Input: uicfhmowqewca\n" + String.format(MESSAGE_UNKNOWN_COMMAND, HelpCommand.MESSAGE_USAGE)); } @Test @@ -195,10 +196,11 @@ private void assertIncorrectIndexFormatBehaviorForCommand(String commandWord, St // the following commands outputs a different expected message dealing with // invalid indices. - expectedMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; - //assertCommandBehavior(commandWord + " +1", expectedMessage); //index should be unsigned [NOT SUPPORTED] + expectedMessage = "Input: delete -1\n" + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; assertCommandBehavior(commandWord + " -1", expectedMessage); //index should be unsigned + expectedMessage = "Input: delete 0\n" + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; assertCommandBehavior(commandWord + " 0", expectedMessage); //index cannot be 0 + expectedMessage = "Input: delete not_a_number\n" + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT) + ": " + IndexParser.INDEX_MUST_BE_POSITIVE; assertCommandBehavior(commandWord + " not_a_number", expectedMessage); } @@ -253,7 +255,7 @@ public void execute_select_jumpsToCorrectPerson() throws Exception { @Test public void execute_deleteInvalidArgsFormat_errorMessageShown() throws Exception { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT); + String expectedMessage = "Input: delete\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.COMMAND_FORMAT); assertIncorrectIndexFormatBehaviorForCommand("delete", expectedMessage); } @@ -280,7 +282,7 @@ public void execute_delete_removesCorrectTask() throws Exception { @Test public void execute_find_invalidArgsFormat() throws Exception { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.COMMAND_FORMAT); + String expectedMessage = "Input: find\n" + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.COMMAND_FORMAT); assertCommandBehavior("find ", expectedMessage); } diff --git a/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java b/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java index 13c9bbd30247..a1856f5245ba 100644 --- a/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java +++ b/src/test/java/seedu/savvytasker/logic/parser/ParserTest.java @@ -32,6 +32,7 @@ public class ParserTest { private RedoCommandParser redoParser; private AliasCommandParser aliasParser; private UnaliasCommandParser unaliasParser; + private StorageCommandParser storageParser; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -52,49 +53,50 @@ public void setup() { redoParser = new RedoCommandParser(); aliasParser = new AliasCommandParser(); unaliasParser = new UnaliasCommandParser(); + storageParser = new StorageCommandParser(); } @Test - public void parse_add_reorder() throws ParseException { + public void parseAdd_reorder() throws ParseException { assertNotNull(addParser.parse("add task l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); } @Test - public void parse_add_multipleSpaces() throws ParseException { + public void parseAdd_multipleSpaces() throws ParseException { assertNotNull(addParser.parse("add Multiple Spaces s/ 2pm")); } @Test - public void parse_add_sameOptionMultipleTimes() throws ParseException { + public void parseAdd_sameOptionMultipleTimes_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task s/ tomorrow 3pm s/ tomorrow 10pm"); } @Test - public void parse_add_missingTaskName() throws ParseException { + public void parseAdd_missingTaskName_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add s/ tomorrow 3pm"); } @Test - public void parse_add_arbitrarySlash() throws ParseException { + public void parseAdd_arbitrarySlash_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task s/ tomorrow 2pm/3pm e/ sunday"); } @Test - public void parse_add_fullValid() throws ParseException { + public void parseAdd_fullValid() throws ParseException { assertNotNull(addParser.parse("add task s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); } @Test - public void parse_add_invalidRecurrenceType() throws ParseException { + public void parseAdd_invalidRecurrenceType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task r/ Error "); } @Test - public void parse_add_invalidPriorityLevel() throws ParseException { + public void parseAdd_invalidPriorityLevel_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); addParser.parse("add task p/ Error "); } @@ -102,34 +104,34 @@ public void parse_add_invalidPriorityLevel() throws ParseException { //================================================================================== @Test - public void parse_delete_noIndexSpecified() throws ParseException { + public void parseDelete_noIndexSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); deleteParser.parse("delete"); } @Test - public void parse_delete_oneIndex() throws ParseException { + public void parseDelete_oneIndex() throws ParseException { assertNotNull(deleteParser.parse("delete 1")); } @Test - public void parse_delete_multipleIndices() throws ParseException { + public void parseDelete_multipleIndices() throws ParseException { assertNotNull(deleteParser.parse("delete 1 2 3")); } @Test - public void parse_delete_multipleSpacesIndices() throws ParseException { + public void parseDelete_multipleSpacesIndices() throws ParseException { assertNotNull(deleteParser.parse("delete 1 2 3")); } @Test - public void parse_delete_negativeIndex() throws ParseException { + public void parseDelete_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); deleteParser.parse("delete -1"); } @Test - public void parse_delete_zeroIndex() throws ParseException { + public void parseDelete_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); deleteParser.parse("delete 0"); } @@ -137,198 +139,198 @@ public void parse_delete_zeroIndex() throws ParseException { //================================================================================== @Test - public void parse_modify_noIndex() throws ParseException { + public void parseModify_noIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify t/ newtask"); } @Test - public void parse_modify_multipleIndex() throws ParseException { + public void parseModify_multipleIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify 1 2 3 t/ newtask"); } @Test - public void parse_modify_negativeIndex() throws ParseException { + public void parseModify_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify -1 t/ newtask"); } @Test - public void parse_modify_zeroIndex() throws ParseException { + public void parseModify_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); modifyParser.parse("modify 0 t/ newtask"); } @Test - public void parse_modify_onlySpecifyIndex() throws ParseException { + public void parseModify_onlySpecifyIndex() throws ParseException { assertNotNull(modifyParser.parse("modify 1")); } @Test - public void parse_modify_fullValid() throws ParseException { + public void parseModify_fullValid() throws ParseException { assertNotNull(modifyParser.parse("modify 3 t/ newtask s/wednesday e/thursday l/ comp p/ high r/ none n/ 1 c/ test d/ test")); } @Test - public void parse_modify_reorder() throws ParseException { + public void parseModify_reorder() throws ParseException { assertNotNull(modifyParser.parse("modify 1 l/ comp e/ tomorrow, 3pm s/ today, 2pm n/ 2")); } @Test - public void parse_modify_multipleSpaces() throws ParseException { + public void parseModify_multipleSpaces() throws ParseException { assertNotNull(modifyParser.parse("modify 1 t/ Multiple Spaces s/ 2pm")); } //================================================================================== @Test - public void parse_clear_spaces() throws ParseException { + public void parseClear_spaces() throws ParseException { assertNotNull(clearParser.parse("clear ")); } @Test - public void parse_clear_invalid() throws ParseException { + public void parseClear_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); clearParser.parse("clear 1"); } @Test - public void parse_clear_valid() throws ParseException { + public void parseClear_valid() throws ParseException { assertNotNull(clearParser.parse("clear")); } //================================================================================== @Test - public void parse_list_noParameters() throws ParseException { + public void parseList_noParameters() throws ParseException { assertNotNull(listParser.parse("list")); } @Test - public void parse_list_noParametersSpaces() throws ParseException { + public void parseList_noParametersSpaces() throws ParseException { assertNotNull(listParser.parse("list ")); } @Test public void parse_list_valid() throws ParseException { - assertNotNull(listParser.parse("list t/ Priority Level ")); + assertNotNull(listParser.parse("list Priority Level ")); } @Test - public void parse_list_invalidType() throws ParseException { + public void parseList_invalidType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); - listParser.parse("list t/ Error "); + listParser.parse("list Error "); } //================================================================================== @Test - public void parse_find_noKeywords() throws ParseException { + public void parseFind_noKeywords_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find"); } @Test - public void parse_find_noKeywordsSpaces() throws ParseException { + public void parseFind_noKeywordsSpaces_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find "); } @Test - public void parse_find_noKeywordsButWithType() throws ParseException { + public void parseFind_noKeywordsButWithType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find t/ Exact "); } @Test - public void parse_find_invalidType() throws ParseException { + public void parseFind_invalidType_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); findParser.parse("find t/ Error some words"); } @Test - public void parse_find_validAfter() throws ParseException { + public void parseFind_validAfter() throws ParseException { assertNotNull(findParser.parse("find t/ Exact this word ")); } @Test - public void parse_find_validBefore() throws ParseException { + public void parseFind_validBefore() throws ParseException { assertNotNull(findParser.parse("find some words t/ Partial ")); } @Test - public void parse_find_validBeforeAndAfter() throws ParseException { + public void parseFind_validBeforeAndAfter() throws ParseException { assertNotNull(findParser.parse("find some words t/ Full some words after ")); } //================================================================================== @Test - public void parse_help_spaces() throws ParseException { + public void parseHelp_spaces() throws ParseException { assertNotNull(helpParser.parse("help ")); } @Test - public void parse_help_invalid() throws ParseException { + public void parseHelp_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("help 1"); } @Test - public void parse_help_valid() throws ParseException { + public void parseHelp_valid() throws ParseException { assertNotNull(helpParser.parse("help")); } //================================================================================== @Test - public void parse_exit_spaces() throws ParseException { + public void parseExit_spaces() throws ParseException { assertNotNull(exitParser.parse("exit ")); } @Test - public void parse_exit_invalid() throws ParseException { + public void parseExit_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("exit 1"); } @Test - public void parse_exit_valid() throws ParseException { + public void parseExit_valid() throws ParseException { assertNotNull(exitParser.parse("exit")); } //================================================================================== @Test - public void parse_mark_noIndexSpecified() throws ParseException { + public void parseMark_noIndexSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); markParser.parse("mark"); } @Test - public void parse_mark_oneIndex() throws ParseException { + public void parseMark_oneIndex() throws ParseException { assertNotNull(markParser.parse("mark 1")); } @Test - public void parse_mark_multipleIndices() throws ParseException { + public void parseMark_multipleIndices() throws ParseException { assertNotNull(markParser.parse("mark 1 2 3")); } @Test - public void parse_mark_multipleSpacesIndices() throws ParseException { + public void parseMark_multipleSpacesIndices() throws ParseException { assertNotNull(markParser.parse("mark 1 2 3")); } @Test - public void parse_mark_negativeIndex() throws ParseException { + public void parseMark_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); markParser.parse("mark -1"); } @Test - public void parse_mark_zeroIndex() throws ParseException { + public void parseMark_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); markParser.parse("mark 0"); } @@ -336,34 +338,34 @@ public void parse_mark_zeroIndex() throws ParseException { //================================================================================== @Test - public void parse_unmark_noIndexSpecified() throws ParseException { + public void parseUnmark_noIndexSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unmarkParser.parse("unmark"); } @Test - public void parse_unmark_oneIndex() throws ParseException { + public void parseUnmark_oneIndex() throws ParseException { assertNotNull(unmarkParser.parse("unmark 1")); } @Test - public void parse_unmark_multipleIndices() throws ParseException { + public void parseUnmark_multipleIndices() throws ParseException { assertNotNull(unmarkParser.parse("unmark 1 2 3")); } @Test - public void parse_unmark_multipleSpacesIndices() throws ParseException { + public void parseUnmark_multipleSpacesIndices() throws ParseException { assertNotNull(unmarkParser.parse("unmark 1 2 3")); } @Test - public void parse_unmark_negativeIndex() throws ParseException { + public void parseUnmark_negativeIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unmarkParser.parse("unmark -1"); } @Test - public void parse_unmark_zeroIndex() throws ParseException { + public void parseUnmark_zeroIndex_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unmarkParser.parse("unmark 0"); } @@ -371,106 +373,118 @@ public void parse_unmark_zeroIndex() throws ParseException { //================================================================================== @Test - public void parse_undo_spaces() throws ParseException { + public void parseUndo_spaces() throws ParseException { assertNotNull(undoParser.parse("undo ")); } @Test - public void parse_undo_invalid() throws ParseException { + public void parseUndo_invalid() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("undo 1"); } @Test - public void parse_undo_valid() throws ParseException { + public void parseUndo_valid() throws ParseException { assertNotNull(undoParser.parse("undo")); } //================================================================================== @Test - public void parse_redo_spaces() throws ParseException { + public void parseRedo_spaces() throws ParseException { assertNotNull(redoParser.parse("redo ")); } @Test - public void parse_redo_invalid() throws ParseException { + public void parseRedo_invalid_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); helpParser.parse("redo 1"); } @Test - public void parse_redo_valid() throws ParseException { + public void parseRedo_valid() throws ParseException { assertNotNull(redoParser.parse("redo")); } //================================================================================== @Test - public void parse_alias_keywordUnspecified() throws ParseException { + public void parseAlias_keywordUnspecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias r/ a string of things"); } @Test - public void parse_alias_textUnspecified() throws ParseException { + public void parseAlias_textUnspecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ xyz"); } @Test - public void parse_alias_noSwitchesSpecified() throws ParseException { + public void parseAlias_noSwitchesSpecified_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias power overwhelming"); } @Test - public void parse_alias_keywordTooLong() throws ParseException { + public void parseAlias_keywordNotSingleWord_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ not a single word r/ project management"); } @Test - public void parse_alias_keywordEmpty() throws ParseException { + public void parseAlias_keywordEmpty_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ r/ project management"); } @Test - public void parse_alias_textEmpty() throws ParseException { + public void parseAlias_textEmpty_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); aliasParser.parse("alias k/ pjm r/ "); } @Test - public void parse_alias_fullValid() throws ParseException { + public void parseAlias_fullValid() throws ParseException { assertNotNull(aliasParser.parse("alias k/ pjm r/ project management ")); } //================================================================================== @Test - public void parse_unalias_emptyKeyword() throws ParseException { + public void parseUnalias_emptyKeyword_exceptionThrown() throws ParseException { thrown.expect(ParseException.class); unaliasParser.parse("unalias "); } @Test - public void parse_unalias_valid() throws ParseException { + public void parseUnalias_valid() throws ParseException { assertNotNull(unaliasParser.parse("unalias something ")); } //================================================================================== - + + @Test + public void parseStorage_invalid_exceptionThrown() throws ParseException { + thrown.expect(ParseException.class); + storageParser.parse("storage "); + } + + @Test + public void parseStorage_valid() throws ParseException { + assertNotNull(storageParser.parse("storage C:/Users/Brown/Desktop/file.xml ")); + } + + //================================================================================== @Test - public void parse_master_subparser() throws ParseException { + public void masterParser_subparserParsing_returnParsedCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); assertTrue(parser.parse(" add A New Task s/ tomorrow e/ the day after tomorrow, l/ SR10 ") instanceof AddCommand); } @Test - public void parse_master_subparserRemoved() throws ParseException { + public void masterParser_subparserRemoved_returnIncorrectCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); parser.unregisterCommandParser("add"); @@ -478,7 +492,7 @@ public void parse_master_subparserRemoved() throws ParseException { } @Test - public void parse_master_alias() throws ParseException { + public void masterParser_alias_returnParsedCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); @@ -487,14 +501,14 @@ public void parse_master_alias() throws ParseException { } @Test - public void parse_master_invalidAlias() throws ParseException { + public void masterParser_invalidAlias_returnFalse() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); assertFalse(parser.addAliasSymbol(new AliasSymbol("add", "add A New Task"))); } @Test - public void parse_master_removedAlias() throws ParseException { + public void masterParser_removedAlias_returnIncorrectCommand() throws ParseException { MasterParser parser = new MasterParser(); parser.registerCommandParser(new AddCommandParser()); parser.addAliasSymbol(new AliasSymbol("xyz", "add A New Task")); diff --git a/src/test/java/seedu/savvytasker/model/task/TaskListTest.java b/src/test/java/seedu/savvytasker/model/task/TaskListTest.java new file mode 100644 index 000000000000..eac412d95d72 --- /dev/null +++ b/src/test/java/seedu/savvytasker/model/task/TaskListTest.java @@ -0,0 +1,92 @@ +package seedu.savvytasker.model.task; + +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.savvytasker.model.task.TaskList; +import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; +import seedu.savvytasker.model.task.TaskList.InvalidDateException; +import seedu.savvytasker.model.task.TaskList.TaskNotFoundException; + +import static org.junit.Assert.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.junit.Rule; + + +//@@author A0139915W +public class TaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void taskList_addDuplicate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(DuplicateTaskException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + tasks.add(t); // passes + assertEquals(1, tasks.getInternalList().size()); + tasks.add(t); // fails + } + + @Test + public void taskList_addInvalidDate() throws DuplicateTaskException, InvalidDateException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.add(t); // fails, end date earlier than start date + } + + @Test + public void taskList_removeNonExistent() throws TaskNotFoundException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.remove(t); // fails + } + + @Test + public void taskList_replaceNonExistent() throws TaskNotFoundException, InvalidDateException { + thrown.expect(TaskNotFoundException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + assertEquals(0, tasks.getInternalList().size()); + tasks.replace(t, t); // fails + } + + @Test + public void taskList_replaceInvalidDate() throws TaskNotFoundException, InvalidDateException, DuplicateTaskException { + thrown.expect(InvalidDateException.class); + TaskList tasks = new TaskList(); + Task t = new Task("Test Task"); + t.setId(1); + t.setStartDateTime(getDate("30/12/2016")); + t.setEndDateTime(getDate("31/12/2016")); + tasks.add(t); + assertEquals(1, tasks.getInternalList().size()); + t.setStartDateTime(getDate("31/12/2016")); + t.setEndDateTime(getDate("31/12/2015")); + tasks.replace(t, t); // fails, end date earlier than start date + } + + private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + private Date getDate(String ddmmyyyy) { + try { + return format.parse(ddmmyyyy); + } catch (Exception e) { + assert false; //should not get an invalid date.... + } + return null; + } +} +//@@author diff --git a/src/test/java/seedu/savvytasker/testutil/TestTask.java b/src/test/java/seedu/savvytasker/testutil/TestTask.java index 56f03a4e97cb..40b75a26f5da 100644 --- a/src/test/java/seedu/savvytasker/testutil/TestTask.java +++ b/src/test/java/seedu/savvytasker/testutil/TestTask.java @@ -1,5 +1,6 @@ package seedu.savvytasker.testutil; +import java.text.SimpleDateFormat; import java.util.Date; import seedu.savvytasker.model.task.PriorityLevel; @@ -13,6 +14,7 @@ public class TestTask implements ReadOnlyTask { private int id; + private int groupId; private String taskName; private Date startDateTime; private Date endDateTime; @@ -31,10 +33,16 @@ public TestTask() { this.numberOfRecurrence = 0; } + @Override public int getId() { return id; } + @Override + public int getGroupId() { + return groupId; + } + @Override public String getTaskName() { return taskName; @@ -93,6 +101,10 @@ public boolean isArchived() { public void setId(int id) { this.id = id; } + + public void setGroupId(int groupId) { + this.groupId = groupId; + } public void setTaskName(String taskName) { this.taskName = taskName; @@ -140,8 +152,32 @@ public String toString() { } public String getAddCommand() { + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HHmm"); StringBuilder sb = new StringBuilder(); sb.append("add " + this.getTaskName()); + if (startDateTime != null) { + sb.append(" s/ ").append(sdf.format(startDateTime)); + } + if (endDateTime != null) { + sb.append(" e/ ").append(sdf.format(endDateTime)); + } + if (location != null && !location.isEmpty()) { + sb.append(" l/ ").append(location); + } + if (priority != null && priority != PriorityLevel.Medium) { + // p/ defaults to medium, if set to medium, take as non-existent + sb.append(" p/ ").append(priority.toString()); + } + if (recurringType != null && recurringType != RecurrenceType.None) { + // r/ defaults to none, if set to none, take as non-existent + sb.append(" r/ ").append(recurringType.toString()); + } + if (category != null && !category.isEmpty()) { + sb.append(" c/ ").append(category); + } + if (description != null && !description.isEmpty()) { + sb.append(" d/ ").append(description); + } return sb.toString(); } } diff --git a/src/test/java/seedu/savvytasker/testutil/TestUtil.java b/src/test/java/seedu/savvytasker/testutil/TestUtil.java index a6ca044f7c42..348a28c5e37b 100644 --- a/src/test/java/seedu/savvytasker/testutil/TestUtil.java +++ b/src/test/java/seedu/savvytasker/testutil/TestUtil.java @@ -280,8 +280,13 @@ public static TestTask[] removeTaskFromList(final TestTask[] list, int targetInd * @param index The index of the task to be replaced. * @return */ - public static TestTask[] replaceTaskFromList(TestTask[] tasks, TestTask task, int index) { - tasks[index] = task; + public static TestTask[] replaceTaskFromList(TestTask[] tasks, TestTask task) { + for (int i = 0; i < tasks.length; ++i) { + if (tasks[i].getId() == task.getId()) { + tasks[i] = task; + break; + } + } return tasks; } diff --git a/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java b/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java index da6b3a91cb1e..31f80cda0527 100644 --- a/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java +++ b/src/test/java/seedu/savvytasker/testutil/TypicalTestTasks.java @@ -7,7 +7,6 @@ import seedu.savvytasker.model.SavvyTasker; import seedu.savvytasker.model.task.PriorityLevel; import seedu.savvytasker.model.task.Task; -import seedu.savvytasker.model.task.TaskList.DuplicateTaskException; import seedu.savvytasker.model.task.TaskList.InvalidDateException; //@@author A0139915W @@ -22,26 +21,26 @@ public class TypicalTestTasks { public TypicalTestTasks() { try { - highPriority = new TaskBuilder().withId(0).withTaskName("High Priority Task") - .withPriority(PriorityLevel.High).build(); - medPriority = new TaskBuilder().withId(1).withTaskName("Medium Priority Task") - .withPriority(PriorityLevel.Medium).build(); - lowPriority = new TaskBuilder().withId(2).withTaskName("Low Priority Task") - .withPriority(PriorityLevel.Low).build(); - furthestDue = new TaskBuilder().withId(3).withTaskName("Furthest Due Task") + highPriority = new TaskBuilder().withId(1).withTaskName("High Priority Task") + .withPriority(PriorityLevel.High).withCategory("priority").build(); + medPriority = new TaskBuilder().withId(2).withTaskName("Medium Priority Task") + .withPriority(PriorityLevel.Medium).withCategory("priority").build(); + lowPriority = new TaskBuilder().withId(3).withTaskName("Low Priority Task") + .withPriority(PriorityLevel.Low).withCategory("priority").build(); + furthestDue = new TaskBuilder().withId(4).withTaskName("Furthest Due Task") .withEndDateTime(getDate("01/12/2016")).build(); - nearerDue = new TaskBuilder().withId(4).withTaskName("Nearer Due Task") + nearerDue = new TaskBuilder().withId(5).withTaskName("Nearer Due Task") .withEndDateTime(getDate("01/11/2016")).build(); - notSoNearerDue = new TaskBuilder().withId(5).withTaskName("Not So Nearer Due Task") + notSoNearerDue = new TaskBuilder().withId(6).withTaskName("Not So Nearer Due Task") .withEndDateTime(getDate("02/11/2016")).build(); - earliestDue = new TaskBuilder().withId(6).withTaskName("Earliest Due Task") + earliestDue = new TaskBuilder().withId(7).withTaskName("Earliest Due Task") .withEndDateTime(getDate("01/10/2016")).build(); - longDue = new TaskBuilder().withId(7).withTaskName("Long Due Task") + longDue = new TaskBuilder().withId(8).withTaskName("Long Due Task") .withEndDateTime(getDate("01/1/2016")).withArchived(true).build(); //Manually added - happy = new TaskBuilder().withId(8).withTaskName("Happy Task").build(); - haloween = new TaskBuilder().withId(9).withTaskName("Haloween Task").build(); + happy = new TaskBuilder().withId(9).withTaskName("Happy Task").build(); + haloween = new TaskBuilder().withId(10).withTaskName("Haloween Task").build(); } catch (IllegalValueException e) { e.printStackTrace(); assert false : "not possible"; @@ -59,8 +58,6 @@ public static void loadSavvyTaskerWithSampleData(SavvyTasker st) { st.addTask(new Task(td.notSoNearerDue)); st.addTask(new Task(td.earliestDue)); st.addTask(new Task(td.longDue)); - } catch (DuplicateTaskException e) { - assert false : "not possible"; } catch (InvalidDateException e) { assert false : "not possible"; }