diff --git a/.gitignore b/.gitignore index 823d175eb670..f69c4d25f02b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ lib/* *.log *.log.* *.csv -config.json +/config.json src/test/data/sandbox/ preferences.json .DS_Store @@ -17,4 +17,5 @@ classes/ /data/ /bin/ src/main/resources/docs/ +src/main/resources/HelpWindow.html out/ diff --git a/.travis.yml b/.travis.yml index 1ffe1f2a5c77..72c45fff75da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: script: >- ./config/travis/run-checks.sh && - travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor copyDummySearchPage + travis_retry ./gradlew clean checkstyleMain checkstyleTest headless nonGuiTests coverage coveralls asciidoctor deploy: skip_cleanup: true diff --git a/README.adoc b/README.adoc index 142ae1b17fbd..9a37cd0c2d06 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ -= Address Book (Level 4) += Task Book ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] +https://travis-ci.org/CS2113-AY1819S1-W13-3/main[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] @@ -15,26 +15,22 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop Task Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is an app for busy students to *manage their daily tasks* and ultimately, *lead a more productive life*. +* If you can type fast, TB can get your management of tasks done *faster than traditional paper notebooks or a mobile application*. + == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. +* Some parts of this sample application were inspired by the excellent https://github.com/se-edu/addressbook-level4[Address Book (Level 4)] by +_se-edu_. * Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..a30b6d676001 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,28 @@ +{ + "formats": ["java", "fxml", "adoc"], + "authors": + [ + { + "githubId": "Jeremyinelysium", + "displayName": "AW ...HEN", + "authorNames": ["Jeremyinelysium", "Jeremy Aw", "Aw Meng Shen"] + }, + { + "githubId": "emobeany", + "displayName": "BEH...SIM", + "authorNames": ["emobeany", "Kha Sim"] + }, + { + "githubId": "ChanChunCheong", + "displayName": "CHA...ONG", + "authorNames": ["ChanChunCheong", "DESKTOP-UO2M0FH\\chanc"] + }, + { + "githubId": "chelseyong", + "displayName": "CHE...HEE", + "authorNames": ["chelseyong"] + } + ] + + +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..bf820db7420e 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'taskbook.jar' destinationDir = file("${buildDir}/jar/") } @@ -116,17 +116,17 @@ tasks.coveralls { onlyIf { System.env.'CI' } } -task(guiTests) +//task(guiTests) task(nonGuiTests) // Run `test` task if `guiTests` or `nonGuiTests` is specified -guiTests.dependsOn test +//guiTests.dependsOn test nonGuiTests.dependsOn test task(allTests) // `allTests` implies both `guiTests` and `nonGuiTests` -allTests.dependsOn guiTests +//allTests.dependsOn guiTests allTests.dependsOn nonGuiTests test { @@ -148,11 +148,12 @@ test { } doFirst { - boolean runGuiTests = gradle.taskGraph.hasTask(guiTests) +// boolean runGuiTests = gradle.taskGraph.hasTask(guiTests) boolean runNonGuiTests = gradle.taskGraph.hasTask(nonGuiTests) - if (!runGuiTests && !runNonGuiTests) { - runGuiTests = true + if (!runNonGuiTests) { + //!runGuiTests && +// runGuiTests = true runNonGuiTests = true } @@ -160,14 +161,14 @@ test { test.include 'seedu/address/**' } - if (runGuiTests) { + /*if (runGuiTests) { test.include 'systemtests/**' test.include 'seedu/address/ui/**' } if (!runGuiTests) { test.exclude 'seedu/address/ui/**' - } + }*/ } } @@ -207,8 +208,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', + 'site-name': 'TaskBook', + 'site-githuburl': 'https://github.com/CS2113-AY1819S1-W13-3/main', 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] @@ -236,10 +237,6 @@ task deployOfflineDocs(type: Copy) { } } -task copyDummySearchPage(type: Copy) { - from 'docs/DummySearchPage.html' - into "${buildDir}/docs/html5" -} deployOfflineDocs.dependsOn asciidoctor processResources.dependsOn deployOfflineDocs diff --git a/collated/functional/ChanChunCheong.md b/collated/functional/ChanChunCheong.md new file mode 100644 index 000000000000..d26d2ef9f967 --- /dev/null +++ b/collated/functional/ChanChunCheong.md @@ -0,0 +1,560 @@ +# ChanChunCheong +###### \java\seedu\address\logic\commands\DeferDeadlineCommand.java +``` java +/** + * Defer deadline of a specific task in the taskbook. + */ + +public class DeferDeadlineCommand extends Command implements CommandParser { + + public static final String COMMAND_WORD = "defer"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Defers the deadline of the selected task in the taskbook. " + + "Existing deadline will be overwritten by the input. " + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_DEADLINE + "deadline \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DEADLINE + "04011996"; + /* + + PREFIX_DAY + "DAY" + + PREFIX_MONTH + "MONTH" + + PREFIX_YEAR + "YEAR \n" + + "Example: " + COMMAND_WORD + "1" + + PREFIX_DAY + "01" + + PREFIX_MONTH + "01" + + PREFIX_YEAR + "2018"; + */ + + public static final String MESSAGE_INVALID_DEADLINE = "The date selected does not exist"; + public static final String MESSAGE_NONEXISTENT_TASK = "This task does not exist in the task book"; + public static final String MESSAGE_SUCCESS = "Date deferred for task: %1$s"; + //public static final String MESSAGE_NOT_IMPLEMENTED_YET = "Defer deadline command not implemented yet"; + + private final Index taskIndex; + private final Deadline deadline; + + public DeferDeadlineCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + taskIndex = null; + deadline = null; + } + + /** + * Creates an DeferDeadlineCommand to add the specified {@code Task & @code Deadline} + */ + public DeferDeadlineCommand(Index taskIndex, Deadline deadline) { + requireNonNull(taskIndex); + requireNonNull(deadline); + this.taskIndex = taskIndex; + this.deadline = deadline; + } + + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (taskIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_NONEXISTENT_TASK); + } + + Task taskToDefer = lastShownList.get(taskIndex.getZeroBased()); // get the task from the filteredtasklist; + model.deferTaskDeadline(taskToDefer, deadline); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, taskToDefer)); + + /* + requireNonNull(model); + throw new CommandException(MESSAGE_NOT_IMPLEMENTED_YET); + */ + } + + @Override + public Command parse(String arguments) throws ParseException { + return new DeferDeadlineCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} +``` +###### \java\seedu\address\logic\commands\SortTaskCommand.java +``` java +/** + * Sorts the tasks list in the task book based on the method chosen + */ +public class SortTaskCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": sort the tasks in the task book by preferred way. " + + "Parameters: " + + PREFIX_SORT + "METHOD \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_SORT + "modules"; + + //public static final String MESSAGE_NOT_IMPLEMENTED_YET = "SortTask command not implemented yet"; + public static final String MESSAGE_ARGUMENTS = "method: %1$s"; + public static final String MESSAGE_SUCCESS = "Sort task based on: %1$s"; + private final String method; + + public SortTaskCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + method = null; + } + + /** + * Creates an DeferDeadlineCommand to add the specified {@code Task & @code Deadline} + */ + public SortTaskCommand(String method) { + requireNonNull(method); + this.method = method.toLowerCase(); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + + requireNonNull(model); + //throw new CommandException(MESSAGE_NOT_IMPLEMENTED_YET); + //throw new CommandException(String.format(MESSAGE_ARGUMENTS, method)); + if (method.equals("modules") || method.equals("deadlines") || method.equals("priority") + || method.equals("title")) { + model.sortTask(method); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, method)); + } else { + //if the methods called are not within the list of methods called then throw CommandException + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortTaskCommand.MESSAGE_USAGE)); + } + + } + + @Override + public Command parse(String arguments) throws ParseException { + return new SortTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + + /* + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof SortTaskCommand)) { + return false; + } + // state check + SortTaskCommand e = (SortTaskCommand) other; + return method.equals(e.method); + } + */ +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Defer {@code key} previous deadline with (@code deadline) from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void deferDeadline(Task key, Deadline deadline) { + requireNonNull(deadline); + tasks.defer(key, deadline); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTask(Task key) { + tasks.remove(key); + } + +``` +###### \java\seedu\address\model\Model.java +``` java + void sortTask(String method); + + /** Gets deadline previously selected from the TaskBook.*/ + Deadline getDeadline(); + + /** Returns true if input deadline is valid.*/ + boolean validDeadline(Deadline deadline); + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the task book. + * The task identity of {@code editedTask} must not be the same as another existing task in the task book. + */ + void updateTask(Task target, Task editedTask); + + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredTaskList(); + + /** + * Updates the filter of the filtered task list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTaskList(Predicate predicate); + + /** + * Returns true if the model has previous task book states to restore. + */ + boolean canUndoTaskBook(); + + /** + * Returns true if the model has undone task book states to restore. + */ + boolean canRedoTaskBook(); + + /** + * Restores the model's task book to its previous state. + */ + void undoTaskBook(); + + /** + * Restores the model's task book to its previously undone state. + */ + void redoTaskBook(); + + /** + * Saves the current task book state for undo/redo. + */ + void commitTaskBook(); + + /** + * Updates task list to + * contain only completed tasks + */ + public void trackProductivity(); +} +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void deferTaskDeadline(Task target, Deadline deadline) { + versionedTaskBook.deferDeadline(target, deadline); + indicateTaskBookChanged(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void sortTask(String method) { + versionedTaskBook.sortTask(method); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + indicateTaskBookChanged(); + } + +``` +###### \java\seedu\address\model\task\PriorityLevel.java +``` java + switch(priority) { + case ("low"): { + priorityLevelInt = 3; + break; + } + case ("medium"): { + priorityLevelInt = 2; + break; + } + case ("high"): { + priorityLevelInt = 1; + break; + } + default: + priorityLevelInt = 0; + } + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidPriorityLevel(String test) { + String testInLowerCase = test.toLowerCase(); + if (testInLowerCase.equals("low") || testInLowerCase.equals("medium") + || testInLowerCase.equals("high")) { + return true; + } else { + return false; + } + } + + @Override + public String toString() { + return priorityLevel; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PriorityLevel // instanceof handles nulls + && priorityLevel.equals(((PriorityLevel) other).priorityLevel)); // state check + } + + @Override + public int hashCode() { + return priorityLevel.hashCode(); + } +} +``` +###### \java\seedu\address\model\task\SortTaskList.java +``` java +/** + * SortTaskList is a comparator for the task book to sort according to lexicographical order + */ +public class SortTaskList { + /** + * Return 0 if self = other. Return 1 if self > other. Return -1 if self < other. + * @param internalList + * @param method + * @return SortedList + */ + public ObservableList sortTask(ObservableList internalList, String method) { + + FXCollections.sort(internalList, new Comparator() { + @Override + public int compare(Task self, Task other) { + switch(method) { + case ("modules"): { + return self.getModuleCode().toLowerCase().compareTo(other.getModuleCode().toLowerCase()); + } + case ("deadlines"): { + return self.getDeadline().toString().compareTo(other.getDeadline().toString()); + } + case ("priority"): { + return self.getPriorityLevelInt() - other.getPriorityLevelInt(); + } + case ("title"): { + return self.getTitle().toLowerCase().compareTo(other.getTitle().toLowerCase()); + } + default: + return 0; + } + } + + }); + return internalList; + } +} +``` +###### \java\seedu\address\model\task\Task.java +``` java + public int getPriorityLevelInt() { + return priorityLevel.priorityLevelInt; + } +``` +###### \java\seedu\address\model\task\Task.java +``` java + /** + * Defers the task to a later + * @param deadline + * @return the new Task + */ + public Task deferred(Deadline deadline) { + Task deferredTask = new Task(this); + deferredTask.deadline = deadline; + return deferredTask; + } + + //@@JeremyInElysium + /** + * Add a milestone to the task. + */ + public Task addMilestone(Milestone milestone) { + Task taskWithMilestones = new Task(this); + taskWithMilestones.milestoneList.add(milestone); + return taskWithMilestones; + } + + /** + * @return list of milestones for the task. + */ + public Set getMilestoneList() { + return Collections.unmodifiableSet(milestoneList); + } + /** + * Returns true if both tasks have the same data fields. + * This defines a stronger notion of equality between two tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.getTitle().equals(getTitle()) + && otherTask.getDeadline().equals(getDeadline()) + && otherTask.getDescription().equals(getDescription()) + && otherTask.getPriorityLevel().equals(getPriorityLevel()) + && otherTask.isCompleted() == isCompleted() + && otherTask.getExpectedNumOfHours() == getExpectedNumOfHours() + && otherTask.getCompletedNumOfHours() == getCompletedNumOfHours(); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(deadline, title, description, priorityLevel, expectedNumOfHours, + completedNumOfHours, isCompleted); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDeadline()) + .append(" | ") + .append(getTitle()) + .append(" : ") + .append(getDescription()) + .append(" Priority: ") + .append(getPriorityLevel()); + /*builder.append(" Expected: "); + builder.append(expectedNumOfHours); + builder.append(" completed? "); + builder.append(isCompleted); + builder.append(" completed hours? "); + builder.append(completedNumOfHours); + builder.append(" Module code: "); + builder.append(moduleCode);*/ + return builder.toString(); + } +} +``` +###### \java\seedu\address\model\task\UniqueTaskList.java +``` java + /** + * Defer the deadline of the task (@code target) in the list with (@code deadline). + * (@code target) must exist in the list. + */ + public void defer(Task target, Deadline deadline) { + requireNonNull(target); + requireNonNull(deadline); + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + Task deferredTask = target.deferred(deadline); + internalList.set(index, deferredTask); + } + + /** + * Replaces the task {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list. + * The task identity of {@code editedPerson} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedPerson) { + requireAllNonNull(target, editedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedPerson) && contains(editedPerson)) { + throw new DuplicateTaskException(); + } + internalList.set(index, editedPerson); + } + + /** + * Removes the equivalent task from the list. + * The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + /** + * Complete a task in the list. + * The task must exist in the list. + */ + public void complete(Task toComplete, int hours) { + requireNonNull(toComplete); + int index = internalList.indexOf(toComplete); + if (index == -1) { + throw new TaskNotFoundException(); + } + Task completedTask = toComplete.completed(hours); + internalList.set(index, completedTask); + } + + public void setTasks(UniqueTaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + requireAllNonNull(tasks); + if (!tasksAreUnique(tasks)) { + throw new DuplicateTaskException(); + } + internalList.setAll(tasks); + } + /* + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + + public ObservableList obtainObservableList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code tasks} contains only unique tasks. + */ + private boolean tasksAreUnique(List tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).isSameTask(tasks.get(j))) { + return false; + } + } + } + return true; + } +} +``` diff --git a/collated/functional/JeremyInElysium.md b/collated/functional/JeremyInElysium.md new file mode 100644 index 000000000000..0a8a17f87d61 --- /dev/null +++ b/collated/functional/JeremyInElysium.md @@ -0,0 +1,398 @@ +# JeremyInElysium +###### \java\seedu\address\commons\events\model\AddMilestoneChangedEvent.java +``` java +/** Indicates the AddressBook in the model has changed*/ +public class AddMilestoneChangedEvent extends BaseEvent { + + public final ReadOnlyTaskBook data; + + public AddMilestoneChangedEvent(ReadOnlyTaskBook data) { + this.data = data; + } + + @Override + public String toString() { + return "number of tasks " + data.getTaskList().size(); + } +} +``` +###### \java\seedu\address\logic\commands\AddMilestoneCommand.java +``` java +/** + * Adds a milestone to a task in the taskbook + */ +public class AddMilestoneCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "add_milestone"; + public static final String MESSAGE_SUCCESS = "New milestone added: %1$s"; + public static final String MESSAGE_TASK_NOT_FOUND = "This task does not exist in the task book"; + public static final String MESSAGE_DUPLICATE_RANK = "Invalid rank entered."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds milestone(s) to selected task. " + + "Parameters: " + + PREFIX_INDEX + "INDEX " + + PREFIX_MILESTONE + "MILESTONE " + + PREFIX_RANK + "RANK \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INDEX + "1 " + + PREFIX_MILESTONE + "Complete Sections 8.1 to 8.5 " + + PREFIX_RANK + "1"; + + private final Index index; + private final Milestone toAdd; + + /** + * Creates a AddMilestoneCommand to serve the purpose of the LogicManager + */ + public AddMilestoneCommand() { + index = null; + toAdd = null; + } + + /** + * Creates a AddMilestoneCommand to add the specified {@code Milestone} + */ + public AddMilestoneCommand(Index index, Milestone milestone) { + requireNonNull(index); + requireNonNull(milestone); + this.index = index; + toAdd = milestone; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_TASK_NOT_FOUND); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + + //TODO: ensure rank of milestone that is being added does not collide with existing milestones' ranks + /* + if(taskToEdit.milestoneSet.size() <= rank) { + throw new CommandException(MESSAGE_DUPLICATE_RANK); + } + */ + + Task editedTask = taskToEdit.addMilestone(toAdd); + model.updateTask(taskToEdit, editedTask); + //model.addMilestone(toAdd); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.toString())); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new AddMilestoneCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + +} +``` +###### \java\seedu\address\logic\parser\AddMilestoneCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddMilestoneCommand object + */ +public class AddMilestoneCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contain empty {@code Optional} values in the given + * {@code ArgumentMultiMap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + @Override + public AddMilestoneCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_INDEX, PREFIX_MILESTONE, PREFIX_RANK); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_MILESTONE, PREFIX_RANK) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddMilestoneCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + String milestoneDescription = ParserUtil.parseMilestoneDescription( + argMultimap.getValue(PREFIX_MILESTONE).get()); + String rank = ParserUtil.parseRank(argMultimap.getValue(PREFIX_RANK).get()); + + Milestone milestone = new Milestone(new MilestoneDescription(milestoneDescription), new Rank(rank)); + + return new AddMilestoneCommand(index, milestone); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Leading and trailing whitespaces will be trimmed from {@code String milestoneDescription} + */ + public static String parseMilestoneDescription(String milestoneDescription) throws ParseException { + requireNonNull(milestoneDescription); + String trimmedMilestoneDescription = milestoneDescription.trim(); + return trimmedMilestoneDescription; + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String rank} + */ + public static String parseRank(String rank) throws ParseException { + requireNonNull(rank); + String trimmedRank = rank.trim(); + return trimmedRank; + } + +} +``` +###### \java\seedu\address\model\task\Milestone.java +``` java +/** + * Represents a Milestone for any Task in the TaskBook + */ +public class Milestone { + private final MilestoneDescription milestoneDescription; + private final Rank rank; + + + public Milestone(MilestoneDescription milestoneDescription, Rank rank) { + //super(title, milestoneDescription, new PriorityLevel("high")); + this.milestoneDescription = milestoneDescription; + this.rank = rank; + } + + public MilestoneDescription getMilestoneDescription() { + return milestoneDescription; + } + + public String getMilestoneDescriptionString() { + return milestoneDescription.milestoneDescription; + } + + + public Rank getRank() { + return rank; + } + + public String getRankString() { + return rank.rank; + } + + + /** + * Returns true if both tasks have the same deadline and title. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameMilestone(Milestone otherMilestone) { + if (otherMilestone == this) { + return true; + } + + return otherMilestone != null + && otherMilestone.getMilestoneDescription().equals(getMilestoneDescription()) + && otherMilestone.getRank().equals(getRank()); + } + + //need to edit this also + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Milestone ") + .append(getRank()) + .append(": ") + .append(getMilestoneDescription()); + return builder.toString(); + } + +} +``` +###### \java\seedu\address\model\task\MilestoneDescription.java +``` java +/** + * Represents a description in the milestone of a task. + */ +public class MilestoneDescription { + + public static final String MESSAGE_MILESTONEDESCRIPTION_CONSTRAINTS = + "Milestone description can only contain alphanumeric characters and spaces, and it should not be blank."; + + /** + * The first character of the milestone description must not be a whitespace, + * otherwise " " (a blank string) will become a valid input + */ + public static final String MILESTONEDESCRIPTION_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String milestoneDescription; + + /** + * Creates a constructor for the milestone description + * Guarantees that the milestone description is not null + * @param milestoneDescription a valid milestone description + */ + public MilestoneDescription(String milestoneDescription) { + requireNonNull(milestoneDescription); + //checkArgument(isValidMilestoneDescription(milestoneDescription), MESSAGE_MILESTONEDESCRIPTION_CONSTRAINTS); + this.milestoneDescription = milestoneDescription; + } + + /** + * Checks whether milestone description entered by the user is valid + * @param milestoneDescription + * @return true if valid + */ + public static boolean isValidMilestoneDescription(String milestoneDescription) { + return true; + //milestoneDescription.matches(MILESTONEDESCRIPTION_VALIDATION_REGEX); + } + + public String getMilestoneDescription() { + return this.milestoneDescription; + } + + @Override + public String toString() { + return milestoneDescription; + } +} +``` +###### \java\seedu\address\model\task\Rank.java +``` java +/** + * Represents a description in the milestone of a task. + */ +public class Rank { + + public static final String MESSAGE_RANK_CONSTRAINTS = + "Rank can only contain positive integers greater than zero, and it should not be blank."; + + /** + * The input must not be a whitespace, zero or a negative integer + */ + public static final String RANK_VALIDATION_REGEX = "[1-9]{1,2}"; + + public final String rank; + + /** + * Creates a constructor for the rank + * Guarantees that the rank is not null + * @param rank a valid rank + */ + public Rank(String rank) { + requireNonNull(rank); + checkArgument(isValidRank(rank), MESSAGE_RANK_CONSTRAINTS); + this.rank = rank; + } + + /** + * Checks whether rank entered by the user is valid + * @param rank + * @return true if valid + */ + public static boolean isValidRank(String rank) { + return rank.matches(RANK_VALIDATION_REGEX); + } + + public String getRank() { + return this.rank; + } + + @Override + public String toString() { + return rank; + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedMilestone.java +``` java +/** + * JAXB-friendly adapted version of the Tag. + */ +public class XmlAdaptedMilestone { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Milestone's %s field is missing."; + + @XmlElement + private String descrip; + @XmlElement + private String rank; + + /** + * Constructs an XmlAdaptedTag. + * This is the no-arg constructor that is required by JAXB. + */ + + public XmlAdaptedMilestone() {} + + /** + * Constructs a {@code XmlAdaptedMilestone} with the given {@code milestone}. + */ + public XmlAdaptedMilestone(MilestoneDescription milestoneDescription, Rank rank) { + this.descrip = milestoneDescription.getMilestoneDescription(); + this.rank = rank.getRank(); + } + + /** + * Converts a given Milestone into this class for JAXB use. + * @param source future changes to this will not affect the created + */ + + public XmlAdaptedMilestone(Milestone source) { + descrip = source.getMilestoneDescriptionString(); + rank = source.getRankString(); + } + + /** + * Converts this jaxb-friendly adapted milestone object into the model's Milestone object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + + public Milestone toModelType() throws IllegalValueException { + if (descrip == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, MilestoneDescription.class.getSimpleName())); + } + + if (!MilestoneDescription.isValidMilestoneDescription(descrip)) { + throw new IllegalValueException(MilestoneDescription.MESSAGE_MILESTONEDESCRIPTION_CONSTRAINTS); + } + final MilestoneDescription modelMilestoneDescription = new MilestoneDescription(descrip); + + if (rank == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, Rank.class.getSimpleName())); + } + + if (!Rank.isValidRank(rank)) { + throw new IllegalValueException(Rank.MESSAGE_RANK_CONSTRAINTS); + } + final Rank modelRank = new Rank(rank); + + + return new Milestone(modelMilestoneDescription, modelRank); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedMilestone)) { + return false; + } + + XmlAdaptedMilestone otherMilestone = (XmlAdaptedMilestone) other; + return Objects.equals(descrip, otherMilestone.descrip) + && Objects.equals(rank, otherMilestone.rank); + } +} + +``` diff --git a/collated/functional/chelseyong.md b/collated/functional/chelseyong.md new file mode 100644 index 000000000000..3fbbf41621bd --- /dev/null +++ b/collated/functional/chelseyong.md @@ -0,0 +1,493 @@ +# chelseyong +###### \java\seedu\address\logic\commands\AddTaskCommand.java +``` java +/** + * Adds a task to the task book + */ +public class AddTaskCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "add"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to the task book. " + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_DESCRIPTION + "DESCRIPTION " + + PREFIX_MODULE_CODE + "MODULE CODE " + + PREFIX_PRIORITY + "PRIORITY " + + PREFIX_HOURS + "HOURS \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Complete code refactoring " + + PREFIX_DESCRIPTION + "refer to notes " + + PREFIX_MODULE_CODE + "CS2113 " + + PREFIX_PRIORITY + "high " + + PREFIX_HOURS + "2"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task book"; + public static final int MAX_HOURS_TO_COMPLETE = 24; + private final Task toAdd; + public AddTaskCommand() { + toAdd = null; + } + /** + * Creates an AddCommand to add the specified {@code Task} + */ + public AddTaskCommand(Task task) { + requireNonNull(task); + toAdd = task; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.hasTask(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } else if (toAdd.getExpectedNumOfHours() == 0) { + throw new CommandException(MESSAGE_ZERO_HOURS_COMPLETION); + } else if (toAdd.getExpectedNumOfHours() >= MAX_HOURS_TO_COMPLETE) { + throw new CommandException(MESSAGE_MAX_HOURS); + } + + toAdd.setDeadline(model.getDeadline()); + model.addTask(toAdd); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTaskCommand // instanceof handles nulls + && toAdd.equals(((AddTaskCommand) other).toAdd)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new AddTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} +``` +###### \java\seedu\address\logic\commands\CommandParser.java +``` java +/** + * CommandParser is able to + * pass in arguments + */ +public interface CommandParser { + public Command parse(String arguments) throws ParseException; + + public String getCommandWord(); +} +``` +###### \java\seedu\address\logic\commands\CompleteTaskCommand.java +``` java +/** + * Completes a task in the Task Book + */ +public class CompleteTaskCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "complete"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Completes the task identified by the index number used in the displayed task list," + + " under a certain number of hours\n" + + "Parameters: " + PREFIX_INDEX + " INDEX(must be a positive integer) " + + PREFIX_HOURS + "HOURS\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_HOURS + "2"; + + public static final String MESSAGE_SUCCESS = "Task completed: %1$s"; + + private final Index targetIndex; + private final int completedNumOfHours; + public CompleteTaskCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + targetIndex = null; + completedNumOfHours = 0; + } + /** + * Creates an CompleteTaskCommand to add the specified {@code Task} + */ + public CompleteTaskCommand(Index targetIndex, int completedNumOfHours) { + this.targetIndex = targetIndex; + this.completedNumOfHours = completedNumOfHours; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } else if (completedNumOfHours == 0) { + throw new CommandException(Messages.MESSAGE_ZERO_HOURS_COMPLETION); + } else if (completedNumOfHours >= MAX_HOURS_TO_COMPLETE) { + throw new CommandException(MESSAGE_MAX_HOURS); + } + Task taskToComplete = lastShownList.get(targetIndex.getZeroBased()); + if (taskToComplete.isCompleted()) { + throw new CommandException(Messages.MESSAGE_COMPLETED_TASK); + } + model.completeTask(taskToComplete, completedNumOfHours); + model.commitTaskBook(); + Task completedTask = lastShownList.get(targetIndex.getZeroBased()); + return new CommandResult(String.format(MESSAGE_SUCCESS, completedTask)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CompleteTaskCommand // instanceof handles nulls + && targetIndex.equals(((CompleteTaskCommand) other).targetIndex)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new CompleteTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} +``` +###### \java\seedu\address\logic\commands\DeleteCommand.java +``` java +/** + * Deletes a task identified using it's displayed index from the address book. + */ +public class DeleteCommand extends Command implements CommandParser { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task identified by the index number used in the displayed task list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s"; + + private final Index targetIndex; + + public DeleteCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + targetIndex = null; + } + public DeleteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTask(taskToDelete); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCommand // instanceof handles nulls + && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + } + + @Override + public Command parse(String arguments) throws ParseException { + return new DeleteCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} +``` +###### \java\seedu\address\logic\commands\TrackProductivityCommand.java +``` java +/** + * Tracks the productivity of tasks + * for the previous week based on hours + */ +public class TrackProductivityCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "track"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Tracks your productivity."; + + public static final String MESSAGE_SUCCESS = "Recent productvity: %1$s"; + + public static final String MESSAGE_NO_COMPLETED_TASK = "There are no completed tasks yet. Start working!"; + //private static final Logger logger = LogsCenter.getLogger(TrackProductivityCommand.class); + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + // filter out Completed tasks + model.trackProductivity(); + ObservableList tasks = model.getFilteredTaskList(); + if (tasks.size() == 0) { + throw new CommandException(MESSAGE_NO_COMPLETED_TASK); + } + double productivity = calculateProductivity(tasks); + String result = Integer.toString((int) (productivity * 100)) + " %"; + return new CommandResult(String.format(MESSAGE_SUCCESS, result)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new TrackProductivityCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + + /** + * Calculates the overall productivity for completed + * @param tasks + * @return the average productivity + */ + public double calculateProductivity(ObservableList tasks) { + double averageProductivity; + double totalProductivity = 0; + for (Task task: tasks) { + double taskProductivity = (double) task.getExpectedNumOfHours() / task.getCompletedNumOfHours(); + totalProductivity += taskProductivity; + } + //if (totalProductivity == 0) + averageProductivity = totalProductivity / tasks.size(); + return averageProductivity; + } +} +``` +###### \java\seedu\address\logic\LogicManager.java +``` java + public LogicManager(Model model) { + this.model = model; + history = new CommandHistory(); + // need to add the commands into the list commands in TaskBookParser + taskBookParser = new TaskBookParser(new AddTaskCommand(), + new ClearCommand(), + new CompleteTaskCommand(), + new DeferDeadlineCommand(), + new DeleteCommand(), + new ListCommand(), + new TrackProductivityCommand(), + new SelectDeadlineCommand(), + new SortTaskCommand(), + new HelpCommand(), + new ExitCommand(), + new HistoryCommand(), + new UndoCommand(), + new RedoCommand(), + new AddMilestoneCommand() + ); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + try { + Command command = taskBookParser.parseCommand(commandText); + return command.execute(model, history); + } finally { + history.add(commandText); + } + } +``` +###### \java\seedu\address\logic\parser\AddTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddTaskCommand object + */ +public class AddTaskCommandParser implements Parser { + private static final Logger logger = LogsCenter.getLogger(AddTaskCommandParser.class); + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + protected static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + @Override + public AddTaskCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_MODULE_CODE, PREFIX_TITLE, PREFIX_DESCRIPTION, + PREFIX_PRIORITY, PREFIX_HOURS); + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE_CODE, PREFIX_TITLE, PREFIX_DESCRIPTION, + PREFIX_PRIORITY, PREFIX_HOURS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + + String moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE_CODE).get()); + String title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()); + String description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + PriorityLevel priority = ParserUtil.parsePriorityLevel(argMultimap.getValue(PREFIX_PRIORITY).get()); + int expectedNumOfHours = ParserUtil.parseHours(argMultimap.getValue(PREFIX_HOURS).get()); + Task task = new Task(moduleCode, title, description, priority, expectedNumOfHours); + + return new AddTaskCommand(task); + } +} +``` +###### \java\seedu\address\logic\parser\CompleteTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new CompleteTaskCommand object + */ +public class CompleteTaskCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the CompleteTaskCommand + * and returns an CompleteTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public CompleteTaskCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(userInput, PREFIX_INDEX, PREFIX_HOURS); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_HOURS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteTaskCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + int expectedNumOfHours = ParserUtil.parseHours(argMultimap.getValue(PREFIX_HOURS).get()); + + return new CompleteTaskCommand(index, expectedNumOfHours); + } +} + + +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Leading and trailing whitespaces will be trimmed from {@code String hours} + * If hours is not an integer or is too big to be an integer, + * @throws ParseException + */ + public static int parseHours(String hours) throws ParseException { + requireNonNull(hours); + String trimmedHours = hours.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedHours)) { + throw new ParseException(MESSAGE_INVALID_HOURS); + } + return Integer.parseInt(trimmedHours); + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String moduleCode} + */ + public static String parseModuleCode(String moduleCode) { + requireNonNull(moduleCode); + String trimmedModuleCode = moduleCode.trim(); + return trimmedModuleCode; + } + + /** + * Parses a {@code String deadline} into an {@code Deadline}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code deadline} is invalid. + */ + public static Deadline parseDeadline(String deadline) throws ParseException { + requireNonNull(deadline); + String trimmedDeadline = deadline.trim(); + //TODO prevent 1/1 + if (!Deadline.isValidDeadline(trimmedDeadline)) { + throw new ParseException(Deadline.MESSAGE_DEADLINE_CONSTRAINTS); + } + return new Deadline(trimmedDeadline); + } + + /** + * Parses a {@code String priority} into an {@code Priority}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code priority} is invalid. + */ + public static PriorityLevel parsePriorityLevel(String priority) throws ParseException { + requireNonNull(priority); + String trimmedPriority = priority.trim(); + if (!PriorityLevel.isValidPriorityLevel(trimmedPriority)) { + throw new ParseException(PriorityLevel.MESSAGE_PRIORITY_CONSTRAINTS); + } + // return new PriorityLevel(trimmedPriority); + return new PriorityLevel(trimmedPriority); + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String title} + */ + public static String parseTitle(String title) throws ParseException { + requireNonNull(title); + String trimmedTitle = title.trim(); + return trimmedTitle; + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String description} + */ + public static String parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + return trimmedDescription; + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void deleteTask(Task target) { + versionedTaskBook.removeTask(target); + indicateTaskBookChanged(); + } + + @Override + public void completeTask(Task target, int hours) { + versionedTaskBook.completeTask(target, hours); + indicateTaskBookChanged(); + } + + @Override + public void addTask(Task task) { + versionedTaskBook.addTask(task); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + indicateTaskBookChanged(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Updates the task to completed tasks only + * So that productivity can be correctly calculated + */ + public void trackProductivity() { + updateFilteredTaskList(predicateShowCompletedTasks); + indicateTaskBookChanged(); + } +} +``` diff --git a/collated/functional/emobeany.md b/collated/functional/emobeany.md new file mode 100644 index 000000000000..115baf183f0a --- /dev/null +++ b/collated/functional/emobeany.md @@ -0,0 +1,292 @@ +# emobeany +###### \java\seedu\address\logic\commands\SelectDeadlineCommand.java +``` java +/** + * Selects a date as a deadline for tasks to be added to + */ +public class SelectDeadlineCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects a date. " + + "Parameters: " + + PREFIX_DAY + "DAY " + + PREFIX_MONTH + "MONTH " + + PREFIX_YEAR + "YEAR \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_DAY + "01 " + + PREFIX_MONTH + "01 " + + PREFIX_YEAR + "2018 "; + + public static final String MESSAGE_SUCCESS = "New date selected: %1$s"; + public static final String MESSAGE_INVALID_DEADLINE = "The date selected does not exist"; + + private final Deadline toSelect; + + /** + * Creates a SelectDeadline to select the specified {@code Deadline} + */ + public SelectDeadlineCommand (Deadline deadline) { + requireNonNull(deadline); + toSelect = deadline; + } + + public SelectDeadlineCommand() { + toSelect = null; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.validDeadline(toSelect)) { + throw new CommandException(MESSAGE_INVALID_DEADLINE); + } + + model.selectDeadline(toSelect); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toSelect)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectDeadlineCommand // instanceof handles nulls + && toSelect.equals(((SelectDeadlineCommand) other).toSelect)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new SelectDeadlineCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Leading and trailing whitespaces will be trimmed from {@code String day} + */ + public static String parseDay(String day) throws ParseException { + requireNonNull(day); + String trimmedDay = day.trim(); + return trimmedDay; + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String month} + */ + public static String parseMonth(String month) throws ParseException { + requireNonNull(month); + String trimmedMonth = month.trim(); + return trimmedMonth; + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String year} + */ + public static String parseYear(String year) throws ParseException { + requireNonNull(year); + String trimmedYear = year.trim(); + return trimmedYear; + } +``` +###### \java\seedu\address\logic\parser\SelectDeadlineCommandParser.java +``` java +/** + * Parses input arguments and creates a new SelectDeadlineCommand object + */ +public class SelectDeadlineCommandParser implements Parser { + + @Override + public SelectDeadlineCommand parse(String userInput) throws ParseException { + Deadline deadlineWithoutPrefixes = parseWithoutPrefixes(userInput); + if (deadlineWithoutPrefixes != null) { + return new SelectDeadlineCommand(deadlineWithoutPrefixes); + } + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_DAY, PREFIX_MONTH, PREFIX_YEAR); + + if (!arePrefixesPresent(argMultimap, PREFIX_DAY, PREFIX_MONTH, PREFIX_YEAR) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SelectDeadlineCommand.MESSAGE_USAGE)); + } + + String day = ParserUtil.parseDay(argMultimap.getValue(PREFIX_DAY).get()); + String month = ParserUtil.parseMonth(argMultimap.getValue(PREFIX_MONTH).get()); + String year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get()); + Deadline deadline = new Deadline(day, month, year); + + return new SelectDeadlineCommand(deadline); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + protected static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Alternative parsing method: + * @param userInput without date, month and year prefixes + * @return the parsed Deadline + */ + public Deadline parseWithoutPrefixes(String userInput) { + try { + return ParserUtil.parseDeadline(userInput); + } catch (ParseException e) { + return null; + } + } +} +``` +###### \java\seedu\address\model\Model.java +``` java + /** Selects the input date as deadline.*/ + void selectDeadline(Deadline deadline); + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void selectDeadline(Deadline deadline) { + versionedTaskBook.selectDeadline(deadline); + updateFilteredTaskList(predicateShowTasksWithSameDate(deadline)); + indicateTaskBookChanged(); + } + + /**{@code Predicate} that returns true when the date is equal*/ + private Predicate predicateShowTasksWithSameDate(Deadline deadline) { + return task -> task.getDeadline().equals(deadline); + } + + public Deadline getDeadline() { + return versionedTaskBook.getDeadline(); + } + + @Override + public boolean validDeadline(Deadline deadline) { + return versionedTaskBook.validDeadline(deadline); + } + +``` +###### \java\seedu\address\model\task\Deadline.java +``` java + +/** + * Represents a deadline in the task book. + * Guarantees: field values are validated, immutable, details are present and not null. + */ + +public class Deadline { + public static final String MESSAGE_DEADLINE_CONSTRAINTS = + "Deadline can only have dd/mm/yyyy format"; + + private final String day; + private final String month; + private final String year; + + public Deadline(String day, String month, String year) { + this.day = day; + this.month = month; + this.year = year; + } + + public Deadline(String deadline) { + String[] entries = deadline.split("/"); + this.day = entries[0]; + this.month = entries[1]; + this.year = entries[2]; + } + + /* + public Deadline(String day, String month) { + this.day = day; + this.month = month; + } + */ + + public String getDay() { + return day; + } + + public String getMonth() { + return month; + } + + public String getYear() { + return year; + } + + /** + * Returns false if any fields are not within the limits (not a valid date). + */ + + public static boolean isValidDeadline(String test) { + String[] entries = test.split("/"); + if (entries.length != 3) { + return false; + } + String day = entries[0]; + String month = entries[1]; + String year = entries[2]; + + // Check that all the characters are numeric first. + if (!isNumeric(day) || !isNumeric(month) || !isNumeric(year)) { + return false; + } else if (Integer.parseInt(day) < 1 || Integer.parseInt(day) > 31) { + return false; + } else if (Integer.parseInt(month) < 1 || Integer.parseInt(month) > 12) { + return false; + } else if (Integer.parseInt(year) < 2018 || Integer.parseInt(year) > 9999) { + return false; + } + return true; + } + + @Override + public int hashCode() { + // custom fields hashing + return Objects.hash(day, month, year); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDay()) + .append("/") + .append(getMonth()) + .append("/") + .append(getYear()); + return builder.toString(); + } + + /** + * Referenced online: Checking if String is numeric + * @param s + * @return true if String is completely numeric + */ + public static boolean isNumeric(String s) { + //s.matches("[-+]?\\d*\\.?\\d+"); + return s != null && s.matches("-?\\d+(\\.\\d+)?"); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } else if (object instanceof Deadline) { + Deadline otherDeadline = (Deadline) object; + return otherDeadline.day.equals(this.day) && otherDeadline.month.equals(this.month) + && otherDeadline.year.equals(this.year); + } + return false; + } +} +``` diff --git a/collated/test/chelseyong.md b/collated/test/chelseyong.md new file mode 100644 index 000000000000..cb7ef104aefd --- /dev/null +++ b/collated/test/chelseyong.md @@ -0,0 +1,627 @@ +# chelseyong +###### \java\seedu\address\logic\commands\CommandTestUtil.java +``` java + public static final String VALID_DEADLINE_1ST_JAN = "1/1/2018"; + public static final String VALID_DEADLINE_31ST_MARCH = "31/3/2018"; + public static final String VALID_DEADLINE_12TH_MAY = "12/5/2018"; + public static final String VALID_MODULE_CODE_CS2113 = "CS2113"; + public static final String VALID_MODULE_CODE_CG2271 = "CG2271"; + public static final String VALID_TITLE_1 = "Complete CS2113 Homework"; + public static final String VALID_TITLE_2 = "Start coding test units"; + public static final String VALID_TITLE_3 = "Prepare OP2"; + public static final String VALID_DESCRIPTION_1 = "Refer to notes"; + public static final String VALID_DESCRIPTION_2 = "Do this before integration tests"; + public static final String VALID_DESCRIPTION_3 = "OP2 has high weightage"; + public static final String VALID_PRIORITY_LEVEL_LOW = "low"; + public static final String VALID_PRIORITY_LEVEL_HIGH = "high"; + public static final String VALID_PRIORITY_LEVEL_MEDIUM = "medium"; + public static final String VALID_1_HOUR = "1"; + public static final String VALID_2_HOURS = "2"; + +``` +###### \java\seedu\address\logic\commands\CompleteTaskCommandTest.java +``` java +public class CompleteTaskCommandTest { + + private static final Logger logger = LogsCenter.getLogger(CompleteTaskCommand.class); + private Model model = new ModelManager(getTypicalTaskBook(), new UserPrefs()); + private CommandHistory commandHistory = new CommandHistory(); + + @Test + public void execute_validIndexUnfilteredList_success() { + int completedHours = 1; + Task taskToComplete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + Task completedTask = new TaskBuilder(taskToComplete).withCompletedNumOfHours(completedHours).build(); + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(INDEX_FIRST_TASK, completedHours); + + String expectedMessage = String.format(CompleteTaskCommand.MESSAGE_SUCCESS, completedTask); + + ModelManager expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.completeTask(taskToComplete, completedHours); + expectedModel.commitTaskBook(); + + assertCommandSuccess(completeTaskCommand, model, commandHistory, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(outOfBoundIndex, 1); + + assertCommandFailure(completeTaskCommand, model, commandHistory, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + @Test + public void execute_taskCompletedAlready_throwsCommandException() { + int completedHours = 1; + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(INDEX_FIRST_TASK, 1); + Task taskToComplete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + Task completedTask = new TaskBuilder(taskToComplete).withCompletedNumOfHours(completedHours).build(); + + ModelManager expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updateTask(taskToComplete, completedTask); + assertCommandFailure(completeTaskCommand, expectedModel, commandHistory, Messages.MESSAGE_COMPLETED_TASK); + } + + @Test + public void execute_taskCompletedZeroHours_throwsCommandException() { + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(INDEX_FIRST_TASK, 0); + assertCommandFailure(completeTaskCommand, model, commandHistory, Messages.MESSAGE_ZERO_HOURS_COMPLETION); + } + + @Test + public void execute_taskCompletedMaxHours_throwsCommandException() { + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(INDEX_FIRST_TASK, MAX_HOURS); + assertCommandFailure(completeTaskCommand, model, commandHistory, Messages.MESSAGE_MAX_HOURS); + } + + @Test + public void executeUndoRedo_validIndexFilteredList_success() throws Exception { + int completedNumOfHours = 1; + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(INDEX_FIRST_TASK, completedNumOfHours); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + Task taskToComplete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + expectedModel.completeTask(taskToComplete, completedNumOfHours); + expectedModel.commitTaskBook(); + + // complete -> completes first task + completeTaskCommand.execute(model, commandHistory); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + expectedModel.undoTaskBook(); + assertCommandSuccess(new UndoCommand(), model, commandHistory, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.redoTaskBook(); + assertCommandSuccess(new RedoCommand(), model, commandHistory, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void equals() { + int completedNumOfHours = 1; + CompleteTaskCommand completeFirstCommand = new CompleteTaskCommand(INDEX_FIRST_TASK, completedNumOfHours); + CompleteTaskCommand completeSecondCommand = new CompleteTaskCommand(INDEX_SECOND_TASK, 2); + + // same object -> returns true + assertTrue(completeFirstCommand.equals(completeFirstCommand)); + + // same values -> returns true + CompleteTaskCommand completeFirstCommandCopy = new CompleteTaskCommand(INDEX_FIRST_TASK, completedNumOfHours); + assertTrue(completeFirstCommand.equals(completeFirstCommandCopy)); + + // different types -> returns false + assertFalse(completeFirstCommand.equals(1)); + + // null -> returns false + assertFalse(completeFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(completeFirstCommand.equals(completeSecondCommand)); + } + +} +``` +###### \java\seedu\address\logic\commands\TrackProductivityCommandTest.java +``` java +public class TrackProductivityCommandTest { + private Model model = new ModelManager(getTypicalTaskBook(), new UserPrefs()); + private CommandHistory commandHistory = new CommandHistory(); + + @Test + public void execute_noCompletedTask_commandException() { + TrackProductivityCommand trackProductivityCommand = new TrackProductivityCommand(); + ModelManager expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.trackProductivity(); + assertCommandFailure(trackProductivityCommand, expectedModel, commandHistory, MESSAGE_NO_COMPLETED_TASK); + } + + @Test + public void execute_withCompletedTask_success() { + TrackProductivityCommand trackProductivityCommand = new TrackProductivityCommand(); + Task taskToComplete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + Task completedTask = new TaskBuilder(taskToComplete).withCompletedNumOfHours(1).build(); + model.updateTask(taskToComplete, completedTask); + model.trackProductivity(); + double productivity = trackProductivityCommand.calculateProductivity(model.getFilteredTaskList()); + String prodInPercentage = Integer.toString((int) (productivity * 100)) + " %"; + String expectedMessage = String.format(TrackProductivityCommand.MESSAGE_SUCCESS, prodInPercentage); + + try { + CommandResult result = trackProductivityCommand.execute(model, commandHistory); + assertEquals(expectedMessage, result.feedbackToUser); + } catch (CommandException e) { + throw new AssertionError("Execution of command should not fail.", e); + } + } +} +``` +###### \java\seedu\address\logic\parser\AddTaskCommandParserTest.java +``` java +public class AddTaskCommandParserTest { + private static final Logger logger = LogsCenter.getLogger(AddTaskCommandParserTest.class); + private AddTaskCommandParser parser = new AddTaskCommandParser(); + @Test + public void parse_allFieldsPresent_success() { + ParserWithDate parser = new ParserWithDate(); + Deadline selectedDeadline = new Deadline(VALID_DEADLINE_1ST_JAN); + Task expectedTask = new TaskBuilder(CS2113_TASK_2).withDeadline(VALID_DEADLINE_1ST_JAN).build(); + //AddTaskCommand commandWithDate = new AddTaskCommand(expectedTask); + // whitespace only preamble + assertParseSuccessWithDate(parser, selectedDeadline, PREAMBLE_WHITESPACE + + MODULE_CODE_CS2113_DESC + TITLE_DESC_2 + DESCRIPTION_DESC_2 + + PRIORITY_LEVEL_DESC_HIGH + HOURS_DESC_1, + new AddTaskCommand(expectedTask)); + + // multiple module codes - last module code accepted + assertParseSuccessWithDate(parser, selectedDeadline, MODULE_CODE_CG2271_DESC + MODULE_CODE_CS2113_DESC + + TITLE_DESC_2 + DESCRIPTION_DESC_2 + PRIORITY_LEVEL_DESC_HIGH + HOURS_DESC_1, + new AddTaskCommand(expectedTask)); + + // multiple titles - last title accepted + assertParseSuccessWithDate(parser, selectedDeadline, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + TITLE_DESC_2 + + DESCRIPTION_DESC_2 + PRIORITY_LEVEL_DESC_HIGH + HOURS_DESC_1, new AddTaskCommand(expectedTask)); + + // multiple descriptions - last description accepted + assertParseSuccessWithDate(parser, selectedDeadline, MODULE_CODE_CS2113_DESC + TITLE_DESC_2 + + DESCRIPTION_DESC_1 + DESCRIPTION_DESC_2 + PRIORITY_LEVEL_DESC_HIGH + HOURS_DESC_1, + new AddTaskCommand(expectedTask)); + + // multiple priorities - last priority accepted + assertParseSuccessWithDate(parser, selectedDeadline, MODULE_CODE_CS2113_DESC + TITLE_DESC_2 + + DESCRIPTION_DESC_2 + PRIORITY_LEVEL_DESC_LOW + PRIORITY_LEVEL_DESC_HIGH + HOURS_DESC_1, + new AddTaskCommand(expectedTask)); + + // multiple hours - last hour accepted + assertParseSuccessWithDate(parser, selectedDeadline, MODULE_CODE_CS2113_DESC + TITLE_DESC_2 + + DESCRIPTION_DESC_2 + PRIORITY_LEVEL_DESC_HIGH + HOURS_DESC_2 + HOURS_DESC_1, + new AddTaskCommand(expectedTask)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE); + + // missing module code prefix + assertParseFailure(parser, VALID_MODULE_CODE_CS2113 + VALID_TITLE_1 + DESCRIPTION_DESC_1 + + PRIORITY_LEVEL_DESC_LOW + HOURS_DESC_1, expectedMessage); + + // missing title prefix + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + VALID_TITLE_1 + DESCRIPTION_DESC_1 + + PRIORITY_LEVEL_DESC_LOW + HOURS_DESC_1, expectedMessage); + + // missing description prefix + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + VALID_DESCRIPTION_1 + + PRIORITY_LEVEL_DESC_LOW + HOURS_DESC_1, expectedMessage); + + // missing priority prefix + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + DESCRIPTION_DESC_1 + + VALID_PRIORITY_LEVEL_LOW + HOURS_DESC_1, expectedMessage); + + // missing hour prefix + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + DESCRIPTION_DESC_1 + + PRIORITY_LEVEL_DESC_LOW + VALID_1_HOUR, expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid Priority Level + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + DESCRIPTION_DESC_1 + + INVALID_PRIORITY_LEVEL_DESC + HOURS_DESC_1, PriorityLevel.MESSAGE_PRIORITY_CONSTRAINTS); + // invalid Hours + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + DESCRIPTION_DESC_1 + + PRIORITY_LEVEL_DESC_LOW + INVALID_HOURS_DESC, MESSAGE_INVALID_HOURS); + // hours > INT_MAX + assertParseFailure(parser, MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + DESCRIPTION_DESC_1 + + PRIORITY_LEVEL_DESC_LOW + INVALID_HOURS_OVERFLOW, MESSAGE_INVALID_HOURS); + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + MODULE_CODE_CS2113_DESC + TITLE_DESC_1 + + DESCRIPTION_DESC_1 + PRIORITY_LEVEL_DESC_LOW, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + + } + /** + * Since AddTaskCommand can only work with ModelManager + * which sets the deadline, parsing has to do the adding + * of deadline here. + */ + public static class ParserWithDate extends AddTaskCommandParser { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + /** + * An overloading method that parses + * @param userInput with task inputs to add + * @param date will be set in the task + * @return AddTaskCommand + * @throws ParseException if parsing is invalid + */ + public Command parse(String userInput, Deadline date) throws ParseException { + logger.info(userInput); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_MODULE_CODE, PREFIX_TITLE, PREFIX_DESCRIPTION, + PREFIX_PRIORITY, PREFIX_HOURS); + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE_CODE, PREFIX_TITLE, PREFIX_DESCRIPTION, + PREFIX_PRIORITY, PREFIX_HOURS) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + + String title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()); + String description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + PriorityLevel priority = ParserUtil.parsePriorityLevel(argMultimap.getValue(PREFIX_PRIORITY).get()); + int expectedNumOfHours = ParserUtil.parseHours(argMultimap.getValue(PREFIX_HOURS).get()); + String moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE_CODE).get()); + Task task = new Task(moduleCode, title, description, priority, expectedNumOfHours); + task.setDeadline(date); + return new AddTaskCommand(task); + } + /** + * Parses user input with deadline into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput, Deadline deadline) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + return parse(arguments, deadline); + } + } +} +``` +###### \java\seedu\address\logic\parser\CommandParserTestUtil.java +``` java + /** + * Asserts that the parsing of {@code userInput} by {@code parser} is successful + * by setting the deadline + * and the command created equals to {@code expectedCommand}. + * Only applicable for AddTaskCommandParserTest + */ + public static void assertParseSuccessWithDate(AddTaskCommandParserTest.ParserWithDate parser, Deadline date, + String userInput, Command expectedCommand) { + try { + Command command = parser.parse(userInput, date); + assertEquals(expectedCommand, command); + } catch (ParseException pe) { + throw new IllegalArgumentException("Invalid userInput.", pe); + } + } +``` +###### \java\seedu\address\model\AddressBookTest.java +``` java +public class AddressBookTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final AddressBook addressBook = new AddressBook(); + + @Test + public void constructor() { + assertEquals(Collections.emptyList(), addressBook.getTaskList()); + } + + @Test + public void resetData_null_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + addressBook.resetData(null); + } + + @Test + public void resetData_withValidReadOnlyAddressBook_replacesData() { + AddressBook newData = getTypicalTaskBook(); + addressBook.resetData(newData); + assertEquals(newData, addressBook); + } + + @Test + public void resetData_withDuplicateTasks_throwsDuplicateTaskException() { + // Two tasks with the same title and deadline fields + Task editedTask1 = new TaskBuilder(CS2113_TASK_1).withDescription(VALID_DESCRIPTION_2) + .withPriority(VALID_PRIORITY_LEVEL_HIGH).build(); + List newTasks = Arrays.asList(CS2113_TASK_1, editedTask1); + TaskBookStub newData = new TaskBookStub(newTasks); + + thrown.expect(DuplicateTaskException.class); + addressBook.resetData(newData); + } + + @Test + public void hasTask_nullTask_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + addressBook.hasTask(null); + } + + @Test + public void hasPerson_personNotInAddressBook_returnsFalse() { + assertFalse(addressBook.hasTask(CS2113_TASK_1)); + } + + @Test + public void hasTask_taskInTaskBook_returnsTrue() { + addressBook.addTask(CS2113_TASK_1); + assertTrue(addressBook.hasTask(CS2113_TASK_1)); + } + + @Test + public void hasTask_taskWithSameTitleAndSameDeadlineInTaskBook_returnsTrue() { + addressBook.addTask(CS2113_TASK_1); + Task editedTask1 = new TaskBuilder(CS2113_TASK_1).withDescription(VALID_DESCRIPTION_2) + .withPriority(VALID_PRIORITY_LEVEL_HIGH).build(); + assertTrue(addressBook.hasTask(editedTask1)); + } + + @Test + public void getTaskList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getTaskList().remove(0); + } + + /** + * A stub ReadOnlyTaskBook whose tasks list can violate interface constraints. + */ + private static class TaskBookStub implements ReadOnlyTaskBook { + private final ObservableList tasks = FXCollections.observableArrayList(); + + TaskBookStub(Collection tasks) { + this.tasks.setAll(tasks); + } + + @Override + public ObservableList getTaskList() { + return tasks; + } + } + +} +``` +###### \java\seedu\address\storage\XmlAdaptedTaskTest.java +``` java +public class XmlAdaptedTaskTest { + private static final String INVALID_DEADLINE = "#$@("; + private static final String INVALID_PRIORITY_LEVEL = "midhigh"; + private static final String INVALID_EXPECTED_NUM_OF_HOURS = "one"; + //private static final String INVALID_TAG = "#friend"; + + private static final String VALID_DEADLINE = CS2102_HOMEWORK.getDeadline().toString(); + private static final String VALID_MODULECODE = CS2102_HOMEWORK.getModuleCode(); + private static final String VALID_TITLE = CS2102_HOMEWORK.getTitle(); + private static final String VALID_DESCRIPTION = CS2102_HOMEWORK.getDescription(); + private static final String VALID_PRIORITY_LEVEL = CS2102_HOMEWORK.getPriorityLevel().toString(); + private static final String VALID_EXPECTED_NUM_OF_HOURS = Integer.toString(CS2102_HOMEWORK.getExpectedNumOfHours()); + /*private static final List VALID_TAGS = BENSON.getTags().stream() + .map(XmlAdaptedTag::new) + .collect(Collectors.toList()); + */ + + @Test + public void toModelType_validTaskDetails_returnsTask() throws Exception { + XmlAdaptedTask task = new XmlAdaptedTask(CS2102_HOMEWORK); + assertEquals(CS2102_HOMEWORK, task.toModelType()); + } + + @Test + public void toModelType_invalidDeadline_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(INVALID_DEADLINE, VALID_MODULECODE, VALID_TITLE, VALID_DESCRIPTION, + VALID_PRIORITY_LEVEL, VALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = Deadline.MESSAGE_DEADLINE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDeadline_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(null, VALID_MODULECODE, VALID_TITLE, VALID_DESCRIPTION, + VALID_PRIORITY_LEVEL, VALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Deadline.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidPriorityLevel_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(VALID_DEADLINE, VALID_MODULECODE, VALID_TITLE, VALID_DESCRIPTION, + INVALID_PRIORITY_LEVEL, VALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = PriorityLevel.MESSAGE_PRIORITY_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullPriorityLevel_throwsIllegalValueException() { + XmlAdaptedTask person = new XmlAdaptedTask(VALID_DEADLINE, VALID_MODULECODE, VALID_TITLE, + VALID_DESCRIPTION, null, VALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, PriorityLevel.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidExpectedNumOfHours_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(VALID_DEADLINE, VALID_MODULECODE, VALID_TITLE, VALID_DESCRIPTION, + VALID_PRIORITY_LEVEL, INVALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = "Expected number of hours have to be an integer"; + Assert.assertThrows(NumberFormatException.class, task::toModelType); + } + + @Test + public void toModelType_nullExpectedNumOfHours_throwsIllegalValueException() { + XmlAdaptedTask person = new XmlAdaptedTask(VALID_DEADLINE, VALID_MODULECODE, VALID_TITLE, VALID_DESCRIPTION, + VALID_PRIORITY_LEVEL, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, + "Expected number of hours expected to complete"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(VALID_DEADLINE, VALID_MODULECODE, null, VALID_DESCRIPTION, + VALID_PRIORITY_LEVEL, VALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Title"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(VALID_DEADLINE, VALID_MODULECODE, VALID_TITLE, null, + VALID_PRIORITY_LEVEL, VALID_EXPECTED_NUM_OF_HOURS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Description"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + /*@Test + public void toModelType_invalidTags_throwsIllegalValueException() { + List invalidTags = new ArrayList<>(VALID_TAGS); + invalidTags.add(new XmlAdaptedTag(INVALID_TAG)); + XmlAdaptedTask person = + new XmlAdaptedTask(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); + Assert.assertThrows(IllegalValueException.class, person::toModelType); + }*/ + +} +``` +###### \java\seedu\address\storage\XmlSerializableTaskBookTest.java +``` java +public class XmlSerializableTaskBookTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "XmlSerializableTaskBookTest"); + private static final Path TYPICAL_TASKS_FILE = TEST_DATA_FOLDER.resolve("typicalTaskBook.xml"); + private static final Path INVALID_TASK_FILE = TEST_DATA_FOLDER.resolve("invalidTaskBook.xml"); + private static final Path DUPLICATE_TASK_FILE = TEST_DATA_FOLDER.resolve("duplicateTasksInTaskBook.xml"); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void toModelType_typicalPersonsFile_success() throws Exception { + XmlSerializableTaskBook dataFromFile = XmlUtil.getDataFromFile(TYPICAL_TASKS_FILE, + XmlSerializableTaskBook.class); + AddressBook addressBookFromFile = dataFromFile.toModelType(); + AddressBook typicalPersonsAddressBook = getTypicalTaskBook(); + assertEquals(addressBookFromFile, typicalPersonsAddressBook); + } + + @Test + public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { + XmlSerializableTaskBook dataFromFile = XmlUtil.getDataFromFile(INVALID_TASK_FILE, + XmlSerializableTaskBook.class); + thrown.expect(IllegalValueException.class); + dataFromFile.toModelType(); + } + + @Test + public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { + XmlSerializableTaskBook dataFromFile = XmlUtil.getDataFromFile(DUPLICATE_TASK_FILE, + XmlSerializableTaskBook.class); + thrown.expect(IllegalValueException.class); + thrown.expectMessage(XmlSerializableTaskBook.MESSAGE_DUPLICATE_TASK); + dataFromFile.toModelType(); + } + +} +``` +###### \java\seedu\address\testutil\TaskUtil.java +``` java +/** + * A utility class for Task. + */ +public class TaskUtil { + /** + * Returns an add command string for adding the {@code task}. + */ + public static String getAddTaskCommand(Task task) { + return AddTaskCommand.COMMAND_WORD + " " + getTaskDetails(task); + } + + /** + * Returns the part of command string for the given {@code task}'s details. + */ + public static String getTaskDetails(Task task) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_MODULE_CODE + task.getModuleCode() + " "); + sb.append(PREFIX_TITLE + task.getTitle() + " "); + sb.append(PREFIX_DESCRIPTION + task.getDescription() + " "); + sb.append(PREFIX_PRIORITY + task.getPriorityLevel().priorityLevel + " "); + sb.append(PREFIX_HOURS + Integer.toString(task.getExpectedNumOfHours()) + " "); + return sb.toString(); + } + +} +``` +###### \java\seedu\address\testutil\TypicalTasks.java +``` java +/** + * A utility class containing a list of {@code Task} objects to be used in tests. + */ +public class TypicalTasks { + // Manually added + public static final Task CS2113_HOMEWORK = new TaskBuilder().withDeadline("1/1/2018").withModuleCode("CS2113") + .withTitle("Complete code refactoring").withDescription("Refer to notes!") + .withPriority("low").build(); + + public static final Task CS2101_HOMEWORK = new TaskBuilder().withDeadline("9/10/2018").withModuleCode("CS2101") + .withTitle("Plan a OP2 meeting").withDescription("OP2 is 40% of the grade") + .withPriority("high").build(); + + public static final Task CS2102_HOMEWORK = new TaskBuilder().withDeadline("11/11/2018").withModuleCode("CS2102") + .withTitle("Set up the backend framework").withDescription("Using Flask") + .withPriority("medium").build(); + + public static final Task CG2271_HOMEWORK = new TaskBuilder().withDeadline("5/6/2018").withModuleCode("CG2271") + .withTitle("Implement message passing").withDescription("Symmetric & indirect naming scheme") + .withDescription("low").build(); + + public static final Task CG1112_HOMEWORK = new TaskBuilder().withDeadline("2/5/2018").withModuleCode("CG1112") + .withTitle("Write buffer class").withDescription("refer to api") + .withDescription("high").build(); + + // Manually added - Task's details found in {@code CommandTestUtil} + public static final Task CS2113_TASK_1 = new TaskBuilder().withDeadline(VALID_DEADLINE_31ST_MARCH) + .withModuleCode(VALID_MODULE_CODE_CS2113).withTitle(VALID_TITLE_1).withDescription(VALID_DESCRIPTION_1) + .withPriority(VALID_PRIORITY_LEVEL_LOW).build(); + public static final Task CS2113_TASK_2 = new TaskBuilder().withDeadline(VALID_DEADLINE_1ST_JAN) + .withModuleCode(VALID_MODULE_CODE_CS2113).withTitle(VALID_TITLE_2).withDescription(VALID_DESCRIPTION_2) + .withPriority(VALID_PRIORITY_LEVEL_HIGH).build(); + public static final Task CS2113_TASK_3 = new TaskBuilder().withDeadline(VALID_DEADLINE_12TH_MAY) + .withModuleCode(VALID_MODULE_CODE_CS2113).withTitle(VALID_TITLE_3).withDescription(VALID_DESCRIPTION_3) + .withPriority(VALID_PRIORITY_LEVEL_MEDIUM).build(); + // public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private TypicalTasks() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical persons. + */ + public static AddressBook getTypicalTaskBook() { + AddressBook ab = new AddressBook(); + for (Task task : getTypicalTasks()) { + ab.addTask(task); + } + return ab; + } + + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(CS2101_HOMEWORK, CS2102_HOMEWORK, CS2113_HOMEWORK)); + } +} +``` diff --git a/collated/test/emobeany.md b/collated/test/emobeany.md new file mode 100644 index 000000000000..d3a7389cca4b --- /dev/null +++ b/collated/test/emobeany.md @@ -0,0 +1,331 @@ +# emobeany +###### \java\seedu\address\logic\commands\CommandTestUtil.java +``` java + public static final String INVALID_DAY_AND_MONTH_0 = "0"; + public static final String VALID_DAY_1 = "1"; + public static final String VALID_DAY_FOR_FEB = "28"; + public static final String VALID_DAY_FOR_LEAP_YEAR_FEB = "29"; + public static final String INVALID_DAY_FOR_COMMON_YEAR_FEB = "29"; + public static final String INVALID_DAY_FOR_LEAP_YEAR_FEB = "30"; + public static final String VALID_DAY_FOR_MONTHS_WITH_30_DAYS = "30"; + public static final String VALID_DAY_FOR_MONTHS_WITH_31_DAYS = "31"; + public static final String INVALID_DAY_FOR_MONTHS_WITH_30_DAYS = "31"; + public static final String INVALID_DAY_FOR_MONTHS_WITH_31_DAYS = "32"; + public static final String VALID_MONTH_JAN = "1"; + public static final String VALID_MONTH_FEB = "2"; + public static final String VALID_MONTH_APR = "4"; + public static final String INVALID_MONTH_13 = "13"; + public static final String VALID_YEAR_2018 = "2018"; + public static final String VALID_YEAR_2020 = "2020"; + public static final String VALID_YEAR_9999 = "9999"; + public static final String INVALID_YEAR_PASSED_2017 = "2017"; + public static final String INVALID_YEAR_10000 = "10000"; + + public static final String DAY_DESC_1 = " " + PREFIX_DAY + "1"; + public static final String DAY_DESC_2 = " " + PREFIX_DAY + "2"; + public static final String MONTH_DESC_1 = " " + PREFIX_MONTH + "1"; + public static final String MONTH_DESC_2 = " " + PREFIX_MONTH + "2"; + public static final String YEAR_DESC_2018 = " " + PREFIX_YEAR + "2018"; + public static final String YEAR_DESC_2019 = " " + PREFIX_YEAR + "2019"; + + public static final String DEADLINE_DESC_1ST_JAN = " " + PREFIX_DEADLINE + VALID_DEADLINE_1ST_JAN; + public static final String DEADLINE_DESC_31ST_MARCH = " " + PREFIX_DEADLINE + VALID_DEADLINE_31ST_MARCH; + public static final String DEADLINE_DESC_12TH_MAY = " " + PREFIX_DEADLINE + VALID_DEADLINE_12TH_MAY; + public static final String MODULE_CODE_CS2113_DESC = " " + PREFIX_MODULE_CODE + VALID_MODULE_CODE_CS2113; + public static final String MODULE_CODE_CG2271_DESC = " " + PREFIX_MODULE_CODE + VALID_MODULE_CODE_CG2271; + public static final String TITLE_DESC_1 = " " + PREFIX_TITLE + VALID_TITLE_1; + public static final String TITLE_DESC_2 = " " + PREFIX_TITLE + VALID_TITLE_2; + public static final String TITLE_DESC_3 = " " + PREFIX_TITLE + VALID_TITLE_3; + public static final String DESCRIPTION_DESC_1 = " " + PREFIX_DESCRIPTION + VALID_DESCRIPTION_1; + public static final String DESCRIPTION_DESC_2 = " " + PREFIX_DESCRIPTION + VALID_DESCRIPTION_2; + public static final String DESCRIPTION_DESC_3 = " " + PREFIX_DESCRIPTION + VALID_DESCRIPTION_3; + public static final String PRIORITY_LEVEL_DESC_LOW = " " + PREFIX_PRIORITY + VALID_PRIORITY_LEVEL_LOW; + public static final String PRIORITY_LEVEL_DESC_HIGH = " " + PREFIX_PRIORITY + VALID_PRIORITY_LEVEL_HIGH; + public static final String PRIORITY_LEVEL_DESC_MEDIUM = " " + PREFIX_PRIORITY + VALID_PRIORITY_LEVEL_MEDIUM; + public static final String HOURS_DESC_1 = " " + PREFIX_HOURS + VALID_1_HOUR; + public static final String HOURS_DESC_2 = " " + PREFIX_HOURS + VALID_2_HOURS; + + public static final String INVALID_DEADLINE_DESC = " " + PREFIX_DEADLINE + "31/2"; // No 31st February in calendar + public static final String INVALID_PRIORITY_LEVEL_DESC = " " + PREFIX_PRIORITY + "mid"; // not a priority level + public static final String INVALID_HOURS_DESC = " " + PREFIX_HOURS + "one"; // not an integer + public static final int OVERFLOW_INT = Integer.MAX_VALUE; + public static final String INVALID_HOURS_OVERFLOW = " " + PREFIX_HOURS + + Long.toString((long) OVERFLOW_INT + 1); // integer overflow + public static final int MAX_HOURS = 24; // integer overflow + public static final String INVALID_MAX_HOURS = " " + PREFIX_HOURS + Integer.toString(24); // integer overflow + + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; + public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + + //Mainly for EditCommandTests --> can remove + //public static final EditCommand.EditPersonDescriptor DESC_AMY; + //public static final EditCommand.EditPersonDescriptor DESC_BOB; + /* + static { + DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) + .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) + .withTags(VALID_TAG_FRIEND).build(); + DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + } + */ +``` +###### \java\seedu\address\logic\parser\SelectDeadlineCommandParserTest.java +``` java +public class SelectDeadlineCommandParserTest { + private static final Logger logger = LogsCenter.getLogger(SelectDeadlineCommandParserTest.class); + private SelectDeadlineCommandParser parser = new SelectDeadlineCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Parser parser = new SelectDeadlineCommandParser(); + Deadline expectedDeadline = VALID_1ST_JAN_2018; + + // preamble only contains whitespace + assertParseSuccess(parser, PREAMBLE_WHITESPACE + DAY_DESC_1 + MONTH_DESC_1 + YEAR_DESC_2018, + new SelectDeadlineCommand(expectedDeadline)); + + // multiple days - last day accepted + assertParseSuccess(parser, DAY_DESC_2 + DAY_DESC_1 + MONTH_DESC_1 + YEAR_DESC_2018, + new SelectDeadlineCommand(expectedDeadline)); + + // multiple months - last month accepted + assertParseSuccess(parser, DAY_DESC_1 + MONTH_DESC_2 + MONTH_DESC_1 + YEAR_DESC_2018, + new SelectDeadlineCommand(expectedDeadline)); + + // multiple years - last year accepted + assertParseSuccess(parser, DAY_DESC_1 + MONTH_DESC_1 + YEAR_DESC_2019 + YEAR_DESC_2018, + new SelectDeadlineCommand(expectedDeadline)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectDeadlineCommand.MESSAGE_USAGE); + + // missing day prefix + assertParseFailure(parser, VALID_DAY_1 + MONTH_DESC_1 + YEAR_DESC_2018, expectedMessage); + + // missing month prefix + assertParseFailure(parser, DAY_DESC_1 + VALID_MONTH_JAN + YEAR_DESC_2018, expectedMessage); + + // missing year prefix + assertParseFailure(parser, DAY_DESC_1 + MONTH_DESC_1 + VALID_YEAR_2018, expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + DAY_DESC_1 + MONTH_DESC_1 + YEAR_DESC_2018, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectDeadlineCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\model\task\DeadlineTest.java +``` java +public class DeadlineTest { + private static final Logger logger = LogsCenter.getLogger(DeadlineTest.class); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void isValidDeadline() { + // Invalid deadline with 0 day or month -> Returns false + assertFalse(Deadline.isValidDeadline(INVALID_0_JAN_2018.toString())); + assertFalse(Deadline.isValidDeadline(INVALID_1ST_0_2018.toString())); + + // Valid deadline -> returns true + assertTrue(Deadline.isValidDeadline(VALID_1ST_JAN_2018.toString())); + + /*// Valid deadline for february -> returns true + assertTrue(Deadline.isValidDeadline(VALID_28TH_FEB_2018.toString())); + + // Invalid deadline for february in common year -> returns false + assertFalse(Deadline.isValidDeadline(INVALID_29TH_FEB_2018.toString())); + + // Valid deadline for february during leap year -> returns true + assertTrue(Deadline.isValidDeadline(VALID_29TH_FEB_2020.toString())); + + // Invalid deadline for february during leap year -> returns false + assertFalse(Deadline.isValidDeadline(INVALID_30TH_FEB_2020.toString())); + + // Valid deadline for months with 30 days -> returns true + assertTrue(Deadline.isValidDeadline(VALID_30TH_APR_2018.toString())); + + // Invalid deadline for months with 30 days -> returns false + assertFalse(Deadline.isValidDeadline(INVALID_31ST_APR_2018.toString())); +*/ + // Valid deadline for months with 31 days -> returns true + assertTrue(Deadline.isValidDeadline(VALID_31ST_JAN_2018.toString())); + + // Invalid deadline for months with 31 days -> returns false + assertFalse(Deadline.isValidDeadline(INVALID_32ND_JAN_2018.toString())); + + // Invalid month -> returns false + assertFalse(Deadline.isValidDeadline(INVALID_1ST_0_2018.toString())); + assertFalse(Deadline.isValidDeadline(INVALID_1ST_13_2018.toString())); + + // Valid month -> returns true + assertTrue(Deadline.isValidDeadline(VALID_1ST_APR_2018.toString())); + + // Valid year -> returns true + assertTrue(Deadline.isValidDeadline(VALID_1ST_JAN_9999.toString())); + + // Invalid year -> returns false + assertFalse(Deadline.isValidDeadline(INVALID_1ST_JAN_2017.toString())); + assertFalse(Deadline.isValidDeadline(INVALID_1ST_JAN_10000.toString())); + } + + @Test + public void equals() { + Deadline copy1stJan2018 = new DeadlineBuilder(VALID_1ST_JAN_2018).build(); + logger.info("original: " + VALID_1ST_JAN_2018); + logger.info("copy: " + copy1stJan2018); + + assertTrue(VALID_1ST_JAN_2018.equals(copy1stJan2018)); + + // Same object -> returns true + assertTrue(VALID_1ST_JAN_2018.equals(VALID_1ST_JAN_2018)); + + // Null -> returns false + assertFalse(VALID_1ST_JAN_2018.equals(null)); + + // Different types -> return false + assertFalse(VALID_1ST_JAN_2018.equals(1)); + + // Different day -> returns false + assertFalse(VALID_1ST_JAN_2018.equals(VALID_31ST_JAN_2018)); + + // Different month -> returns false + assertFalse(VALID_1ST_JAN_2018.equals(VALID_1ST_APR_2018)); + + // Different year -> returns false + assertFalse(VALID_1ST_JAN_2018.equals(VALID_1ST_JAN_9999)); + } +} +``` +###### \java\seedu\address\testutil\DeadlineBuilder.java +``` java +/** + * A utility class to build Deadline objects. + */ + +public class DeadlineBuilder { + + public static final String DEFAULT_DAY = "1"; + public static final String DEFAULT_MONTH = "1"; + public static final String DEFAULT_YEAR = "2018"; + + private String day; + private String month; + private String year; + + public DeadlineBuilder() { + this.day = DEFAULT_DAY; + this.month = DEFAULT_MONTH; + this.year = DEFAULT_YEAR; + } + + /** + * Initialises the DeadBuilder with the data of {@code deadlineToCopu}. + */ + public DeadlineBuilder(Deadline deadlineToCopy) { + this.day = deadlineToCopy.getDay(); + this.month = deadlineToCopy.getMonth(); + this.year = deadlineToCopy.getYear(); + } + + /** + * Sets the {@code Day} of the {@code Deadline} that we are building. + */ + public DeadlineBuilder withDay(String day) { + this.day = day; + return this; + } + + /** + * Sets the {@code Month} of the {@code Deadline} that we are building. + */ + public DeadlineBuilder withMonth(String month) { + this.month = month; + return this; + } + + /** + * Sets the {@code Year} of the {@code Deadline} that we are building. + */ + public DeadlineBuilder withYear(String year) { + this.year = year; + return this; + } + + public Deadline build() { + return new Deadline(day, month, year); + } +} +``` +###### \java\seedu\address\testutil\TypicalDeadlines.java +``` java +/** + * A utility class containing a list of {@code Deadline} objects to be used in tests. + */ +public class TypicalDeadlines { + // For day validity testing + public static final Deadline INVALID_0_JAN_2018 = new DeadlineBuilder().withDay(INVALID_DAY_AND_MONTH_0) + .withMonth(VALID_MONTH_JAN).withYear(VALID_YEAR_2018).build(); + + public static final Deadline VALID_1ST_JAN_2018 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(VALID_MONTH_JAN).withYear(VALID_YEAR_2018).build(); + + // For february + public static final Deadline VALID_28TH_FEB_2018 = new DeadlineBuilder().withDay(VALID_DAY_FOR_FEB) + .withMonth(VALID_MONTH_FEB).withYear(VALID_YEAR_2018).build(); + + public static final Deadline INVALID_29TH_FEB_2018 = new DeadlineBuilder() + .withDay(INVALID_DAY_FOR_COMMON_YEAR_FEB).withMonth(VALID_MONTH_JAN).withYear(VALID_YEAR_2018).build(); + + // For leap year + public static final Deadline VALID_29TH_FEB_2020 = new DeadlineBuilder().withDay(VALID_DAY_FOR_LEAP_YEAR_FEB) + .withMonth(VALID_MONTH_FEB).withYear(VALID_YEAR_2020).build(); + + public static final Deadline INVALID_30TH_FEB_2020 = new DeadlineBuilder().withDay(INVALID_DAY_FOR_LEAP_YEAR_FEB) + .withMonth(VALID_MONTH_FEB).withYear(VALID_YEAR_2020).build(); + + // Months with 30 days + public static final Deadline VALID_30TH_APR_2018 = new DeadlineBuilder().withDay(VALID_DAY_FOR_MONTHS_WITH_30_DAYS) + .withMonth(VALID_MONTH_APR).withYear(VALID_YEAR_2018).build(); + + public static final Deadline INVALID_31ST_APR_2018 = new DeadlineBuilder() + .withDay(INVALID_DAY_FOR_MONTHS_WITH_30_DAYS).withMonth(VALID_MONTH_APR).withYear(VALID_YEAR_2018).build(); + + // Months with 31 days + public static final Deadline VALID_31ST_JAN_2018 = new DeadlineBuilder().withDay(VALID_DAY_FOR_MONTHS_WITH_31_DAYS) + .withMonth(VALID_MONTH_JAN).withYear(VALID_YEAR_2018).build(); + + public static final Deadline INVALID_32ND_JAN_2018 = new DeadlineBuilder() + .withDay(INVALID_DAY_FOR_MONTHS_WITH_31_DAYS).withMonth(VALID_MONTH_JAN).withYear(VALID_YEAR_2018).build(); + + // For month validity testing + public static final Deadline VALID_1ST_APR_2018 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(VALID_MONTH_APR).withYear(VALID_YEAR_2018).build(); + + public static final Deadline INVALID_1ST_0_2018 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(INVALID_DAY_AND_MONTH_0).withYear(VALID_YEAR_2018).build(); + + public static final Deadline INVALID_1ST_13_2018 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(INVALID_MONTH_13).withYear(VALID_YEAR_2018).build(); + + // For year validity testing + public static final Deadline INVALID_1ST_JAN_2017 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(VALID_MONTH_JAN).withYear(INVALID_YEAR_PASSED_2017).build(); + + public static final Deadline VALID_1ST_JAN_9999 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(VALID_MONTH_JAN).withYear(VALID_YEAR_9999).build(); + + public static final Deadline INVALID_1ST_JAN_10000 = new DeadlineBuilder().withDay(VALID_DAY_1) + .withMonth(VALID_MONTH_JAN).withYear(INVALID_YEAR_10000).build(); + + private TypicalDeadlines() {} // prevents instantiation +} +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..fcfc9306cd11 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,45 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +Task Book was developed by the team. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] - -Role: Project Advisor - -''' - -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Chelsey Ong +image::chelseyong.png[width="150", align="left"] +{empty} [https://github.com/chelseyong[github]] [https://cs2113-ay1819s1-w13-3.github.io/main/team/chelseyong.html[portfolio]] Role: Team Lead + -Responsibilities: UI +Responsibilities: Task features ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Aw Meng Shen +image::jeremyinelysium.png[width="150", align="left"] +{empty}[http://github.com/jeremyinelysium[github]] [https://cs2113-ay1819s1-w13-3.github.io/main/team/jeremyinelysium.html[portfolio]] Role: Developer + -Responsibilities: Data +Responsibilities: Milestone features ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Beh Kha Sim +image::emobeany.png[width="150", align="left"] +{empty}[https://github.com/emobeany[github]] [https://cs2113-ay1819s1-w13-3.github.io/main/team/emobeany.html[portfolio]] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Deadline features ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Chan Chun Cheong +image::chanchuncheong.png[width="150", align="left"] +{empty}[https://github.com/ChanChunCheong[github]] [https://cs2113-ay1819s1-w13-3.github.io/main/team/chanchuncheong.html[portfolio]] Role: Developer + -Responsibilities: UI +Responsibilities: Task , Deadline and Tag features ''' + diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index ea58481e4740..ad0147e85ed5 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += Task Book - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,9 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2113-AY1819S1-W13-3/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +Since: `Aug 2018`      Licence: `MIT` == Setting up @@ -49,8 +49,16 @@ This will generate all resources required by the application and tests. === Verifying the setup -. Run the `seedu.address.MainApp` and try a few commands -. <> to ensure they all pass. +. Run the `seedu.address.MainApp` and try a few commands. +To find this particular file, refer to Figure 1 for guidance. + +.Subdirectories of MainApp +image::MainApp_directory.png[width="600"] + +. <> to ensure they all pass. Refer to Figure 2. + +.Running all tests +image::RunAllTests.png[width="600"] === Configurations to do before writing code @@ -144,7 +152,7 @@ image::LogicClassDiagram.png[width="800"] The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. .Component interactions for `delete 1` command (part 1) -image::SDforDeletePerson.png[width="800"] +image::SDforDeleteTask.png[width="800"] [NOTE] Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. @@ -152,7 +160,7 @@ Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. .Component interactions for `delete 1` command (part 2) -image::SDforDeletePersonEventHandling.png[width="800"] +image::SDforDeleteTaskEventHandling.png[width="800"] [NOTE] Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components. @@ -167,7 +175,7 @@ image::UiClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TaskListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -187,15 +195,15 @@ image::LogicClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `TaskBookParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a task) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. .Interactions Inside the Logic Component for the `delete 1` Command -image::DeletePersonSdForLogic.png[width="800"] +image::DeleteTaskSdForLogic.png[width="800"] [[Design-Model]] === Model component @@ -208,14 +216,14 @@ image::ModelClassDiagram.png[width="800"] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Task Book data. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:ModelClassBetterOopDiagram.png[width="800"] +//[NOTE] +//As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +// + +//image:ModelClassBetterOopDiagram.png[width="800"] [[Design-Storage]] === Storage component @@ -228,7 +236,7 @@ image::StorageClassDiagram.png[width="800"] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the Task Book data in xml format and read it back. [[Design-Commons]] === Common classes @@ -239,58 +247,152 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. +// tag::selectDeadlineImplementation[] +=== Select Deadline feature +==== Current Implementation + +The `select` mechanism is facilitated by `VersionedTaskBook`. + +* `VersionedTaskBook#selectDeadline()` - Selects date to be set as deadline +* `indicateTaskBookChanged()` — Event raised to indicate that the TaskBook in the model has changed. + +These operations are exposed in the `Model` interface as `Model#selectDeadline()`, `Model#updateFilteredTaskList()` and `Model#commitTaskBook()` respectively. + +Given below is an example usage scenario and how the `select` mechanism behaves are each step. + +Step 1. The user launches the application. If it is the first time he/she is launching it, the `VersionedTaskBook` will be initialized with a sample task book data. If the user has already launched it previously and made changes to it, the `VersionedTaskBook` launched will contain the data that he/she has entered in the previous launch. + +Step 2. The user executes `select 1/1/2018` or `select dd/1 mm/1 yyyy/2018` to select the deadline. + +Step 3. The `select` command will call `Model#selectDeadline()` and select the deadline as 1/1/2018. `Model#updateFilteredTaskList()` will be called within `Model#selectDeadline()` to update the list that is being shown to the user with only show tasks with 1/1/2018 as the deadline. + +Step 4. Lastly, `Model#commitTaskBook()` will be called to update the `TaskBookStateList` and `currentStatePointer`. + +The following sequence diagram illustrates how the `select` command is implemented. + +.Sequence diagram for `select` command +image::SelectDeadline.PNG[width=”600”] + +==== Design considerations +===== Aspect: Format for selection of date + +* **Alternative 1 (current choice):** Allows users to use string dd/mm/yyyy format +** Pros: Easy for users to type command as it is more intuitive without the need to type in all the prefixes. +** Cons: A separate method of parsing has to be created to handle such a format. +* **Alternative 2:** Only allow format with prefixes. +** Pros: No need for a separate method to handle another format by limiting users to only the format with prefixes. +** Cons: Users may find it difficult to enter the date since it is less intuitive which might result in multiple failed attempts to do so. + +===== Aspect: Method to set deadline + +* **Alternative 1 (current choice):** Deadline is selected by a separate `Select` deadline function. +** Pros: Allows users to add multiple tasks to the same deadline without having to type the deadline over and over again. Also allows a `filteredTaskList` based on deadline to be shown to users for them to see how many tasks are currently added to the current deadline. +** Cons: A separate function has to be created to select the date. +* **Alternative 2: **Deadline is selected as a field in the `add` command. +** Pros: Separate `select` function is not required since deadline is set with every `add` command. +** Cons: Users have to type the deadline repeatedly if multiple tasks are added to the same deadline. A separate function has to be created to view only tasks that are added to a specific deadline. +// end::selectDeadlineImplementation[] + +// tag::editTaskImplementation[] +=== Edit Task feature +==== Current Implementation + +The `edit` mechanism is facilitated by `VersionedTaskBook`. + +Additionally, it implements the following operations: + +* `Model#getFilteredTaskList()` — Obtains the current list of Tasks that is being displayed to the user +* `VersionedTaskBook#updateTask()` — Edits and updates the specified task with the new values. +* `indicateTaskBookChanged()` — Event raised to indicate that the TaskBook in the model has changed. + +These operations are exposed in the `Model` interface as `Model#updateTask()`, `Model#updateFilteredTaskList()` and `Model#commitTaskBook()` respectively. +Given below is an example usage scenario and how the `edit` mechanism behaves at each step. + +Step 1. The user launches the application. If it is the first time he/she is launching it, the `VersionedTaskBook` will be initialized with a sample task book data. If the user has already launched it previously and made changes to it, the `VersionedTaskBook` launched will contain the data that he/she has entered in the previous launch. + +Step 2. The user executes `edit i/1 t/Complete CS2113 tutorial` to edit the title of the existing task at index 1 in the list. The `edit` command obtains the data of the task that the user wants to change based on the input index. + +Step 3. The `edit` command will retrieve and copy the details of the task at index 1 to a new task `editedTask` and edit the title to `Complete CS2113 tutorial`. + +Step 4. The `edit` command will call `Model#updateTask()` and update the task at index 1 with the details of `editedTask`. Also, `Model#updateFilteredTaskList()` will be called to update the list that is being shown to the user with the updated task. + +Step 5. Lastly, `Model#commitTaskBook()` will be called to update the `TaskBookStateList` and `currentStatePointer`. + +The following sequence diagram illustrates how the `edit` command is implemented. + +.Sequence diagram for `edit` command +image::EditTaskSequenceDiagram.PNG[width=”600”] + +==== Design considerations +===== Aspect: How to edit different fields of a task +* **Alternative 1 (current choice):** Allows users to specify which fields they want to change using `Prefix` +** Pros: Users can change more than one fields at a time. +** Cons: Command might be longer and harder for users to type. +* **Alternative 2:** Have individual functions for editing every field +** Pros: Easy to implement. +** Cons: Too many different commands for the users to handle. + +===== Aspect: Choosing a task to edit +* **Alternative 1 (current choice):** Choose a `Task` based on `Index` from the `filteredTaskList`. +** Pros: Easy to implement since Index is already implemented in TaskBook. +** Cons: Users have to scroll through the entire list of tasks to find the `Task` they want to edit. If the current `filteredTaskList` shown to them does not contain the task they want to edit, they have to use the ‘List’ command first, resulting in a even longer list of tasks to filter. +* **Alternative 2:** Choose a `Task` by a find command +** Pros: Users do not have to scroll through long task lists to find the task they want to edit. +** Cons: A separate find command has to be implemented. Since there may be task titles that are similar or even the same, the find command will have to show a `filteredTaskList` for users to select the task to be edited by index which is similar to the first implementation. +// end::editTaskImplementation[] + // tag::undoredo[] === Undo/Redo feature ==== Current Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. +The undo/redo mechanism is facilitated by `VersionedTaskBook`. +It extends `TaskBook` with an undo/redo history, stored internally as an `taskBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +* `VersionedTaskBook#commit()` -- Saves the current task book state in its history. +* `VersionedTaskBook#undo()` -- Restores the previous task book state from its history. +* `VersionedTaskBook#redo()` -- Restores a previously undone task book state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitTaskBook()`, `Model#undoTaskBook()` and `Model#redoTaskBook()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedTaskBook` will be initialized with the initial task book state, and the `currentStatePointer` pointing to that single task book state. image::UndoRedoStartingStateListDiagram.png[width="800"] -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th task in the task book. The `delete` command calls `Model#commitTaskBook()`, causing the modified state of the task book after the `delete 5` command executes to be saved in the `taskBookStateList`, and the `currentStatePointer` is shifted to the newly inserted task book state. image::UndoRedoNewCommand1StateListDiagram.png[width="800"] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add t/Do math homework ...` to add a new task. The `add` command also calls `Model#commitTaskBook()`, causing another modified task book state to be saved into the `taskBookStateList`. image::UndoRedoNewCommand2StateListDiagram.png[width="800"] [NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +If a command fails its execution, it will not call `Model#commitTaskBook()`, so the task book state will not be saved into the `taskBookStateList`. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the task was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous task book state, and restores the task book to that state. image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] [NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +If the `currentStatePointer` is at index 0, pointing to the initial task book state, then there are no previous task book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the task book to that state. [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +If the `currentStatePointer` is at index `taskBookStateList.size() - 1`, pointing to the latest task book state, then there are no undone task book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. Commands that do not modify the task book, such as `list`, will usually not call `Model#commitTaskBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `taskBookStateList` remains unchanged. image::UndoRedoNewCommand3StateListDiagram.png[width="800"] -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`, which calls `Model#commitTaskBook()`. Since the `currentStatePointer` is not pointing at the end of the `taskBookStateList`, all task book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/Do math homework ...` command. This is the behavior that most modern desktop applications follow. image::UndoRedoNewCommand4StateListDiagram.png[width="800"] @@ -302,153 +404,158 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire task book. ** Pros: Easy to implement. ** Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). +** Pros: Will use less memory (e.g. for `delete`, just save the task being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. +* **Alternative 1 (current choice):** Use a list to store the history of task book states. ** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedTaskBook`. * **Alternative 2:** Use `HistoryManager` for undo/redo ** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. // end::undoredo[] -// tag::dataencryption[] -=== [Proposed] Data Encryption - -_{Explain here how the data encryption feature will be implemented}_ - -// end::dataencryption[] - -=== Logging - -We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. - -* The logging level can be controlled using the `logLevel` setting in the configuration file (See <>) -* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level -* Currently log messages are output through: `Console` and to a `.log` file. - -*Logging Levels* +// tag::milestone_feature[] +=== Add Milestone feature -* `SEVERE` : Critical problem detected which may possibly cause the termination of the application -* `WARNING` : Can continue, but with caution -* `INFO` : Information showing the noteworthy actions by the App -* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size - -[[Implementation-Configuration]] -=== Configuration +==== Current Implementation -Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`). +Whenever a `Task` object is created, it will be instantiated with an empty `List` of `Milestone` objects. -== Documentation +When the user calls the `add_milestone` command, +he/she will enter an `index` to select an existing `Task` and the desired arguments with the appropriate prefixes. (e.g `i/` INDEX `m/` MILESTONE DESCRIPTION `r/` RANK) -We use asciidoc for writing documentation. +The `index` represents the `Task` in the list from `PersonListPanel` displayed in the GUI. -[NOTE] -We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. +The following sequence diagram illustrates how the `add_milestone` command is implemented. -=== Editing Documentation +image::DG_AddMilestoneCommand.png[width="790"] -See <> to learn how to render `.adoc` files locally to preview the end result of your edits. -Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your `.adoc` files in real-time. -=== Publishing Documentation +==== Design Considerations -See <> to learn how to deploy GitHub Pages using Travis. +===== Aspect: How to order all the milestones for each task -=== Converting Documentation to PDF format +* **Alternative 1 (current choice):** Use an `ArrayList` to implement a `List` of `Milestone` objects for each `Task` and `sort` them each time a `Milestone` is added using a custom comparator. +** Pros: Easy to implement. +** Cons: `List` interface does not prevent adding of duplicate `Milestone` objects +* **Alternative 2:** Use a `TreeSet` to implement a `List` of `Milestone` objects for each `Task` +** Pros: Does not allow duplicate `Milestone` objects to be added. +** Cons: Requires in-depth understanding of how to use the SortedSet interface. + +===== Aspect: Selecting an existing task to add a milestone to + +* **Alternative 1 (current choice):** Select an existing `Task` using the `index` +** Pros: Prevents ambiguity when selecting the desired `Task` using other parameters such as `title` as the `index` of each `Task` is unique and ordered +** Cons: User has to take time to scroll through the list to find the desired `Task` which can be quite troublesome and time-consuming if the list is long due to a large number of existing tasks. +* **Alternative 2:** Select an existing `Task` using the `title` +** Pros: More convenient for the user as there is no command to search for `index` of desired `Task` +** Cons: May result in confusion especially if there are tasks with very similar `title`. +// end::milestone_feature[] + +// tag::deferDeadline_feature[] +=== Defer Deadline feature +==== Current Implementation +The `defer deadline` mechanism is facilitated by `VersionedTaskBook`. The `defer deadline command` takes in the `index +prefix` with `index` of the task to be deferred, followed by the `deadline prefix` with its corresponding `number of days` +which will defer the deadline in the task by the number of days. -We use https://www.google.com/chrome/browser/desktop/[Google Chrome] for converting documentation to PDF format, as Chrome's PDF engine preserves hyperlinks used in webpages. +The index represents the `Task` in the list from `PersonListPanel` displayed in the `GUI`. -Here are the steps to convert the project documentation files to PDF format. +The validity of the input by the user is checked through the `DeferDeadlineCommandParser` and `TaskBookParser`. A +`DeferDeadlineCommand` object is returned and the `execute()` method will be performed. The validity of the index will be +checked and the task to be deferred is retrieve using the `getFilterTaskList()` method. A copy of the task will be +instantiated as deferredTask. The deadline of `deferredTask` will be deferred by the number of days inputted by the user. +The validity of the deadline and existence of duplicate tasks will be checked. Task to be deferred will be updated with + `deferredTask` through `updateTask()` and `updateFiteredTaskList()` method in the Model Component. -. Follow the instructions in <> to convert the AsciiDoc files in the `docs/` directory to HTML format. -. Go to your generated HTML files in the `build/docs` folder, right click on them and select `Open with` -> `Google Chrome`. -. Within Chrome, click on the `Print` option in Chrome's menu. -. Set the destination to `Save as PDF`, then click `Save` to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below. +Step 1. The user launches the application. If it is the first time he/she is launching it, the `VersionedTaskBook` will be + initialized with a sample task book data. If the user has already launched it previously and made changes to it, the + `VersionedTaskBook` launched will contain the data that he/she has entered in the previous launch. -.Saving documentation as PDF files in Chrome -image::chrome_save_as_pdf.png[width="300"] +Step 2. The user executes `defer i/1 dd/1` to defer the deadline of the selected task at index 1 in the list. The `defer` +command obtains the data of the task that the user wants to change based on the input index. -[[Docs-SiteWideDocSettings]] -=== Site-wide Documentation Settings +Step 3. The `defer` command will retrieve and copy the details of the task at index 1 and instantiate a new task +`deferredTask` and `defer the deadline by 1 day`. -The link:{repoURL}/build.gradle[`build.gradle`] file specifies some project-specific https://asciidoctor.org/docs/user-manual/#attributes[asciidoc attributes] which affects how all documentation files within this project are rendered. +Step 4. The defer command will call `Model#updateTask()` and update the task at index 1 with the details of `deferredTask`. +Also, `Model#updateFilteredTaskList()` will be called to update the list that is being shown to the user with the updated task. -[TIP] -Attributes left unset in the `build.gradle` file will use their *default value*, if any. +Step 5. Lastly, `Model#commitTaskBook()` will be called causing the modified state of the task book after the defer i/1 dd/1 command +executes to be saved in the `TaskBookStateList`, and the `currentStatePointer` to be updated. -[cols="1,2a,1", options="header"] -.List of site-wide attributes -|=== -|Attribute name |Description |Default value +The following sequence diagram illustrates how the `defer` command is implemented. -|`site-name` -|The name of the website. -If set, the name will be displayed near the top of the page. -|_not set_ +image::DeferDeadline_SequenceDiagram.png[width="790"] -|`site-githuburl` -|URL to the site's repository on https://github.com[GitHub]. -Setting this will add a "View on GitHub" link in the navigation bar. -|_not set_ -|`site-seedu` -|Define this attribute if the project is an official SE-EDU project. -This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. -|_not set_ +==== Design Considerations -|=== +===== Aspect: How should the deadline of the task be deferred +* **Alternative 1 (current choice):** Choose to `defer` the task based on the `number of days required` and the +deadline will be `automatically updated`. +** Pros: More `user-friendly` as it allows user to defer the deadline by the `number of days required` and the deadline +will be `automatically updated` without the user finding the exact deadline through calendar. +** Cons: More `complex` implementation of the feature as `multiple conditional statements` are required to ensure the +deadline is updated correctly by taking into consideration the `features of a calendar`. For example, which are the months + have 30 or 31 days and how is leap year calculated? +* **Alternative 2:** Choose to `defer` the task based on the `exact date` +** Pros: `Easy` implementation of defer deadline command as just have to change the deadline `based on users input`. +** Cons: By allowing users to `defer` the tasks by entering the `exact deadline` to defer will result in defer deadline +command to be `similar to edit task` as it is simply just editing the task’s deadline by keying in a new deadline. -[[Docs-PerFileDocSettings]] -=== Per-file Documentation Settings +// end::deferDeadline_feature[] -Each `.adoc` file may also specify some file-specific https://asciidoctor.org/docs/user-manual/#attributes[asciidoc attributes] which affects how the file is rendered. -Asciidoctor's https://asciidoctor.org/docs/user-manual/#builtin-attributes[built-in attributes] may be specified and used as well. +// tag::lockunlock[] +=== Locking/Unlocking Task Book feature [coming in v2.0] +==== Proposed Implementation +To ensure encryption keys are both sufficiently random and hard to brute force, +we will use standard password-based encryption (PBE) key derivation methods. + -[TIP] -Attributes left unset in `.adoc` files will use their *default value*, if any. +When the student uses Task Book for the first time, he or she will be requested to enter a new password. +Since password recovery methods may not be implemented, this password must be easy to remember by the student. +Along with the password text provided by the student, a salt is appended to produce a hash (Figure below). + -[cols="1,2a,1", options="header"] -.List of per-file attributes, excluding Asciidoctor's built-in attributes -|=== -|Attribute name |Description |Default value +.Hashing of the password +image::salt_hashing.png[width="500"] -|`site-section` -|Site section that the document belongs to. -This will cause the associated item in the navigation bar to be highlighted. -One of: `UserGuide`, `DeveloperGuide`, ``LearningOutcomes``{asterisk}, `AboutUs`, `ContactUs` +An AES or DES encryption key is thus derived from this process. +To unlock the Task Book, the same salt is appended to the password provided to generate the decryption key. -_{asterisk} Official SE-EDU projects only_ -|_not set_ -|`no-site-header` -|Set this attribute to remove the site navigation bar. -|_not set_ +In the Sequence Diagram, these are the interactions within the Logic component for the execute("lock") API call (Figure below): -|=== +. `Logic` parses the command and returns the LockCommand. +. During the command execution, `Model` checks whether a password has been set. +. If so, `Encryptor` will encrypt `taskbook.xml` file so the Task Book cannot be seen by anyone without the password. +. Else, a `CommandException` is returned to prompt the user to set a password. -=== Site Template +.Sequence diagram of user locking Task Book +image::EncryptionLogicSequenceDiagram.png[width="500"] -The files in link:{repoURL}/docs/stylesheets[`docs/stylesheets`] are the https://developer.mozilla.org/en-US/docs/Web/CSS[CSS stylesheets] of the site. -You can modify them to change some properties of the site's design. +==== Design Considerations +There are a few ways to implement the password encryption for our product. + +However, each method has its strengths and weaknesses. We will be explaining why we chose this particular implementation design. -The files in link:{repoURL}/docs/templates[`docs/templates`] controls the rendering of `.adoc` files into HTML5. -These template files are written in a mixture of https://www.ruby-lang.org[Ruby] and http://slim-lang.com[Slim]. +===== Aspect: Online password authentication +* **Alternative 1:** Offline password authentication +** Pros: Simple and efficient method to log into Task Book with a lower risk of data breach +*** If student has set a complex password, it will be harder to hack into Task Book +** Cons: Possible data loss if student's password is forgotten +* **Alternative 2:** Connect student logging session to an online authentication system +** Pros: Allows students to reset their password, if forgotten +** Cons: Extra step to connect to the internet and send hashed password to verify with the database. +Additional space is also required in database to store users and their passwords securely. -[WARNING] -==== -Modifying the template files in link:{repoURL}/docs/templates[`docs/templates`] requires some knowledge and experience with Ruby and Asciidoctor's API. -You should only modify them if you need greater control over the site's layout than what stylesheets can provide. -The SE-EDU team does not provide support for modified template files. -==== +// end::lockunlock[] [[Testing]] == Testing @@ -480,11 +587,6 @@ To run tests in headless mode, open a console and run the command `gradlew clean === Types of tests -We have two types of tests: - -. *GUI Tests* - These are tests involving the GUI. They include, -.. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. -.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. . *Non-GUI Tests* - These are tests not involving the GUI. They include, .. _Unit tests_ targeting the lowest level methods/classes. + e.g. `seedu.address.commons.StringUtilTest` @@ -534,359 +636,345 @@ b. Require developers to download those libraries manually (this creates extra w [[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started - -Suggested path for new programmers: - -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +== Product Scope -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +*Target user profile*: -[[GetStartedProgramming-EachComponent]] -=== Improving each component +* Students who need to manage a significant number of daily tasks +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +*Value proposition*: manage daily tasks faster than Google calendar/handwritten notebook and become more productive -[discrete] -==== `Logic` component +[appendix] +== User Stories -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +|`* * *` |forgetful student |add new task |keep track of my workload -[discrete] -==== `Model` component +|`* * *` |efficient student |complete a task |keep track of my incomplete tasks -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +|`* * *` |indecisive student |edits a task |change information of my existing tasks -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** +|`* * *` |tidy student |delete a task |remove tasks that I do not intend to complete -[discrete] -==== `Ui` component +|`* * *` |organized student |sort the tasks in the order preferred to have a more organised task list| complete tasks with more urgent deadlines/ highest priority firsts. View tasks in a list sorted by the lexicographical order of the title or module codes. -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +|`* * *` |tidy student |Add tags to tasks | to organise, categorise and identify the tasks easily -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +|`* * *` |organized student |Remove tags to tasks | to remove the unsuitable tags that were added previously for better organisation, categorisation and identification of the tasks easily -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** +|`* * *` |structured student |Select a tag to view |shows a list of tasks with the selected tag to help with the planning of work schedule for that type of tasks -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** - -[discrete] -==== `Storage` component - -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. - -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. - -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +|`* * *` |busy student |defer deadlines |allow for a more flexible schedule when workload becomes too heavy -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +|`* * *` |unorganised student |select a date |add/delete/complete tasks for that particular day -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +|`* * *` |objective student |break up my task into smaller tasks |manage them more effectively -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +|`* * *` |targeted student |track the productivity of how fast tasks are being completed |learn more about my studying habits and work more effectively -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +|======================================================================= -Examples: -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +[appendix] +== Use Cases -==== Step-by-step Instructions +(For all use cases below, the *System* is the `TaskBook` and the *Actor* is the `student`, unless specified otherwise) -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +// tag::select[] +[discrete] -**Main:** +=== Use case: Select a date -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +*MSS* -**Tests:** +1. Student requests to select date required +2. TB checks for its validity and changes to the required date ++ +Use case ends. -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +*Extensions* -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +* 1a. Date entered by the student is not valid e.g. dd/29 mm/2 yyyy/2018 +** 1a1. TB prompts student to enter a correct date ++ +Use case resumes at step 2. +// end::select[] -**Main:** +//tag::addTask[] +[discrete] +=== Use case: Add new task -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +*MSS* -**Tests:** +1. Student selects the deadline for a task +2. TB updates the selected date +3. Student requests to add a new task with some details +4. TB checks for the validity of command and adds the task to the list ++ +Use case ends. -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +*Extensions* -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +* 3a. Student did not enter one or more compulsory input(s) for the task +** 3a1. TB tells student that input(s) is/are empty ++ +Use case ends. -**Main:** +* 3b. Student enters a duplicated task +** 3b1. TB shows that task already exists in TB ++ +Use case ends. +//end::addTask[] -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +//tag::completeTask[] +[discrete] +=== Use case: Complete task -**Tests:** +*MSS* -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +1. Student selects the date of completed task +2. TB updates the selected date +3. Student requests to complete the task +4. TB checks for its validity and completes the task in TB ++ +Use case ends. -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +*Extensions* -**Main:** +* 3a. Student attempts to complete the task in less than 1 hour +** 3a1. TB requests for student to enter a more suitable number of hour(s) ++ +Use case resumes at step 4. -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +* 3a. Student wants to complete a completed task +** 3a1. TB gives an error to show that task is completed already ++ +Use case ends. +//end::completeTask[] -**Tests:** +[discrete] +=== Use case: Delete task -. Add test for `Remark`, to test the `Remark#equals()` method. +*MSS* -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +1. Student requests to delete a task by providing its index +2. TB removes deadline from the task ++ +Use case ends. -**Main:** +*Extensions* -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +* 1a. Student provides an invalid index of the task +** 1a1. TB outputs an error message -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +[discrete] +=== Use case: Sort tasks -**Main:** +*MSS* -. Add a new Xml field for `Remark`. +1. Student requests to sort his or her tasks in the lists and provides the sorting method +2. TB checks for the validity of the sorting method +3. TB displays the sorted tasks list based on the sorting method. ++ +Use case ends. -**Tests:** +*Extensions* -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +* 1a. Student provided an invalid sorting method +** 1a1. TB outputs an error message to request for a valid sorting method ++ +Use case ends. +// tag::usecase_deferDeadlines[] +[discrete] +=== Use case: Defer deadlines -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +*MSS* -**Tests:** +1. Student requests to defer the deadline for an existing task by number of days +2. TB checks the validity of the index and number of days. +3. TB updates and display the new deadline for the existing task ++ +Use case ends. -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +*Extensions* -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +* 1a. Student wants to defer a deadline for an non-existent task +** 1a1. TB outputs an error message -**Main:** +* 1b. Student wants to defer to a invalid deadline for the selected task +** 1b1. TB outputs an error message ++ +Use case ends. +// end::usecase_deferDeadlines[] +[discrete] -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +[discrete] +// tag::usecase_milestone[] +=== Use case: Add tag -**Tests:** +*MSS* -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +1. Student requests to add a new tag and provides the index and the tag +2. TB adds the tag to the selected task ++ +Use case ends. -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +*Extensions* -**Main:** +* 1a. Student enters an invalid index when list of tasks added is empty +** 1a1. TB outputs error message for invalid index ++ +Use case ends. -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +* 1b. Student enters index, tag and rank without all the required prefixes +** 1b1. TB outputs error message for invalid format ++ +Use case ends. -**Tests:** +* 1c. Student enters an invalid tag +** 1c1. TB outputs error message for invalid format ++ +Use case ends. -. Update `RemarkCommandTest` to test that the `execute()` logic works. +[discrete] +=== Use case: remove tag -==== Full Solution +*MSS* -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +1. Student requests to remove a tag and provides the index and the tag +2. TB removes the tag from the selected task ++ +Use case ends. -[appendix] -== Product Scope +*Extensions* +* 1a. Student enters an invalid index when list of tasks added is empty +** 1a1. TB outputs error message for invalid index ++ +Use case ends. -*Target user profile*: +* 1b. Student enters index, tag and rank without all the required prefixes +** 1b1. TB outputs error message for invalid format ++ +Use case ends. -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +* 1c. Student enters an invalid tag +** 1c1. TB outputs error message for invalid format ++ +Use case ends. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +* 1d. Student selected the task that doesn't have the tag indicated +** 1d1. TB outputs error message for invalid format ++ +Use case ends. -[appendix] -== User Stories +[discrete] +=== Use case: select tag -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +*MSS* -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +1. Student requests to display list of task with the selected tag and provides the tag +2. TB removes the tag from the selected task ++ +Use case ends. -|`* * *` |user |add a new person | +* 1c. Student enters an invalid tag +** 1c1. TB outputs error message for invalid format ++ +Use case ends. +// tag::editTask[] +=== Use case: Edit task -|`* * *` |user |delete a person |remove entries that I no longer need +*MSS* -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +1. Student requests to edit a selected task by providing its index and the fields with the values to be updated. +2. TB checks for validity of the index and updates the fields with the values provided. ++ +Use case ends. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +*Extensions* -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +* 1a. Student provided an invalid index +** 1a1. TB outputs error message +** 1a2. Student enters a new Edit command ++ +Use case ends. -_{More to be added}_ +* 1b. Student did not provide any field or values to update selected task +** 1b1. TB returns an error message +** 1b2. Student enters a new Edit command ++ +Use case ends. -[appendix] -== Use Cases +* 1c. Values provided by student results in the exact same task as before it was edited +** 1c1. TB returns no field edited error message +** 1c2. Student enters a new Edit command ++ +Use case ends. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +* 1d. Values provided by student results in an edited task exactly the same as another existing task +** 1d1. TB returns duplicate task info message +** 1d2. Student enters a new Edit command ++ +Use case ends. +// end::editTask[] [discrete] -=== Use case: Delete person +// tag::usecase_milestone[] +=== Use case: Add milestone *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Student requests to add a new milestone and provides the index, milestone description and rank +2. TB adds the milestone to the selected task + Use case ends. *Extensions* -[none] -* 2a. The list is empty. +* 1a. Student enters an invalid index when list of tasks added is empty +** 1a1. TB outputs error message for invalid index + Use case ends. -* 3a. The given index is invalid. +* 1b. Student enters index, milestone description and rank without all the required prefixes +** 1b1. TB outputs error message for invalid format + -[none] -** 3a1. AddressBook shows an error message. +Use case ends. + +* 1c. Student enters duplicate milestone description +** 1c1. TB outputs error message for duplicate milestone description + -Use case resumes at step 2. +Use case ends. -_{More to be added}_ +* 1d. Student enters duplicate rank +** 1d1. TB outputs error message for duplicate rank ++ +Use case ends. +// end::usecase_milestone[] [appendix] +//tag::nonFunctionalReq[] == Non Functional Requirements +Here are some conditions that are not explicitly stated in the features that Task Book provides, +but are crucial features that allow users to operate the system functionally. . Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to respond within 2 seconds. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -_{More to be added}_ +. Will be offered free for students. +. Not built to contain sensitive information due to lack of password protection. +. Tasks dated as far as 10 years ago may be difficult to retrieve, unless data is backed up in the cloud storage. +//end::nonFunctionalReq[] [appendix] == Glossary @@ -897,23 +985,6 @@ Windows, Linux, Unix, OS-X [[private-contact-detail]] Private contact detail:: A contact detail that is not meant to be shared with others -[appendix] -== Product Survey - -*Product Name* - -Author: ... - -Pros: - -* ... -* ... - -Cons: - -* ... -* ... - [appendix] == Instructions for Manual Testing @@ -938,24 +1009,127 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ -=== Deleting a person - -. Deleting a person while all persons are listed - -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +// tag::selectDeadlineTest[] +=== Selecting a date as a deadline +. Select a deadline + +.. Test case: `select 1` + + Expected: No deadline selected. Message of invalid command format error is shown. +.. Test case: `select dd/1` + + Expected: No deadline selected. Message of invalid command format error is shown. +.. Prerequisite: Select command has not been called before. + + Test case: `select 1/1` + + Expected: Deadline of 1/1/2018 is selected. Message of select success is shown. +.. Prerequisite: Select command has not been called before. + + Test case: `select dd/1 mm/1` + + Expected: Deadline of 1/1/2018 is selected. Message of select success is shown. +.. Prerequisite: Latest select command selected a deadline with 2020 as year (e.g.1/1/2020) + + Test case: 'select 1/1` + + Expected: Deadline of 1/1/2020 is selected. Message of select success is shown. +.. Prerequisite: Latest select command selected a deadline with 2020 as year (e.g.1/1/2020) + + Test case: 'select dd/1 mm/1` + + Expected: Deadline of 1/1/2020 is selected. Message of select success is shown. +.. Test case: `select 1/1/2018` + + Expected: Deadline of 1/1/2018 is selected. Message of select success is shown. +.. Test case: `select dd/1 mm/1 yyyy/2018` + + Expected: Deadline of 1/1/2018 is selected. Message of select success is shown. +.. Test case: `select 29/2/2018` + + Expected: No deadline selected. Message of invalid date error is shown. +.. Test case: `select 1/13/2018` + + Expected: No deadline selected. Message of invalid date error is shown. +.. Test case: `select 1/1/201` + + Expected: No deadline selected. Message of invalid date error is shown. +.. Test case: `select a29/2/2018` + + Expected: No deadline selected. Message of deadline contains illegal characters error is shown. +.. Test case: `select 01/01/02018` + + Expected: Deadline of 1/1/2018 is selected. Message of select success is shown. + +. Updated filteredTaskList shown after selecting deadline + +.. Prerequisites: Multiple tasks in the list with different deadlines. At least one task has the deadline of 1/1/2018. +.. Test case: `select 1/1/2018` + + Expected: Deadline of 1/1/2018 is selected. Filtered task list only shows tasks with deadline of 1/1/2018. +.. Test case: `select 29/2/2018` + + Expected: No deadline selected. Filtered task list will not be updated and will show what is was showing previously. +// end::selectDeadlineTest[] + +=== Deleting a task + +. Deleting a task while all tasks are listed + +.. Prerequisites: List all tasks using the `list` command. Multiple tasks in the list. .. Test case: `delete 1` + Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No task is deleted. Error details shown in the status message. Status bar remains the same. .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. _{ more test cases ... }_ +// tag::editTaskTest[] +=== Editing a task + +. Editing a task while all tasks are listed + +.. Prerequisites: List all tasks using `list` command. Only 3 tasks are in the list. +.. Test case: `edit` + + Expected: Task is not edited. Message of invalid command format is shown. +.. Test case: `edit 1` + + Expected: Task is not edited. Message of invalid command format is shown. +.. Test case: `edit t/Do CS2113 tutorial` + + Expected: Task is not edited. Message of invalid command format is shown. +.. Test case: `edit i/` + + Expected: Task is not edited. Message of empty index is shown. +.. Test case: `edit i/1 t/` + + Expected: Task is not edited. Message of empty title is shown. +.. Test case: `edit i/1 d/` + + Expected: Task is not edited. Message of empty description is shown. +.. Test case: `edit i/1 c/` + + Expected: Task is not edited. Message of empty module code is shown. +.. Test case: `edit i/1 p/` + + Expected: Task is not edited. Message of empty priority level is shown. +.. Test case: `edit i/1 h/` + + Expected: Task is not edited. Message of empty hours is shown. +.. Test case: `edit i/0 t/Do CS2113 tutorial` + + Expected: Task is not edited. Message of invalid index (index must be a non-zero unsigned integer) is shown. +.. Test case: `edit i/4 t/Do CS2113 tutorial` + + Expected: Task is not edited. Message of invalid task displayed index (the task index provided is invalid) is shown. +.. Prerequisites: Tasks in list do not have the exact same fields. + + Test case: `edit i/1 t/Do CS2113 tutorial` + + Expected: Task at index 1 is edited and the original title is replaced by "Do CS2113 tutorial". Message of edit success is shown. +.. Prerequisites: Task at i/1 is exactly the same another existing task, existingTask, except for the title. The title of existingTask is "Do CS2113 tutorial". + + Test case: `edit i/1 t/Do CS2113 tutorial` + + Expected: Task is not edited. Message of duplicate task error is shown. +.. Prerequisites: Task at i/1 has a title of "Do CS2113 tutorial". + + Test case: `edit i/1 t/Do CS2113 tutorial` + + Expected: Task is not edited. Message of task not edited error is shown. +// end::editTaskTest[] + +//tag::appendix[] +=== Adding a task + +. Select a particular date + +.. Select the date using the `select` command, or the date-picker in the UI. +.. Test case: `select 11/11/2018` + + Expected: Tasks with the similar deadline are listed. Timestamp in the status bar is updated. +. Add a task with title, description, priority and expected number of hours entered. +.. Test case: `add t/Do project portfolio d/convert to pdf format p/high h/3 c/CS2101` + + Expected: A task will be added to the list, together with the other tasks with the same deadline. +.. Other incorrect add commands: `add` or `add t/ d/ p/ h/` with compulsory fields not entered +or `add t/Do coding d/very fun p/midhigh h/1` with invalid priority level. + + === Saving data . Dealing with missing/corrupted data files -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +.. If the file is corrupted due to illegal values in the data +* Go to `data/taskbook.xml` and delete the file -_{ more test cases ... }_ +.. If the file is missing: +* The filename may be incorrect, i.e. not `taskbook.xml`, or +* `taskbook.xml` may not be in the `/data` folder + +//end::appendix[] diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..bdd2de3c7fbf 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += Task Book - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,18 +12,18 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2113-AY1819S1-W13-3/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +Since: `Aug 2018` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +Task Book is a desktop task manager application that is designed for students to manage their daily tasks and ultimately, lead a more productive life. More importantly, Task Book is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Task Book can get your management of tasks done faster than traditional paper notebooks or a mobile application. Interested? Jump to the <> to get started. Enjoy! == Quick Start . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. +. Download the latest `taskbook.jar` link:{repoURL}/releases[here]. . Copy the file to the folder you want to use as the home folder for your Address Book. . Double-click the file to start the app. The GUI should appear in a few seconds. + @@ -33,9 +33,7 @@ image::Ui.png[width="790"] e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* *`list`* : lists all tasks for that day * *`exit`* : exits the app . Refer to <> for details of each command. @@ -46,113 +44,412 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add t/TASK`, `TASK` is a parameter which can be used as `add t/Do homework`. +//* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. ==== === Viewing help : `help` Format: `help` -=== Adding a person: `add` +// tag::selectDeadline[] +=== Selecting a date: `select` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Chooses a particular date to be set as the deadline for tasks to be added. + +Format: `select dd/DAY mm/MONTH [yyyy/YEAR]` or +`select DAY/MONTH[/YEAR]` + +[NOTE] +==== +A valid year *must* be between 2018 and 9999 (inclusive) +==== + +Examples: + +* `select dd/1 mm/1` +* `select 1/1` +* `select dd/1 mm/1 yyyy/2018` +* `select 1/1/2018` + +|=== +|=== +*A. Using select command* +|=== +|=== + +How it should look like: + +Step 1. Entering `select 1/1/2018` will select a date as the deadline for tasks to be added. Type the command into the command box as shown below. + +.GUI before using the select command to select a date +image::BeforeSelectDeadlineGUI.PNG[width="600"] + +Step 2. If you have chosen a valid date, you should be able to see a success message as shown below, highlighted with a red box. + +.GUI after using the select command to select a date +image::AfterSelectDeadlineGUI.PNG[width="600"] + + You have successfully selected a date. Nice! + +|=== +|=== +*B. Using Date Picker* +|=== +|=== + +How it should look like: + +Step 1. Alternatively, to make things even simpler, you can choose to use the Date Picker as highlighted below. To use the Date Picker, click on the calender icon. + +.Calander icon on Date Picker +image::DatePickerWhereToPress.PNG[width="600"] + +Step 3. After clicking on the icon, you should be able to see a calendar. Use the left and right arrows to navigate to different months and years. + +.Left and right arrow keys in Date Picker +image::DatePickerLeftRightArrows.PNG[width="600"] + +Step 5. Click on the date you want to select. + +.GUI after using Date Picker to select a date +image::DatePickerSelectDate.PNG[width="600"] + +Step 6. A success message will be shown (refer to Figure 2). + + You have successfully selected a date. Good job! + +// end::selectDeadline[] + +// tag::add[] +=== Adding a task: `add` + +Adds a task with its title, description, priority level (low, medium, high) and the number of hours (positive integers only) expected +to complete this task, to the Task Book + + +[NOTE] +==== +Module code is an optional input. + +==== + +[CAUTION] +==== +Tasks with similar deadline, title and module code are considered duplicate. +If a duplicate task is added, Task Book will give an error message: `This task already exists in the task book` + +However, if the original task has no module code, you are allowed to add +a similar task with a new module code. +==== +Format: `add t/TITLE d/DESCRIPTION p/PRIORITY h/HOURS [c/MODULE_CODE]` + +Examples: + +* `add t/Complete 2113 Tutorial d/with code done p/high h/1` +* `add t/Complete 2113 Tutorial d/with code done p/high h/1 c/CS2113` +// end::add[] + +// tag::complete[] +=== Complete a task: `complete` + +Complete a task in the Task Book by providing its index and the actual number of hours taken to complete the task + +Format: `complete i/INDEX h/HOURS_TO_COMPLETE` +[NOTE] +==== +You are not allowed to complete a task in less than 1 hour! +==== [TIP] -A person can have any number of tags (including 0) +If it takes more than 1 day to complete the task, it is recommended to set milestones for it. Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `complete i/1 h/2` +// end::complete[] -=== Listing all persons : `list` +=== Delete a task : `delete` -Shows a list of all persons in the address book. + -Format: `list` +Removes a task from the task book +Format: `delete [INDEX]` -=== Editing a person : `edit` +Examples: + +* `delete 1` +//@@author ChanChunCheong + +// tag::sort_task[] +=== Sort the taskbook: `sort` +|=== +|Need to view the tasks in a specific order you prefer? For example, you would like the tasks to be displayed from the +highest priority, so you can focus your attention on the most importan task at hand? You can sort the tasks based on +their priority! +|=== -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +[NOTE] +==== +* Tasks will be sorted in lexicographical order for `title` and `module` sorting methods. +* Tasks will be sorted from higher to lower priority for `priority` sorting method. +* Tasks will be sorted in ascending order of deadlines for `deadline` sorting method. +* An empty TaskBook will accept the sort command. +* Sort command uses stable sorting method +* Two Tasks with equal values will appear in the same order in sorted output as they appear in the input array to be sorted. +==== -**** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. -**** +Sort the tasks in the task book via `priority`, `deadline`, `module`, or `title` + +Format: `sort s/METHOD` Examples: -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `sort s/deadlines` + +*Before: "sort s/deadlines" is entered* + +image::SortTaskCommand_before.png[width="790"] + +*After: The list of tasks is sorted based on ascending order of the deadline* + +image::SortTaskCommand_after.png[width="790"] +// end::sort_task[] +// tag::defer_deadline[] + +=== Deferring a deadline: `defer` +|=== +|Need a way to quickly extend your deadline by a few days or up to a month? You can easily defer the deadline of your +task and the deadline will be automatically adjusted. +|=== +Defers a deadline for a task + +Format: `defer i/INDEX dd/DAY` -=== Locating persons by name: `find` +[NOTE] +==== +* Deadline of the selected task will be automatically updated by the number of days deferred from the deadline +==== + + + +[WARNING] +==== +* Selected task must exist in the TaskBook +* A task cannot be deferred by the number of days if it will result in two similar tasks with the same deadlines. +==== -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` -**** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` -**** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `defer i/1 dd/04` + +*Before: "defer i/1 dd/1" is entered* + +image::DeferDeadlineCommand_before.png[width="790"] + +*After: deadline for the first task is deferred by 1 day* -=== Deleting a person : `delete` +image::DeferDeadlineCommand_after.png[width="790"] +// end::defer_deadline[] +//@@author -Deletes the specified person from the address book. + -Format: `delete INDEX` +// tag::editTask[] +=== Editing a task: `edit` -**** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* 1, 2, 3, ... -**** +Edits one or more fields in a selected task. + +Format: `edit i/INDEX [t/TITLE] [d/DESCRIPTION] [c/MODULE CODE] [p/PRIORITY] [h/HOURS]` + +[WARNING] +==== +* Index must be present and must be a non-zero positive integer +* Index selected must exist in the Task Display Panel +* At least one optional field to be edited must be provided +==== Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `edit i/1 t/Complete CS2113 tutorial` +* `edit i/1 d/Edit editTask to fit TaskBook h/4` +* `edit i/1 t/Complete CS2271 tutorial d/Edit editTask to fit TaskBook c/CS2113 p/high h/4` + +How it should look like: + +* Entering the `edit i/1 t/Complete CS2113 tutorial` command will edit the title of the first task on the Task Display Panel to 'Complete CS2113 tutorial'. Type the command into the Command Box as shown below. + + As you can see, the current title of the first task (highlighted with a red box) is 'COMPLETE CODE REFACTORING'. + + +.GUI before using edit command to change the title +image::EditCommandBefore.PNG[width="600"] + +* After entering a valid command, you should see that the title of the first task has been edited to 'Complete CS2113 tutorial' and also a success message as highlighted below. + +.GUI after using edit command to change the title +image::EditCommandAfter.PNG[width="600"] + + You have successfully edited the title of the task. Well done! -=== Selecting a person : `select` +// end::editTask[] + +// tag::add_milestone[] +=== Adding a milestone: `add_milestone` + +|=== +|Have a task that requires a lot of things to be done? Break it up into smaller, more manageable subtasks called milestones! +|=== + +Adds a milestone to an existing task in the task book + +Format: `add_milestone i/INDEX m/MILESTONE DESCRIPTION r/RANK` + + + +[WARNING] +==== +* Milestone description cannot be longer than 40 characters! + +* Index must be a non-zero, positive integer not greater than the number of tasks currently available +in the Task Book! +==== -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` -**** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* `1, 2, 3, ...` -**** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `add_milestone i/1 m/Q1 - 3 r/1` +* `add_milestone i/1 m/Q4 - 6 r/2` +* `add_milestone i/1 m/Q7 & 8 r/3` +* `add_milestone i/1 m/Diagrams r/4` +* `add_milestone i/1 m/References r/5` + +*Before* + +image::AddMilestoneCommand_before.png[width="790"] + +*After* + +image::AddMilestoneCommand_after.png[width="790"] + +[NOTE] +==== +Rank is the level of importance assigned to that particular milestone by the user. Milestones are automatically sorted by rank with the most important one at the top (*Rank 1*). +==== + +// end::add_milestone[] + +//@@author ChanChunChoeng + +// tag::add_tag[] +=== Adding a tag: `add_tag` +|=== +|Want a way to better mark your tasks, or to quickly tell what a task is about? Adding tags to your tasks will make it + easier for you to identify the tasks! +|=== +Add a tag to a task + +Format: `add_tag i/INDEX t/TAG` + +[NOTE] +==== +* Tag names are all in lower case. +* Duplicate tags are ignored and the tag list for the selected task will remain unchanged +==== + + + +[WARNING] +==== +* Tag names has to be alphanumeric +* Selected task must exist in the TaskBook +==== + + + +Examples: + +* `add_tag i/1 t/homework` + +*Before: "add_tag i/1 t/homework" is entered* + +image::AddTagCommand_before.png[width="790"] + +*After: The tag "homework" is added to the first task* + +image::AddTagCommand_after.png[width="790"] + +// end::add_tag[] + +// tag::remove_tag[] +=== Removing a tag: `remove_tag` +|=== +|Found the tags added previously to your tasks not suitable? You can remove the tags easily and conveniently! +|=== +Removes a tag from a task + +Format: `remove_tag i/INDEX t/TAG` + +[NOTE] +==== +* Selected tag to be removed from the task is case-insensitive. +==== + + + +[WARNING] +==== +* Tag names has to be alphanumeric +* Selected task must exist in the TaskBook +==== + + + +Examples: + +* `remove_tag i/1 t/homework` + +*Before: "remove_tag i/1 t/homework" is entered* + +image::RemoveTagCommand_before.png[width="790"] + +*After: The tag "homework" is removed from the first task* + +image::RemoveTagCommand_after.png[width="790"] +// end::remove_tag[] + +// tag::select_tag[] +=== Selecting a tag: `select_tag` +|=== +|Need to view your tasks with the specific tag? For example, viewing all the tasks which are tagged with "homework"? +You can easily do it via selecting the tag you want! +|=== +Show a list of tasks with the selected tag + +Format: `select_tag t/TAG` + +[NOTE] +==== +* Selected tag is case-insensitive. +==== + +[WARNING] +==== +* Tag names has to be alphanumeric +==== + +Examples: + +* `select_tag t/homework` + +*Before: "select_tag t/homework" is entered* + +image::SelectTagCommand_before.png[width="790"] + +*After: A list of tasks with tag "homework" is shown* + +image::SelectTagCommand_after.png[width="790"] +// end::select_tag[] + +//@@author + +=== Tracking productivity : `track` + +Tracks your productivity for all completed tasks, by returning an average productivity (in percentage). + +Format: `track` + +=== Listing all existing Tasks : `list` + +Lists all the existing tasks in task book. + +Format: `list` === Listing entered commands : `history` @@ -172,7 +469,7 @@ Format: `undo` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the address book's content (`add`, `complete`, `sort`, `add_milestone`, `defer`, `delete`, `select` and `clear`). ==== Examples: @@ -181,7 +478,7 @@ Examples: `list` + `undo` (reverses the `delete 1` command) + -* `select 1` + +* `track` + `list` + `undo` + The `undo` command fails as there are no undoable commands executed previously. @@ -229,32 +526,77 @@ Format: `exit` Address book data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +// tag::calendar_synchronisation[] +=== Real time synchronisation with calendar `[coming in v2.0]` + +TaskBook will be able to synchronise with the calendar in real time so that functionalities +that require real time date tracking can be introduced. +// end::calendar_synchronisation[] + +// tag::reminder_setting[] +=== Setting reminders `[coming in v2.0]` -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +Users will be able to set reminders that can be triggered a few days before the actual deadline +to remind them that a task has to be completed. +// end::reminder_setting[] + +// tag::check_milestone[] +=== Checking off completed milestones `[coming in v2.0]` + +Users will soon be able to strike off (not delete!) their milestones when they have completed them for easier tracking of what they have or have not done! +// end::check_milestone[] + +// tag::lockunlock[] +=== Lock Task Book [coming in v2.0] +Task Book will be password-protected to ward off intruders. + +Format: `lock` + +=== Unlock Task Book [coming in v2.0] +Task Book can be unlocked so that authenticated students can log into the Task Book to access sensitive information. + +Format: `unlock PASSWORD` +// end::lockunlock[] == FAQ *Q*: How do I transfer my data to another Computer? + *A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. -== Command Summary +// tag::FAQ_milestone[] +*Q*: Why do I get this error when I tried to add milestones with a rank of "0" or "-1"? -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` +image::FAQ_invalidRank.png[width="790"] + +*A*: You can only enter *non-zero*, *positive* integers for rank! + +*Q*: Why do I get this error when I tried to add a milestone? + +image::FAQ_invalidIndex.png[width="790"] + +*A*: You can only add milestones to *existing tasks*! In this case, there is only 1 task entered into the Task Book, hence attempting to add a milestone to index 2 of the Task Book will result in an error! +// end::FAQ_milestone[] + +== Command Summary * *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` +* *Select* : `select dd/DAY mm/MONTH yyyy/YEAR` or `select DAY/MONTH/YEAR` + +e.g. `select dd/1 mm/1 yyyy/2018` +* *Add task* : `add c/MODULE_CODE t/ACTION d/DESCRIPTION p/PRIORITY_LEVEL h/HOURS_TO_COMPLETE` + +e.g. `add c/CS2113 t/Complete 2113 Tutorial d/with code done p/high h/2` +* *Complete* : `complete i/INDEX h/HOURS_COMPLETED` + +e.g. `complete i/1 h/2` +* *Delete* : `delete INDEX` +e.g. `delete 1` +* *Sort* : `sort s/METHOD` + +e.g. `sort s/priority` +* *Defer deadline* : `defer i/INDEX dd/DAY mm/MONTH yyyy/YEAR` + +e.g. `defer i/1 dd/01 mm/01 yyyy/2018` +* *Edit* : `edit i/INDEX [t/TITLE] [d/DESCRIPTION] [c/MODULE CODE] [p/PRIORITY] [h/HOURS]` + +e.g. `edit i/1 t/Complete CS2271 tutorial d/Edit editTask to fit TaskBook c/CS2113 p/high h/4` +* *Add milestone* : `add_milestone i/INDEX m/MILESTONE DESCRIPTION r/RANK` + +e.g. `add_milestone i/1 m/Q1 - 3 r/1` +* *Track* : `track` +* *List* : `list` * *History* : `history` * *Undo* : `undo` * *Redo* : `redo` +* *Clear* : `clear` +* *Exit* : `exit` diff --git a/docs/diagrams/DeferDeadlineSequenceDiagram.pptx b/docs/diagrams/DeferDeadlineSequenceDiagram.pptx new file mode 100644 index 000000000000..6b68d52573f2 Binary files /dev/null and b/docs/diagrams/DeferDeadlineSequenceDiagram.pptx differ diff --git a/docs/diagrams/EncryptionLogicComponentSequenceDiagram.pptx b/docs/diagrams/EncryptionLogicComponentSequenceDiagram.pptx new file mode 100644 index 000000000000..c89b4a222b21 Binary files /dev/null and b/docs/diagrams/EncryptionLogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..f3e6a64a3608 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..72ca886a8ae1 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..38b59eba67ff 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..79f6e381212d 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..1f408579331c 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..e908d9855f34 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoActivityDiagram.pptx b/docs/diagrams/UndoRedoActivityDiagram.pptx index 16fec930cf3f..e655d3b9e8d7 100644 Binary files a/docs/diagrams/UndoRedoActivityDiagram.pptx and b/docs/diagrams/UndoRedoActivityDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx index 6fd31b5f3fbd..e65d27f7f141 100644 Binary files a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx and b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx index 1f3261976dce..270e74dd90d6 100644 Binary files a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx index e2907d4a9cae..4734504b436c 100644 Binary files a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx index 4ecc659bd600..bfca4d24abc4 100644 Binary files a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx index 16ebf585ddbd..bc18efd1df62 100644 Binary files a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 5ccc1042caac..c37640439e3e 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx index 98ce067642ff..6372cf77a88a 100644 Binary files a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx and b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx differ diff --git a/docs/images/AddMilestoneCommand_after.png b/docs/images/AddMilestoneCommand_after.png new file mode 100644 index 000000000000..4ff4e0185c11 Binary files /dev/null and b/docs/images/AddMilestoneCommand_after.png differ diff --git a/docs/images/AddMilestoneCommand_before.png b/docs/images/AddMilestoneCommand_before.png new file mode 100644 index 000000000000..ac43cc83b067 Binary files /dev/null and b/docs/images/AddMilestoneCommand_before.png differ diff --git a/docs/images/AddTagCommand_after.png b/docs/images/AddTagCommand_after.png new file mode 100644 index 000000000000..571cd9d5a27d Binary files /dev/null and b/docs/images/AddTagCommand_after.png differ diff --git a/docs/images/AddTagCommand_before.png b/docs/images/AddTagCommand_before.png new file mode 100644 index 000000000000..6432d121a91b Binary files /dev/null and b/docs/images/AddTagCommand_before.png differ diff --git a/docs/images/AfterSelectDeadlineGUI.PNG b/docs/images/AfterSelectDeadlineGUI.PNG new file mode 100644 index 000000000000..df235d16a446 Binary files /dev/null and b/docs/images/AfterSelectDeadlineGUI.PNG differ diff --git a/docs/images/BeforeSelectDeadlineGUI.PNG b/docs/images/BeforeSelectDeadlineGUI.PNG new file mode 100644 index 000000000000..1f0197cd0a05 Binary files /dev/null and b/docs/images/BeforeSelectDeadlineGUI.PNG differ diff --git a/docs/images/ChanChunCheong.png b/docs/images/ChanChunCheong.png new file mode 100644 index 000000000000..6f7b31f019d3 Binary files /dev/null and b/docs/images/ChanChunCheong.png differ diff --git a/docs/images/DG_AddMilestoneCommand.png b/docs/images/DG_AddMilestoneCommand.png new file mode 100644 index 000000000000..a40b9aa408ef Binary files /dev/null and b/docs/images/DG_AddMilestoneCommand.png differ diff --git a/docs/images/DatePickerLeftRightArrows.PNG b/docs/images/DatePickerLeftRightArrows.PNG new file mode 100644 index 000000000000..d2cee1ed774f Binary files /dev/null and b/docs/images/DatePickerLeftRightArrows.PNG differ diff --git a/docs/images/DatePickerSelectDate.PNG b/docs/images/DatePickerSelectDate.PNG new file mode 100644 index 000000000000..9fd6ffa7cddd Binary files /dev/null and b/docs/images/DatePickerSelectDate.PNG differ diff --git a/docs/images/DatePickerWhereToPress.PNG b/docs/images/DatePickerWhereToPress.PNG new file mode 100644 index 000000000000..7d731f0525ae Binary files /dev/null and b/docs/images/DatePickerWhereToPress.PNG differ diff --git a/docs/images/DeferDeadlineCommand_after.png b/docs/images/DeferDeadlineCommand_after.png new file mode 100644 index 000000000000..a1464991e22d Binary files /dev/null and b/docs/images/DeferDeadlineCommand_after.png differ diff --git a/docs/images/DeferDeadlineCommand_before.png b/docs/images/DeferDeadlineCommand_before.png new file mode 100644 index 000000000000..29f843594c8a Binary files /dev/null and b/docs/images/DeferDeadlineCommand_before.png differ diff --git a/docs/images/DeferDeadline_SequenceDiagram.png b/docs/images/DeferDeadline_SequenceDiagram.png new file mode 100644 index 000000000000..1a373b38be86 Binary files /dev/null and b/docs/images/DeferDeadline_SequenceDiagram.png differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png deleted file mode 100644 index 0462b9b7be6e..000000000000 Binary files a/docs/images/DeletePersonSdForLogic.png and /dev/null differ diff --git a/docs/images/DeleteTaskSdForLogic.png b/docs/images/DeleteTaskSdForLogic.png new file mode 100644 index 000000000000..d4b3fadcc0df Binary files /dev/null and b/docs/images/DeleteTaskSdForLogic.png differ diff --git a/docs/images/EditCommandAfter.PNG b/docs/images/EditCommandAfter.PNG new file mode 100644 index 000000000000..7f515a7185cc Binary files /dev/null and b/docs/images/EditCommandAfter.PNG differ diff --git a/docs/images/EditCommandBefore.PNG b/docs/images/EditCommandBefore.PNG new file mode 100644 index 000000000000..e68a5d6658d7 Binary files /dev/null and b/docs/images/EditCommandBefore.PNG differ diff --git a/docs/images/EditTaskSequenceDiagram.PNG b/docs/images/EditTaskSequenceDiagram.PNG new file mode 100644 index 000000000000..8c8f1217798d Binary files /dev/null and b/docs/images/EditTaskSequenceDiagram.PNG differ diff --git a/docs/images/EncryptionLogicSequenceDiagram.png b/docs/images/EncryptionLogicSequenceDiagram.png new file mode 100644 index 000000000000..8be40b0262c1 Binary files /dev/null and b/docs/images/EncryptionLogicSequenceDiagram.png differ diff --git a/docs/images/FAQ_invalidIndex.png b/docs/images/FAQ_invalidIndex.png new file mode 100644 index 000000000000..90ca54226245 Binary files /dev/null and b/docs/images/FAQ_invalidIndex.png differ diff --git a/docs/images/FAQ_invalidRank.png b/docs/images/FAQ_invalidRank.png new file mode 100644 index 000000000000..83c3d2a16b8a Binary files /dev/null and b/docs/images/FAQ_invalidRank.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..8fa92339dccd 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/MainApp_directory.png b/docs/images/MainApp_directory.png new file mode 100644 index 000000000000..5472e1b8868d Binary files /dev/null and b/docs/images/MainApp_directory.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..583fc36e1ddc 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/RemoveTagCommand_after.png b/docs/images/RemoveTagCommand_after.png new file mode 100644 index 000000000000..a6da2b2e0d81 Binary files /dev/null and b/docs/images/RemoveTagCommand_after.png differ diff --git a/docs/images/RemoveTagCommand_before.png b/docs/images/RemoveTagCommand_before.png new file mode 100644 index 000000000000..a7ff302a1575 Binary files /dev/null and b/docs/images/RemoveTagCommand_before.png differ diff --git a/docs/images/RunAllTests.png b/docs/images/RunAllTests.png new file mode 100644 index 000000000000..3e10eb639afa Binary files /dev/null and b/docs/images/RunAllTests.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png deleted file mode 100644 index 1e836f10dcd8..000000000000 Binary files a/docs/images/SDforDeletePerson.png and /dev/null differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png deleted file mode 100644 index ecec0805d32c..000000000000 Binary files a/docs/images/SDforDeletePersonEventHandling.png and /dev/null differ diff --git a/docs/images/SDforDeleteTask.png b/docs/images/SDforDeleteTask.png new file mode 100644 index 000000000000..04ca6caac644 Binary files /dev/null and b/docs/images/SDforDeleteTask.png differ diff --git a/docs/images/SDforDeleteTaskEventHandling.png b/docs/images/SDforDeleteTaskEventHandling.png new file mode 100644 index 000000000000..0855dc96be3d Binary files /dev/null and b/docs/images/SDforDeleteTaskEventHandling.png differ diff --git a/docs/images/SelectDeadline.PNG b/docs/images/SelectDeadline.PNG new file mode 100644 index 000000000000..357b065140c4 Binary files /dev/null and b/docs/images/SelectDeadline.PNG differ diff --git a/docs/images/SelectTagCommand_after.png b/docs/images/SelectTagCommand_after.png new file mode 100644 index 000000000000..af7719f488b8 Binary files /dev/null and b/docs/images/SelectTagCommand_after.png differ diff --git a/docs/images/SelectTagCommand_before.png b/docs/images/SelectTagCommand_before.png new file mode 100644 index 000000000000..8ecf26ea3479 Binary files /dev/null and b/docs/images/SelectTagCommand_before.png differ diff --git a/docs/images/SortTaskCommand_after.png b/docs/images/SortTaskCommand_after.png new file mode 100644 index 000000000000..7f10efd0cf46 Binary files /dev/null and b/docs/images/SortTaskCommand_after.png differ diff --git a/docs/images/SortTaskCommand_before.png b/docs/images/SortTaskCommand_before.png new file mode 100644 index 000000000000..083d2d2b067c Binary files /dev/null and b/docs/images/SortTaskCommand_before.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..4d55016f01a9 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..dfb8aa9ad2cb 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 369469ef176e..dcd431a14623 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png index 55e4138cc64f..bdf1f0a2b955 100644 Binary files a/docs/images/UndoRedoActivityDiagram.png and b/docs/images/UndoRedoActivityDiagram.png differ diff --git a/docs/images/UndoRedoExecuteUndoStateListDiagram.png b/docs/images/UndoRedoExecuteUndoStateListDiagram.png index 29c365d6b4a1..916a095c77d2 100644 Binary files a/docs/images/UndoRedoExecuteUndoStateListDiagram.png and b/docs/images/UndoRedoExecuteUndoStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand1StateListDiagram.png b/docs/images/UndoRedoNewCommand1StateListDiagram.png index 76e661d62027..f57d50a38c94 100644 Binary files a/docs/images/UndoRedoNewCommand1StateListDiagram.png and b/docs/images/UndoRedoNewCommand1StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand2StateListDiagram.png b/docs/images/UndoRedoNewCommand2StateListDiagram.png index adcb9aeadc51..e034f902b3e6 100644 Binary files a/docs/images/UndoRedoNewCommand2StateListDiagram.png and b/docs/images/UndoRedoNewCommand2StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand3StateListDiagram.png b/docs/images/UndoRedoNewCommand3StateListDiagram.png index aac9c5fe05db..268931918715 100644 Binary files a/docs/images/UndoRedoNewCommand3StateListDiagram.png and b/docs/images/UndoRedoNewCommand3StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand4StateListDiagram.png b/docs/images/UndoRedoNewCommand4StateListDiagram.png index 66a0a3b5f323..dcd9b1bf163d 100644 Binary files a/docs/images/UndoRedoNewCommand4StateListDiagram.png and b/docs/images/UndoRedoNewCommand4StateListDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index 5c9d5936f098..7a3cf9f56241 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/UndoRedoStartingStateListDiagram.png b/docs/images/UndoRedoStartingStateListDiagram.png index 002f3e2bbf79..5b8c4f11aa6a 100644 Binary files a/docs/images/UndoRedoStartingStateListDiagram.png and b/docs/images/UndoRedoStartingStateListDiagram.png differ diff --git a/docs/images/behkhasim.png b/docs/images/behkhasim.png new file mode 100644 index 000000000000..0d67d421c299 Binary files /dev/null and b/docs/images/behkhasim.png differ diff --git a/docs/images/chanchuncheong.png b/docs/images/chanchuncheong.png new file mode 100644 index 000000000000..6f7b31f019d3 Binary files /dev/null and b/docs/images/chanchuncheong.png differ diff --git a/docs/images/chelseyong.png b/docs/images/chelseyong.png new file mode 100644 index 000000000000..170f34636932 Binary files /dev/null and b/docs/images/chelseyong.png differ diff --git a/docs/images/emobeany.png b/docs/images/emobeany.png new file mode 100644 index 000000000000..0d67d421c299 Binary files /dev/null and b/docs/images/emobeany.png differ diff --git a/docs/images/jeremyinelysium.png b/docs/images/jeremyinelysium.png new file mode 100644 index 000000000000..214effc1540b Binary files /dev/null and b/docs/images/jeremyinelysium.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/salt_hashing.png b/docs/images/salt_hashing.png new file mode 100644 index 000000000000..14f30e2728a6 Binary files /dev/null and b/docs/images/salt_hashing.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/chanchuncheong.adoc b/docs/team/chanchuncheong.adoc new file mode 100644 index 000000000000..ceed9976ad94 --- /dev/null +++ b/docs/team/chanchuncheong.adoc @@ -0,0 +1,101 @@ += Chan Chun Cheong - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Task Book + +--- + +== Overview + +This portfolio is to document my contributions to the CS2113T Software Engineering project, Task Book, to showcase my +ability to work in a team in terms of writing functionalities, pull requests, adhering to software engineering +principles etc. + +*Task Book* is a desktop task management application that is designed for students. The objective of *Task Book* is to +improve users' productivity through efficient task management This is done by enabling busy students to organise +their daily tasks, keep track, check, set deadlines, and improve their productivity in the long run. *Task Book* is +is optimized for students who prefer Command Line Interface (CLI), and it has a Graphical User Interface implemented +with JavaFX. + +== Summary of contributions + +|=== +|_This section summarises my contributions to the project. These contributions range from simple, minor enhancements to complex, major enhancements.._ +|=== + +* *Major enhancement*: added the ability to *sort tasks* in the task management system. +** What it does: It allows the user to sort the tasks displayed based on the title, description, priority, module codes of the tasks. +** Justification: This feature improves the product significantly because it provides a convenient way for user to organise +the task list based on the order they preferred. It helps to make the task list more reader-friendly for the user. In addition, +it allows user to plan their work schedule based on the priorities, deadlines of the tasks. This helps user to improve his +productivity. +** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth +analysis and consideration of user interaction and needs plays a huge part in the sorting manner the function offer. +For example, user might prefer tasks to be shown from higher to lower priority. However, if the tasks are sort according + to the strings values in the priority level, by lexicographical order, tasks will be shown from lower to higher + priority. Therefore, integer values have to be added to the priority levels. In addition, the implementation too was + challenging as it required changes in multiple components in the application . + + +* *Major enhancement*: added the ability to *defer deadline* of tasks in the task management system. +** What it does: It allows the user to defer the deadline of the tasks easily. +** Justification: This feature improves the product significantly because it helps to provide a better user experience +of the application by enabling user to defer the deadline of their tasks easily by stating the number of days to defer from the deadline, + without having to key in the exact dates. +** Highlights: It required an in-depth understanding of the calendar, knowing which months have 30 days or 31 days, how +is the leap year counted and establishing multiple conditional statements to ensure that the deadline is deferred +correctly by just entering the number of days to defer . User interaction is a key factor in this function. During the +prototype stage, to defer a deadline, it required an exact date from the user. However, through considerations for the +user needs, I decided to simplify the process to just keying in the number of days to defer as the objective of +taskbook is to help user to better organise their tasks easily. + + +* *Minor enhancement*: Implemented an *add tag command* which *tags can be added to each task*. + +* *Minor enhancement*: Implemented a *remove tag command* which *tags can be removed from each task*. + +* *Code contributed*: https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=chan%20chun&sort=displayName&since=2018-09-12&until=2018-11-05&timeframe=day&reverse=false&repoSort=true[Functional/Test code] + +* *Other contributions*: + +** Project management: +** Enhancements to existing features: +*** Modified the GUI for each task to update the deadline of the selected task (Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/35[#35]) +*** Wrote tests for defer deadline feature to ensure adequate code coverage(Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/106[#106], https://github.com/CS2113-AY1819S1-W13-3/main/pull/118[#118]) +*** Wrote tests for sort task feature to ensure adequate code coverage(Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/106[#106], https://github.com/CS2113-AY1819S1-W13-3/main/pull/118[#118]) +*** Wrote tests for add tag, remove tag, select tag feature to ensure adequate code coverage(Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/106[#106,]) + +** Documentation: +*** Did cosmetic tweaks and revised existing contents of the User Guide(Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/118[#118]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2113-AY1819S1-W13-3/main/pull/18[#18] +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2113-AY1819S1-W12-3/main/issues/154[1], https://github.com/CS2113-AY1819S1-W12-3/main/issues/145[2]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write clear and concise documentation +targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=defer_deadline] + +include::../UserGuide.adoc[tag=sort_task] + +include::../UserGuide.adoc[tag=select_tag] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project +with the aim of enabling future developers to extend/maintain the application_ +|=== + +include::../DeveloperGuide.adoc[tag=deferDeadline_feature] +include::../DeveloperGuide.adoc[tag=usecase_deferDeadlines] + + + diff --git a/docs/team/chelseyong.adoc b/docs/team/chelseyong.adoc new file mode 100644 index 000000000000..c36e8fea7f83 --- /dev/null +++ b/docs/team/chelseyong.adoc @@ -0,0 +1,91 @@ += Chelsey Ong Hee - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +This portfolio documents my ability to write functional code, +user and developer documentation and respond to pull requests. + +== PROJECT: Task Book + +--- + +== Overview + +`*_Task Book_*` is a desktop to-do list application. It is a GUI app but most of its user interactions happen in the Command Line Interface (CLI). +It is targeted at *helping busy students manage their daily tasks* +by keeping track of their to-do things, approaching deadlines and categorise them in an orderly manner. +Furthermore, `*_Task Book_*` allows you to set milestones for big projects so that you can finish them manageably on time. +If you can type fast, `*_Task Book_*` can manage your tasks faster than traditional paper notebooks or a mobile application. + +== Summary of contributions +Here is an overview of what I have done to support my team in achieving the final goal of our project. + +* *Code contributions*: https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=chelseyong[tracked by Reposense] +* *Main feature implemented*: Allows students *to add tasks into the* `*_Task Book_*` +** What it does: Adds a new task with its title, description, priority level and expected hours of completion +** Justification: This is a core feature that serves the main purpose of `*_Task Book_*` and lays the foundation for other features to be built upon +** Highlights: Written model stubs and applied equivalence partitioning for unit-testing the feature to ensure sufficient coverage for the feature + +* *Other minor enhancements*: + +** Feature that allows students to *complete existing tasks* in the `*_Task Book_*` +*** What it does: Strikes off an incomplete task +*** Justification: Another crucial feature that allows students to focus on incomplete tasks and also track productivity + +** Feature that allows students to *track their productivity* +*** What it does: Calculates the average productivity for all the completed tasks +*** Justification: Helps students be more aware of their productivity rate + +** Modified code to follow software engineering principles such as Open-Closed Principle (OCP) over +https://github.com/CS2113-AY1819S1-W13-3/main/commit/81afea553395e947d162b64f1120c7ec8bb80ca2#diff-4e59bb2089ae847de1ffd4ff31f38cb9R42[here] and https://github.com/CS2113-AY1819S1-W13-3/main/commit/81afea553395e947d162b64f1120c7ec8bb80ca2#diff-bb522e2a9d767e5adac5bf64bb9ed87eR31[there] + +** Beautified the User Interface (UI) to be more user-friendly (Pull request https://github.com/CS2113-AY1819S1-W13-3/main/pull/63[#63]) + +* *Other contributions*: + +** Project management: +*** Set up the organization repository and its issue tracker +*** Closes milestones and tags repository biweekly +*** Managed releases `v1.1` - `v1.4` (4 https://github.com/CS2113-AY1819S1-W13-3/main/releases[releases]) on GitHub +*** Added https://github.com/CS2113-AY1819S1-W13-3/main/issues?utf8=%E2%9C%93&q=is%3Aissue+author%3Achelseyong+[issue labels and assigned milestones] to the team's pull requests +*** Opened issues https://github.com/CS2113-AY1819S1-W13-3/main/issues/7[#7], https://github.com/CS2113-AY1819S1-W13-3/main/issues/8[#8], https://github.com/CS2113-AY1819S1-W13-3/main/issues/22[#22] +to describe user stories of the product as well as to break down user stories into smaller tasks e.g. https://github.com/CS2113-AY1819S1-W13-3/main/issues/39[#39], https://github.com/CS2113-AY1819S1-W13-3/main/issues/40[#40] +** Documentation: +*** Wrote the use cases and user stories for other feature enhancements, as well +*** Drawn architecture and sequence diagrams to further substantiate how the feature is developed +** Community: +*** Reviewed my teammates’ pull requests like https://github.com/CS2113-AY1819S1-W13-3/main/pull/46[#46], https://github.com/CS2113-AY1819S1-W13-3/main/pull/48[#48] and ensure that Travis build is successful before merging the pull requests +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2113-AY1819S1-T09-2/main/issues/94[#94], https://github.com/CS2113-AY1819S1-T09-2/main/issues/96[#96], https://github.com/CS2113-AY1819S1-T09-2/main/issues/98[#98], https://github.com/CS2113-AY1819S1-T09-2/main/issues/102[#102]) + +== Contributions to the User Guide + +|=== +|_The following showcases my ability to write user documentation for add task, complete task, + and locking/unlocking the_ `*_Task Book_*` +|=== + +include::../UserGuide.adoc[tag=add] + +include::../UserGuide.adoc[tag=complete] + +include::../UserGuide.adoc[tag=lockunlock] + +== Contributions to the Developer Guide + +|=== +|_The following showcases my ability to write use cases for add task, complete task, +developer documentation for locking_ `*_Task Book_*`, _and the appendix._ +|=== + +include::../DeveloperGuide.adoc[tag=addTask] + +include::../DeveloperGuide.adoc[tag=completeTask] + +include::../DeveloperGuide.adoc[tag=lockunlock] + +[appendix] +include::../DeveloperGuide.adoc[tag=appendix] + +[appendix] +include::../DeveloperGuide.adoc[tag=nonFunctionalReq] diff --git a/docs/team/emobeany.adoc b/docs/team/emobeany.adoc new file mode 100644 index 000000000000..30242216ffa0 --- /dev/null +++ b/docs/team/emobeany.adoc @@ -0,0 +1,87 @@ += Beh Kha Sim - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: TaskBook + +--- + +== Introduction + +This portfolio documents my role and contributions to the CS2113 Software Engineering project in terms of my ability to write functional code, test code and using various platforms such as github effectively. + +*TASK BOOK* (TB) is a desktop schedule-managing application for busy students who prefer to use a desktop application to manage and better plan their daily tasks to ultimately lead a more productive life. More importantly, TB is optimized for those who prefer to work with a Command Line Interface (CLI) while still including the benefits of a Graphical User Interface (GUI). TB can help you manage your tasks faster than traditional paper notebooks or mobile calendar applications. + +TB is managed by a dedicated team of 4 members and we were tasked to morph an existing application into one that fits our defined users and cater to their needs. We worked hard and came up with the *TaskBook* that we are very proud of. + + +== Summary of contributions +|=== +|_This section summarises my contributions to the project, including major and minor enhancements to features in TB as well as other contributions made._ +|=== + +* *Major enhancement*: + +** Added the ability for users to select a specific date for tasks to be added to. +*** What it does: This feature allows the user to choose a date to be set as the deadline for tasks to be added. It filters out invalid dates such as 29 February on common, non-leap years, so that users will not be able to add tasks to non-existing dates. +*** Justification: Users may have many tasks that they want to be added to the same deadline. This enhancement allows the user to add multiple tasks to the selected deadline without having to repeatedly type in the same date. +*** Highlights: To be implemented properly, this enhancement requires the understanding of how different components work together in TaskBook. This enhancement is further improved with the addition of a date picker in the GUI where users can click on the mini calendar to select date without having to use the CLI. This caters to users who prefer to use the GUI to select a date. + +* *Minor enhancement*: + +** Added the ability for TB to filter tasks added based on their deadlines when users select a date. +//*** What it does: This feature allows TB to only show tasks that have the selected date as their deadline in the task panel of the GUI. +*** Justification: This allows users to view how many tasks with the same deadline have already been added and possibly defer the deadline of some less important tasks. +** Modified the edit command to fit the use of TB where task details can be edited by the user. + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=emobeany[Functional code/Test code]] + +* *Other contributions*: + +** Documentation: + +*** Adjusted existing contents of the User Guide to make the flow smoother: (Pull request https://github.com/CS2113-AY1819S1-W13-3/main/pull/115[#115]) +*** Added in sections that are relevant to my enhancements: (Pull request https://github.com/CS2113-AY1819S1-W13-3/main/pull/117[#117]) + +** Community: + +*** Reported bugs and suggestions for other teams in the class: (Issues https://github.com/CS2113-AY1819S1-T12-3/main/issues/184[#184], https://github.com/CS2113-AY1819S1-T12-3/main/issues/190[#190], https://github.com/CS2113-AY1819S1-T12-3/main/issues/201[#201]) + +== Contributions to the User Guide + +|=== +|_This section highlights the documentation I have contributed to the User Guide. They showcase my ability to write documentation targeting end-users to provide simple and effective instructions to help them when using TB._ +|=== + +include::../UserGuide.adoc[tag=selectDeadline] + +include::../UserGuide.adoc[tag=editTask] + +=== Future releases v2.0 + +include::../UserGuide.adoc[tag=calendar_synchronisation] + +include::../UserGuide.adoc[tag=reminder_setting] + +== Contributions to the Developer Guide + +|=== +|_This section highlights the technical documentation I have contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project as well as provide sufficient details for future developers to maintain TaskBook._ +|=== +//// +include::../DeveloperGuide.adoc[tag=select] +//// +include::../DeveloperGuide.adoc[tag=selectDeadlineImplementation] +//// +include::../DeveloperGuide.adoc[tag=editTask] +//// +include::../DeveloperGuide.adoc[tag=editTaskImplementation] + +//// +=== Manual testing instructions + +include::../DeveloperGuide.adoc[tag=selectDeadlineTest] + +include::../DeveloperGuide.adoc[tag=editTaskTest] +//// diff --git a/docs/team/jeremyinelysium.adoc b/docs/team/jeremyinelysium.adoc new file mode 100644 index 000000000000..f71cbf4d8de7 --- /dev/null +++ b/docs/team/jeremyinelysium.adoc @@ -0,0 +1,77 @@ += Aw Meng Shen - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Task Book + +--- + +== Overview + +*Task Book* is a desktop task manager application that is designed for students. It allows users to keep track of the +progress of their active tasks and manage their workload accordingly. The goal of *Task Book* is to improve the +user’s productivity through efficient task management. *Task Book* is optimized for students who prefer to work with +a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). + +== Summary of contributions + +|=== +|_This section summarises my contributions to the project. These contributions range from simple, minor enhancements +to complex, major enhancements._ +|=== + +* *Major enhancement*: Implemented the option to *add multiple milestones a selected task*. +** What it does: It allows the user to split up a huge task into smaller, manageable subtasks called milestones. +** Justification: This feature enables the user to better manage his time and resources by splitting a task up. It will allow the user to ensure that he/she is on track to complete the task by hitting +each milestone specified by the user. Furthermore, this creates a sense of progress as the user clears each milestone and serves as a form of motivation for the user to not give up until the task is completed. +** Highlights: Implementing this feature and command required changes to the UI of the application. It required in-depth knowledge of JavaFX and the ability to use SceneBuilder to assist in developing the UI. The implementation of the UI was too challenging and an alternative solution was required to ensure the UI remained presentable. + + +* *Minor enhancement*: Implemented a sort function for each task such that the *milestones for each task are +sorted by their ranks*. The list of milestones will be displayed in the order that the user should go about doing, starting with the most important milestone(*Rank 1*) at the top. + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=jeremyinelysium[Functional/Test code]] + +* *Other contributions*: + +** Project management: +** Enhancements to existing features: +*** Modified the GUI for each task to include a list of the milestones for that particular task (Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/54[#54], https://github.com/CS2113-AY1819S1-W13-3/main/pull/97[#97]) +*** Wrote tests for milestone feature to ensure adequate code coverage(Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/100[#100], https://github.com/CS2113-AY1819S1-W13-3/main/pull/102[#102], https://github.com/CS2113-AY1819S1-W13-3/main/pull/108[#108], https://github.com/CS2113-AY1819S1-W13-3/main/pull/109[#109]) +** Documentation: +*** Did cosmetic tweaks and revised existing contents of the User Guide(Pull requests https://github.com/CS2113-AY1819S1-W13-3/main/pull/109[#109]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2113-AY1819S1-W13-3/main/pull/18[#18] +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2113-AY1819S1-F09-2/main/issues/146[1], https://github.com/CS2113-AY1819S1-F09-2/main/issues/151[2], https://github.com/CS2113-AY1819S1-F09-2/main/issues/154[3], https://github.com/CS2113-AY1819S1-F09-2/main/issues/164[4]) + +== Contributions to the User Guide + + +|=== +|_This section highlights the documentation I contributed towards the User Guide with the aim of providing clear and +concise instructions at a level that is easy for the end-user to follow._ +|=== + +include::../UserGuide.adoc[tag=add_milestone] + +include::../UserGuide.adoc[tag=check_milestone] + +=== FAQ + +include::../UserGuide.adoc[tag=FAQ_milestone] + + + +== Contributions to the Developer Guide + +|=== +|_This section highlights the technical documentation I contributed towards the Developer Guide. It provides the +technical depth and details required for future developers of this application to maintain/expand it._ +|=== + +include::../DeveloperGuide.adoc[tag=milestone_feature] + +include::../DeveloperGuide.adoc[tag=usecase_milestone] + + diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..1d5d29bcee31 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -14,6 +14,7 @@ import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.core.Version; +//import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.util.ConfigUtil; @@ -23,15 +24,15 @@ import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyTaskBook; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; +import seedu.address.storage.TaskBookStorage; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlTaskBookStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -40,7 +41,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -62,8 +63,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + TaskBookStorage taskBookStorage = new XmlTaskBookStorage(userPrefs.getAddressBookFilePath()); + storage = new StorageManager(taskBookStorage, userPrefsStorage); initLogging(config); @@ -82,16 +83,16 @@ public void init() throws Exception { * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional addressBookOptional; + ReadOnlyTaskBook initialData; try { - addressBookOptional = storage.readAddressBook(); + addressBookOptional = storage.readTaskBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample TaskBook"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty TaskBook"); initialData = new AddressBook(); } catch (IOException e) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); @@ -179,7 +180,7 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting TaskBook " + MainApp.VERSION); ui.start(primaryStage); } diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/address/commons/core/ComponentManager.java index 05a400773ae8..9ab2cfad4654 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/address/commons/core/ComponentManager.java @@ -25,4 +25,5 @@ public ComponentManager(EventsCenter eventsCenter) { protected void raise(BaseEvent event) { eventsCenter.post(event); } + } diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..400742ace856 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "Task Book"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..6581c60754df 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,13 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_INVALID_MILESTONEDESCRIPTION = + "Milestone description cannot be more than 40 characters! \n%1$s"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_COMPLETED_TASK = "The task has completed already..."; + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_ZERO_HOURS_COMPLETION = "It is impossible to complete it in less than 1 hour ;)"; + public static final String MESSAGE_INVALID_DEADLINE = "The date selected does not exist"; + public static final String MESSAGE_DEADLINE_CONTAINS_ILLEGAL_CHARACTERS = "Input deadline " + + "contains illegal characters"; } diff --git a/src/main/java/seedu/address/commons/events/model/AddMilestoneChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddMilestoneChangedEvent.java new file mode 100644 index 000000000000..45bdec6ec2bb --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/AddMilestoneChangedEvent.java @@ -0,0 +1,20 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyTaskBook; + +//@@author JeremyInElysium +/** Indicates the AddressBook in the model has changed*/ +public class AddMilestoneChangedEvent extends BaseEvent { + + public final ReadOnlyTaskBook data; + + public AddMilestoneChangedEvent(ReadOnlyTaskBook data) { + this.data = data; + } + + @Override + public String toString() { + return "number of tasks " + data.getTaskList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/model/TaskBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/TaskBookChangedEvent.java new file mode 100644 index 000000000000..c3f9900cfbc3 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/TaskBookChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyTaskBook; + +/** Indicates the AddressBook in the model has changed*/ +public class TaskBookChangedEvent extends BaseEvent { + + public final ReadOnlyTaskBook data; + + public TaskBookChangedEvent(ReadOnlyTaskBook data) { + this.data = data; + } + + @Override + public String toString() { + return "number of tasks " + data.getTaskList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java index c5c8b9ce90ed..9c0f0c9430da 100644 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java @@ -1,7 +1,7 @@ package seedu.address.commons.events.ui; import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * Represents a selection change in the Person List Panel @@ -9,9 +9,9 @@ public class PersonPanelSelectionChangedEvent extends BaseEvent { - private final Person newSelection; + private final Task newSelection; - public PersonPanelSelectionChangedEvent(Person newSelection) { + public PersonPanelSelectionChangedEvent(Task newSelection) { this.newSelection = newSelection; } @@ -20,7 +20,7 @@ public String toString() { return getClass().getSimpleName(); } - public Person getNewSelection() { + public Task getNewSelection() { return newSelection; } } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..7eed1b949e85 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * API of the Logic component @@ -20,7 +20,7 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredTaskList(); /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..5faa727e5a81 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -5,13 +5,32 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AddMilestoneCommand; +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.CompleteTaskCommand; +import seedu.address.logic.commands.DeferDeadlineCommand; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveTagCommand; +import seedu.address.logic.commands.SelectDeadlineCommand; +import seedu.address.logic.commands.SelectTagCommand; +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.logic.commands.TrackProductivityCommand; +import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.TaskBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * The main LogicManager of the app. @@ -21,28 +40,48 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; - + private final TaskBookParser taskBookParser; + //@@author chelseyong public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + // need to add the commands into the list commands in TaskBookParser + taskBookParser = new TaskBookParser(new AddTaskCommand(), + new ClearCommand(), + new CompleteTaskCommand(), + new DeferDeadlineCommand(), + new DeleteCommand(), + new ListCommand(), + new TrackProductivityCommand(), + new SelectDeadlineCommand(), + new SelectTagCommand(), + new SortTaskCommand(), + new HelpCommand(), + new ExitCommand(), + new HistoryCommand(), + new UndoCommand(), + new RedoCommand(), + new AddMilestoneCommand(), + new AddTagCommand(), + new RemoveTagCommand(), + new EditTaskCommand() + ); } @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); + Command command = taskBookParser.parseCommand(commandText); return command.execute(model, history); } finally { history.add(commandText); } } - + //@@author @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index d88e831ff1ce..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddMilestoneCommand.java b/src/main/java/seedu/address/logic/commands/AddMilestoneCommand.java new file mode 100644 index 000000000000..a64d1e372d87 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddMilestoneCommand.java @@ -0,0 +1,104 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MILESTONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RANK; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddMilestoneCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Milestone; +import seedu.address.model.task.Task; + +//@@author JeremyInElysium +/** + * Adds a milestone to a task in the taskbook + */ +public class AddMilestoneCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "add_milestone"; + public static final String MESSAGE_SUCCESS = "New milestone added: %1$s"; + public static final String MESSAGE_TASK_NOT_FOUND = "This task does not exist in the task book"; + public static final String MESSAGE_DUPLICATE_RANK = "Duplicate rank entered."; + public static final String MESSAGE_DUPLICATE_MILESTONEDESCRIPTION = "Duplicate milestone entered."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds milestone(s) to selected task. " + + "Parameters: " + + PREFIX_INDEX + "INDEX " + + PREFIX_MILESTONE + "MILESTONE DESCRIPTION " + + PREFIX_RANK + "RANK \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INDEX + "1 " + + PREFIX_MILESTONE + "Complete Q1 - 3 " + + PREFIX_RANK + "1"; + + private final Index index; + private final Milestone toAdd; + + /** + * Creates a AddMilestoneCommand to serve the purpose of the LogicManager + */ + public AddMilestoneCommand() { + index = null; + toAdd = null; + } + + /** + * Creates a AddMilestoneCommand to add the specified {@code Milestone} + */ + public AddMilestoneCommand(Index index, Milestone milestone) { + requireNonNull(index); + requireNonNull(milestone); + this.index = index; + toAdd = milestone; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_TASK_NOT_FOUND); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + + for (Milestone temp: taskToEdit.getMilestoneList()) { + if (temp.getRank().equals(toAdd.getRank())) { + throw new CommandException(MESSAGE_DUPLICATE_RANK); + } + if (temp.getMilestoneDescription().equals(toAdd.getMilestoneDescription())) { + throw new CommandException(MESSAGE_DUPLICATE_MILESTONEDESCRIPTION); + } + } + + Task editedTask = taskToEdit.addMilestone(toAdd); + model.updateTask(taskToEdit, editedTask); + //model.addMilestone(toAdd); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddMilestoneCommand // instanceof handles nulls + && toAdd.equals(((AddMilestoneCommand) other).toAdd)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new AddMilestoneCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java new file mode 100644 index 000000000000..599b63cfa21a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java @@ -0,0 +1,87 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.List; + +import seedu.address.commons.core.index.Index; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddTagCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; + +//@@author ChanChunCheong +/** + * Adds a tag to a task in the taskbook + */ +public class AddTagCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "add_tag"; + public static final String MESSAGE_SUCCESS = "New tag added to task [%1$s]: %2$s"; + public static final String MESSAGE_TASK_NOT_FOUND = "This task does not exist in the task book"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds tag(s) to selected task. " + + "Parameters: " + + PREFIX_INDEX + "INDEX " + + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INDEX + "1 " + + PREFIX_TAG + "Sport"; + + private final Index index; + private final Tag tag; + + /** + * Creates a AddTagCommand to serve the purpose of the LogicManager + */ + public AddTagCommand() { + index = null; + tag = null; + } + + /** + * Creates a AddTagCommand to add the specified {@code Tag} + */ + public AddTagCommand(Index index, Tag tag) { + requireNonNull(index); + requireNonNull(tag); + this.index = index; + this.tag = tag; + } + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_TASK_NOT_FOUND); + } + + Task taskToAdd = lastShownList.get(index.getZeroBased()); + model.addTag(taskToAdd, tag); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, taskToAdd.getTitle(), tag.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTagCommand // instanceof handles nulls + && index.equals(((AddTagCommand) other).index) + && tag.equals(((AddTagCommand) other).tag)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new AddTagCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTaskCommand.java b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java new file mode 100644 index 000000000000..ab80e27f3dda --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTaskCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_ZERO_HOURS_COMPLETION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOURS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddTaskCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +// @@author chelseyong +/** + * Adds a task to the task book + */ +public class AddTaskCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "add"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to the task book. " + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_DESCRIPTION + "DESCRIPTION " + + PREFIX_MODULE_CODE + "MODULE CODE " + + PREFIX_PRIORITY + "PRIORITY " + + PREFIX_HOURS + "HOURS \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Complete code refactoring " + + PREFIX_DESCRIPTION + "refer to notes " + + PREFIX_MODULE_CODE + "CS2113 " + + PREFIX_PRIORITY + "high " + + PREFIX_HOURS + "2"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task book"; + private final Task toAdd; + public AddTaskCommand() { + toAdd = null; + } + /** + * Creates an AddCommand to add the specified {@code Task} + */ + public AddTaskCommand(Task task) { + requireNonNull(task); + toAdd = task; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + toAdd.setDeadline(model.getDeadline()); + if (model.hasTask(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } else if (toAdd.getExpectedNumOfHours() == 0) { + throw new CommandException(MESSAGE_ZERO_HOURS_COMPLETION); + } + + toAdd.setDeadline(model.getDeadline()); + model.addTask(toAdd); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTaskCommand // instanceof handles nulls + && toAdd.equals(((AddTaskCommand) other).toAdd)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new AddTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1f85bcfe85a8..bb66da745d8a 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -3,23 +3,34 @@ import static java.util.Objects.requireNonNull; import seedu.address.logic.CommandHistory; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.AddressBook; import seedu.address.model.Model; /** * Clears the address book. */ -public class ClearCommand extends Command { +public class ClearCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "Task book has been cleared!"; @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); model.resetData(new AddressBook()); - model.commitAddressBook(); + model.commitTaskBook(); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public Command parse(String arguments) throws ParseException { + return new ClearCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 34e99d786ec6..e94e0be5d89a 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -18,5 +18,4 @@ public abstract class Command { * @throws CommandException If an error occurs during command execution. */ public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; - } diff --git a/src/main/java/seedu/address/logic/commands/CommandParser.java b/src/main/java/seedu/address/logic/commands/CommandParser.java new file mode 100644 index 000000000000..44271f4a4daf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CommandParser.java @@ -0,0 +1,14 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author chelseyong +/** + * CommandParser is able to + * pass in arguments + */ +public interface CommandParser { + public Command parse(String arguments) throws ParseException; + + public String getCommandWord(); +} diff --git a/src/main/java/seedu/address/logic/commands/CompleteTaskCommand.java b/src/main/java/seedu/address/logic/commands/CompleteTaskCommand.java new file mode 100644 index 000000000000..d2d6e915b880 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CompleteTaskCommand.java @@ -0,0 +1,85 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOURS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.CompleteTaskCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +//@@author chelseyong +/** + * Completes a task in the Task Book + */ +public class CompleteTaskCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "complete"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Completes the task identified by the index number used in the displayed task list," + + " under a certain number of hours\n" + + "Parameters: " + PREFIX_INDEX + " INDEX(must be a positive integer) " + + PREFIX_HOURS + "HOURS\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_HOURS + "2"; + + public static final String MESSAGE_SUCCESS = "Task completed: %1$s"; + + private final Index targetIndex; + private final int completedNumOfHours; + public CompleteTaskCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + targetIndex = null; + completedNumOfHours = 0; + } + /** + * Creates an CompleteTaskCommand to add the specified {@code Task} + */ + public CompleteTaskCommand(Index targetIndex, int completedNumOfHours) { + this.targetIndex = targetIndex; + this.completedNumOfHours = completedNumOfHours; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } else if (completedNumOfHours == 0) { + throw new CommandException(Messages.MESSAGE_ZERO_HOURS_COMPLETION); + } + Task taskToComplete = lastShownList.get(targetIndex.getZeroBased()); + if (taskToComplete.isCompleted()) { + throw new CommandException(Messages.MESSAGE_COMPLETED_TASK); + } + model.completeTask(taskToComplete, completedNumOfHours); + model.commitTaskBook(); + Task completedTask = lastShownList.get(targetIndex.getZeroBased()); + return new CommandResult(String.format(MESSAGE_SUCCESS, completedTask)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CompleteTaskCommand // instanceof handles nulls + && targetIndex.equals(((CompleteTaskCommand) other).targetIndex)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new CompleteTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeferDeadlineCommand.java b/src/main/java/seedu/address/logic/commands/DeferDeadlineCommand.java new file mode 100644 index 000000000000..8bb9011bdc8c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeferDeadlineCommand.java @@ -0,0 +1,104 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.DeferDeadlineCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +//@@author ChanChunCheong +/** + * Defer deadline of a specific task in the taskbook. + */ + +public class DeferDeadlineCommand extends Command implements CommandParser { + + public static final String COMMAND_WORD = "defer"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Defers the deadline of the selected task in the taskbook. \n" + + "Parameters: " + + PREFIX_INDEX + "INDEX (must be a positive integer) " + + PREFIX_DAY + "DAY \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INDEX + "1 " + + PREFIX_DAY + "04 "; + + public static final String MESSAGE_NONEXISTENT_TASK = "This task does not exist in the task book"; + public static final String MESSAGE_DUPLICATE_TASK = "Deadline for task can't be deferred as " + + "a same task already exists in the Task book"; + public static final String MESSAGE_SUCCESS = "Date deferred for task: %1$s"; + public static final String MESSAGE_INVALID_DEFERRED_DEADLINE = "The deferred deadline is not valid"; + + private final Index taskIndex; + private final int deferredDays; + + public DeferDeadlineCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + taskIndex = null; + deferredDays = 0; + } + + /** + * Creates an DeferDeadlineCommand to add the specified {@code Task & @code Deadline} + */ + public DeferDeadlineCommand(Index taskIndex, int deferredDays) { + requireNonNull(taskIndex); + requireNonNull(deferredDays); + this.taskIndex = taskIndex; + this.deferredDays = deferredDays; + } + + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (taskIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_NONEXISTENT_TASK); + } + + Task taskToDefer = lastShownList.get(taskIndex.getZeroBased()); // get the task from the filteredtasklist; + Task deferredTask = new Task(taskToDefer); + deferredTask = deferredTask.deferred(deferredDays); + String deferredTaskDeadline = deferredTask.getDeadline().toString(); + + if (!(deferredTask.getDeadline().isValidDeadline(deferredTaskDeadline))) { + throw new CommandException(MESSAGE_INVALID_DEFERRED_DEADLINE); + } + if (model.hasTask(deferredTask)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.updateTask(taskToDefer, deferredTask); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, taskToDefer)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeferDeadlineCommand // instanceof handles nulls + && taskIndex.equals(((DeferDeadlineCommand) other).taskIndex) + && deferredDays == (((DeferDeadlineCommand) other).deferredDays)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new DeferDeadlineCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index a20e9d49eac7..b8cec6cdd0d6 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -8,25 +8,33 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.DeleteCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.task.Task; +//@@author chelseyong /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a task identified using it's displayed index from the address book. */ -public class DeleteCommand extends Command { +public class DeleteCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the task identified by the index number used in the displayed task list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s"; private final Index targetIndex; + public DeleteCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + targetIndex = null; + } public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; } @@ -34,16 +42,16 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredTaskList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Task taskToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTask(taskToDelete); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); } @Override @@ -52,4 +60,14 @@ public boolean equals(Object other) { || (other instanceof DeleteCommand // instanceof handles nulls && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check } + + @Override + public Command parse(String arguments) throws ParseException { + return new DeleteCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index dc782d8e230f..000000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,228 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.updatePerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditTaskCommand.java b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java new file mode 100644 index 000000000000..1b604d95ea71 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditTaskCommand.java @@ -0,0 +1,248 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOURS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.EditTaskCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Milestone; +import seedu.address.model.task.ModuleCode; +import seedu.address.model.task.PriorityLevel; +import seedu.address.model.task.Task; + +//@@author emobeany +/** + * Edits the details of an existing task that has been added to task book. + */ +public class EditTaskCommand extends Command implements CommandParser { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the task identified " + + "by the index number used in the displayed task list. " + + "Existing values will be overwritten by user's input values.\n" + + "Parameters: " + PREFIX_INDEX + "INDEX " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_MODULE_CODE + "MODULE CODE] " + + "[" + PREFIX_PRIORITY + "PRIORITY] " + + "[" + PREFIX_HOURS + "HOURS] \n" + + "Example: " + COMMAND_WORD + PREFIX_INDEX + "1" + + PREFIX_DESCRIPTION + "write your own notes" + + PREFIX_PRIORITY + "high"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited Task: %1$s"; + public static final String MESSAGE_NOT_EDITED = "There is nothing to edit. All the fields are unchanged"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the Task book"; + public static final String MESSAGE_NO_FIELD_PROVIDED = "At least one field to edit must be provided."; + + private final Index index; + private final EditTaskDescriptor editTaskDescriptor; + + public EditTaskCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + index = null; + editTaskDescriptor = null; + } + + /** + * @param index of the task in the filtered task list to edit + * @param editTaskDescriptor details to edit the task with + */ + public EditTaskCommand(Index index, EditTaskDescriptor editTaskDescriptor) { + requireNonNull(index); + requireNonNull(editTaskDescriptor); + + this.index = index; + this.editTaskDescriptor = editTaskDescriptor; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Task taskToEdit = lastShownList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor); + + if (taskToEdit.equals(editedTask)) { + throw new CommandException(MESSAGE_NOT_EDITED); + } + + if (model.isTheExactSameTaskAs(editedTask) || (!taskToEdit.isSameTask(editedTask) + && model.hasTask(editedTask))) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.updateTask(taskToEdit, editedTask); + model.updateFilteredTaskList(model.PREDICATE_SHOW_ALL_TASKS); + model.commitTaskBook(); + + return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + } + + /** + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor} + */ + private static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) { + assert taskToEdit != null; + + Deadline deadline = taskToEdit.getDeadline(); + ModuleCode updatedModuleCode = editTaskDescriptor.getModuleCode().orElse(taskToEdit.getModuleCode()); + String updatedTitle = editTaskDescriptor.getTitle().orElse(taskToEdit.getTitle()); + String updatedDescription = editTaskDescriptor.getDescription().orElse(taskToEdit.getDescription()); + PriorityLevel updatedPriority = editTaskDescriptor.getPriorityLevel().orElse(taskToEdit.getPriorityLevel()); + int updatedHours = editTaskDescriptor.getExpectedNumOfHours().orElse(taskToEdit.getExpectedNumOfHours()); + int completedNumOfHours = taskToEdit.getCompletedNumOfHours(); + boolean isCompleted = taskToEdit.isCompleted(); + List milestoneList = taskToEdit.getMilestoneList(); + Set tag = taskToEdit.getTags(); + + return new Task(deadline, updatedModuleCode, updatedTitle, updatedDescription, updatedPriority, updatedHours, + completedNumOfHours, isCompleted, milestoneList, tag); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instance of handles nulls + if (!(other instanceof EditTaskCommand)) { + return false; + } + + // state check + EditTaskCommand edit = (EditTaskCommand) other; + return index.equals(edit.index) + && editTaskDescriptor.equals(edit.editTaskDescriptor); + } + + /** + * Stores details to be edited into existing Task + * Each non-empty field value will replace the existing corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private String title; + private String description; + private ModuleCode moduleCode; + private PriorityLevel priorityLevel; + private Integer expectedNumOfHours; + + public EditTaskDescriptor() {} + + /** + * Copy constructor + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setTitle(toCopy.title); + setDescription(toCopy.description); + setModuleCode(toCopy.moduleCode); + setPriorityLevel(toCopy.priorityLevel); + setExpectedNumOfHours(toCopy.expectedNumOfHours); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(title, description, moduleCode, priorityLevel, expectedNumOfHours); + } + + public Optional getTitle() { + return Optional.ofNullable(title); + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public Optional getModuleCode() { + return Optional.ofNullable(moduleCode); + } + + public Optional getPriorityLevel() { + return Optional.ofNullable(priorityLevel); + } + + public Optional getExpectedNumOfHours() { + return Optional.ofNullable(expectedNumOfHours); + } + + public void setTitle(String title) { + this.title = title; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setModuleCode(ModuleCode moduleCode) { + this.moduleCode = moduleCode; + } + + public void setPriorityLevel(PriorityLevel priorityLevel) { + this.priorityLevel = priorityLevel; + } + + public void setExpectedNumOfHours(Integer expectedNumOfHours) { + this.expectedNumOfHours = expectedNumOfHours; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskDescriptor)) { + return false; + } + + // state check + EditTaskDescriptor edit = (EditTaskDescriptor) other; + + return getTitle().equals(edit.getTitle()) + && getDescription().equals((edit.getDescription())) + && getModuleCode().equals(edit.getModuleCode()) + && getPriorityLevel().equals(edit.getPriorityLevel()) + && getExpectedNumOfHours().equals((edit.getExpectedNumOfHours())); + } + } + + @Override + public Command parse(String arguments) throws ParseException { + return new EditTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index e848fa918964..66e67f383e90 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -3,12 +3,13 @@ import seedu.address.commons.core.EventsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.logic.CommandHistory; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** * Terminates the program. */ -public class ExitCommand extends Command { +public class ExitCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "exit"; @@ -20,4 +21,13 @@ public CommandResult execute(Model model, CommandHistory history) { return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); } + @Override + public Command parse(String arguments) throws ParseException { + return new ExitCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index 66305e95d8f2..0836b1768ee4 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -3,12 +3,13 @@ import seedu.address.commons.core.EventsCenter; import seedu.address.commons.events.ui.ShowHelpRequestEvent; import seedu.address.logic.CommandHistory; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** * Format full help instructions for every command for display. */ -public class HelpCommand extends Command { +public class HelpCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "help"; @@ -22,4 +23,14 @@ public CommandResult execute(Model model, CommandHistory history) { EventsCenter.getInstance().post(new ShowHelpRequestEvent()); return new CommandResult(SHOWING_HELP_MESSAGE); } + + @Override + public Command parse(String arguments) throws ParseException { + return new HelpCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f1541fb57f20..477c4f636573 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -6,12 +6,13 @@ import java.util.List; import seedu.address.logic.CommandHistory; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** * Lists all the commands entered by user from the start of app launch. */ -public class HistoryCommand extends Command { +public class HistoryCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "history"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; @@ -30,4 +31,13 @@ public CommandResult execute(Model model, CommandHistory history) { return new CommandResult(String.format(MESSAGE_SUCCESS, String.join("\n", previousCommands))); } + @Override + public Command parse(String arguments) throws ParseException { + return new HistoryCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..0b158c6b32a8 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,25 +1,36 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; import seedu.address.logic.CommandHistory; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** * Lists all persons in the address book to the user. */ -public class ListCommand extends Command { +public class ListCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all tasks"; @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public Command parse(String arguments) throws ParseException { + return new ListCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..b99d45484dcd 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -1,16 +1,17 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** * Reverts the {@code model}'s address book to its previously undone state. */ -public class RedoCommand extends Command { +public class RedoCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "redo"; public static final String MESSAGE_SUCCESS = "Redo success!"; @@ -20,12 +21,22 @@ public class RedoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canRedoAddressBook()) { + if (!model.canRedoTaskBook()) { throw new CommandException(MESSAGE_FAILURE); } - model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.redoTaskBook(); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public Command parse(String arguments) throws ParseException { + return new RedoCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java b/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java new file mode 100644 index 000000000000..1ba5ebf0ed97 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java @@ -0,0 +1,95 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.RemoveTagCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; + +//@@author ChanChunCheong +/** + * Adds a tag to a task in the taskbook + */ +public class RemoveTagCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "remove_tag"; + public static final String MESSAGE_SUCCESS = "tag removed from task [%1$s]: %2$s"; + //public static final String MESSAGE_SUCCESS_1 = "tag removed from all tasks: %1$s "; + public static final String MESSAGE_TASK_NOT_FOUND = "This task does not exist in the task book"; + public static final String MESSAGE_TAG_NOT_FOUND = "This tag does not exist in the task book"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Remove tag(s) from selected task. " + + "Parameters: " + + PREFIX_INDEX + "INDEX " + + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INDEX + "1 " + + PREFIX_TAG + "Sport"; + + private final Index index; + private final Tag tag; + + /** + * Creates a AddTagCommand to serve the purpose of the LogicManager + */ + public RemoveTagCommand() { + index = null; + tag = null; + } + + /** + * Creates a AddTagCommand to add the specified {@code Tag} + */ + public RemoveTagCommand(Index index, Tag tag) { + requireNonNull(index); + requireNonNull(tag); + this.index = index; + this.tag = tag; + } + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(MESSAGE_TASK_NOT_FOUND); + } + + Task taskToRemove = lastShownList.get(index.getZeroBased()); + Set newTags = new HashSet<>(taskToRemove.getTags()); + if (!(newTags.contains(tag))) { + throw new CommandException(MESSAGE_TAG_NOT_FOUND); + } + + model.removeTag(taskToRemove, tag); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, taskToRemove.getTitle(), tag.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveTagCommand // instanceof handles nulls + && index.equals(((RemoveTagCommand) other).index) + && tag.equals(((RemoveTagCommand) other).tag)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new RemoveTagCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index f5e8c1a8722e..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Selects a person identified using it's displayed index from the address book. - */ -public class SelectCommand extends Command { - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - private final Index targetIndex; - - public SelectCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - List filteredPersonList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && targetIndex.equals(((SelectCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/SelectDeadlineCommand.java b/src/main/java/seedu/address/logic/commands/SelectDeadlineCommand.java new file mode 100644 index 000000000000..284dd0a6c492 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SelectDeadlineCommand.java @@ -0,0 +1,87 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DEADLINE_CONTAINS_ILLEGAL_CHARACTERS; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.SelectDeadlineCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Deadline; + +//@@author emobeany +/** + * Selects a date as a deadline for tasks to be added to + */ +public class SelectDeadlineCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects a date. " + + "Parameters: " + + PREFIX_DAY + "DAY " + + PREFIX_MONTH + "MONTH " + + "[" + PREFIX_YEAR + "YEAR ]" + + " or DAY/MONTH/{YEAR]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_DAY + "01 " + + PREFIX_MONTH + "01 " + + PREFIX_YEAR + "2018 " + + "or 1/1/[2018]"; + + public static final String MESSAGE_SUCCESS = "New date selected: %1$s"; + + private final Deadline toSelect; + + /** + * Creates a SelectDeadline to select the specified {@code Deadline} + */ + public SelectDeadlineCommand (Deadline deadline) { + requireNonNull(deadline); + toSelect = deadline; + } + + public SelectDeadlineCommand() { + toSelect = null; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (toSelect.getYear() == null) { + toSelect.setYear(model.getYear()); + } + if (Deadline.containsIllegalCharacters(toSelect.toString())) { + throw new CommandException(MESSAGE_DEADLINE_CONTAINS_ILLEGAL_CHARACTERS); + } + if (!Deadline.isValidDeadline(toSelect.toString())) { + throw new CommandException(MESSAGE_INVALID_DEADLINE); + } + + model.selectDeadline(toSelect); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toSelect)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectDeadlineCommand // instanceof handles nulls + && toSelect.equals(((SelectDeadlineCommand) other).toSelect)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new SelectDeadlineCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectTagCommand.java b/src/main/java/seedu/address/logic/commands/SelectTagCommand.java new file mode 100644 index 000000000000..b7625aa3ea37 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SelectTagCommand.java @@ -0,0 +1,66 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.SelectTagCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.tag.Tag; + +//@@author ChanChunCheong +/** + * Selects a date as a deadline for tasks to be added to + */ +public class SelectTagCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "select_tag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects a tag. " + + "Parameters: " + + PREFIX_TAG + "TAG \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TAG + "friend "; + + public static final String MESSAGE_SUCCESS = "New tag selected: %1$s"; + + private final Tag selectedTag; + + /** + * Creates a SelectDeadline to select the specified {@code Deadline} + */ + public SelectTagCommand (Tag tag) { + requireNonNull(tag); + selectedTag = tag; + } + + public SelectTagCommand() { + selectedTag = null; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + model.selectTag(selectedTag); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, selectedTag.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectTagCommand // instanceof handles nulls + && selectedTag.equals(((SelectTagCommand) other).selectedTag)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new SelectTagCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortTaskCommand.java b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java new file mode 100644 index 000000000000..e84a34e416eb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.SortTaskCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +//@@author ChanChunCheong +/** + * Sorts the tasks list in the task book based on the method chosen + */ +public class SortTaskCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": sort the tasks in the task book by preferred way. " + + "Parameters: " + + PREFIX_SORT + "METHOD \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_SORT + "modules"; + + public static final String MESSAGE_ARGUMENTS = "method: %1$s"; + public static final String MESSAGE_SUCCESS = "Sort task based on: %1$s"; + private final String method; + + public SortTaskCommand() { + // Null so that it can be initialized in LogicManager + // Check in JUnit test + method = null; + } + + /** + * Creates an DeferDeadlineCommand to add the specified {@code Task & @code Deadline} + */ + public SortTaskCommand(String method) { + requireNonNull(method); + this.method = method.toLowerCase(); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + model.sortTask(method); + model.commitTaskBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, method)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortTaskCommand // instanceof handles nulls + && method.equals(((SortTaskCommand) other).method)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new SortTaskCommandParser().parse(arguments); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/TrackProductivityCommand.java b/src/main/java/seedu/address/logic/commands/TrackProductivityCommand.java new file mode 100644 index 000000000000..bcf5259ea36e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TrackProductivityCommand.java @@ -0,0 +1,66 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; +//@@author chelseyong +/** + * Tracks the productivity of tasks + * for the previous week based on hours + */ +public class TrackProductivityCommand extends Command implements CommandParser { + public static final String COMMAND_WORD = "track"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Tracks your productivity."; + + public static final String MESSAGE_SUCCESS = "Recent productvity: %1$s"; + + public static final String MESSAGE_NO_COMPLETED_TASK = "There are no completed tasks yet. Start working!"; + //private static final Logger logger = LogsCenter.getLogger(TrackProductivityCommand.class); + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + // filter out Completed tasks + model.trackProductivity(); + ObservableList tasks = model.getFilteredTaskList(); + if (tasks.size() == 0) { + throw new CommandException(MESSAGE_NO_COMPLETED_TASK); + } + double productivity = calculateProductivity(tasks); + String result = Integer.toString((int) (productivity * 100)) + " %"; + return new CommandResult(String.format(MESSAGE_SUCCESS, result)); + } + + @Override + public Command parse(String arguments) throws ParseException { + return new TrackProductivityCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + + /** + * Calculates the overall productivity for completed + * @param tasks + * @return the average productivity + */ + public double calculateProductivity(ObservableList tasks) { + double averageProductivity; + double totalProductivity = 0; + for (Task task: tasks) { + double taskProductivity = (double) task.getExpectedNumOfHours() / task.getCompletedNumOfHours(); + totalProductivity += taskProductivity; + } + //if (totalProductivity == 0) + averageProductivity = totalProductivity / tasks.size(); + return averageProductivity; + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..4d9f56d4f861 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -1,16 +1,17 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** * Reverts the {@code model}'s address book to its previous state. */ -public class UndoCommand extends Command { +public class UndoCommand extends Command implements CommandParser { public static final String COMMAND_WORD = "undo"; public static final String MESSAGE_SUCCESS = "Undo success!"; @@ -20,12 +21,22 @@ public class UndoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canUndoAddressBook()) { + if (!model.canUndoTaskBook()) { throw new CommandException(MESSAGE_FAILURE); } - model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.undoTaskBook(); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public Command parse(String arguments) throws ParseException { + return new UndoCommand(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } } diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cde..6071fc473ef6 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands.exceptions; /** - * Represents an error which occurs during execution of a {@link Command}. + * Represents an error which occurs during execution of a Command. */ public class CommandException extends Exception { public CommandException(String message) { diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e83..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddMilestoneCommandParser.java b/src/main/java/seedu/address/logic/parser/AddMilestoneCommandParser.java new file mode 100644 index 000000000000..8f0990ca81e0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddMilestoneCommandParser.java @@ -0,0 +1,60 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MILESTONEDESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MILESTONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RANK; +import static seedu.address.model.task.MilestoneDescription.padMilestoneDescription; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddMilestoneCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.Milestone; +import seedu.address.model.task.MilestoneDescription; +import seedu.address.model.task.Rank; + +//@@author JeremyInElysium +/** + * Parses input arguments and creates a new AddMilestoneCommand object + */ +public class AddMilestoneCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contain empty {@code Optional} values in the given + * {@code ArgumentMultiMap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + @Override + public AddMilestoneCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_INDEX, PREFIX_MILESTONE, PREFIX_RANK); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_MILESTONE, PREFIX_RANK) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddMilestoneCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + String milestoneDescription = ParserUtil.parseMilestoneDescription( + argMultimap.getValue(PREFIX_MILESTONE).get()); + + if (milestoneDescription.length() > 40) { + throw new ParseException(String.format(MESSAGE_INVALID_MILESTONEDESCRIPTION, + AddMilestoneCommand.MESSAGE_USAGE)); + } + + milestoneDescription = padMilestoneDescription(milestoneDescription); + + String rank = ParserUtil.parseRank(argMultimap.getValue(PREFIX_RANK).get()); + + Milestone milestone = new Milestone(new MilestoneDescription(milestoneDescription), new Rank(rank)); + + return new AddMilestoneCommand(index, milestone); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java new file mode 100644 index 000000000000..c6cda5ce7518 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.tag.Tag.MESSAGE_TAG_CONSTRAINTS; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author ChanChunCheong +/** + * Parses input arguments and creates a new AddTagCommand object + */ +public class AddTagCommandParser implements Parser { + public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; + /** + * Parses the given {@code String} of arguments in the context of the AddTagCommand + * and returns an AddTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_INDEX, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTagCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).orElse("")); + String tag = argMultimap.getValue(PREFIX_TAG).orElse(""); + if (!isValidTagName(tag)) { + throw new ParseException(MESSAGE_TAG_CONSTRAINTS); + } + Tag tagName = new Tag(tag.toLowerCase()); + + return new AddTagCommand(index, tagName); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if a given string is a valid tag name. + */ + public static boolean isValidTagName(String test) { + return test.matches(TAG_VALIDATION_REGEX); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java new file mode 100644 index 000000000000..8a3edeca00f0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOURS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.ModuleCode; +import seedu.address.model.task.PriorityLevel; +import seedu.address.model.task.Task; + +//@@author chelseyong +/** + * Parses input arguments and creates a new AddTaskCommand object + */ +public class AddTaskCommandParser implements Parser { + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + protected static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + @Override + public AddTaskCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_MODULE_CODE, PREFIX_TITLE, PREFIX_DESCRIPTION, + PREFIX_PRIORITY, PREFIX_HOURS); + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_DESCRIPTION, + PREFIX_PRIORITY, PREFIX_HOURS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + ModuleCode moduleCode = null; + if (argMultimap.getValue(PREFIX_MODULE_CODE).orElse(null) != null) { + moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE_CODE).get()); + } + String title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()).toLowerCase(); + String description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + PriorityLevel priority = ParserUtil.parsePriorityLevel(argMultimap.getValue(PREFIX_PRIORITY).get()); + int expectedNumOfHours = ParserUtil.parseHours(argMultimap.getValue(PREFIX_HOURS).get()); + Task task = new Task(null, moduleCode, title, description, priority, expectedNumOfHours); + + return new AddTaskCommand(task); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index b7d57f5db86a..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,92 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.HistoryCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case HistoryCommand.COMMAND_WORD: - return new HistoryCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); - - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..74544410753e 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -6,10 +6,19 @@ public class CliSyntax { /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + public static final Prefix PREFIX_TITLE = new Prefix("t/"); + public static final Prefix PREFIX_INDEX = new Prefix("i/"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_PRIORITY = new Prefix("p/"); + public static final Prefix PREFIX_HOURS = new Prefix("h/"); + public static final Prefix PREFIX_MILESTONE = new Prefix("m/"); + public static final Prefix PREFIX_MODULE_CODE = new Prefix("c/"); + public static final Prefix PREFIX_RANK = new Prefix("r/"); + public static final Prefix PREFIX_SORT = new Prefix("s/"); + public static final Prefix PREFIX_DEADLINE = new Prefix("de/"); + public static final Prefix PREFIX_DAY = new Prefix ("dd/"); + public static final Prefix PREFIX_MONTH = new Prefix ("mm/"); + public static final Prefix PREFIX_YEAR = new Prefix ("yyyy/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); } diff --git a/src/main/java/seedu/address/logic/parser/CompleteTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/CompleteTaskCommandParser.java new file mode 100644 index 000000000000..fe61b28c1bd0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CompleteTaskCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOURS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CompleteTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +//@@author chelseyong +/** + * Parses input arguments and creates a new CompleteTaskCommand object + */ +public class CompleteTaskCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the CompleteTaskCommand + * and returns an CompleteTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public CompleteTaskCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(userInput, PREFIX_INDEX, PREFIX_HOURS); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_HOURS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteTaskCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + int expectedNumOfHours = ParserUtil.parseHours(argMultimap.getValue(PREFIX_HOURS).get()); + + return new CompleteTaskCommand(index, expectedNumOfHours); + } +} + + diff --git a/src/main/java/seedu/address/logic/parser/DeferDeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/DeferDeadlineCommandParser.java new file mode 100644 index 000000000000..e28df8979dc7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeferDeadlineCommandParser.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeferDeadlineCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author ChanChunCheong +/** + * Parses input arguments and creates a new DeferDeadlineCommand object + */ +public class DeferDeadlineCommandParser implements Parser { + public static final String MESSAGE_INVALID_DEFERRED_DAYS_EXCEEDED = "Deferred Days need to be positive integer and " + + "less than 32"; + /** + * Parses the given {@code String} of arguments in the context of the {@code DeferDeadlineCommand} + * and returns a {@code DeferDeadlineCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeferDeadlineCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_INDEX, PREFIX_DAY); + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_DAY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeferDeadlineCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).orElse("")); + String day = argMultimap.getValue(PREFIX_DAY).orElse(""); + int deferredDay = ParserUtil.parseDefferedDays(day); + if (deferredDay < 1 || deferredDay > 31) { + throw new ParseException(MESSAGE_INVALID_DEFERRED_DAYS_EXCEEDED); + } + return new DeferDeadlineCommand(index, deferredDay); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea1..000000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java new file mode 100644 index 000000000000..68d22b7c4c50 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditTaskCommandParser.java @@ -0,0 +1,78 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOURS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditTaskCommand; +import seedu.address.logic.commands.EditTaskCommand.EditTaskDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author emobeany +/** + * Parses input arguments and creates new EditTaskCommand object + */ +public class EditTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the EditTaskCommand + * and returns an EditTaskCommand object for execution + * @throws ParseException if the user input does not conform to the expected format + */ + @Override + public EditTaskCommand parse(String userInput) throws ParseException { + requireNonNull(userInput); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(userInput, PREFIX_INDEX, PREFIX_MODULE_CODE, + PREFIX_TITLE, PREFIX_DESCRIPTION, PREFIX_PRIORITY, PREFIX_HOURS); + + Index index; + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE)); + } + + index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + + EditTaskDescriptor editTaskDescriptor = new EditTaskDescriptor(); + + if (argMultimap.getValue(PREFIX_MODULE_CODE).isPresent()) { + editTaskDescriptor.setModuleCode(ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE_CODE) + .get())); + } + if (argMultimap.getValue(PREFIX_TITLE).isPresent()) { + editTaskDescriptor.setTitle(ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get())); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editTaskDescriptor.setDescription(ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION) + .get())); + } + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + editTaskDescriptor.setPriorityLevel(ParserUtil.parsePriorityLevel(argMultimap.getValue(PREFIX_PRIORITY) + .get())); + } + if (argMultimap.getValue(PREFIX_HOURS).isPresent()) { + editTaskDescriptor.setExpectedNumOfHours(ParserUtil.parseHours(argMultimap.getValue(PREFIX_HOURS).get())); + } + if (!editTaskDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditTaskCommand.MESSAGE_NO_FIELD_PROVIDED); + } + + return new EditTaskCommand(index, editTaskDescriptor); + } + + /** + * Returns true if prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index b186a967cb94..000000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns an FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..261f2cdd84f1 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,26 +1,34 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import static seedu.address.commons.core.Messages.MESSAGE_DEADLINE_CONTAINS_ILLEGAL_CHARACTERS; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.util.StringUtil.isNonZeroUnsignedInteger; import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; +import seedu.address.logic.commands.SelectDeadlineCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.ModuleCode; +import seedu.address.model.task.PriorityLevel; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_DEFERRED_DAYS = "Deferred Days is a not-zero unsigned integer."; + public static final String MESSAGE_INVALID_INDEX = "Index must be a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_HOURS = "Hour(s) must be an integer greater than 0!"; + public static final String MESSAGE_INVALID_RANK = "Rank must be a non-zero positive integer."; + public static final String MESSAGE_EMPTY_DESCRIPTION = "Description is empty!"; + public static final String MESSAGE_EMPTY_DEFERRED_DAYS = "Deferred Days is empty!"; + public static final String MESSAGE_EMPTY_TITLE = "Title is empty!"; + public static final String MESSAGE_EMPTY_MODULE_CODE = "Module code is empty!"; + public static final String MESSAGE_EMPTY_HOURS = "Hours is empty!"; + public static final String MESSAGE_EMPTY_MILESTONE = "Milestone description is empty!"; + public static final String MESSAGE_EMPTY_RANK = "Rank is empty!"; + public static final String MESSAGE_EMPTY_INDEX = "Index is empty!"; + public static final String MESSAGE_EMPTY_PRIORITY = "Priority level is empty!"; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -29,96 +37,186 @@ public class ParserUtil { */ public static Index parseIndex(String oneBasedIndex) throws ParseException { String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + if (trimmedIndex.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_INDEX); + } else if (!isNonZeroUnsignedInteger(trimmedIndex)) { throw new ParseException(MESSAGE_INVALID_INDEX); } return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + //@@author ChanChunCheong /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. + * Leading and trailing whitespaces will be trimmed from {@code String hours} + * If DeferredDays is not a positive integer or is too big to be an integer, + * @throws ParseException */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_NAME_CONSTRAINTS); + public static int parseDefferedDays(String deferredDays) throws ParseException { + requireNonNull(deferredDays); + String trimmedIndex = deferredDays.trim(); + if (trimmedIndex.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_DEFERRED_DAYS); + } else if (!isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_DEFERRED_DAYS); } - return new Name(trimmedName); + return Integer.parseInt(deferredDays); } + //@@author + //@@author chelseyong /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. + * Leading and trailing whitespaces will be trimmed from {@code String hours} + * If hours is not an integer or is too big to be an integer, + * @throws ParseException */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_PHONE_CONSTRAINTS); + public static int parseHours(String hours) throws ParseException { + requireNonNull(hours); + String trimmedHours = hours.trim(); + if (trimmedHours.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_HOURS); + } else if (!isNonZeroUnsignedInteger(trimmedHours)) { + throw new ParseException(MESSAGE_INVALID_HOURS); } - return new Phone(trimmedPhone); + return Integer.parseInt(trimmedHours); } /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. + * Leading and trailing whitespaces will be trimmed from {@code String moduleCode} */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + public static ModuleCode parseModuleCode(String moduleCode) throws ParseException { + requireNonNull(moduleCode); + String trimmedModuleCode = moduleCode.trim().toUpperCase(); + if (trimmedModuleCode.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_MODULE_CODE); + } else if (!ModuleCode.isValidModuleCode(trimmedModuleCode)) { + throw new ParseException(ModuleCode.MESSAGE_MODULE_CODE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new ModuleCode(trimmedModuleCode); } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String deadline} into an {@code Deadline}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code deadline} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_EMAIL_CONSTRAINTS); + public static Deadline parseDeadline(String deadline) throws ParseException { + requireNonNull(deadline); + String trimmedDeadline = deadline.trim(); + String[] entries = deadline.split("/"); + + if (entries.length < 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SelectDeadlineCommand.MESSAGE_USAGE)); + } + if (entries.length > 3) { + throw new ParseException(Deadline.MESSAGE_DEADLINE_CONSTRAINTS); + } + // Command Exception is thrown to handle 1/mm/yyyy etc. + for (String s: entries) { + if (!isNonZeroUnsignedInteger(s.trim())) { + throw new ParseException(MESSAGE_DEADLINE_CONTAINS_ILLEGAL_CHARACTERS); + } } - return new Email(trimmedEmail); + return new Deadline(trimmedDeadline); } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String priority} into an {@code Priority}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code priority} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_TAG_CONSTRAINTS); + public static PriorityLevel parsePriorityLevel(String priority) throws ParseException { + requireNonNull(priority); + String trimmedPriority = priority.trim(); + if (trimmedPriority.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_PRIORITY); } - return new Tag(trimmedTag); + if (!PriorityLevel.isValidPriorityLevel(trimmedPriority)) { + throw new ParseException(PriorityLevel.MESSAGE_PRIORITY_CONSTRAINTS); + } + return new PriorityLevel(trimmedPriority); + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String title} + */ + public static String parseTitle(String title) throws ParseException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (trimmedTitle.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_TITLE); + } + return trimmedTitle; } /** - * Parses {@code Collection tags} into a {@code Set}. + * Leading and trailing whitespaces will be trimmed from {@code String description} */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static String parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (trimmedDescription.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_DESCRIPTION); } - return tagSet; + return trimmedDescription; + } + + //@@author emobeany + /** + * Leading and trailing whitespaces will be trimmed from {@code String day} + */ + public static String parseDay(String day) throws ParseException { + requireNonNull(day); + String trimmedDay = day.trim(); + return trimmedDay; } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String month} + */ + public static String parseMonth(String month) throws ParseException { + requireNonNull(month); + String trimmedMonth = month.trim(); + return trimmedMonth; + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String year} + */ + public static String parseYear(String year) throws ParseException { + requireNonNull(year); + String trimmedYear = year.trim(); + return trimmedYear; + } + //@@author JeremyInElysium + /** + * Leading and trailing whitespaces will be trimmed from {@code String milestoneDescription} + */ + public static String parseMilestoneDescription(String milestoneDescription) throws ParseException { + requireNonNull(milestoneDescription); + String trimmedMilestoneDescription = milestoneDescription.trim(); + if (trimmedMilestoneDescription.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_MILESTONE); + } + return trimmedMilestoneDescription; + } + + /** + * Leading and trailing whitespaces will be trimmed from {@code String rank} + */ + public static String parseRank(String rank) throws ParseException { + requireNonNull(rank); + String trimmedRank = rank.trim(); + if (trimmedRank.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_RANK); + } else if (!isNonZeroUnsignedInteger(trimmedRank)) { + throw new ParseException(MESSAGE_INVALID_RANK); + } + + + return trimmedRank; + } + } diff --git a/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java new file mode 100644 index 000000000000..59ad018b1d34 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java @@ -0,0 +1,62 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.tag.Tag.MESSAGE_TAG_CONSTRAINTS; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RemoveTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author ChanChunCheong +/** + * Parses input arguments and creates a new RemoveTagCommand object + */ +public class RemoveTagCommandParser implements Parser { + public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; + /** + * Parses the given {@code String} of arguments in the context of the RemoveTagCommand + * and returns an RemoveTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveTagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_INDEX, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveTagCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).orElse("")); + String tag = argMultimap.getValue(PREFIX_TAG).orElse(""); + if (!isValidTagName(tag)) { + throw new ParseException(MESSAGE_TAG_CONSTRAINTS); + } + Tag tagName = new Tag(tag.toLowerCase()); + + return new RemoveTagCommand(index, tagName); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if a given string is a valid tag name. + */ + public static boolean isValidTagName(String test) { + return test.matches(TAG_VALIDATION_REGEX); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java deleted file mode 100644 index 565b7f04bfe1..000000000000 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new SelectCommand object - */ -public class SelectCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the SelectCommand - * and returns an SelectCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public SelectCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new SelectCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE), pe); - } - } -} diff --git a/src/main/java/seedu/address/logic/parser/SelectDeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectDeadlineCommandParser.java new file mode 100644 index 000000000000..26d07b53c405 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SelectDeadlineCommandParser.java @@ -0,0 +1,79 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_YEAR; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.SelectDeadlineCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.Deadline; + +//@@author emobeany +/** + * Parses input arguments and creates a new SelectDeadlineCommand object + */ +public class SelectDeadlineCommandParser implements Parser { + @Override + public SelectDeadlineCommand parse(String userInput) throws ParseException { + + Deadline deadlineWithoutPrefixes = parseWithoutPrefixes(userInput); + if (deadlineWithoutPrefixes != null) { + return new SelectDeadlineCommand(deadlineWithoutPrefixes); + } + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_DAY, PREFIX_MONTH, PREFIX_YEAR); + // shows correct error message when illegal character is used in dd/mm/yyyy format + if (noPrefixesPresent(argMultimap, PREFIX_DAY, PREFIX_MONTH, PREFIX_YEAR)) { + ParserUtil.parseDeadline(userInput); + } + if (!arePrefixesPresent(argMultimap, PREFIX_DAY, PREFIX_MONTH) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SelectDeadlineCommand.MESSAGE_USAGE)); + } + + String day = ParserUtil.parseDay(argMultimap.getValue(PREFIX_DAY).orElse("")); + String month = ParserUtil.parseMonth(argMultimap.getValue(PREFIX_MONTH).orElse("")); + String year; + if (argMultimap.getValue(PREFIX_YEAR).isPresent()) { + year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR).get()); + } else { + Deadline deadline = new Deadline(day, month); + return new SelectDeadlineCommand(deadline); + } + Deadline deadline = new Deadline(day, month, year); + return new SelectDeadlineCommand(deadline); + } + + /** + * Returns true if all of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + protected static boolean noPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).noneMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + protected static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Alternative parsing method: + * @param userInput without date, month and year prefixes + * @return the parsed Deadline + */ + protected static Deadline parseWithoutPrefixes(String userInput) { + try { + return ParserUtil.parseDeadline(userInput); + } catch (ParseException e) { + return null; + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/SelectTagCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectTagCommandParser.java new file mode 100644 index 000000000000..abd1782f2414 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SelectTagCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.tag.Tag.MESSAGE_TAG_CONSTRAINTS; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.SelectTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author ChanChunCheong +/** + * Parses input arguments and creates a new AddTagCommand object + */ + +public class SelectTagCommandParser implements Parser { + public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; + /** + * Parses the given {@code String} of arguments in the context of the AddTagCommand + * and returns an AddTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SelectTagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TAG); + if (!arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectTagCommand.MESSAGE_USAGE)); + } + + String tag = argMultimap.getValue(PREFIX_TAG).orElse(""); + if (!isValidTagName(tag)) { + throw new ParseException(MESSAGE_TAG_CONSTRAINTS); + } + Tag tagName = new Tag(tag.toLowerCase()); + + + return new SelectTagCommand(tagName); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if a given string is a valid tag name. + */ + public static boolean isValidTagName(String test) { + return test.matches(TAG_VALIDATION_REGEX); + } +} diff --git a/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java new file mode 100644 index 000000000000..626165a973e8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@ChanChunCheong +/** + * Parses input arguments and creates a new SortTaskCommand object + */ +public class SortTaskCommandParser implements Parser { + @Override + public SortTaskCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_SORT); + + if (!arePrefixesPresent(argMultimap, PREFIX_SORT) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortTaskCommand.MESSAGE_USAGE)); + } + String method = argMultimap.getValue(PREFIX_SORT).orElse(""); + + //method entered has to be one of the 4 methods + if (method.equals("module") || method.equals("deadline") || method.equals("priority") + || method.equals("title")) { + return new SortTaskCommand(method); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortTaskCommand.MESSAGE_USAGE)); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/TaskBookParser.java b/src/main/java/seedu/address/logic/parser/TaskBookParser.java new file mode 100644 index 000000000000..a69fa54a5071 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TaskBookParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandParser; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class TaskBookParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + private List commands; + + /** + * Takes in + * @param commandsUsed and keeps them in an + * array {@code commands} (to enforce OCP) + */ + public TaskBookParser(CommandParser... commandsUsed) { + commands = new ArrayList<>(); + for (CommandParser command: commandsUsed) { + commands.add(command); + } + } + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + Command commandToReturn = null; + for (CommandParser command : commands) { + if (command.getCommandWord().equals(commandWord)) { + commandToReturn = command.parse(arguments); + break; + } + } + if (commandToReturn == null) { + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + return commandToReturn; + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 7f85c8b9258b..a080bc002cd0 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -5,16 +5,22 @@ import java.util.List; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.SortTaskList; +import seedu.address.model.task.Task; +import seedu.address.model.task.UniqueTaskList; /** * Wraps all data at the address-book level * Duplicates are not allowed (by .isSamePerson comparison) */ -public class AddressBook implements ReadOnlyAddressBook { +public class AddressBook implements ReadOnlyTaskBook { - private final UniquePersonList persons; + private static final Deadline PLACEHOLDER_DEADLINE = new Deadline("1/1/2018"); + //private static final Logger logger = LogsCenter.getLogger(AddressBook.class); + private final UniqueTaskList tasks; + private Deadline currentDate; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -24,7 +30,7 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + tasks = new UniqueTaskList(); } public AddressBook() {} @@ -32,7 +38,7 @@ public AddressBook() {} /** * Creates an AddressBook using the Persons in the {@code toBeCopied} */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { + public AddressBook(ReadOnlyTaskBook toBeCopied) { this(); resetData(toBeCopied); } @@ -40,81 +46,159 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { //// list overwrite operations /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of the task list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setTasks(List tasks) { + this.tasks.setTasks(tasks); } /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ - public void resetData(ReadOnlyAddressBook newData) { + public void resetData(ReadOnlyTaskBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setTasks(newData.getTaskList()); } - //// person-level operations + //// task-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if there is a task in task book that has exactly the same fields as input task */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); } + //@@author emobeany /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Returns true if a task with the same identity as {@code task} exists in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public boolean isTheExactSameTaskAs(Task task) { + requireNonNull(task); + return tasks.containsExactCopyOf(task); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Adds a task to the address book. + * The task must not already exist in the address book. + */ + public void addTask(Task t) { + tasks.add(t); + } + + //@@author ChanChunCheong + /** + * Adds a tag to a tag in the address book. + * The tag must not already exist in the task. + */ + public void addTag(Task target, Tag tag) { + tasks.addTag(target, tag); + } + + //@@author ChanChunCheong + /** + * Adds a tag to a tag in the address book. + * The tag must not already exist in the task. + */ + public void removeTag(Task target, Tag tag) { + tasks.removeTagFromTask(target, tag); + } + + //@@author emobeany + /** + * Replaces the given task {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The task identity of {@code editedPerson} must not be the same as another existing task in the address book. */ - public void updatePerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public void updateTask(Task target, Task editedTask) { + requireNonNull(editedTask); - persons.setPerson(target, editedPerson); + tasks.setTask(target, editedTask); } + //@@author /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. */ - public void removePerson(Person key) { - persons.remove(key); + public void completeTask(Task key, int hours) { + tasks.complete(key, hours); } - //// util methods + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTask(Task key) { + tasks.remove(key); + } + + //@@author emobeany + /** + * Selects the date for Task Book. + * Update the list. + */ + public void selectDeadline(Deadline deadline) { + currentDate = deadline; + } + + public String getYear() { + if (currentDate == null) { + currentDate = PLACEHOLDER_DEADLINE; + } + return currentDate.getYear(); + } + + public Deadline getDeadline() { + if (currentDate == null) { + currentDate = PLACEHOLDER_DEADLINE; + } + return currentDate; + } + + //@@ ChanChunCheong + /** + * Sorts the Task Book based on the method chosen. + * Update the list. + */ + public void sortTask(String method) { + requireNonNull(method); + SortTaskList sortList = new SortTaskList(); + ObservableList copyList = sortList.sortTask(obtainModifiableObservableList(), method); + UniqueTaskList updateList = new UniqueTaskList(); + updateList.setTasks(copyList); + tasks.setTasks(updateList); + } + + // util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later + return tasks.asUnmodifiableObservableList().size() + " tasks"; } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getTaskList() { + return tasks.asUnmodifiableObservableList(); + } + + public ObservableList obtainModifiableObservableList() { + return tasks.obtainObservableList(); } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && tasks.equals(((AddressBook) other).tasks)); } @Override public int hashCode() { - return persons.hashCode(); + return tasks.hashCode(); } + } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..73926d02c958 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,76 +3,129 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Task; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); + void resetData(ReadOnlyTaskBook newData); /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyTaskBook getAddressBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a task with the same identity as {@code task} exists in the task book. */ - boolean hasPerson(Person person); + boolean hasTask(Task task); + //@@author emobeany /** - * Deletes the given person. - * The person must exist in the address book. + * Returns true if there is a task in task book that has exactly the same fields as input task */ - void deletePerson(Person target); + boolean isTheExactSameTaskAs(Task task); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Deletes the given task. + * The task must exist in the task book. */ - void addPerson(Person person); + void deleteTask(Task target); /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * Completes the given task. + * The task must exist in the task book. */ - void updatePerson(Person target, Person editedPerson); + void completeTask(Task target, int hours); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Adds the given task. + * {@code task} must not already exist in the task book. + */ + void addTask(Task task); + + //@@author ChanChunCheong + /** + * Adds the to the selected task. + * {@code task} must not already exist in the task book. + */ + void addTag(Task task, Tag tag); + + /** + * Removes the tag from the selected task. + * {@code task} must not already exist in the task book. + */ + void removeTag(Task task, Tag tag); + + /** Selects the input tag as Tag.*/ + void selectTag(Tag tag); + //@@author + + //@@author emobeany + /** Selects the input date as deadline.*/ + void selectDeadline(Deadline deadline); + + /** Gets deadline previously selected from the TaskBook.*/ + Deadline getDeadline(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Gets year of previously selected year from the TaskBook + */ + String getYear(); + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the task book. + * The task identity of {@code editedTask} must not be the same as another existing task in the task book. + */ + void updateTask(Task target, Task editedTask); + + //@@author ChanChunCheong + void sortTask(String method); + //@@author + + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredTaskList(); + + /** + * Updates the filter of the filtered task list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredTaskList(Predicate predicate); + + /** + * Returns true if the model has previous task book states to restore. + */ + boolean canUndoTaskBook(); /** - * Returns true if the model has previous address book states to restore. + * Returns true if the model has undone task book states to restore. */ - boolean canUndoAddressBook(); + boolean canRedoTaskBook(); /** - * Returns true if the model has undone address book states to restore. + * Restores the model's task book to its previous state. */ - boolean canRedoAddressBook(); + void undoTaskBook(); /** - * Restores the model's address book to its previous state. + * Restores the model's task book to its previously undone state. */ - void undoAddressBook(); + void redoTaskBook(); /** - * Restores the model's address book to its previously undone state. + * Saves the current task book state for undo/redo. */ - void redoAddressBook(); + void commitTaskBook(); /** - * Saves the current address book state for undo/redo. + * Updates task list to + * contain only completed tasks */ - void commitAddressBook(); + public void trackProductivity(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..9f2a95cb3267 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,122 +11,199 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.commons.events.model.TaskBookChangedEvent; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Task; /** - * Represents the in-memory model of the address book data. + * Represents the in-memory model of the task book data. */ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final VersionedAddressBook versionedAddressBook; - private final FilteredList filteredPersons; + private final VersionedTaskBook versionedTaskBook; + private final FilteredList filteredTasks; + private final Predicate predicateShowCompletedTasks = Task::isCompleted; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given taskBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyTaskBook taskBook, UserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(taskBook, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with task book: " + taskBook + " and user prefs " + userPrefs); - versionedAddressBook = new VersionedAddressBook(addressBook); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + versionedTaskBook = new VersionedTaskBook(taskBook); + filteredTasks = new FilteredList<>(versionedTaskBook.getTaskList()); } public ModelManager() { this(new AddressBook(), new UserPrefs()); } + //initialise the a new task book with new user prefs @Override - public void resetData(ReadOnlyAddressBook newData) { - versionedAddressBook.resetData(newData); - indicateAddressBookChanged(); + public void resetData(ReadOnlyTaskBook newData) { + versionedTaskBook.resetData(newData); + indicateTaskBookChanged(); } @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; + public ReadOnlyTaskBook getAddressBook() { + return versionedTaskBook; } /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); + private void indicateTaskBookChanged() { + raise(new TaskBookChangedEvent(versionedTaskBook)); } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); + public boolean hasTask(Task task) { + requireNonNull(task); + return versionedTaskBook.hasTask(task); } + //@@author emobeany @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - indicateAddressBookChanged(); + public boolean isTheExactSameTaskAs(Task otherTask) { + requireNonNull(otherTask); + return versionedTaskBook.isTheExactSameTaskAs(otherTask); } + //@@author chelseyong @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); + public void deleteTask(Task target) { + versionedTaskBook.removeTask(target); + indicateTaskBookChanged(); } @Override - public void updatePerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void completeTask(Task target, int hours) { + versionedTaskBook.completeTask(target, hours); + indicateTaskBookChanged(); + } + + @Override + public void addTask(Task task) { + versionedTaskBook.addTask(task); + updateFilteredTaskList(predicateShowTasksWithSameDate(task.getDeadline())); + indicateTaskBookChanged(); + } + + //@@author ChanChunCheong + @Override + public void addTag(Task task, Tag tag) { + versionedTaskBook.addTag(task, tag); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + indicateTaskBookChanged(); + } + + @Override + public void removeTag(Task task, Tag tag) { + versionedTaskBook.removeTag(task, tag); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + indicateTaskBookChanged(); + } + + @Override + public void sortTask(String method) { + versionedTaskBook.sortTask(method); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + indicateTaskBookChanged(); + } + + @Override + public void selectTag(Tag tag) { + updateFilteredTaskList(predicateShowTasksWithSameTag(tag)); + indicateTaskBookChanged(); + } + //@@author + + //@@author emobeany + @Override + public void updateTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + versionedTaskBook.updateTask(target, editedTask); + indicateTaskBookChanged(); + } + + @Override + public void selectDeadline(Deadline deadline) { + versionedTaskBook.selectDeadline(deadline); + updateFilteredTaskList(predicateShowTasksWithSameDate(deadline)); + indicateTaskBookChanged(); + } - versionedAddressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); + /**{@code Predicate} that returns true when the date is equal*/ + private Predicate predicateShowTasksWithSameDate(Deadline deadline) { + return task -> task.getDeadline().equals(deadline); } - //=========== Filtered Person List Accessors ============================================================= + //@author ChanChunCheong + /**{@code Predicate} that returns true when the date is equal*/ + private Predicate predicateShowTasksWithSameTag(Tag tag) { + return task -> task.getTags().contains(tag); + } + //@@author + + @Override + public Deadline getDeadline() { + return versionedTaskBook.getDeadline(); + } + + @Override + public String getYear() { + return versionedTaskBook.getYear(); + } + + //@@author + //=========== Filtered Task List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * Returns an unmodifiable view of the list of {@code Task} backed by the internal list of + * {@code versionedTaskBook} */ @Override - public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + public ObservableList getFilteredTaskList() { + return FXCollections.unmodifiableObservableList(filteredTasks); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredTaskList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredTasks.setPredicate(predicate); } //=========== Undo/Redo ================================================================================= @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); + public boolean canUndoTaskBook() { + return versionedTaskBook.canUndo(); } @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); + public boolean canRedoTaskBook() { + return versionedTaskBook.canRedo(); } @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - indicateAddressBookChanged(); + public void undoTaskBook() { + versionedTaskBook.undo(); + indicateTaskBookChanged(); } @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - indicateAddressBookChanged(); + public void redoTaskBook() { + versionedTaskBook.redo(); + indicateTaskBookChanged(); } @Override - public void commitAddressBook() { - versionedAddressBook.commit(); + public void commitTaskBook() { + versionedTaskBook.commit(); } @Override @@ -143,8 +220,15 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); + return versionedTaskBook.equals(other.versionedTaskBook); + } + //@@author chelseyong + /** + * Updates the task to completed tasks only + * So that productivity can be correctly calculated + */ + public void trackProductivity() { + updateFilteredTaskList(predicateShowCompletedTasks); + indicateTaskBookChanged(); } - } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyTaskBook.java similarity index 67% rename from src/main/java/seedu/address/model/ReadOnlyAddressBook.java rename to src/main/java/seedu/address/model/ReadOnlyTaskBook.java index 6ddc2cd9a290..301214b7fd79 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyTaskBook.java @@ -1,17 +1,17 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * Unmodifiable view of an address book */ -public interface ReadOnlyAddressBook { +public interface ReadOnlyTaskBook { /** * Returns an unmodifiable view of the persons list. * This list will not contain any duplicate persons. */ - ObservableList getPersonList(); + ObservableList getTaskList(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 980b2b388852..410671931a6e 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -12,7 +12,7 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path addressBookFilePath = Paths.get("data" , "taskbook.xml"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedTaskBook.java similarity index 67% rename from src/main/java/seedu/address/model/VersionedAddressBook.java rename to src/main/java/seedu/address/model/VersionedTaskBook.java index 227a335045d7..df44cada6359 100644 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ b/src/main/java/seedu/address/model/VersionedTaskBook.java @@ -6,16 +6,16 @@ /** * {@code AddressBook} that keeps track of its own history. */ -public class VersionedAddressBook extends AddressBook { +public class VersionedTaskBook extends AddressBook { - private final List addressBookStateList; + private final List taskBookStateList; private int currentStatePointer; - public VersionedAddressBook(ReadOnlyAddressBook initialState) { + public VersionedTaskBook(ReadOnlyTaskBook initialState) { super(initialState); - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); + taskBookStateList = new ArrayList<>(); + taskBookStateList.add(new AddressBook(initialState)); currentStatePointer = 0; } @@ -25,12 +25,12 @@ public VersionedAddressBook(ReadOnlyAddressBook initialState) { */ public void commit() { removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); + taskBookStateList.add(new AddressBook(this)); currentStatePointer++; } private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); + taskBookStateList.subList(currentStatePointer + 1, taskBookStateList.size()).clear(); } /** @@ -41,7 +41,7 @@ public void undo() { throw new NoUndoableStateException(); } currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); + resetData(taskBookStateList.get(currentStatePointer)); } /** @@ -52,7 +52,7 @@ public void redo() { throw new NoRedoableStateException(); } currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); + resetData(taskBookStateList.get(currentStatePointer)); } /** @@ -66,7 +66,7 @@ public boolean canUndo() { * Returns true if {@code redo()} has address book states to redo. */ public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; + return currentStatePointer < taskBookStateList.size() - 1; } @Override @@ -77,16 +77,16 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { + if (!(other instanceof VersionedTaskBook)) { return false; } - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; + VersionedTaskBook otherVersionedTaskBook = (VersionedTaskBook) other; // state check - return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) - && currentStatePointer == otherVersionedAddressBook.currentStatePointer; + return super.equals(otherVersionedTaskBook) + && taskBookStateList.equals(otherVersionedTaskBook.taskBookStateList) + && currentStatePointer == otherVersionedTaskBook.currentStatePointer; } /** diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a1409233ceb9..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 38a7629e9a2d..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_EMAIL_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String EMAIL_VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_EMAIL_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 9982393dabb5..000000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_NAME_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - - public final String fullName; - - /** - * Constructs a {@code Name}. - * - * @param name A valid name. - */ - public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_NAME_CONSTRAINTS); - fullName = name; - } - - /** - * Returns true if a given string is a valid name. - */ - public static boolean isValidName(String test) { - return test.matches(NAME_VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd51..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index a22e51653835..000000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_PHONE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String PHONE_VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_PHONE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(PHONE_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 5856aa42e6b5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,135 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return FXCollections.unmodifiableObservableList(internalList); - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f594423..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca73..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 8cdff2773ac9..8d71f9310c8d 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -1,3 +1,4 @@ +//@@author package seedu.address.model.tag; import static java.util.Objects.requireNonNull; @@ -7,7 +8,7 @@ * Represents a Tag in the address book. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ -public class Tag { +public class Tag implements Comparable { public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; @@ -25,6 +26,13 @@ public Tag(String tagName) { this.tagName = tagName; } + + + public Tag() { + this.tagName = null; + } + + /** * Returns true if a given string is a valid tag name. */ @@ -32,6 +40,11 @@ public static boolean isValidTagName(String test) { return test.matches(TAG_VALIDATION_REGEX); } + @Override + public int compareTo(Tag other) { + return this.tagName.compareTo(other.tagName); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object @@ -50,5 +63,4 @@ public int hashCode() { public String toString() { return '[' + tagName + ']'; } - } diff --git a/src/main/java/seedu/address/model/task/Deadline.java b/src/main/java/seedu/address/model/task/Deadline.java new file mode 100644 index 000000000000..448906a8d6dd --- /dev/null +++ b/src/main/java/seedu/address/model/task/Deadline.java @@ -0,0 +1,255 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Objects; + +//@@author emobeany + +/** + * Represents a deadline in the task book. + * Guarantees: field values are validated, immutable, details are present and not null. + */ + +public class Deadline { + public static final String MESSAGE_DEADLINE_CONSTRAINTS = + "Deadline can only have dd/mm/yyyy format"; + public static final String MESSAGE_CONTAINS_ILLEGAL_CHARACTERS = + "Deadline can only have dd/mm/yyyy format"; + + private final String day; + private final String month; + private String year; + + public Deadline(String day, String month, String year) { + this.day = day.replaceFirst("^0+(?!$)", ""); + this.month = month.replaceFirst("^0+(?!$)", ""); + this.year = year.replaceFirst("^0+(?!$)", ""); + } + + public Deadline(String day, String month) { + this.day = day.replaceFirst("^0+(?!$)", ""); + this.month = month.replaceFirst("^0+(?!$)", ""); + } + + public Deadline(String deadline) { + requireNonNull(deadline); + checkArgument(isValidFormat(deadline), MESSAGE_DEADLINE_CONSTRAINTS); + String[] entries = deadline.split("/"); + this.day = entries[0].replaceFirst("^0+(?!$)", ""); + this.month = entries[1].replaceFirst("^0+(?!$)", ""); + if (entries.length == 3) { + this.year = entries[2].replaceFirst("^0+(?!$)", ""); + } + } + + public static boolean isValidFormat(String deadline) { + return deadline.split("/").length >= 2; + } + + public String getDay() { + return day; + } + + public String getMonth() { + return month; + } + + public String getYear() { + return year; + } + + //@@author ChanChunCheong + public Date getDate() { + DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + String dateAsString = toString(); + Date date = null; + try { + date = dateFormat.parse(dateAsString); + + } catch (Exception ex) { + System.out.println(ex); + } + return date; + } + //@@author + + public void setYear(String year) { + this.year = year; + } + + /** + * Returns false if any fields contains illegal characters. + */ + public static boolean containsIllegalCharacters(String deadline) { + String[] entries = deadline.split("/"); + + String day = entries[0]; + String month = entries[1]; + String year = entries[2]; + + return (!isNumeric(day) || !isNumeric(month) || !isNumeric(year)); + } + + //@@author emobeany + /** + * Returns false if any fields are not within the limits (not a valid date). + */ + public static boolean isValidDeadline(String deadline) { + String[] entries = deadline.split("/"); + if (entries.length != 3) { + return false; + } + int day = Integer.parseInt(entries[0]); + int month = Integer.parseInt(entries[1]); + int year = Integer.parseInt(entries[2]); + + + if (month < 1 || month > 12) { + return false; + } else if (year < 2018 || year > 9999) { + return false; + } else if (isMonthWith30Days(month)) { + return (day > 0 && day < 31); + } else if (isMonthWith31Days(month)) { + return (day > 0 && day < 32); + } else { + if (isLeapYear(year)) { + return (day > 0 && day < 30); + } else { + return (day > 0 && day < 29); + } + } + } + //@@author + //@@author ChanChunCheong + /** + * Defers the deadline of the task. + */ + public Deadline deferDeadline(int deferredDays) { + int year = Integer.parseInt(getYear()); + int month = Integer.parseInt(getMonth()); + int day = Integer.parseInt(getDay()); + int baseDays; + int numofMonths = 0; + //int numofYears = 0; + int newDay; + int updatedDay; + + if (isMonthWith30Days(month)) { + baseDays = 30; + } else if (isMonthWith31Days(month)) { + baseDays = 31; + } else { + if (isLeapYear(year)) { + baseDays = 29; + } else { + baseDays = 28; + } + } + + newDay = day + deferredDays; + updatedDay = newDay % baseDays; + // if daystoAdd == 0 then day = end of the month. + if (updatedDay == 0) { + day = baseDays; + } else { + day = updatedDay; + } + + //Count the number of months added + while (newDay > baseDays) { + newDay = day - baseDays; + numofMonths++; + } + + if (numofMonths > 0) { + if (month + numofMonths > 12) { + year = year + 1; + } + } + + month = (month + numofMonths) % 12; + if (month == 0) { + //month is December + month = 12; + } + + String deferredDay = Integer.toString(day); + String deferredMonth = Integer.toString(month); + String deferredYear = Integer.toString(year); + Deadline deferredDeadline = new Deadline(deferredDay, deferredMonth, deferredYear); + return deferredDeadline; + } + + /** + * Returns false if any fields are not within the limits (not a valid date). + */ + public static boolean isMonthWith30Days(int month) { + ArrayList monthsWith30Days = new ArrayList<>(Arrays.asList(4, 6, 9, 11)); + return monthsWith30Days.contains(month); + } + + /** + * Returns false if any fields are not within the limits (not a valid date). + */ + public static boolean isMonthWith31Days(int month) { + ArrayList monthsWith31Days = new ArrayList<>(Arrays.asList(1, 3, 5, 7, 8, 10, 12)); + return monthsWith31Days.contains(month); + } + + //@@author emobeany + @Override + public int hashCode() { + // custom fields hashing + return Objects.hash(day, month, year); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDay()) + .append("/") + .append(getMonth()) + .append("/") + .append(getYear()); + return builder.toString(); + } + + /** + * Referenced online: Checking if String is numeric + * @param s + * @return true if String is completely numeric + */ + public static boolean isNumeric(String s) { + //s.matches("[-+]?\\d*\\.?\\d+"); + return s != null && s.matches("-?\\d+(\\.\\d+)?"); + } + + /** + * Referenced online: Checking if year is a leap year + * @param year selected + * @return true if year is a leap year + */ + public static boolean isLeapYear(Integer year) { + return ((year % 400 == 0 || year % 100 != 0) && year % 4 == 0); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } else if (object instanceof Deadline) { + Deadline otherDeadline = (Deadline) object; + return otherDeadline.day.equals(this.day) && otherDeadline.month.equals(this.month) + && otherDeadline.year.equals(this.year); + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/task/Milestone.java b/src/main/java/seedu/address/model/task/Milestone.java new file mode 100644 index 000000000000..6d7281698732 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Milestone.java @@ -0,0 +1,75 @@ +package seedu.address.model.task; + +//@@author JeremyInElysium +/** + * Represents a Milestone for any Task in the TaskBook + */ +public class Milestone implements Comparable { + private final MilestoneDescription milestoneDescription; + private final Rank rank; + + + public Milestone(MilestoneDescription milestoneDescription, Rank rank) { + //super(title, milestoneDescription, new PriorityLevel("high")); + this.milestoneDescription = milestoneDescription; + this.rank = rank; + } + + public MilestoneDescription getMilestoneDescription() { + return milestoneDescription; + } + + public String getMilestoneDescriptionString() { + return milestoneDescription.milestoneDescription; + } + + + public Rank getRank() { + return rank; + } + + public String getRankString() { + return rank.rank; + } + + + /** + * Returns true if both tasks have the same deadline and title. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameMilestone(Milestone otherMilestone) { + if (otherMilestone == this) { + return true; + } + + return otherMilestone != null + && otherMilestone.getMilestoneDescription().equals(getMilestoneDescription()) + && otherMilestone.getRank().equals(getRank()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Milestone // instanceof handles nulls + && rank.equals(((Milestone) other).rank)); + } + + /** + * To ensure that the milestoneList is sorted by the rank of each milestone + */ + @Override + public int compareTo(Milestone other) { + return Integer.compare(this.rank.getRankInteger(), other.rank.getRankInteger()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Milestone ") + .append(getRank()) + .append(": ") + .append(getMilestoneDescription()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/task/MilestoneDescription.java b/src/main/java/seedu/address/model/task/MilestoneDescription.java new file mode 100644 index 000000000000..c77e749d0b78 --- /dev/null +++ b/src/main/java/seedu/address/model/task/MilestoneDescription.java @@ -0,0 +1,72 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; + +//@@author JeremyInElysium +/** + * Represents a description in the milestone of a task. + */ +public class MilestoneDescription { + + public static final String MESSAGE_MILESTONEDESCRIPTION_CONSTRAINTS = + "Milestone description can only contain alphanumeric characters and spaces, and it should not be blank."; + + /** + * The first character of the milestone description must not be a whitespace, + * otherwise " " (a blank string) will become a valid input + */ + public static final String MILESTONEDESCRIPTION_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String milestoneDescription; + + /** + * Creates a constructor for the milestone description + * Guarantees that the milestone description is not null + * @param milestoneDescription a valid milestone description + */ + public MilestoneDescription(String milestoneDescription) { + requireNonNull(milestoneDescription); + //checkArgument(isValidMilestoneDescription(milestoneDescription), MESSAGE_MILESTONEDESCRIPTION_CONSTRAINTS); + this.milestoneDescription = milestoneDescription; + } + + + /** + * Checks whether milestone description entered by the user is valid + * @param milestoneDescription + * @return true if valid + */ + /* + public static boolean isValidMilestoneDescription(String milestoneDescription) { + return true; + //milestoneDescription.matches(MILESTONEDESCRIPTION_VALIDATION_REGEX); + } + */ + + public String getMilestoneDescription() { + return this.milestoneDescription; + } + + /** + * Pads milestone description to fill up 40 characters for neater layout in the UI + * @param milestoneDescription + * @return padded milestoneDescription + */ + public static String padMilestoneDescription(String milestoneDescription) { + int milestoneDescriptionLength = milestoneDescription.length(); + int toPad = 41 - milestoneDescriptionLength; + return String.format("%1$-" + toPad + "s", milestoneDescription); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MilestoneDescription // instanceof handles nulls + && milestoneDescription.equals(((MilestoneDescription) other).milestoneDescription)); + } + + @Override + public String toString() { + return milestoneDescription; + } +} diff --git a/src/main/java/seedu/address/model/task/ModuleCode.java b/src/main/java/seedu/address/model/task/ModuleCode.java new file mode 100644 index 000000000000..69bec5cd3d3f --- /dev/null +++ b/src/main/java/seedu/address/model/task/ModuleCode.java @@ -0,0 +1,60 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author chelseyong +/** + * Represents a Task's Module Code in the address book. + * Guarantees: immutable; + */ +public class ModuleCode { + public static final String MESSAGE_MODULE_CODE_CONSTRAINTS = + "Module code must have AAXXXX where A is an alphabet and X is a number."; + + public final String moduleCode; + + public ModuleCode(String moduleCode) { + requireNonNull(moduleCode); + checkArgument(isValidModuleCode(moduleCode), MESSAGE_MODULE_CODE_CONSTRAINTS); + this.moduleCode = moduleCode.toUpperCase(); + } + + /** + * If @param s module code is valid + * @return true + */ + public static boolean isValidModuleCode(String s) { + if (s.length() != 6) { + return false; + } + char firstLetter = s.charAt(0); + char secondLetter = s.charAt(1); + if (!(firstLetter >= 'A' && firstLetter <= 'Z' && secondLetter >= 'A' && secondLetter <= 'Z')) { + return false; + } + for (int i = 2; i < 6; i++) { + if (!(s.charAt(i) >= '0') || !(s.charAt(i) <= '9')) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return moduleCode; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleCode // instanceof handles nulls + && moduleCode.equals(((ModuleCode) other).moduleCode)); // state check + } + + @Override + public int hashCode() { + return moduleCode.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/task/PriorityLevel.java b/src/main/java/seedu/address/model/task/PriorityLevel.java new file mode 100644 index 000000000000..8c1325209341 --- /dev/null +++ b/src/main/java/seedu/address/model/task/PriorityLevel.java @@ -0,0 +1,76 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Task's PriorityLevel in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidPriorityLevel(String)} + */ +public class PriorityLevel { + public static final String MESSAGE_PRIORITY_CONSTRAINTS = + "Priority can only be of low, medium or high level"; + + public final String priorityLevel; + public final int priorityLevelInt; + + /** + * Constructs a {@code PriorityLevel}. + * + * @param priority A valid priority. + */ + public PriorityLevel(String priority) { + requireNonNull(priority); + checkArgument(isValidPriorityLevel(priority), MESSAGE_PRIORITY_CONSTRAINTS); + priorityLevel = priority.toLowerCase(); + + //@@author ChanChunCheong + switch(priority) { + case ("low"): { + priorityLevelInt = 3; + break; + } + case ("medium"): { + priorityLevelInt = 2; + break; + } + case ("high"): { + priorityLevelInt = 1; + break; + } + default: + priorityLevelInt = 0; + } + //@@author + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidPriorityLevel(String test) { + String testInLowerCase = test.toLowerCase(); + if (testInLowerCase.equals("low") || testInLowerCase.equals("medium") + || testInLowerCase.equals("high")) { + return true; + } else { + return false; + } + } + + @Override + public String toString() { + return priorityLevel; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PriorityLevel // instanceof handles nulls + && priorityLevel.equals(((PriorityLevel) other).priorityLevel)); // state check + } + + @Override + public int hashCode() { + return priorityLevel.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/task/Rank.java b/src/main/java/seedu/address/model/task/Rank.java new file mode 100644 index 000000000000..50326a42dbf5 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Rank.java @@ -0,0 +1,59 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; + +//@@author JeremyInElysium +/** + * Represents a description in the milestone of a task. + */ +public class Rank { + + public static final String MESSAGE_RANK_CONSTRAINTS = + "Rank can only contain non-zero positive integers."; + + /** + * The input must not be a whitespace, zero or a negative integer + */ + public static final String RANK_VALIDATION_REGEX = "^^\\d*[1-9]\\d*$"; + + public final String rank; + + /** + * Creates a constructor for the rank + * Guarantees that the rank is not null + * @param rank a valid rank + */ + public Rank(String rank) { + requireNonNull(rank); + this.rank = rank; + } + + /** + * Checks whether rank entered by the user is valid + * @param rank + * @return true if valid + */ + public static boolean isValidRank(String rank) { + return rank.matches(RANK_VALIDATION_REGEX); + } + + public String getRank() { + return this.rank; + } + + public Integer getRankInteger() { + return Integer.valueOf(this.rank); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Rank // instanceof handles nulls + && rank.equals(((Rank) other).rank)); + } + + @Override + public String toString() { + return rank; + } +} diff --git a/src/main/java/seedu/address/model/task/SortTaskList.java b/src/main/java/seedu/address/model/task/SortTaskList.java new file mode 100644 index 000000000000..fe636942b96b --- /dev/null +++ b/src/main/java/seedu/address/model/task/SortTaskList.java @@ -0,0 +1,46 @@ +package seedu.address.model.task; + +import java.util.Comparator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +//@@author ChanChunCheong +/** + * SortTaskList is a comparator for the task book to sort according to lexicographical order + */ +public class SortTaskList { + /** + * Return 0 if self = other. Return 1 if self > other. Return -1 if self < other. + * @param internalList + * @param method + * @return SortedList + */ + public ObservableList sortTask(ObservableList internalList, String method) { + + FXCollections.sort(internalList, new Comparator() { + @Override + public int compare(Task self, Task other) { + switch(method) { + case ("module"): { + return self.getModuleCode().toString().toLowerCase() + .compareTo(other.getModuleCode().toString().toLowerCase()); + } + case ("deadline"): { + return self.getDeadline().getDate().compareTo(other.getDeadline().getDate()); + } + case ("priority"): { + return self.getPriorityLevelInt() - other.getPriorityLevelInt(); + } + case ("title"): { + return self.getTitle().toLowerCase().compareTo(other.getTitle().toLowerCase()); + } + default: + return 0; + } + } + + }); + return internalList; + } +} diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java new file mode 100644 index 000000000000..722e6a19c553 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Task.java @@ -0,0 +1,274 @@ +package seedu.address.model.task; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; + +/** + * Represents a Task in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Task { + + //private static final Logger logger = LogsCenter.getLogger(Task.class); + private Deadline deadline; + private ModuleCode moduleCode; + private final String title; + private final String description; + private final PriorityLevel priorityLevel; + private boolean isCompleted = false; + private final int expectedNumOfHours; + private int completedNumOfHours = -1; + private final List milestoneList = new ArrayList(); + private final Set tagList = new HashSet<>(); + + /** + * Creating a new task + * which is incomplete + */ + public Task(Deadline deadline, ModuleCode moduleCode, String title, String description, PriorityLevel priorityLevel, + int expectedNumOfHours) { + this.deadline = deadline; + this.moduleCode = moduleCode; + this.title = title; + this.description = description; + this.priorityLevel = priorityLevel; + this.expectedNumOfHours = expectedNumOfHours; + } + + /** + * Re-creating a task + * which has been completed + */ + public Task(Deadline deadline, ModuleCode moduleCode, String title, String description, PriorityLevel priorityLevel, + int expectedNumOfHours, int completedNumOfHours, boolean isCompleted, + List milestoneList, Set tagList) { + this.deadline = deadline; + this.moduleCode = moduleCode; + this.title = title; + this.description = description; + this.priorityLevel = priorityLevel; + this.expectedNumOfHours = expectedNumOfHours; + this.completedNumOfHours = completedNumOfHours; + this.isCompleted = isCompleted; + this.milestoneList.addAll(milestoneList); + this.tagList.addAll(tagList); + } + + public Task(Task other) { + this.deadline = other.deadline; + this.moduleCode = other.moduleCode; + this.title = other.title; + this.description = other.description; + this.priorityLevel = other.priorityLevel; + this.isCompleted = other.isCompleted; + this.expectedNumOfHours = other.expectedNumOfHours; + this.completedNumOfHours = other.completedNumOfHours; + this.milestoneList.addAll(other.milestoneList); + this.tagList.addAll(other.tagList); + } + + public Deadline getDeadline() { + return deadline; + } + + public ModuleCode getModuleCode() { + return moduleCode; + } + + public void setDeadline(Deadline deadline) { + this.deadline = deadline; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public PriorityLevel getPriorityLevel() { + return priorityLevel; + } + + public int getExpectedNumOfHours() { + return expectedNumOfHours; + } + + public int getCompletedNumOfHours() { + return completedNumOfHours; + } + + public Set getTags() { + return Collections.unmodifiableSet(tagList); + } + + public boolean isCompleted() { + return isCompleted; + } + + /** + * Marks the task as completed by + * setting @code {isCompleted} to true + */ + public Task completed(int hours) { + Task completedTask = new Task(this); + completedTask.isCompleted = true; + completedTask.completedNumOfHours = hours; + return completedTask; + } + + /** + * Returns true if both tasks have the same deadline, title, and module code. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameTask(Task otherTask) { + if (otherTask == this) { + return true; + } else if (otherTask == null) { + return false; + } else if (getModuleCode() == null && otherTask.getModuleCode() != null) { + return false; + } else if (getModuleCode() != null && otherTask.getModuleCode() == null) { + return false; + } else if (getModuleCode() == null && otherTask.getModuleCode() == null) { + return otherTask.getDeadline().equals(getDeadline()) + && otherTask.getTitle().equals(getTitle()); + } else { + return otherTask.getDeadline().equals(getDeadline()) + && otherTask.getTitle().equals(getTitle()) + && otherTask.getModuleCode().equals(getModuleCode()); + } + } + //@@author ChanChunCheong + /** + * Add tag to a task + * @param tag + * @return the new Task + */ + public Task addTag(Tag tag) { + Task deferredTask = new Task(this); + deferredTask.tagList.add(tag); + return deferredTask; + } + + /** + * Removes a tag to a task + * @param tag + * @return the new Task + */ + public Task removeTag(Tag tag) { + Task deferredTask = new Task(this); + deferredTask.tagList.remove(tag); + return deferredTask; + } + + /** + * Defers the task to a later + * @param deferredDays + * @return the new Task + */ + public Task deferred(int deferredDays) { + Task deferredTask = new Task(this); + Deadline deferredDeadline = deferredTask.deadline.deferDeadline(deferredDays); + deferredTask.deadline = deferredDeadline; + return deferredTask; + } + //@@author + + public int getPriorityLevelInt() { + return priorityLevel.priorityLevelInt; + } + + //@@author JeremyInElysium + /** + * Add a milestone to the task. + */ + public Task addMilestone(Milestone milestone) { + Task taskWithMilestones = new Task(this); + taskWithMilestones.milestoneList.add(milestone); + Collections.sort(taskWithMilestones.milestoneList); + return taskWithMilestones; + } + + /** + * @return list of milestones for the task. + */ + public List getMilestoneList() { + return Collections.unmodifiableList(milestoneList); + } + + //@@author chelseyong + /** + * Returns true if both tasks have the same data fields. + * This defines a stronger notion of equality between two tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + if (getModuleCode() == null && otherTask.getModuleCode() != null) { + return false; + } else if (getModuleCode() != null && otherTask.getModuleCode() == null) { + return false; + } else if (getModuleCode() == null && otherTask.getModuleCode() == null) { + return otherTask.getTitle().equals(getTitle()) + && otherTask.getDeadline().equals(getDeadline()) + && otherTask.getDescription().equals(getDescription()) + && otherTask.getPriorityLevel().equals(getPriorityLevel()) + && otherTask.isCompleted() == isCompleted() + && otherTask.getExpectedNumOfHours() == getExpectedNumOfHours() + && otherTask.getCompletedNumOfHours() == getCompletedNumOfHours(); + } else { + return otherTask.getTitle().equals(getTitle()) + && otherTask.getDeadline().equals(getDeadline()) + && otherTask.getDescription().equals(getDescription()) + && otherTask.getPriorityLevel().equals(getPriorityLevel()) + && otherTask.isCompleted() == isCompleted() + && otherTask.getExpectedNumOfHours() == getExpectedNumOfHours() + && otherTask.getCompletedNumOfHours() == getCompletedNumOfHours() + && otherTask.getModuleCode().equals(getModuleCode()); + } + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(deadline, title, description, priorityLevel, expectedNumOfHours, + completedNumOfHours, isCompleted, moduleCode); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDeadline()) + .append(" | ") + .append(getTitle()) + .append(" : ") + .append(getDescription()) + .append(" Priority: ") + .append(getPriorityLevel()); + /*builder.append(" Expected: "); + builder.append(expectedNumOfHours); + builder.append(" completed? "); + builder.append(isCompleted); + builder.append(" completed hours? "); + builder.append(completedNumOfHours); + builder.append(" Module code: "); + builder.append(moduleCode);*/ + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java similarity index 52% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java index c9b5868427ca..6c6ba864d983 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java @@ -1,4 +1,5 @@ -package seedu.address.model.person; +//@@author +package seedu.address.model.task; import java.util.List; import java.util.function.Predicate; @@ -6,26 +7,25 @@ import seedu.address.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Task}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class TaskContainsKeywordsPredicate implements Predicate { private final List keywords; - public NameContainsKeywordsPredicate(List keywords) { + public TaskContainsKeywordsPredicate(List keywords) { this.keywords = keywords; } @Override - public boolean test(Person person) { + public boolean test(Task task) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getTitle(), keyword)); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check + || (other instanceof TaskContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TaskContainsKeywordsPredicate) other).keywords)); // state check } - } diff --git a/src/main/java/seedu/address/model/task/UniqueTaskList.java b/src/main/java/seedu/address/model/task/UniqueTaskList.java new file mode 100644 index 000000000000..8aaeb777069b --- /dev/null +++ b/src/main/java/seedu/address/model/task/UniqueTaskList.java @@ -0,0 +1,192 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; + +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * A task is considered unique by comparing using {@code Task#isSameTask(Task)}. As such, adding and updating of + * tasks uses Task#isSameTask(Task) for equality so as to ensure that the task being added or updated is + * unique in terms of identity in the UniqueTaskList. However, the removal of a task uses Task#equals(Object) so + * as to ensure that the task with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Task#isSameTask(Task) + */ +public class UniqueTaskList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTask); + } + + //@@author emobeany + /** + * Returns true if the list contains an exact replica of the task as the given argument. + */ + public boolean containsExactCopyOf(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a task to the list. + * The task must not already exist in the list. + */ + public void add(Task toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + //@@author ChanChunCheong + /** + * Adds a task to the list. + * The task must not already exist in the list. + */ + public void addTag(Task target, Tag tag) { + requireNonNull(target); + requireNonNull(tag); + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + Task targetedTask = target.addTag(tag); + internalList.set(index, targetedTask); + } + /** + * Removes {@code tag} from {@code person} in this {@code AddressBook}. + */ + public void removeTagFromTask(Task target, Tag tag) { + requireNonNull(target); + requireNonNull(tag); + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + Task targetedTask = target.removeTag(tag); + internalList.set(index, targetedTask); + } + //@@author + + + /** + * Replaces the task {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list. + * The task identity of {@code editedPerson} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedPerson) { + requireAllNonNull(target, editedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedPerson) && contains(editedPerson)) { + throw new DuplicateTaskException(); + } + internalList.set(index, editedPerson); + } + + /** + * Removes the equivalent task from the list. + * The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + /** + * Complete a task in the list. + * The task must exist in the list. + */ + public void complete(Task toComplete, int hours) { + requireNonNull(toComplete); + int index = internalList.indexOf(toComplete); + if (index == -1) { + throw new TaskNotFoundException(); + } + Task completedTask = toComplete.completed(hours); + internalList.set(index, completedTask); + } + + public void setTasks(UniqueTaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + requireAllNonNull(tasks); + if (!tasksAreUnique(tasks)) { + throw new DuplicateTaskException(); + } + internalList.setAll(tasks); + } + /* + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + + public ObservableList obtainObservableList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code tasks} contains only unique tasks. + */ + private boolean tasksAreUnique(List tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).isSameTask(tasks.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 000000000000..09bf8955cf9a --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/InvalidDeadlineException.java b/src/main/java/seedu/address/model/task/exceptions/InvalidDeadlineException.java new file mode 100644 index 000000000000..6db32bfba3e0 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/InvalidDeadlineException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that an invalid deadline is being entered. + */ + +public class InvalidDeadlineException extends RuntimeException { + public InvalidDeadlineException() { + super("Invalid deadline entered"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 000000000000..8d122a5692c9 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..46b802849f68 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,49 +1,42 @@ package seedu.address.model.util; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.ReadOnlyTaskBook; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.ModuleCode; +import seedu.address.model.task.PriorityLevel; +import seedu.address.model.task.Task; /** * Contains utility methods for populating {@code AddressBook} with sample data. + * TODO: At least 6 sets of Tasks */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + public static Task[] getSampleTasks() { + return new Task[] { + new Task(new Deadline("1/11/2018"), new ModuleCode("CS2113"), "Complete code refactoring", + "refer to notes", new PriorityLevel("high"), 2, -1, + false, new ArrayList<>(), new HashSet<>()), + new Task(new Deadline("2/11/2018"), new ModuleCode("CG2271"), "Complete lab 4", + "Synchronization", new PriorityLevel("medium"), 2, -1, + false, new ArrayList<>(), new HashSet<>()), + new Task(new Deadline("3/11/2018"), new ModuleCode("CS2101"), "Prepare presentation", + "slides not done", new PriorityLevel("low"), 2, -1, + false, new ArrayList<>(), new HashSet<>()), }; } - public static ReadOnlyAddressBook getSampleAddressBook() { + public static ReadOnlyTaskBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Task sampleTask : getSampleTasks()) { + sampleAb.addTask(sampleTask); } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f92..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 28791127999b..c7dfc48f6985 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -4,16 +4,16 @@ import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.TaskBookChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyTaskBook; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends TaskBookStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -22,18 +22,19 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(UserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getTaskBookFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readTaskBook() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveTaskBook(ReadOnlyTaskBook taskBook) throws IOException; /** * Saves the current version of the Address Book to the hard disk. * Creates the data file if it is missing. * Raises {@link DataSavingExceptionEvent} if there was an error during saving. */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); + void handleAddressBookChangedEvent(TaskBookChangedEvent abce); + } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index b0df908a76a7..1660e9dd65c7 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -9,10 +9,10 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.TaskBookChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyTaskBook; import seedu.address.model.UserPrefs; /** @@ -21,13 +21,13 @@ public class StorageManager extends ComponentManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private TaskBookStorage taskBookStorage; private UserPrefsStorage userPrefsStorage; - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(TaskBookStorage taskBookStorage, UserPrefsStorage userPrefsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.taskBookStorage = taskBookStorage; this.userPrefsStorage = userPrefsStorage; } @@ -52,42 +52,41 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { // ================ AddressBook methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getTaskBookFilePath() { + return taskBookStorage.getTaskBookFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readTaskBook() throws DataConversionException, IOException { + return readTaskBook(taskBookStorage.getTaskBookFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readTaskBook(Path filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return taskBookStorage.readTaskBook(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveTaskBook(ReadOnlyTaskBook taskBook) throws IOException { + saveTaskBook(taskBook, taskBookStorage.getTaskBookFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveTaskBook(ReadOnlyTaskBook taskBook, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + taskBookStorage.saveTaskBook(taskBook, filePath); } @Override @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + public void handleAddressBookChangedEvent(TaskBookChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); try { - saveAddressBook(event.data); + saveTaskBook(event.data); } catch (IOException e) { raise(new DataSavingExceptionEvent(e)); } } - } diff --git a/src/main/java/seedu/address/storage/TaskBookStorage.java b/src/main/java/seedu/address/storage/TaskBookStorage.java new file mode 100644 index 000000000000..027c19aa093a --- /dev/null +++ b/src/main/java/seedu/address/storage/TaskBookStorage.java @@ -0,0 +1,45 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.ReadOnlyTaskBook; + +/** + * Represents a storage for {@link seedu.address.model.AddressBook}. + */ +public interface TaskBookStorage { + + /** + * Returns the file path of the data file. + */ + Path getTaskBookFilePath(); + + /** + * Returns AddressBook data as a {@link ReadOnlyTaskBook}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readTaskBook() throws DataConversionException, IOException; + + /** + * @see #getTaskBookFilePath() + */ + Optional readTaskBook(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTaskBook} to the storage. + * @param taskBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTaskBook(ReadOnlyTaskBook taskBook) throws IOException; + + /** + * @see #saveTaskBook(ReadOnlyTaskBook) + */ + void saveTaskBook(ReadOnlyTaskBook taskBook, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedMilestone.java b/src/main/java/seedu/address/storage/XmlAdaptedMilestone.java new file mode 100644 index 000000000000..24294526bc76 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedMilestone.java @@ -0,0 +1,98 @@ +package seedu.address.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Milestone; +import seedu.address.model.task.MilestoneDescription; +import seedu.address.model.task.Rank; + + +//@@author JeremyInElysium +/** + * JAXB-friendly adapted version of the Tag. + */ +public class XmlAdaptedMilestone { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Milestone's %s field is missing."; + + @XmlElement + private String descrip; + @XmlElement + private String rank; + + /** + * Constructs an XmlAdaptedTag. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedMilestone() {} + + /** + * Constructs a {@code XmlAdaptedMilestone} with the given {@code milestone}. + */ + public XmlAdaptedMilestone(MilestoneDescription milestoneDescription, Rank rank) { + this.descrip = milestoneDescription.getMilestoneDescription(); + this.rank = rank.getRank(); + } + + /** + * Converts a given Milestone into this class for JAXB use. + * @param source future changes to this will not affect the created + */ + + public XmlAdaptedMilestone(Milestone source) { + descrip = source.getMilestoneDescriptionString(); + rank = source.getRankString(); + } + + /** + * Converts this jaxb-friendly adapted milestone object into the model's Milestone object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + + public Milestone toModelType() throws IllegalValueException { + if (descrip == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, MilestoneDescription.class.getSimpleName())); + } + + /* + if (!MilestoneDescription.isValidMilestoneDescription(descrip)) { + throw new IllegalValueException(MilestoneDescription.MESSAGE_MILESTONEDESCRIPTION_CONSTRAINTS); + } + */ + final MilestoneDescription modelMilestoneDescription = new MilestoneDescription(descrip); + + if (rank == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, Rank.class.getSimpleName())); + } + + if (!Rank.isValidRank(rank)) { + throw new IllegalValueException(Rank.MESSAGE_RANK_CONSTRAINTS); + } + final Rank modelRank = new Rank(rank); + + + return new Milestone(modelMilestoneDescription, modelRank); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedMilestone)) { + return false; + } + + XmlAdaptedMilestone otherMilestone = (XmlAdaptedMilestone) other; + return Objects.equals(descrip, otherMilestone.descrip) + && Objects.equals(rank, otherMilestone.rank); + } +} + diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index c03785e5700f..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List tagged = new ArrayList<>(); - - /** - * Constructs an XmlAdaptedPerson. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedPerson() {} - - /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. - */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged = new ArrayList<>(tagged); - } - } - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = source.getTags().stream() - .map(XmlAdaptedTag::new) - .collect(Collectors.toList()); - } - - /** - * Converts this jaxb-friendly adapted person object into the model's Person object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedPerson)) { - return false; - } - - XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; - return Objects.equals(name, otherPerson.name) - && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java index d3e2d8be9c4f..5fcc6c37d562 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedTag.java @@ -8,38 +8,47 @@ /** * JAXB-friendly adapted version of the Tag. */ + public class XmlAdaptedTag { @XmlValue private String tagName; + /** * Constructs an XmlAdaptedTag. * This is the no-arg constructor that is required by JAXB. */ + public XmlAdaptedTag() {} + /** * Constructs a {@code XmlAdaptedTag} with the given {@code tagName}. */ + public XmlAdaptedTag(String tagName) { this.tagName = tagName; } + /** * Converts a given Tag into this class for JAXB use. * * @param source future changes to this will not affect the created */ + public XmlAdaptedTag(Tag source) { tagName = source.tagName; } + /** * Converts this jaxb-friendly adapted tag object into the model's Tag object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person + * @throws IllegalValueException if there were any data constraints violated in the adapted task */ + public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); @@ -60,3 +69,4 @@ public boolean equals(Object other) { return tagName.equals(((XmlAdaptedTag) other).tagName); } } + diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTask.java b/src/main/java/seedu/address/storage/XmlAdaptedTask.java new file mode 100644 index 000000000000..4ada3cfed2f4 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedTask.java @@ -0,0 +1,241 @@ +package seedu.address.storage; + +import static seedu.address.commons.core.Messages.MESSAGE_ZERO_HOURS_COMPLETION; +import static seedu.address.commons.util.StringUtil.isNonZeroUnsignedInteger; +import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_HOURS; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Milestone; +import seedu.address.model.task.ModuleCode; +import seedu.address.model.task.PriorityLevel; +import seedu.address.model.task.Task; + + +/** + * JAXB-friendly version of the Task. + */ +public class XmlAdaptedTask { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + @XmlElement(required = true) + private String deadline; + @XmlElement + private String moduleCode; + @XmlElement(required = true) + private String title; + @XmlElement(required = true) + private String description; + @XmlElement(required = true) + private String priorityLevel; + @XmlElement(required = true) + private String expectedNumOfHours; + @XmlElement (required = true) + private String completedNumOfHours; + @XmlElement(required = true) + private boolean isCompleted; + @XmlElement + private List milestonelist; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedTask. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedTask() {} + + /** + * Constructs an {@code XmlAdaptedTask} with the given task details. + */ + public XmlAdaptedTask(String deadline, String moduleCode, String title, String description, String priorityLevel, + String expectedNumOfHours, String completedNumOfHours, + boolean isCompleted, List milestoneList, List tagged) { + this.deadline = deadline; + this.moduleCode = moduleCode; + this.title = title; + this.description = description; + this.priorityLevel = priorityLevel; + this.expectedNumOfHours = expectedNumOfHours; + this.completedNumOfHours = completedNumOfHours; + this.isCompleted = isCompleted; + this.milestonelist = new ArrayList<>(); + if (milestoneList != null) { + this.milestonelist = new ArrayList<>(milestonelist); + } + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + if (moduleCode != null) { + this.moduleCode = moduleCode; + } + } + /** + * Constructs an {@code XmlAdaptedTask} with the given task details. + */ + public XmlAdaptedTask(String deadline, String moduleCode, String title, String description, String priorityLevel, + String expectedNumOfHours) { + this.deadline = deadline; + if (moduleCode != null) { + this.moduleCode = moduleCode; + } + this.title = title; + this.description = description; + this.priorityLevel = priorityLevel; + this.expectedNumOfHours = expectedNumOfHours; + this.completedNumOfHours = "0"; + this.isCompleted = false; + this.milestonelist = new ArrayList<>(); + this.tagged = new ArrayList<>(); + } + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedPerson + */ + public XmlAdaptedTask(Task source) { + deadline = source.getDeadline().toString(); + if (source.getModuleCode() != null) { + moduleCode = source.getModuleCode().toString(); + } + title = source.getTitle(); + description = source.getDescription(); + priorityLevel = source.getPriorityLevel().toString(); + expectedNumOfHours = Integer.toString(source.getExpectedNumOfHours()); + completedNumOfHours = Integer.toString(source.getCompletedNumOfHours()); + isCompleted = source.isCompleted(); + milestonelist = source.getMilestoneList().stream().map(XmlAdaptedMilestone::new).collect(Collectors.toList()); + tagged = source.getTags().stream().map(XmlAdaptedTag::new).collect(Collectors.toList()); + } + + /** + * Converts this jaxb-friendly adapted task object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + // Check validity of tags + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + // Check validity of deadline + if (deadline == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Deadline.class.getSimpleName())); + } + if (!Deadline.isValidFormat(deadline)) { + throw new IllegalValueException(Deadline.MESSAGE_DEADLINE_CONSTRAINTS); + } else if (!Deadline.isValidDeadline(deadline)) { + throw new IllegalValueException(Messages.MESSAGE_INVALID_DEADLINE); + } + final Deadline modelDeadline = new Deadline(deadline); + + // Check validity of module code. Module code may be null. + if (moduleCode != null && !ModuleCode.isValidModuleCode(moduleCode)) { + throw new IllegalValueException(String.format(ModuleCode.MESSAGE_MODULE_CODE_CONSTRAINTS)); + } + ModuleCode modelModuleCode = null; + if (moduleCode != null) { + modelModuleCode = new ModuleCode(moduleCode); + } + + // Check validity of title + if (title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Title")); + } + final String modelTitle = title; + + // Check validity of description + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Description")); + } + final String modelDescription = description; + + // Check validity of priority level + if (priorityLevel == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PriorityLevel.class.getSimpleName())); + } + if (!PriorityLevel.isValidPriorityLevel(priorityLevel)) { + throw new IllegalValueException(PriorityLevel.MESSAGE_PRIORITY_CONSTRAINTS); + } + final PriorityLevel modelPriority = new PriorityLevel(priorityLevel); + + // Check validity of expected num of hours + if (expectedNumOfHours == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + "Expected number of hours expected to complete")); + } else if (!isNonZeroUnsignedInteger(expectedNumOfHours)) { + throw new IllegalValueException(MESSAGE_INVALID_HOURS); + } + final int modelExpectedNumOfHours = Integer.parseInt(expectedNumOfHours); + + // Check validity of completed num of hours + final int modelCompletedNumOfHours; + if (completedNumOfHours == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + "Number of hours taken to complete")); + } else { + modelCompletedNumOfHours = Integer.parseInt(completedNumOfHours); + } + + // Check validity of isCompleted + //Boolean cannot be checked for null --> if (isCompleted == null) + final boolean modelIsCompleted = isCompleted; + + if (isCompleted && modelCompletedNumOfHours <= 0) { + throw new IllegalValueException(MESSAGE_ZERO_HOURS_COMPLETION); + } else if (!isCompleted && modelCompletedNumOfHours > 0) { + throw new IllegalValueException("Task is not completed yet ..."); + } + + // Check validity of Milestones + final List milestoneEntries = new ArrayList(); + if (milestonelist != null && !milestonelist.isEmpty()) { + for (XmlAdaptedMilestone entry : milestonelist) { + milestoneEntries.add(entry.toModelType()); + } + + } + + final Set modelTags = new HashSet<>(taskTags); + + return new Task(modelDeadline, modelModuleCode, modelTitle, modelDescription, + modelPriority, modelExpectedNumOfHours, + modelCompletedNumOfHours, modelIsCompleted, milestoneEntries, modelTags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTask)) { + return false; + } + + XmlAdaptedTask otherTask = (XmlAdaptedTask) other; + return Objects.equals(deadline, otherTask.deadline) + && Objects.equals(title, otherTask.title) + && Objects.equals(description, otherTask.description) + && Objects.equals(priorityLevel, otherTask.priorityLevel) + && Objects.equals(moduleCode, otherTask.moduleCode) + && Objects.equals(expectedNumOfHours, otherTask.expectedNumOfHours) + && Objects.equals(completedNumOfHours, otherTask.completedNumOfHours); + + } +} diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index d8f65dc036ab..5a2c5b8bac05 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -15,7 +15,7 @@ public class XmlFileStorage { /** * Saves the given addressbook data to the specified file. */ - public static void saveDataToFile(Path file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(Path file, XmlSerializableTaskBook addressBook) throws FileNotFoundException { try { XmlUtil.saveDataToFile(file, addressBook); @@ -27,10 +27,10 @@ public static void saveDataToFile(Path file, XmlSerializableAddressBook addressB /** * Returns address book in the file or an empty address book */ - public static XmlSerializableAddressBook loadDataFromSaveFile(Path file) throws DataConversionException, + public static XmlSerializableTaskBook loadDataFromSaveFile(Path file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableTaskBook.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableTaskBook.java similarity index 56% rename from src/main/java/seedu/address/storage/XmlSerializableAddressBook.java rename to src/main/java/seedu/address/storage/XmlSerializableTaskBook.java index b85fa4a8f07e..5055637ac3fe 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableTaskBook.java @@ -1,5 +1,7 @@ package seedu.address.storage; +import static seedu.address.logic.commands.AddTaskCommand.MESSAGE_DUPLICATE_TASK; + import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -9,34 +11,33 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyTaskBook; +import seedu.address.model.task.Task; /** * An Immutable AddressBook that is serializable to XML format */ @XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; +public class XmlSerializableTaskBook { + public static final String MESSAGE_DUPLICATE_TASK = "Task list contains duplicate task(s)."; @XmlElement - private List persons; + private List tasks; /** - * Creates an empty XmlSerializableAddressBook. + * Creates an empty XmlSerializableTaskBook. * This empty constructor is required for marshalling. */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); + public XmlSerializableTaskBook() { + tasks = new ArrayList<>(); } /** * Conversion */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { + public XmlSerializableTaskBook(ReadOnlyTaskBook src) { this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + tasks.addAll(src.getTaskList().stream().map(XmlAdaptedTask::new).collect(Collectors.toList())); } /** @@ -47,12 +48,12 @@ public XmlSerializableAddressBook(ReadOnlyAddressBook src) { */ public AddressBook toModelType() throws IllegalValueException { AddressBook addressBook = new AddressBook(); - for (XmlAdaptedPerson p : persons) { - Person person = p.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + for (XmlAdaptedTask p : tasks) { + Task task = p.toModelType(); + if (addressBook.hasTask(task)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_TASK); } - addressBook.addPerson(person); + addressBook.addTask(task); } return addressBook; } @@ -63,9 +64,9 @@ public boolean equals(Object other) { return true; } - if (!(other instanceof XmlSerializableAddressBook)) { + if (!(other instanceof XmlSerializableTaskBook)) { return false; } - return persons.equals(((XmlSerializableAddressBook) other).persons); + return tasks.equals(((XmlSerializableTaskBook) other).tasks); } } diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlTaskBookStorage.java similarity index 62% rename from src/main/java/seedu/address/storage/XmlAddressBookStorage.java rename to src/main/java/seedu/address/storage/XmlTaskBookStorage.java index ecf0e7ec23a8..a26558012e9e 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlTaskBookStorage.java @@ -13,45 +13,45 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyTaskBook; /** * A class to access AddressBook data stored as an xml file on the hard disk. */ -public class XmlAddressBookStorage implements AddressBookStorage { +public class XmlTaskBookStorage implements TaskBookStorage { - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(XmlTaskBookStorage.class); private Path filePath; - public XmlAddressBookStorage(Path filePath) { + public XmlTaskBookStorage(Path filePath) { this.filePath = filePath; } - public Path getAddressBookFilePath() { + public Path getTaskBookFilePath() { return filePath; } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); + public Optional readTaskBook() throws DataConversionException, IOException { + return readTaskBook(filePath); } /** - * Similar to {@link #readAddressBook()} + * Similar to {@link #readTaskBook()} * @param filePath location of the data. Cannot be null * @throws DataConversionException if the file is not in the correct format. */ - public Optional readAddressBook(Path filePath) throws DataConversionException, + public Optional readTaskBook(Path filePath) throws DataConversionException, FileNotFoundException { requireNonNull(filePath); if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); + logger.info("TaskBook file " + filePath + " not found"); return Optional.empty(); } - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); + XmlSerializableTaskBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); try { return Optional.of(xmlAddressBook.toModelType()); } catch (IllegalValueException ive) { @@ -61,20 +61,20 @@ public Optional readAddressBook(Path filePath) throws DataC } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); + public void saveTaskBook(ReadOnlyTaskBook addressBook) throws IOException { + saveTaskBook(addressBook, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} + * Similar to {@link #saveTaskBook(ReadOnlyTaskBook)} * @param filePath location of the data. Cannot be null */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveTaskBook(ReadOnlyTaskBook addressBook, Path filePath) throws IOException { requireNonNull(addressBook); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); + XmlFileStorage.saveDataToFile(filePath, new XmlSerializableTaskBook(addressBook)); } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index b43de90a2b9f..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection()); - } -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 3d7aaded5640..8f1bb5d151d1 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,9 +1,11 @@ package seedu.address.ui; +import java.time.LocalDate; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.fxml.FXML; +import javafx.scene.control.DatePicker; import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; @@ -30,12 +32,68 @@ public class CommandBox extends UiPart { @FXML private TextField commandTextField; + @FXML + private DatePicker date; + public CommandBox(Logic logic) { super(FXML); this.logic = logic; // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); historySnapshot = logic.getHistorySnapshot(); + + DatePicker datePicker = new DatePicker(); + + } + + /** + * Converts + * @param date from datePicker in UI + * @return deadline in correct parsing format + */ + private String convertToDeadline(LocalDate date) { + int year = date.getYear(); + int month = date.getMonthValue(); + int day = date.getDayOfMonth(); + StringBuilder deadline = new StringBuilder(); + deadline.append(day); + deadline.append("/"); + deadline.append(month); + deadline.append("/"); + deadline.append(year); + return deadline.toString(); + } + + /** + * When user picks a date, + * that deadline will be selected, + * using selectDeadlineCommand + */ + @FXML + public void selectDate() { + StringBuilder commandEntered = new StringBuilder(); + LocalDate deadlineFromInput = date.getValue(); + String deadlineParsed = convertToDeadline(deadlineFromInput); + + commandEntered.append("select "); + commandEntered.append(deadlineParsed); + //logger.info("Command entered ====" + commandEntered.toString()); + try { + CommandResult commandResult = logic.execute(commandEntered.toString()); + initHistory(); + historySnapshot.next(); + // process result of the command + commandTextField.setText(""); + logger.info("Result: " + commandResult.feedbackToUser); // feedbackToUser returns a string. + raise(new NewResultAvailableEvent(commandResult.feedbackToUser)); + + } catch (CommandException | ParseException e) { + initHistory(); + // handle command failure + setStyleToIndicateCommandFailure(); + logger.info("Invalid command: " + commandTextField.getText()); + raise(new NewResultAvailableEvent(e.getMessage())); + } } /** @@ -106,7 +164,7 @@ private void handleCommandEntered() { historySnapshot.next(); // process result of the command commandTextField.setText(""); - logger.info("Result: " + commandResult.feedbackToUser); + logger.info("Result: " + commandResult.feedbackToUser); // feedbackToUser returns a string. raise(new NewResultAvailableEvent(commandResult.feedbackToUser)); } catch (CommandException | ParseException e) { diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 0e361a4d7baf..07e5ef6e62d3 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -12,6 +12,7 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; + import seedu.address.commons.core.Config; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; @@ -34,7 +35,6 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; private PersonListPanel personListPanel; private Config config; private UserPrefs prefs; @@ -119,10 +119,7 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanel = new PersonListPanel(logic.getFilteredTaskList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); @@ -191,10 +188,6 @@ public PersonListPanel getPersonListPanel() { return personListPanel; } - void releaseResources() { - browserPanel.freeResources(); - } - @Subscribe private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index f6727ea83abd..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,70 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index 80080adb4305..5383798f689b 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -13,7 +13,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.JumpToListRequestEvent; import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * Panel containing the list of persons. @@ -23,16 +23,16 @@ public class PersonListPanel extends UiPart { private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); @FXML - private ListView personListView; + private ListView personListView; - public PersonListPanel(ObservableList personList) { + public PersonListPanel(ObservableList taskList) { super(FXML); - setConnections(personList); + setConnections(taskList); registerAsAnEventHandler(this); } - private void setConnections(ObservableList personList) { - personListView.setItems(personList); + private void setConnections(ObservableList taskList) { + personListView.setItems(taskList); personListView.setCellFactory(listView -> new PersonListViewCell()); setEventHandlerForSelectionChangeEvent(); } @@ -41,14 +41,14 @@ private void setEventHandlerForSelectionChangeEvent() { personListView.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 PersonPanelSelectionChangedEvent(newValue)); } }); } /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. + * Scrolls to the {@code TaskCard} at the {@code index} and selects it. */ private void scrollTo(int index) { Platform.runLater(() -> { @@ -64,18 +64,18 @@ private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { } /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + * Custom {@code ListCell} that displays the graphics of a {@code Task} using a {@code TaskCard}. */ - class PersonListViewCell extends ListCell { + class PersonListViewCell extends ListCell { @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); - if (empty || person == null) { + if (empty || task == null) { setGraphic(null); setText(null); } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + setGraphic(new TaskCard(task, getIndex() + 1).getRoot()); } } } diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index f6ba29502422..45f8f6c3862e 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -14,7 +14,7 @@ import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.TaskBookChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -74,7 +74,7 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleAddressBookChangedEvent(TaskBookChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 000000000000..f8850afcab82 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,117 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; + +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.task.Task; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class TaskCard extends UiPart { + + private static final String FXML = "PersonListCard.fxml"; + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Task task; + + @FXML + private VBox cardPane; + @FXML + private Label title; + @FXML + private Label deadline; + @FXML + private Label moduleCodes; + @FXML + private Label id; + @FXML + private Label description; + @FXML + private Label priorityLevel; + @FXML + private Label expectedNumOfHours; + @FXML + private Label status; + @FXML + private FlowPane milestones; + @FXML + private FlowPane tags; + + /* + @FXML + private Label email; + @FXML + private FlowPane tags; + */ + + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + title.setText(task.getTitle().toUpperCase()); + deadline.setText(task.getDeadline().toString()); + if (task.getModuleCode() != null) { + moduleCodes.setText(task.getModuleCode().toString()); + } else { + moduleCodes.setText(""); + } + description.setText(task.getDescription()); + priorityLevel.setText(task.getPriorityLevel().priorityLevel); + expectedNumOfHours.setText(Integer.toString(task.getExpectedNumOfHours()) + " hours"); + task.getMilestoneList().forEach(milestone -> milestones.getChildren() + .add(new Label(milestone.getMilestoneDescriptionString()))); + task.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + setTextForStatus(task); + setColorForPriorityLevel(task); + } + + private void setColorForPriorityLevel(Task task) { + if (task.getPriorityLevel().priorityLevel.equals("high")) { + priorityLevel.setStyle("-fx-text-fill: red;"); + } else if (task.getPriorityLevel().priorityLevel.equals("medium")) { + priorityLevel.setStyle("-fx-text-fill: #f45713;"); + } else { + priorityLevel.setStyle("-fx-text-fill: orange;"); + } + } + + private void setTextForStatus(Task task) { + if (task.isCompleted()) { + StringBuilder result = new StringBuilder(); + result.append("Completed in "); + result.append(task.getCompletedNumOfHours()); + result.append(" hours!"); + status.setText(result.toString()); + } else { + status.setText("Not completed :("); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..c12353144e2f 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/application_icon.png"; private Logic logic; private Config config; @@ -66,7 +66,6 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/java/seedu/address/ui/UriBuilder.java b/src/main/java/seedu/address/ui/UriBuilder.java new file mode 100644 index 000000000000..04a13802119d --- /dev/null +++ b/src/main/java/seedu/address/ui/UriBuilder.java @@ -0,0 +1,55 @@ +package seedu.address.ui; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; + +/** + * To build a uri path for html files specifically, in HelpWindow.html + */ +public class UriBuilder { + private StringBuilder filePath; + private StringBuilder queries; + + public UriBuilder() { + filePath = new StringBuilder(); + queries = new StringBuilder(); + } + /** + * Adding @param folder to the path of url + */ + public void addPath(String folder) { + filePath.append("/"); + filePath.append(folder); + } + + /** + * Adding @param folder to the path of url + */ + public void addPath(URL folder) { + filePath.append(folder); + } + + /** + * Adds a html query + * @param parameter + * @param value + * @throws UnsupportedEncodingException + */ + public void addQuery(String parameter, String value) throws UnsupportedEncodingException { + if (queries.toString().length() > 0) { + queries.append("&"); + } + queries.append(parameter); + queries.append("="); + queries.append(URLEncoder.encode(value, "UTF-8")); + } + + public String getUrl() throws URISyntaxException, MalformedURLException { + URI uri = new URI(null, null, filePath.toString(), queries.toString(), null); + return uri.toString(); + } +} diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd93..000000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/application_icon.png b/src/main/resources/images/application_icon.png new file mode 100644 index 000000000000..2ae0916c5681 Binary files /dev/null and b/src/main/resources/images/application_icon.png differ diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 70bd59ab3215..b07d51c723ac 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,15 @@ + + - - - - + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..14c5a587209c 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -100,11 +100,11 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #6497ef; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #5eafe5; } .list-cell:filled:selected { @@ -132,6 +132,23 @@ -fx-text-fill: #010504; } +#id { + -fx-font-family: "Segoe UI"; + -fx-font-size: 15px; + -fx-text-fill: black; +} + +#deadline { + -fx-text-fill: #28292b; + -fx-font-weight: bold; +} + +#title { + -fx-font-family: "Gill Sans"; + -fx-font-size: 15px; + -fx-text-fill: black; +} + .stack-pane { -fx-background-color: derive(#1d1d1d, 20%); } diff --git a/docs/DummySearchPage.html b/src/main/resources/view/DummySearchPage.html similarity index 55% rename from docs/DummySearchPage.html rename to src/main/resources/view/DummySearchPage.html index 1607d4c57291..d0d791034646 100644 --- a/docs/DummySearchPage.html +++ b/src/main/resources/view/DummySearchPage.html @@ -1,5 +1,5 @@ - + Dummy Search Page @@ -20,10 +20,12 @@ - Hi : This is a placeholder page for se-edu/addressbook-level4.
- You may update the code to load a page from a real service (e.g., Google search).
- This dummy page is used here because, given the high number of forks of this repo, loading a page from a real third-party service by default can result in that service taking counter-measures (e.g., redirecting to captcha pages) due to the high number of rapid requests received from a single IP.
- When you have made the change, please remove: + Task:
+
+ priority
+ + +
  1. This file (docs/DummySearchPage.html).
  2. Task copyDummySearchPage in both build.gradle and .travis.yml.
  3. diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index daf386d8f5b8..f795d7b85d4f 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -14,7 +14,7 @@ - + @@ -33,7 +33,8 @@ - + @@ -47,18 +48,12 @@ - + - - - - - - diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..82f2996a538f 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,79 @@ + - - - - - - - - - - - - - + + + + + diff --git a/src/test/data/ConfigUtilTest/EmptyConfig.json b/src/test/data/ConfigUtilTest/EmptyConfig.json deleted file mode 100644 index 0db3279e44b0..000000000000 --- a/src/test/data/ConfigUtilTest/EmptyConfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} diff --git a/src/test/data/ConfigUtilTest/ExtraValuesConfig.json b/src/test/data/ConfigUtilTest/ExtraValuesConfig.json deleted file mode 100644 index 413273f7b26f..000000000000 --- a/src/test/data/ConfigUtilTest/ExtraValuesConfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "appTitle" : "Typical App Title", - "logLevel" : "INFO", - "userPrefsFilePath" : "preferences.json", - "extra" : "extra value" -} diff --git a/src/test/data/ConfigUtilTest/NotJsonFormatConfig.json b/src/test/data/ConfigUtilTest/NotJsonFormatConfig.json deleted file mode 100644 index f2071ce5742b..000000000000 --- a/src/test/data/ConfigUtilTest/NotJsonFormatConfig.json +++ /dev/null @@ -1 +0,0 @@ -this file is not in json format! diff --git a/src/test/data/ConfigUtilTest/TypicalConfig.json b/src/test/data/ConfigUtilTest/TypicalConfig.json deleted file mode 100644 index fbf982090fef..000000000000 --- a/src/test/data/ConfigUtilTest/TypicalConfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "appTitle" : "Typical App Title", - "logLevel" : "INFO", - "userPrefsFilePath" : "preferences.json" -} diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml deleted file mode 100644 index 41e411568a5f..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Hans Muster - 9482424 - hans@example.com -
    4th street
    -
    - - - Hans Muster - 948asdf2424 - hans@example.com -
    4th street
    -
    -
    diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml deleted file mode 100644 index cfa128e72828..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Ha!ns Mu@ster - 9482424 - hans@example.com -
    4th street
    -
    -
    diff --git a/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml deleted file mode 100644 index ac02230263d3..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Alice Pauline - 94351253 - alice@example.com -
    123, Jurong West Ave 6, #08-111
    - friends -
    - - - - Alice Pauline - 94351253 - pauline@example.com -
    4th street
    -
    - -
    diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml deleted file mode 100644 index 13d5b1cb1c8a..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Hans Muster - 9482424 - hans@exam!32ple -
    4th street
    -
    -
    diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml deleted file mode 100644 index d812b05e32bb..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - Alice Pauline - 94351253 - alice@example.com -
    123, Jurong West Ave 6, #08-111
    - friends -
    - - Benson Meier - 98765432 - johnd@example.com -
    311, Clementi Ave 2, #02-25
    - owesMoney - friends -
    - - Carl Kurz - 95352563 - heinz@example.com -
    wall street
    -
    - - Daniel Meier - 87652533 - cornelia@example.com -
    10th street
    - friends -
    - - Elle Meyer - 9482224 - werner@example.com -
    michegan ave
    -
    - - Fiona Kunz - 9482427 - lydia@example.com -
    little tokyo
    -
    - - George Best - 9482442 - anna@example.com -
    4th street
    -
    -
    diff --git a/src/test/data/XmlSerializableTaskBookTest/duplicateTasksInTaskBook.xml b/src/test/data/XmlSerializableTaskBookTest/duplicateTasksInTaskBook.xml new file mode 100644 index 000000000000..8864a3a78277 --- /dev/null +++ b/src/test/data/XmlSerializableTaskBookTest/duplicateTasksInTaskBook.xml @@ -0,0 +1,27 @@ + + + + + 31/3/2018 + CS2113 + Complete CS2113 Homework + Refer to notes + low + 1 + -1 + false + + + + + 31/3/2018 + CS2113 + Complete CS2113 Homework + Refer to notes + low + 1 + -1 + false + + + diff --git a/src/test/data/XmlSerializableTaskBookTest/invalidTaskBook.xml b/src/test/data/XmlSerializableTaskBookTest/invalidTaskBook.xml new file mode 100644 index 000000000000..db0ba0cf2770 --- /dev/null +++ b/src/test/data/XmlSerializableTaskBookTest/invalidTaskBook.xml @@ -0,0 +1,12 @@ + + + + + 31/3 + Complete CS2113 Homework + Refer to notes + midhigh + 3 + false + + diff --git a/src/test/data/XmlSerializableTaskBookTest/typicalTaskBook.xml b/src/test/data/XmlSerializableTaskBookTest/typicalTaskBook.xml new file mode 100644 index 000000000000..68dbaf51928a --- /dev/null +++ b/src/test/data/XmlSerializableTaskBookTest/typicalTaskBook.xml @@ -0,0 +1,44 @@ + + + + + 9/10/2018 + CS2101 + Plan a OP2 meeting + OP2 is 40% of the grade + high + 1 + -1 + false + + + 11/11/2018 + CS2102 + Set up the backend framework + Using Flask + medium + 1 + -1 + false + + + 1/1/2018 + CS2113 + Complete code refactoring + Refer to notes! + low + 1 + -1 + false + + + 1/1/2018 + CS2114 + Start on code + Find out more about syntax of new language + High + 1 + -1 + false + + diff --git a/src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml b/src/test/data/XmlTaskBookStorageTest/NotXmlFormatAddressBook.xml similarity index 100% rename from src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml rename to src/test/data/XmlTaskBookStorageTest/NotXmlFormatAddressBook.xml diff --git a/src/test/data/XmlTaskBookStorageTest/invalidAndValidTaskBook.xml b/src/test/data/XmlTaskBookStorageTest/invalidAndValidTaskBook.xml new file mode 100644 index 000000000000..e405ef66dbaf --- /dev/null +++ b/src/test/data/XmlTaskBookStorageTest/invalidAndValidTaskBook.xml @@ -0,0 +1,25 @@ + + + + + 4/11/2018 + CG2271 + Read notes + about Deadlock + low + 1 + -1 + false + + + + 22/11/2018 + C271 + Do lab + be productive! + high + 1 + -1 + false + + diff --git a/src/test/data/XmlTaskBookStorageTest/invalidTaskBook.xml b/src/test/data/XmlTaskBookStorageTest/invalidTaskBook.xml new file mode 100644 index 000000000000..970e54d12e5e --- /dev/null +++ b/src/test/data/XmlTaskBookStorageTest/invalidTaskBook.xml @@ -0,0 +1,12 @@ + + + + + 31/3/2018 + Complete CS2113 Homework + Refer to notes + midhigh + 3 + false + + diff --git a/src/test/data/XmlUtilTest/invalidPersonField.xml b/src/test/data/XmlUtilTest/invalidPersonField.xml index ba49c971e884..7c0feb4f9e69 100644 --- a/src/test/data/XmlUtilTest/invalidPersonField.xml +++ b/src/test/data/XmlUtilTest/invalidPersonField.xml @@ -1,9 +1,9 @@ - - + + Hans Muster 9482asf424 hans@example
    4th street
    friends -
    + diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml index c0da5c86d080..f214d3ddf202 100644 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ b/src/test/data/XmlUtilTest/missingPersonField.xml @@ -1,8 +1,8 @@ - - + + 9482424 hans@example
    4th street
    friends -
    + diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempAddressBook.xml index 4773cf598f4b..4014d480e290 100644 --- a/src/test/data/XmlUtilTest/tempAddressBook.xml +++ b/src/test/data/XmlUtilTest/tempAddressBook.xml @@ -1,6 +1,6 @@ - + 1 John Doe @@ -8,5 +8,5 @@ - + diff --git a/src/test/data/XmlUtilTest/tempTaskBook.xml b/src/test/data/XmlUtilTest/tempTaskBook.xml new file mode 100644 index 000000000000..f70b5db9a39d --- /dev/null +++ b/src/test/data/XmlUtilTest/tempTaskBook.xml @@ -0,0 +1,12 @@ + + + + 1 + 5/5/2018 + Just coding + All my life + high + 100 + false + + diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml index 6265778674d3..b0515fc8412b 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validAddressBook.xml @@ -1,57 +1,57 @@ - + Hans Muster 9482424 hans@example.com
    4th street
    -
    - + + Ruth Mueller 87249245 ruth@example.com
    81th street
    -
    - + + Heinz Kurz 95352563 heinz@example.com
    wall street
    -
    - + + Cornelia Meier 87652533 cornelia@example.com
    10th street
    -
    - + + Werner Meyer 9482224 werner@example.com
    michegan ave
    -
    - + + Lydia Kunz 9482427 lydia@example.com
    little tokyo
    -
    - + + Anna Best 9482442 anna@example.com
    4th street
    -
    - + + Stefan Meier 8482424 stefan@example.com
    little india
    -
    - + + Martin Mueller 8482131 hans@example.com
    chicago ave
    -
    +
    diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml index c029008d54f4..90f0f965a1f0 100644 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ b/src/test/data/XmlUtilTest/validPerson.xml @@ -1,8 +1,8 @@ - + Hans Muster 9482424 hans@example
    4th street
    friends -
    + diff --git a/src/test/java/guitests/GuiRobot.java b/src/test/java/guitests/GuiRobot.java deleted file mode 100644 index 7f3031fbef16..000000000000 --- a/src/test/java/guitests/GuiRobot.java +++ /dev/null @@ -1,119 +0,0 @@ -package guitests; - -import java.util.Optional; -import java.util.function.BooleanSupplier; - -import org.testfx.api.FxRobot; - -import guitests.guihandles.exceptions.StageNotFoundException; -import javafx.stage.Stage; - -/** - * Robot used to simulate user actions on the GUI. - * Extends {@link FxRobot} by adding some customized functionality and workarounds. - */ -public class GuiRobot extends FxRobot { - - private static final int PAUSE_FOR_HUMAN_DELAY_MILLISECONDS = 250; - private static final int DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS = 5000; - - private static final String PROPERTY_TESTFX_HEADLESS = "testfx.headless"; - - private final boolean isHeadlessMode; - - public GuiRobot() { - String headlessPropertyValue = System.getProperty(PROPERTY_TESTFX_HEADLESS); - isHeadlessMode = headlessPropertyValue != null && headlessPropertyValue.equals("true"); - } - - /** - * Pauses execution for {@code PAUSE_FOR_HUMAN_DELAY_MILLISECONDS} milliseconds for a human to examine the - * effects of the test. This method will be disabled when the GUI tests are executed in headless mode to avoid - * unnecessary delays. - */ - public void pauseForHuman() { - if (isHeadlessMode) { - return; - } - - sleep(PAUSE_FOR_HUMAN_DELAY_MILLISECONDS); - } - - /** - * Returns true if tests are run in headless mode. - */ - public boolean isHeadlessMode() { - return isHeadlessMode; - } - - /** - * Waits for {@code event} to be true by {@code DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS} milliseconds. - * - * @throws EventTimeoutException if the time taken exceeds {@code DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS} - * milliseconds. - */ - public void waitForEvent(BooleanSupplier event) { - waitForEvent(event, DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS); - } - - /** - * Waits for {@code event} to be true. - * - * @param timeOut in milliseconds - * @throws EventTimeoutException if the time taken exceeds {@code timeOut}. - */ - public void waitForEvent(BooleanSupplier event, int timeOut) { - int timePassed = 0; - final int retryInterval = 50; - - while (!event.getAsBoolean()) { - sleep(retryInterval); - timePassed += retryInterval; - - if (timePassed >= timeOut) { - throw new EventTimeoutException(); - } - } - - pauseForHuman(); - } - - /** - * Returns true if the window with {@code stageTitle} is currently open. - */ - public boolean isWindowShown(String stageTitle) { - return getNumberOfWindowsShown(stageTitle) >= 1; - } - - /** - * Returns the number of windows with {@code stageTitle} that are currently open. - */ - public int getNumberOfWindowsShown(String stageTitle) { - return (int) listTargetWindows().stream() - .filter(window -> window instanceof Stage && ((Stage) window).getTitle().equals(stageTitle)) - .count(); - } - - /** - * Returns the first stage, ordered by proximity to the current target window, with the stage title. - * The order that the windows are searched are as follows (proximity): current target window, - * children of the target window, rest of the windows. - * - * @throws StageNotFoundException if the stage is not found. - */ - public Stage getStage(String stageTitle) { - Optional targetStage = listTargetWindows().stream() - .filter(Stage.class::isInstance) // checks that the window is of type Stage - .map(Stage.class::cast) - .filter(stage -> stage.getTitle().equals(stageTitle)) - .findFirst(); - - return targetStage.orElseThrow(StageNotFoundException::new); - } - - /** - * Represents an error which occurs when a timeout occurs when waiting for an event. - */ - private class EventTimeoutException extends RuntimeException { - } -} diff --git a/src/test/java/guitests/guihandles/AlertDialogHandle.java b/src/test/java/guitests/guihandles/AlertDialogHandle.java deleted file mode 100644 index 92150be91837..000000000000 --- a/src/test/java/guitests/guihandles/AlertDialogHandle.java +++ /dev/null @@ -1,32 +0,0 @@ -package guitests.guihandles; - -import javafx.scene.control.DialogPane; -import javafx.stage.Stage; -import seedu.address.ui.UiManager; - -/** - * A handle for the {@code AlertDialog} of the UI. - */ -public class AlertDialogHandle extends StageHandle { - private final DialogPane dialogPane; - - public AlertDialogHandle(Stage stage) { - super(stage); - - dialogPane = getChildNode("#" + UiManager.ALERT_DIALOG_PANE_FIELD_ID); - } - - /** - * Returns the text of the header in the {@code AlertDialog}. - */ - public String getHeaderText() { - return dialogPane.getHeaderText(); - } - - /** - * Returns the text of the content in the {@code AlertDialog}. - */ - public String getContentText() { - return dialogPane.getContentText(); - } -} diff --git a/src/test/java/guitests/guihandles/BrowserPanelHandle.java b/src/test/java/guitests/guihandles/BrowserPanelHandle.java deleted file mode 100644 index bd3633af78f3..000000000000 --- a/src/test/java/guitests/guihandles/BrowserPanelHandle.java +++ /dev/null @@ -1,64 +0,0 @@ -package guitests.guihandles; - -import java.net.URL; - -import guitests.GuiRobot; -import javafx.concurrent.Worker; -import javafx.scene.Node; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; - -/** - * A handler for the {@code BrowserPanel} of the UI. - */ -public class BrowserPanelHandle extends NodeHandle { - - public static final String BROWSER_ID = "#browser"; - - private boolean isWebViewLoaded = true; - - private URL lastRememberedUrl; - - public BrowserPanelHandle(Node browserPanelNode) { - super(browserPanelNode); - - WebView webView = getChildNode(BROWSER_ID); - WebEngine engine = webView.getEngine(); - new GuiRobot().interact(() -> engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { - if (newState == Worker.State.RUNNING) { - isWebViewLoaded = false; - } else if (newState == Worker.State.SUCCEEDED) { - isWebViewLoaded = true; - } - })); - } - - /** - * Returns the {@code URL} of the currently loaded page. - */ - public URL getLoadedUrl() { - return WebViewUtil.getLoadedUrl(getChildNode(BROWSER_ID)); - } - - /** - * Remembers the {@code URL} of the currently loaded page. - */ - public void rememberUrl() { - lastRememberedUrl = getLoadedUrl(); - } - - /** - * Returns true if the current {@code URL} is different from the value remembered by the most recent - * {@code rememberUrl()} call. - */ - public boolean isUrlChanged() { - return !lastRememberedUrl.equals(getLoadedUrl()); - } - - /** - * Returns true if the browser is done loading a page, or if this browser has yet to load any page. - */ - public boolean isLoaded() { - return isWebViewLoaded; - } -} diff --git a/src/test/java/guitests/guihandles/CommandBoxHandle.java b/src/test/java/guitests/guihandles/CommandBoxHandle.java deleted file mode 100644 index aa59b1e470e2..000000000000 --- a/src/test/java/guitests/guihandles/CommandBoxHandle.java +++ /dev/null @@ -1,42 +0,0 @@ -package guitests.guihandles; - -import javafx.collections.ObservableList; -import javafx.scene.control.TextField; -import javafx.scene.input.KeyCode; - -/** - * A handle to the {@code CommandBox} in the GUI. - */ -public class CommandBoxHandle extends NodeHandle { - - public static final String COMMAND_INPUT_FIELD_ID = "#commandTextField"; - - public CommandBoxHandle(TextField commandBoxNode) { - super(commandBoxNode); - } - - /** - * Returns the text in the command box. - */ - public String getInput() { - return getRootNode().getText(); - } - - /** - * Enters the given command in the Command Box and presses enter. - */ - public void run(String command) { - click(); - guiRobot.interact(() -> getRootNode().setText(command)); - guiRobot.pauseForHuman(); - - guiRobot.type(KeyCode.ENTER); - } - - /** - * Returns the list of style classes present in the command box. - */ - public ObservableList getStyleClass() { - return getRootNode().getStyleClass(); - } -} diff --git a/src/test/java/guitests/guihandles/HelpWindowHandle.java b/src/test/java/guitests/guihandles/HelpWindowHandle.java deleted file mode 100644 index ec5fc547aa86..000000000000 --- a/src/test/java/guitests/guihandles/HelpWindowHandle.java +++ /dev/null @@ -1,34 +0,0 @@ -package guitests.guihandles; - -import java.net.URL; - -import guitests.GuiRobot; -import javafx.stage.Stage; - -/** - * A handle to the {@code HelpWindow} of the application. - */ -public class HelpWindowHandle extends StageHandle { - - public static final String HELP_WINDOW_TITLE = "Help"; - - private static final String HELP_WINDOW_BROWSER_ID = "#browser"; - - public HelpWindowHandle(Stage helpWindowStage) { - super(helpWindowStage); - } - - /** - * Returns true if a help window is currently present in the application. - */ - public static boolean isWindowPresent() { - return new GuiRobot().isWindowShown(HELP_WINDOW_TITLE); - } - - /** - * Returns the {@code URL} of the currently loaded page. - */ - public URL getLoadedUrl() { - return WebViewUtil.getLoadedUrl(getChildNode(HELP_WINDOW_BROWSER_ID)); - } -} diff --git a/src/test/java/guitests/guihandles/MainMenuHandle.java b/src/test/java/guitests/guihandles/MainMenuHandle.java deleted file mode 100644 index 0bc475c1d02b..000000000000 --- a/src/test/java/guitests/guihandles/MainMenuHandle.java +++ /dev/null @@ -1,39 +0,0 @@ -package guitests.guihandles; - -import java.util.Arrays; - -import javafx.scene.Node; -import javafx.scene.input.KeyCode; - -/** - * Provides a handle to the main menu of the app. - */ -public class MainMenuHandle extends NodeHandle { - public static final String MENU_BAR_ID = "#menuBar"; - - public MainMenuHandle(Node mainMenuNode) { - super(mainMenuNode); - } - - /** - * Opens the {@code HelpWindow} using the menu bar in {@code MainWindow}. - */ - public void openHelpWindowUsingMenu() { - clickOnMenuItemsSequentially("Help", "F1"); - } - - /** - * Opens the {@code HelpWindow} by pressing the shortcut key associated - * with the menu bar in {@code MainWindow}. - */ - public void openHelpWindowUsingAccelerator() { - guiRobot.push(KeyCode.F1); - } - - /** - * Clicks on {@code menuItems} in order. - */ - private void clickOnMenuItemsSequentially(String... menuItems) { - Arrays.stream(menuItems).forEach(guiRobot::clickOn); - } -} diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java deleted file mode 100644 index 34e36054f4fd..000000000000 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ /dev/null @@ -1,51 +0,0 @@ -package guitests.guihandles; - -import javafx.stage.Stage; - -/** - * Provides a handle for {@code MainWindow}. - */ -public class MainWindowHandle extends StageHandle { - - private final PersonListPanelHandle personListPanel; - private final ResultDisplayHandle resultDisplay; - private final CommandBoxHandle commandBox; - private final StatusBarFooterHandle statusBarFooter; - private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; - - public MainWindowHandle(Stage stage) { - super(stage); - - personListPanel = new PersonListPanelHandle(getChildNode(PersonListPanelHandle.PERSON_LIST_VIEW_ID)); - resultDisplay = new ResultDisplayHandle(getChildNode(ResultDisplayHandle.RESULT_DISPLAY_ID)); - commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); - statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); - mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); - } - - public PersonListPanelHandle getPersonListPanel() { - return personListPanel; - } - - public ResultDisplayHandle getResultDisplay() { - return resultDisplay; - } - - public CommandBoxHandle getCommandBox() { - return commandBox; - } - - public StatusBarFooterHandle getStatusBarFooter() { - return statusBarFooter; - } - - public MainMenuHandle getMainMenu() { - return mainMenu; - } - - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; - } -} diff --git a/src/test/java/guitests/guihandles/NodeHandle.java b/src/test/java/guitests/guihandles/NodeHandle.java deleted file mode 100644 index 628b6d584fef..000000000000 --- a/src/test/java/guitests/guihandles/NodeHandle.java +++ /dev/null @@ -1,44 +0,0 @@ -package guitests.guihandles; - -import static java.util.Objects.requireNonNull; - -import java.util.Optional; - -import guitests.GuiRobot; -import guitests.guihandles.exceptions.NodeNotFoundException; -import javafx.scene.Node; - -/** - * Provides access to a node in a JavaFx application for GUI testing purposes. - */ -public abstract class NodeHandle { - protected final GuiRobot guiRobot = new GuiRobot(); - - private final T rootNode; - - protected NodeHandle(T rootNode) { - this.rootNode = requireNonNull(rootNode); - } - - protected T getRootNode() { - return rootNode; - } - - /** - * Retrieves the {@code query} node owned by the {@code rootNode}. - * - * @param query name of the CSS selector for the node to retrieve. - * @throws NodeNotFoundException if no such node exists. - */ - protected Q getChildNode(String query) { - Optional node = guiRobot.from(rootNode).lookup(query).tryQuery(); - return node.orElseThrow(NodeNotFoundException::new); - } - - /** - * Clicks on the root node (i.e. itself). - */ - public void click() { - guiRobot.clickOn(rootNode); - } -} diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java deleted file mode 100644 index 1789735e49a8..000000000000 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ /dev/null @@ -1,87 +0,0 @@ -package guitests.guihandles; - -import java.util.List; -import java.util.stream.Collectors; - -import com.google.common.collect.ImmutableMultiset; - -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * Provides a handle to a person card in the person list panel. - */ -public class PersonCardHandle extends NodeHandle { - private static final String ID_FIELD_ID = "#id"; - private static final String NAME_FIELD_ID = "#name"; - private static final String ADDRESS_FIELD_ID = "#address"; - private static final String PHONE_FIELD_ID = "#phone"; - private static final String EMAIL_FIELD_ID = "#email"; - private static final String TAGS_FIELD_ID = "#tags"; - - private final Label idLabel; - private final Label nameLabel; - private final Label addressLabel; - private final Label phoneLabel; - private final Label emailLabel; - private final List