diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index aefe676f3c8..9d32936d104 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -8,6 +8,8 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.task.Task; +import seedu.address.model.task.UniqueTaskList; /** * Wraps all data at the address-book level @@ -16,6 +18,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueTaskList tasks; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -26,6 +29,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + tasks = new UniqueTaskList(); } public AddressBook() {} @@ -48,6 +52,14 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the task list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + this.tasks.setTasks(tasks); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -55,6 +67,7 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setTasks(newData.getTaskList()); } //// person-level operations @@ -101,6 +114,24 @@ public void removeAllPerson() { persons.removeAll(); } + //// task-level operations + + /** + * Returns true if a task with the same identity as {@code task} exists in the address book. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + + /** + * 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); + } + //// util methods @Override @@ -115,6 +146,11 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getTaskList() { + return tasks.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..afe24331f5c 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -2,6 +2,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * Unmodifiable view of an address book @@ -14,4 +15,10 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the tasks list. + * This list will not contain any duplicate tasks. + */ + ObservableList getTaskList(); + } diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 408082be276..931a88c85e9 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -86,6 +86,10 @@ public void removeAll() { internalList.clear(); } + /** + * Replaces the contents of this list with another {@code UniquePersonList}. + * @param replacement the {@code UniquePersonList} to replace this list with. + */ public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); 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 00000000000..7ef5f302d2f --- /dev/null +++ b/src/main/java/seedu/address/model/task/UniqueTaskList.java @@ -0,0 +1,153 @@ +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.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 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) + * 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(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * 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); + } + + /** + * 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); + } + + /** + * Replaces the task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + * The task identity of {@code editedTask} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedTask) && contains(editedTask)) { + throw new DuplicateTaskException(); + } + + internalList.set(index, editedTask); + } + + /** + * 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(); + } + } + + /** + * Replaces the contents of this list with another {@code UniqueTaskList}. + * @param replacement the {@code UniqueTaskList} to replace this list with. + */ + 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 internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueTaskList)) { + return false; + } + + UniqueTaskList otherUniqueTaskList = (UniqueTaskList) other; + return internalList.equals(otherUniqueTaskList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * 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/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..c4782e9dfe7 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,62 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Note; +import seedu.address.model.task.Task; +import seedu.address.model.task.Title; + +/** + * Jackson-friendly version of {@link Task}. + */ +class JsonAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + private final String title; + private final String note; + + /** + * Constructs a {@code JsonAdaptedTask} with the given task details. + */ + @JsonCreator + public JsonAdaptedTask(@JsonProperty("title") String title, @JsonProperty("note") String note) { + this.title = title; + this.note = note; + } + + /** + * Converts a given {@code Task} into this class for Jackson use. + */ + public JsonAdaptedTask(Task source) { + title = source.getTitle().value; + note = source.getNote().value; + } + + /** + * Converts this Jackson-friendly adapted task object into the model's {@code Task} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task. + */ + public Task toModelType() throws IllegalValueException { + if (title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(title)) { + throw new IllegalValueException(Title.MESSAGE_CONSTRAINTS); + } + final Title modelTitle = new Title(title); + + if (note == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Note.class.getSimpleName())); + } + if (!Note.isValidNote(note)) { + throw new IllegalValueException(Note.MESSAGE_CONSTRAINTS); + } + final Note modelNote = new Note(note); + + return new Task(modelTitle, modelNote); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..38e4466c8d7 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -12,6 +12,7 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * An Immutable AddressBook that is serializable to JSON format. @@ -20,15 +21,19 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_TASK = "Tasks list contains duplicate task(s)."; private final List persons = new ArrayList<>(); + private final List tasks = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons and tasks. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("tasks") List tasks) { this.persons.addAll(persons); + this.tasks.addAll(tasks); } /** @@ -38,6 +43,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List getPersonList() { return persons; } + + @Override + public ObservableList getTaskList() { + return null; + } } } diff --git a/src/test/java/seedu/address/model/task/UniqueTaskListTest.java b/src/test/java/seedu/address/model/task/UniqueTaskListTest.java new file mode 100644 index 00000000000..2a5c2c50ab6 --- /dev/null +++ b/src/test/java/seedu/address/model/task/UniqueTaskListTest.java @@ -0,0 +1,57 @@ +package seedu.address.model.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalTasks.AGENDA; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.TaskBuilder; + +public class UniqueTaskListTest { + + private final UniqueTaskList uniqueTaskList = new UniqueTaskList(); + + @Test + public void contains_nullTask_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueTaskList.contains(null)); + } + + @Test + public void contains_taskNotInList_returnsFalse() { + assertFalse(uniqueTaskList.contains(AGENDA)); + } + + @Test + public void contains_taskInList_returnsTrue() { + uniqueTaskList.add(AGENDA); + assertTrue(uniqueTaskList.contains(AGENDA)); + } + + @Test + public void contains_taskWithSameFieldsInList_returnsTrue() { + uniqueTaskList.add(AGENDA); + String agendaTaskTitleString = AGENDA.getTitle().toString(); + String agendaTaskNoteString = AGENDA.getNote().toString(); + Task agendaCopy = new TaskBuilder() + .withTitle(agendaTaskTitleString) + .withNote(agendaTaskNoteString) + .build(); + assertTrue(uniqueTaskList.contains(agendaCopy)); + } + + // TODO: Add more tests for different methods of UniqueTaskList + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () + -> uniqueTaskList.asUnmodifiableObservableList().remove(0)); + } + + @Test + public void toStringMethod() { + assertEquals(uniqueTaskList.asUnmodifiableObservableList().toString(), uniqueTaskList.toString()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java new file mode 100644 index 00000000000..3034324e38e --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java @@ -0,0 +1,63 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.task.Note; +import seedu.address.model.task.Task; +import seedu.address.model.task.Title; + +public class JsonAdaptedTaskTest { + + public static final String EMPTY_TITLE = " "; + public static final String EMPTY_NOTE = " "; + public static final String INVALID_TITLE = " Invalid Title 123"; + public static final String INVALID_NOTE = " Invalid Note 123"; + + + public static final String VALID_TITLE = "Valid Title 1234 !@#$"; + public static final String VALID_NOTE = "Valid Note 1234 !@#$"; + public static final Task VALID_TASK = new Task(new Title(VALID_TITLE), new Note(VALID_NOTE)); + + @Test + public void toModelType_validTaskDetails_returnsTask() throws Exception { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_TASK); + assertEquals(VALID_TASK, task.toModelType()); + } + + @Test + public void toModelType_invalidTitle_throwsIllegalValueException() { + JsonAdaptedTask emptyTaskTitle = new JsonAdaptedTask(EMPTY_TITLE, VALID_NOTE); + JsonAdaptedTask invalidTaskTitle = new JsonAdaptedTask(INVALID_TITLE, VALID_NOTE); + String expectedMessage = Title.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, emptyTaskTitle::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, invalidTaskTitle::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(null, VALID_NOTE); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidNote_throwsIllegalValueException() { + JsonAdaptedTask emptyTaskNote = new JsonAdaptedTask(VALID_TITLE, EMPTY_NOTE); + JsonAdaptedTask invalidTaskNote = new JsonAdaptedTask(VALID_TITLE, INVALID_NOTE); + String expectedMessage = Note.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, emptyTaskNote::toModelType); + assertThrows(IllegalValueException.class, expectedMessage, invalidTaskNote::toModelType); + } + + @Test + public void toModelType_nullNote_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_TITLE, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Note.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java index 188c9058d20..b7e00970270 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java @@ -12,14 +12,22 @@ import seedu.address.commons.util.JsonUtil; import seedu.address.model.AddressBook; import seedu.address.testutil.TypicalPersons; +import seedu.address.testutil.TypicalTasks; public class JsonSerializableAddressBookTest { private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); + + // test persons private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); + // test tasks + public static final Path TYPICAL_TASKS_FILE = TEST_DATA_FOLDER.resolve("typicalTasksAddressBook.json"); + public static final Path DUPLICATE_TASK_FILE = TEST_DATA_FOLDER.resolve("duplicateTaskAddressBook.json"); + public static final Path INVALID_TASK_FILE = TEST_DATA_FOLDER.resolve("invalidTaskAddressBook.json"); + @Test public void toModelType_typicalPersonsFile_success() throws Exception { JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, @@ -44,4 +52,27 @@ public void toModelType_duplicatePersons_throwsIllegalValueException() throws Ex dataFromFile::toModelType); } + @Test + public void toModelType_typicalTasksFile_success() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_TASKS_FILE, + JsonSerializableAddressBook.class).get(); + AddressBook addressBookFromFile = dataFromFile.toModelType(); + AddressBook typicalTasksAddressBook = TypicalTasks.getTypicalAddressBook(); + assertEquals(addressBookFromFile, typicalTasksAddressBook); + } + + @Test + public void toModelType_invalidTaskFile_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_TASK_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicateTasks_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_TASK_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_TASK, + dataFromFile::toModelType); + } } diff --git a/src/test/java/seedu/address/testutil/TaskBuilder.java b/src/test/java/seedu/address/testutil/TaskBuilder.java new file mode 100644 index 00000000000..a72d8840924 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TaskBuilder.java @@ -0,0 +1,53 @@ +package seedu.address.testutil; + +import seedu.address.model.task.Note; +import seedu.address.model.task.Task; +import seedu.address.model.task.Title; + +/** + * A utility class to help with building Task objects. + */ +public class TaskBuilder { + + public static final String DEFAULT_TITLE = "Plan the event"; + public static final String DEFAULT_NOTE = "Come up with a comprehensive plan for the event."; + + private Title title; + private Note note; + + /** + * Creates a {@code TaskBuilder} with the default details. + */ + public TaskBuilder() { + title = new Title(DEFAULT_TITLE); + note = new Note(DEFAULT_NOTE); + } + + /** + * Initializes the TaskBuilder with the data of {@code taskToCopy}. + */ + public TaskBuilder(Task taskToCopy) { + title = taskToCopy.getTitle(); + note = taskToCopy.getNote(); + } + + /** + * Sets the {@code Title} of the {@code Task} that we are building. + */ + public TaskBuilder withTitle(String title) { + this.title = new Title(title); + return this; + } + + /** + * Sets the {@code Note} of the {@code Task} that we are building. + */ + public TaskBuilder withNote(String note) { + this.note = new Note(note); + return this; + } + + public Task build() { + return new Task(title, note); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalTasks.java b/src/test/java/seedu/address/testutil/TypicalTasks.java new file mode 100644 index 00000000000..273f1d176fd --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalTasks.java @@ -0,0 +1,85 @@ +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.task.Task; + +/** + * A utility class containing a list of {@code Person} objects to be used in tests. + */ +public class TypicalTasks { + + public static final Task AGENDA = new TaskBuilder() + .withTitle("Prepare Agenda") + .withNote("To book venue") + .build(); + + public static final Task BUDGET = new TaskBuilder() + .withTitle("Prepare Budget") + .withNote("For CS2102") + .build(); + + public static final Task CATERING = new TaskBuilder() + .withTitle("Book Catering") + .withNote("For CS2101") + .build(); + + public static final Task DRAFT = new TaskBuilder() + .withTitle("Send Budget and Draft") + .withNote("For CS2100") + .build(); + + public static final Task ENTERTAINMENT = new TaskBuilder() + .withTitle("Book Entertainment") + .withNote("For CS1101") + .build(); + + public static final Task FUNDING = new TaskBuilder() + .withTitle("Prepare Funding") + .withNote("For MA2001") + .build(); + + public static final Task GUESTLIST = new TaskBuilder() + .withTitle("Prepare Guestlist") + .withNote("For CS2103T") + .build(); + + public static final Task HOSPITALITY = new TaskBuilder() + .withTitle("Ensure Hospitality") + .withNote("For CS2103T") + .build(); + + public static final Task INVITATION = new TaskBuilder() + .withTitle("Send Invitations and Guestlist") + .withNote("For CS2103T") + .build(); + + // Keywords + public static final String KEYWORD_MATCHING_BOOK = "Book"; + public static final String KEYWORD_MATCHING_PREPARE = "Prepare"; + public static final String KEYWORD_MATCHING_SEND = "Send"; + public static final String KEYWORD_MATCHING_FOR = "For"; + public static final String KEYWORD_MATCHING_CS2103T = "CS2103T"; + + private TypicalTasks() { + } // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical tasks. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (Task task : getTypicalTasks()) { + ab.addTask(task); + } + return ab; + } + + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList( + AGENDA, BUDGET, CATERING, DRAFT, ENTERTAINMENT, FUNDING, GUESTLIST, HOSPITALITY, INVITATION)); + } +}