diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 80c333abc98..4d305f391e8 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -292,14 +292,14 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli | `* * *` | NUS student | search for a name in the contact | easily find the person’s contact | | `* * *` | NUS student | add contacts into my address book easily | retrieve the saved contact | | `* * *` | NUS student living on campus | save the contacts of my neighbors | contact them in case of any emergencies | -| `* * *` | NUS student living on campus | save the addresses of my neighbours | locate them easily when necessary | +| `* * *` | NUS student living on campus | save the addresses of my neighbours | locate them easily when necessary | | `* * *` | NUS student in multiple CCAs | filter my contacts by tags to identify all people in a group | find the relevant contacts in a certain group quickly | | `* * *` | student staying on campus | label multiple tags to my contacts | locate my friends taking the same module and staying in the same campus residence as me | | `* * *` | user that is familiar with the keyboard | use the keyboard to type commands in the applications | access the features of the application | | `* * *` | user with bad memory | save a short description of my contact | identify my contacts better | | `* * *` | visual-reliant user | save a photo of the person into my contacts | quickly recognise and find them | | `* * *` | non-tech-savvy user | use the help feature of the app | navigate about the app easily | -| `* *` | NUS student | import the NUS calendar into the application | view all academic commitments more conveniently | +| `* *` | NUS student | import the NUS calendar into the application | view all academic commitments more conveniently | | `* *` | NUS Student | compare timetables/calendars with my peers easily | plan meetings more conveniently | | `* *` | NUS student | allocate tasks and responsibilities within a project or CCA group | tasks can be done efficiently | | `* *` | NUS Student in multiple CCAs | group my contacts | identify which group my contacts belong to | diff --git a/docs/UserGuide.md b/docs/UserGuide.md index ec47f0d8656..61ce3909631 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -6,10 +6,6 @@ UniMate is a desktop app for students to **manage contacts** and **manage schedules** optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, UniMate can get your contact management tasks done faster than traditional GUI apps. -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. - - - diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..b1ab27c450a 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -21,9 +21,14 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.calendar.Calendar; +import seedu.address.model.calendar.ReadOnlyCalendar; +import seedu.address.model.util.SampleCalendarUtil; import seedu.address.model.util.SampleDataUtil; import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.CalendarStorage; import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.JsonCalendarStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; @@ -58,7 +63,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + CalendarStorage calendarStorage = new JsonCalendarStorage(userPrefs.getCalendarFilePath()); + storage = new StorageManager(addressBookStorage, calendarStorage, userPrefsStorage); model = initModelManager(storage, userPrefs); @@ -76,21 +82,37 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { logger.info("Using data file : " + storage.getAddressBookFilePath()); Optional addressBookOptional; - ReadOnlyAddressBook initialData; + ReadOnlyAddressBook addressBookInitialData; + Optional calendarOptional; + ReadOnlyCalendar calendarInitialData; + try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { logger.info("Creating a new data file " + storage.getAddressBookFilePath() + " populated with a sample AddressBook."); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + addressBookInitialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + } catch (DataLoadingException e) { + logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." + + " Will be starting with an empty AddressBook."); + addressBookInitialData = new AddressBook(); + } + + try { + calendarOptional = storage.readCalendar(); + if (!calendarOptional.isPresent()) { + logger.info("Creating a new data file " + storage.getCalendarFilePath() + + " populated with a sample Calendar."); + } + calendarInitialData = calendarOptional.orElseGet(SampleCalendarUtil::getSampleCalendar); } catch (DataLoadingException e) { logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." + " Will be starting with an empty AddressBook."); - initialData = new AddressBook(); + calendarInitialData = new Calendar(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(addressBookInitialData, calendarInitialData, userPrefs); } private void initLogging(Config config) { diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 670097ab93d..c7ec65cca22 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -52,6 +52,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE try { storage.saveAddressBook(model.getAddressBook()); + storage.saveCalendar(model.getCalendar()); } catch (AccessDeniedException e) { throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); } catch (IOException ioe) { diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 2b684d7462d..5d9daa2c9f1 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -7,6 +7,7 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.calendar.ReadOnlyCalendar; import seedu.address.model.event.Event; import seedu.address.model.event.exceptions.EventNotFoundException; import seedu.address.model.person.Person; @@ -56,6 +57,24 @@ public interface Model { /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); + /** + * Returns the user prefs' calendar file path. + */ + Path getCalendarFilePath(); + + /** + * Sets the user prefs' calendar file path. + */ + void setCalendarFilePath(Path calendarFilePath); + + /** + * Replaces calendar data with the data in {@code calendar}. + */ + void setCalendar(ReadOnlyCalendar calendar); + + /** Returns the Calendar */ + ReadOnlyCalendar getCalendar(); + /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a86dda7f49c..51c669face0 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -15,6 +15,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.calendar.Calendar; +import seedu.address.model.calendar.ReadOnlyCalendar; import seedu.address.model.event.Event; import seedu.address.model.event.exceptions.EventNotFoundException; import seedu.address.model.person.Person; @@ -26,27 +27,26 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; + private final Calendar calendar; private final UserPrefs userPrefs; private final FilteredList filteredPersons; - private final Calendar calendar; - /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given addressBook, calendar and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { + public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyCalendar calendar, ReadOnlyUserPrefs userPrefs) { requireAllNonNull(addressBook, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); this.addressBook = new AddressBook(addressBook); + this.calendar = new Calendar(calendar); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - calendar = new Calendar(); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new AddressBook(), new Calendar(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -84,6 +84,16 @@ public void setAddressBookFilePath(Path addressBookFilePath) { userPrefs.setAddressBookFilePath(addressBookFilePath); } + @Override + public Path getCalendarFilePath() { + return userPrefs.getCalendarFilePath(); + } + + @Override + public void setCalendarFilePath(Path calendarFilePath) { + requireNonNull(calendarFilePath); + userPrefs.setCalendarFilePath(calendarFilePath); + } //=========== AddressBook ================================================================================ @@ -129,6 +139,17 @@ public void sortPersonList(Comparator personComparator) { } //=========== Calendar =================================================================================== + + @Override + public void setCalendar(ReadOnlyCalendar calendar) { + this.calendar.resetData(calendar); + } + + @Override + public ReadOnlyCalendar getCalendar() { + return calendar; + } + @Override public boolean canAddEvent(Event event) { return calendar.canAddEvent(event); @@ -195,6 +216,7 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) + && calendar.equals(otherModelManager.calendar) && userPrefs.equals(otherModelManager.userPrefs) && filteredPersons.equals(otherModelManager.filteredPersons); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..bcce5a4241d 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path calendarFilePath = Paths.get("data", "calendar.json"); /** * Creates a {@code UserPrefs} with default values. @@ -56,6 +57,15 @@ public void setAddressBookFilePath(Path addressBookFilePath) { this.addressBookFilePath = addressBookFilePath; } + public Path getCalendarFilePath() { + return calendarFilePath; + } + + public void setCalendarFilePath(Path calendarFilePath) { + requireNonNull(calendarFilePath); + this.calendarFilePath = calendarFilePath; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/calendar/Calendar.java b/src/main/java/seedu/address/model/calendar/Calendar.java index 649daddbb15..aa984e1b846 100644 --- a/src/main/java/seedu/address/model/calendar/Calendar.java +++ b/src/main/java/seedu/address/model/calendar/Calendar.java @@ -3,15 +3,17 @@ import static java.util.Objects.requireNonNull; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; +import javafx.collections.ObservableList; import seedu.address.model.event.AllDaysEventListManager; import seedu.address.model.event.Event; /** * Represents a calendar that stores and manages events. */ -public class Calendar { +public class Calendar implements ReadOnlyCalendar { private final AllDaysEventListManager eventManager; /** @@ -21,6 +23,33 @@ public Calendar() { this.eventManager = new AllDaysEventListManager(); } + /** + * Creates a Calendar using the Events in the {@code toBeCopied} + */ + public Calendar(ReadOnlyCalendar toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// overwrite operations + + /** + * Replaces the contents of the Calendar with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + events.forEach(this::addEvent); + } + + /** + * Resets the existing data of this {@code Calendar} with {@code newData}. + */ + public void resetData(ReadOnlyCalendar newData) { + requireNonNull(newData); + + setEvents(newData.getEventList()); + } + /** * Return the AllDaysEventListManager managing the events for this calendar. * @@ -107,6 +136,11 @@ public boolean hasEvents() { return false; } + @Override + public ObservableList getEventList() { + return eventManager.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/src/main/java/seedu/address/model/ReadOnlyCalendar.java b/src/main/java/seedu/address/model/calendar/ReadOnlyCalendar.java similarity index 89% rename from src/main/java/seedu/address/model/ReadOnlyCalendar.java rename to src/main/java/seedu/address/model/calendar/ReadOnlyCalendar.java index 5ff8dc3593e..11ea85ef9e2 100644 --- a/src/main/java/seedu/address/model/ReadOnlyCalendar.java +++ b/src/main/java/seedu/address/model/calendar/ReadOnlyCalendar.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package seedu.address.model.calendar; import javafx.collections.ObservableList; import seedu.address.model.event.Event; diff --git a/src/main/java/seedu/address/model/event/AllDaysEventListManager.java b/src/main/java/seedu/address/model/event/AllDaysEventListManager.java index 6172f57f770..606c954e4f6 100644 --- a/src/main/java/seedu/address/model/event/AllDaysEventListManager.java +++ b/src/main/java/seedu/address/model/event/AllDaysEventListManager.java @@ -7,7 +7,10 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.TreeMap; +import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import seedu.address.model.event.exceptions.EventNotFoundException; /** @@ -128,6 +131,17 @@ public boolean isEmpty() { return this.dayToEventListMap.isEmpty(); } + /** + * Returns the event list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + List list = dayToEventListMap.values().stream() + .flatMap(singleDayEventList -> singleDayEventList.getDayEventList().stream()) + .collect(Collectors.toList()); + + return FXCollections.observableList(list); + } + /** * Checks if there are any events at all in the manager. * diff --git a/src/main/java/seedu/address/model/event/EventPeriod.java b/src/main/java/seedu/address/model/event/EventPeriod.java index a0df2cc1e57..30608de1e15 100644 --- a/src/main/java/seedu/address/model/event/EventPeriod.java +++ b/src/main/java/seedu/address/model/event/EventPeriod.java @@ -129,6 +129,17 @@ public List getDates() { return listOfDates; } + /** + * Get the string representation of the EventPeriod. + * + * @return string representation of the period the eventPeriod spans. + */ + public String getFormattedPeriod() { + String startString = start.format(DATE_TIME_STRING_FORMATTER); + String endString = end.format(DATE_TIME_STRING_FORMATTER); + return startString + " - " + endString; + } + @Override public boolean equals(Object other) { requireNonNull(other); diff --git a/src/main/java/seedu/address/model/util/SampleCalendarUtil.java b/src/main/java/seedu/address/model/util/SampleCalendarUtil.java new file mode 100644 index 00000000000..2f81fb20bcc --- /dev/null +++ b/src/main/java/seedu/address/model/util/SampleCalendarUtil.java @@ -0,0 +1,32 @@ +package seedu.address.model.util; + +import seedu.address.model.calendar.Calendar; +import seedu.address.model.calendar.ReadOnlyCalendar; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventDescription; +import seedu.address.model.event.EventPeriod; + +/** + * Contains utility methods for populating {@code Calendar} with sample data. + */ +public class SampleCalendarUtil { + public static Event[] getSampleEvents() { + return new Event[] { + new Event(new EventDescription("Weekly team meeting"), + new EventPeriod("2023-10-25 09:00", "2023-10-25 10:30")), + new Event(new EventDescription(" Annual health checkup"), + new EventPeriod("2023-11-10 15:15", "2023-11-10 16:00")), + new Event(new EventDescription(" Birthday celebration"), + new EventPeriod("2023-11-15 18:00", "2023-11-15 22:00")) + }; + } + + public static ReadOnlyCalendar getSampleCalendar() { + Calendar sampleCalendar = new Calendar(); + for (Event sampleEvent : getSampleEvents()) { + sampleCalendar.addEvent(sampleEvent); + } + return sampleCalendar; + } + +} diff --git a/src/main/java/seedu/address/storage/CalendarStorage.java b/src/main/java/seedu/address/storage/CalendarStorage.java index 4040e35ab1c..2aa09aebb85 100644 --- a/src/main/java/seedu/address/storage/CalendarStorage.java +++ b/src/main/java/seedu/address/storage/CalendarStorage.java @@ -1,8 +1,45 @@ package seedu.address.storage; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.model.calendar.ReadOnlyCalendar; + /** * Represents a storage for the Calendar. */ public interface CalendarStorage { + /** + * Returns the file path of the data file. + */ + Path getCalendarFilePath(); + + /** + * Returns AddressBook data as a {@link ReadOnlyCalendar}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataLoadingException if loading the data from storage failed. + */ + Optional readCalendar() throws DataLoadingException; + + /** + * @see #getCalendarFilePath() + */ + Optional readCalendar(Path filePath) throws DataLoadingException; + + /** + * Saves the given {@link ReadOnlyCalendar} to the storage. + * @param calendar cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveCalendar(ReadOnlyCalendar calendar) throws IOException; + + /** + * @see #saveCalendar(ReadOnlyCalendar) + */ + void saveCalendar(ReadOnlyCalendar addressBook, Path filePath) throws IOException; + } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedEvent.java b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java new file mode 100644 index 00000000000..4e0a48e0cf6 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java @@ -0,0 +1,80 @@ +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.event.Event; +import seedu.address.model.event.EventDescription; +import seedu.address.model.event.EventPeriod; + +/** + * Jackson-friendly version of {@link Event}. + */ +class JsonAdaptedEvent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Event's %s field is missing!"; + public static final String INVALID_FIELD_MESSAGE_FORMAT = EventPeriod.MESSAGE_CONSTRAINTS; + + private final String description; + private final String eventPeriod; + + /** + * Constructs a {@code JsonAdaptedEvent} with the given event details. + */ + @JsonCreator + public JsonAdaptedEvent(@JsonProperty("description") String description, + @JsonProperty("eventPeriod") String eventPeriod) { + this.description = description; + this.eventPeriod = eventPeriod; + } + + /** + * Converts a given {@code Event} into this class for Jackson use. + */ + public JsonAdaptedEvent(Event source) { + description = source.getDescription().getDescription(); + eventPeriod = source.getEventPeriod().getFormattedPeriod(); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Event} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event. + */ + public Event toModelType() throws IllegalValueException { + if (description == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + EventDescription.class.getSimpleName())); + } + if (!EventDescription.isValid(description)) { + throw new IllegalValueException(EventDescription.MESSAGE_CONSTRAINTS); + } + final EventDescription modelDescription = new EventDescription(description); + + // Event Periods are expected to be saved in this format yyyy-MM-dd HH:mm - yyyy-MM-dd HH:mm + String start; + String end; + if (eventPeriod == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + EventPeriod.class.getSimpleName())); + } + String[] parts = eventPeriod.split(" - "); + if (parts.length == 2) { + start = parts[0]; + end = parts[1]; + } else { + throw new IllegalValueException(String.format(INVALID_FIELD_MESSAGE_FORMAT, + EventPeriod.class.getSimpleName())); + } + if (!EventPeriod.isValidPeriod(start, end)) { + throw new IllegalValueException(EventPeriod.MESSAGE_CONSTRAINTS); + } + final EventPeriod modelEventPeriod = new EventPeriod(start, end); + + return new Event(modelDescription, modelEventPeriod); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonCalendarStorage.java b/src/main/java/seedu/address/storage/JsonCalendarStorage.java new file mode 100644 index 00000000000..98405417432 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonCalendarStorage.java @@ -0,0 +1,80 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.calendar.ReadOnlyCalendar; + +/** + * A class to access Calendar data stored as a json file on the hard disk. + */ +public class JsonCalendarStorage implements CalendarStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonCalendarStorage.class); + + private Path filePath; + + public JsonCalendarStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getCalendarFilePath() { + return filePath; + } + + @Override + public Optional readCalendar() throws DataLoadingException { + return readCalendar(filePath); + } + + /** + * Similar to {@link #readCalendar()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataLoadingException if loading the data from storage failed. + */ + public Optional readCalendar(Path filePath) throws DataLoadingException { + requireNonNull(filePath); + + Optional jsonCalendar = JsonUtil.readJsonFile( + filePath, JsonSerializableCalendar.class); + if (!jsonCalendar.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonCalendar.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataLoadingException(ive); + } + } + + @Override + public void saveCalendar(ReadOnlyCalendar calendar) throws IOException { + saveCalendar(calendar, filePath); + } + + /** + * Similar to {@link #saveCalendar(ReadOnlyCalendar)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveCalendar(ReadOnlyCalendar calendar, Path filePath) throws IOException { + requireNonNull(calendar); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableCalendar(calendar), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableCalendar.java b/src/main/java/seedu/address/storage/JsonSerializableCalendar.java new file mode 100644 index 00000000000..2efbfba88cf --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableCalendar.java @@ -0,0 +1,59 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.calendar.Calendar; +import seedu.address.model.calendar.ReadOnlyCalendar; +import seedu.address.model.event.Event; + +/** + * An Immutable Calendar that is serializable to JSON format. + */ +@JsonRootName(value = "calendar") +class JsonSerializableCalendar { + public static final String MESSAGE_DUPLICATE_EVENT = "Calendar contains duplicate event(s)."; + + private final List events = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableCalendar} with the given events. + */ + @JsonCreator + public JsonSerializableCalendar(@JsonProperty("events") List events) { + this.events.addAll(events); + } + + /** + * Converts a given {@code ReadOnlyCalendar} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableCalendar}. + */ + public JsonSerializableCalendar(ReadOnlyCalendar source) { + events.addAll(source.getEventList().stream().map(JsonAdaptedEvent::new).collect(Collectors.toList())); + } + + /** + * Converts this address book into the model's {@code AddressBook} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public Calendar toModelType() throws IllegalValueException { + Calendar calendar = new Calendar(); + for (JsonAdaptedEvent jsonAdaptedEvent : events) { + Event event = jsonAdaptedEvent.toModelType(); + if (calendar.contains(event)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_EVENT); + } + calendar.addEvent(event); + } + return calendar; + } + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 9fba0c7a1d6..e385559b189 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -8,11 +8,12 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.calendar.ReadOnlyCalendar; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends AddressBookStorage, CalendarStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataLoadingException; @@ -29,4 +30,10 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { @Override void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + @Override + Optional readCalendar() throws DataLoadingException; + + @Override + void saveCalendar(ReadOnlyCalendar calendar) throws IOException; + } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 94bf0589c16..40ee593fa9d 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -10,6 +10,7 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.calendar.ReadOnlyCalendar; /** * Manages storage of AddressBook data in local storage. @@ -18,13 +19,17 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); private AddressBookStorage addressBookStorage; + private CalendarStorage calendarStorage; private UserPrefsStorage userPrefsStorage; /** * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(AddressBookStorage addressBookStorage, + CalendarStorage calendarStorage, + UserPrefsStorage userPrefsStorage) { this.addressBookStorage = addressBookStorage; + this.calendarStorage = calendarStorage; this.userPrefsStorage = userPrefsStorage; } @@ -75,6 +80,31 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro } // ================ Calendar methods ================================= + @Override + public Path getCalendarFilePath() { + return addressBookStorage.getAddressBookFilePath(); + } + + @Override + public Optional readCalendar() throws DataLoadingException { + return readCalendar(calendarStorage.getCalendarFilePath()); + } + + @Override + public Optional readCalendar(Path filePath) throws DataLoadingException { + logger.fine("Attempting to read data from file: " + filePath); + return calendarStorage.readCalendar(filePath); + } + + @Override + public void saveCalendar(ReadOnlyCalendar calendar) throws IOException { + saveCalendar(calendar, calendarStorage.getCalendarFilePath()); + } + @Override + public void saveCalendar(ReadOnlyCalendar calendar, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + calendarStorage.saveCalendar(calendar, filePath); + } } diff --git a/src/test/data/JsonCalendarStorageTest/invalidAndValidEventCalendar.json b/src/test/data/JsonCalendarStorageTest/invalidAndValidEventCalendar.json new file mode 100644 index 00000000000..04c0664eb31 --- /dev/null +++ b/src/test/data/JsonCalendarStorageTest/invalidAndValidEventCalendar.json @@ -0,0 +1,9 @@ +{ + "events" : [ { + "description" : "Valid", + "eventPeriod" : "2023-10-25 09:00 - 2023-10-25 10:30" + }, { + "description" : "", + "eventPeriod" : "2023-11-10 15:15 - 2023-11-10 16:00" + } ] +} diff --git a/src/test/data/JsonCalendarStorageTest/invalidEventCalendar.json b/src/test/data/JsonCalendarStorageTest/invalidEventCalendar.json new file mode 100644 index 00000000000..c0c2b747ab8 --- /dev/null +++ b/src/test/data/JsonCalendarStorageTest/invalidEventCalendar.json @@ -0,0 +1,6 @@ +{ + "events" : [ { + "description" : "", + "eventPeriod" : "2023-10-25 09:00 - 2023-10-25 10:30" + } ] +} diff --git a/src/test/data/JsonCalendarStorageTest/notJsonFormatCalendar.json b/src/test/data/JsonCalendarStorageTest/notJsonFormatCalendar.json new file mode 100644 index 00000000000..a1097343b5d --- /dev/null +++ b/src/test/data/JsonCalendarStorageTest/notJsonFormatCalendar.json @@ -0,0 +1 @@ +not json format! diff --git a/src/test/data/JsonSerializableCalendarTest/duplicateEventCalendar.json b/src/test/data/JsonSerializableCalendarTest/duplicateEventCalendar.json new file mode 100644 index 00000000000..24bcb850430 --- /dev/null +++ b/src/test/data/JsonSerializableCalendarTest/duplicateEventCalendar.json @@ -0,0 +1,9 @@ +{ + "events" : [ { + "description" : "Weekly team meeting", + "eventPeriod" : "2023-10-25 09:00 - 2023-10-25 10:30" + }, { + "description" : "Weekly team meeting", + "eventPeriod" : "2023-10-25 09:00 - 2023-10-25 10:30" + } ] +} diff --git a/src/test/data/JsonSerializableCalendarTest/invalidEventCalendar.json b/src/test/data/JsonSerializableCalendarTest/invalidEventCalendar.json new file mode 100644 index 00000000000..c0c2b747ab8 --- /dev/null +++ b/src/test/data/JsonSerializableCalendarTest/invalidEventCalendar.json @@ -0,0 +1,6 @@ +{ + "events" : [ { + "description" : "", + "eventPeriod" : "2023-10-25 09:00 - 2023-10-25 10:30" + } ] +} diff --git a/src/test/data/JsonSerializableCalendarTest/typicalEventsCalendar.json b/src/test/data/JsonSerializableCalendarTest/typicalEventsCalendar.json new file mode 100644 index 00000000000..67acc329e62 --- /dev/null +++ b/src/test/data/JsonSerializableCalendarTest/typicalEventsCalendar.json @@ -0,0 +1,19 @@ +{ + "_comment": "Calendar save file which contains the same Event values as in TypicalEvents#getTypicalCalendar()", + "events" : [ { + "description" : "Conference", + "eventPeriod" : "2023-11-15 08:30 - 2023-11-15 17:00" + }, { + "description" : "Launch and Marketing Strategy Discussion", + "eventPeriod" : "2023-12-05 10:00 - 2023-12-05 12:00" + }, { + "description" : "Team Meeting", + "eventPeriod" : "2023-10-25 09:00 - 2023-10-25 10:30" + }, { + "description" : "Webinar", + "eventPeriod" : "2023-10-30 15:00 - 2023-10-30 16:30" + }, { + "description" : "Workshop", + "eventPeriod" : "2023-11-10 14:00 - 2023-11-10 16:30" + } ] +} diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index baf8ce336a2..ef217781bc4 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -29,6 +29,7 @@ import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.JsonCalendarStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.StorageManager; import seedu.address.testutil.PersonBuilder; @@ -47,8 +48,9 @@ public class LogicManagerTest { public void setUp() { JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + JsonCalendarStorage calendarStorage = new JsonCalendarStorage(temporaryFolder.resolve("calendar.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); - StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); + StorageManager storage = new StorageManager(addressBookStorage, calendarStorage, userPrefsStorage); logic = new LogicManager(model, storage); } @@ -123,7 +125,7 @@ private void assertCommandException(String inputCommand, String expectedMessage) */ private void assertCommandFailure(String inputCommand, Class expectedException, String expectedMessage) { - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getAddressBook(), model.getCalendar(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } @@ -141,7 +143,8 @@ private void assertCommandFailure(String inputCommand, Class modelManager.setCalendarFilePath(null)); + } + + @Test + public void setCalendarFilePath_validPath_setsAddressBookFilePath() { + Path path = Paths.get("calendar/file/path"); + modelManager.setCalendarFilePath(path); + assertEquals(path, modelManager.getCalendarFilePath()); + } + @Test public void hasPerson_nullPerson_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> modelManager.hasPerson(null)); @@ -122,11 +138,13 @@ public void findEvent_throwsEventNotFoundException() { public void equals() { AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); AddressBook differentAddressBook = new AddressBook(); + Calendar calendar = new CalendarBuilder().withEvent(TEST_EVENT_A).withEvent(TEST_EVENT_B).build(); + Calendar differentCalendar = new Calendar(); UserPrefs userPrefs = new UserPrefs(); // same values -> returns true - modelManager = new ModelManager(addressBook, userPrefs); - ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs); + modelManager = new ModelManager(addressBook, calendar, userPrefs); + ModelManager modelManagerCopy = new ModelManager(addressBook, calendar, userPrefs); assertTrue(modelManager.equals(modelManagerCopy)); // same object -> returns true @@ -138,13 +156,19 @@ public void equals() { // different types -> returns false assertFalse(modelManager.equals(5)); - // different addressBook -> returns false - assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs))); + // different addressBook, same calendar -> returns false + assertFalse(modelManager.equals(new ModelManager(differentAddressBook, calendar, userPrefs))); + + // same addressBook, different calendar -> returns false + assertFalse(modelManager.equals(new ModelManager(addressBook, differentCalendar, userPrefs))); + + // different addressBook, different calendar -> returns false + assertFalse(modelManager.equals(new ModelManager(differentAddressBook, differentCalendar, userPrefs))); // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); - assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + assertFalse(modelManager.equals(new ModelManager(addressBook, calendar, userPrefs))); // resets modelManager to initial state for upcoming tests modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); @@ -152,6 +176,6 @@ public void equals() { // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs(); differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath")); - assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); + assertFalse(modelManager.equals(new ModelManager(addressBook, calendar, differentUserPrefs))); } } diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/address/model/UserPrefsTest.java index b1307a70d52..c6fcb85ac5f 100644 --- a/src/test/java/seedu/address/model/UserPrefsTest.java +++ b/src/test/java/seedu/address/model/UserPrefsTest.java @@ -18,4 +18,10 @@ public void setAddressBookFilePath_nullPath_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null)); } + @Test + public void setCalendarFilePath_nullPath_throwsNullPointerException() { + UserPrefs userPrefs = new UserPrefs(); + assertThrows(NullPointerException.class, () -> userPrefs.setCalendarFilePath(null)); + } + } diff --git a/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java b/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java new file mode 100644 index 00000000000..d72faddee7f --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java @@ -0,0 +1,57 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedEvent.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalEvents.CONFERENCE; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.EventDescription; +import seedu.address.model.event.EventPeriod; + +public class JsonAdaptedEventTest { + private static final String INVALID_DESCRIPTION = ""; + private static final String INVALID_EVENT_PERIOD = "2010-10-10 10:00-2010-10-10 12:00"; + + private static final String VALID_DESCRIPTION = CONFERENCE.getDescription().getDescription(); + private static final String VALID_EVENT_PERIOD = CONFERENCE.getEventPeriod().getFormattedPeriod(); + + @Test + public void toModelType_validEventDetails_returnsEvent() throws Exception { + JsonAdaptedEvent event = new JsonAdaptedEvent(CONFERENCE); + assertEquals(CONFERENCE, event.toModelType()); + } + + @Test + public void toModelType_invalidDescription_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(INVALID_DESCRIPTION, VALID_EVENT_PERIOD); + String expectedMessage = EventDescription.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + JsonAdaptedEvent event = new JsonAdaptedEvent(null, VALID_EVENT_PERIOD); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EventDescription.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidEventPeriod_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(VALID_DESCRIPTION, INVALID_EVENT_PERIOD); + String expectedMessage = EventPeriod.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullEventPeriod_throwsIllegalValueException() { + JsonAdaptedEvent event = new JsonAdaptedEvent(VALID_DESCRIPTION, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EventPeriod.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/JsonCalendarStorageTest.java b/src/test/java/seedu/address/storage/JsonCalendarStorageTest.java new file mode 100644 index 00000000000..67abe5c5974 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonCalendarStorageTest.java @@ -0,0 +1,108 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalEvents.REVIEW; +import static seedu.address.testutil.TypicalEvents.TRAINING; +import static seedu.address.testutil.TypicalEvents.getTypicalCalendar; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.model.calendar.Calendar; +import seedu.address.model.calendar.ReadOnlyCalendar; + +public class JsonCalendarStorageTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonCalendarStorageTest"); + + @TempDir + public Path testFolder; + + @Test + public void readCalendar_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> readCalendar(null)); + } + + private java.util.Optional readCalendar(String filePath) throws Exception { + return new JsonCalendarStorage(Paths.get(filePath)).readCalendar(addToTestDataPathIfNotNull(filePath)); + } + + private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { + return prefsFileInTestDataFolder != null + ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) + : null; + } + + @Test + public void read_missingFile_emptyResult() throws Exception { + assertFalse(readCalendar("NonExistentFile.json").isPresent()); + } + + @Test + public void read_notJsonFormat_exceptionThrown() { + assertThrows(DataLoadingException.class, () -> readCalendar("notJsonFormatCalendar.json")); + } + + @Test + public void readCalendar_invalidEventCalendar_throwDataLoadingException() { + assertThrows(DataLoadingException.class, () -> readCalendar("invalidEventCalendar.json")); + } + + @Test + public void readAddressBook_invalidAndValidEventCalendar_throwDataLoadingException() { + assertThrows(DataLoadingException.class, () -> readCalendar("invalidAndValidEventCalendar.json")); + } + + @Test + public void readAndSaveCalendar_allInOrder_success() throws Exception { + Path filePath = testFolder.resolve("TempCalendar.json"); + Calendar original = getTypicalCalendar(); + JsonCalendarStorage jsonCalendarStorage = new JsonCalendarStorage(filePath); + + // Save in new file and read back + jsonCalendarStorage.saveCalendar(original, filePath); + ReadOnlyCalendar readBack = jsonCalendarStorage.readCalendar(filePath).get(); + assertEquals(original, new Calendar(readBack)); + + // Modify data, overwrite exiting file, and read back + original.addEvent(TRAINING); + jsonCalendarStorage.saveCalendar(original, filePath); + readBack = jsonCalendarStorage.readCalendar(filePath).get(); + assertEquals(original, new Calendar(readBack)); + + // Save and read without specifying file path + original.addEvent(REVIEW); + jsonCalendarStorage.saveCalendar(original); // file path not specified + readBack = jsonCalendarStorage.readCalendar().get(); // file path not specified + assertEquals(original, new Calendar(readBack)); + + } + + @Test + public void saveCalendar_nullCalendar_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveCalendar(null, "SomeFile.json")); + } + + /** + * Saves {@code calendar} at the specified {@code filePath}. + */ + private void saveCalendar(ReadOnlyCalendar calendar, String filePath) { + try { + new JsonCalendarStorage(Paths.get(filePath)) + .saveCalendar(calendar, addToTestDataPathIfNotNull(filePath)); + } catch (IOException ioe) { + throw new AssertionError("There should not be an error writing to the file.", ioe); + } + } + + @Test + public void saveAddressBook_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveCalendar(new Calendar(), null)); + } +} diff --git a/src/test/java/seedu/address/storage/JsonSerializableCalendarTest.java b/src/test/java/seedu/address/storage/JsonSerializableCalendarTest.java new file mode 100644 index 00000000000..e3025a22413 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonSerializableCalendarTest.java @@ -0,0 +1,47 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.calendar.Calendar; +import seedu.address.testutil.TypicalEvents; + +public class JsonSerializableCalendarTest { + + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableCalendarTest"); + private static final Path TYPICAL_EVENTS_FILE = TEST_DATA_FOLDER.resolve("typicalEventsCalendar.json"); + private static final Path INVALID_EVENT_FILE = TEST_DATA_FOLDER.resolve("invalidEventCalendar.json"); + private static final Path DUPLICATE_EVENT_FILE = TEST_DATA_FOLDER.resolve("duplicateEventCalendar.json"); + + @Test + public void toModelType_typicalEventsFile_success() throws Exception { + JsonSerializableCalendar dataFromFile = JsonUtil.readJsonFile(TYPICAL_EVENTS_FILE, + JsonSerializableCalendar.class).get(); + Calendar calendarFromFile = dataFromFile.toModelType(); + Calendar typicalEventsCalendar = TypicalEvents.getTypicalCalendar(); + assertEquals(calendarFromFile, typicalEventsCalendar); + } + + @Test + public void toModelType_invalidEventFile_throwsIllegalValueException() throws Exception { + JsonSerializableCalendar dataFromFile = JsonUtil.readJsonFile(INVALID_EVENT_FILE, + JsonSerializableCalendar.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicateEvents_throwsIllegalValueException() throws Exception { + JsonSerializableCalendar dataFromFile = JsonUtil.readJsonFile(DUPLICATE_EVENT_FILE, + JsonSerializableCalendar.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableCalendar.MESSAGE_DUPLICATE_EVENT, + dataFromFile::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 99a16548970..143c225aae5 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static seedu.address.testutil.TypicalEvents.getTypicalCalendar; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.nio.file.Path; @@ -14,6 +15,8 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.UserPrefs; +import seedu.address.model.calendar.Calendar; +import seedu.address.model.calendar.ReadOnlyCalendar; public class StorageManagerTest { @@ -25,8 +28,9 @@ public class StorageManagerTest { @BeforeEach public void setUp() { JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab")); + JsonCalendarStorage calendarStorage = new JsonCalendarStorage(getTempFilePath("calendar")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs")); - storageManager = new StorageManager(addressBookStorage, userPrefsStorage); + storageManager = new StorageManager(addressBookStorage, calendarStorage, userPrefsStorage); } private Path getTempFilePath(String fileName) { @@ -60,9 +64,32 @@ public void addressBookReadSave() throws Exception { assertEquals(original, new AddressBook(retrieved)); } + @Test + public void calendarReadSave() throws Exception { + /* + * Note: This is an integration test that verifies the StorageManager is properly wired to the + * {@link JsonCalendarStorage} class. + * More extensive testing of UserPref saving/reading is done in {@link JsonCalendarStorageTest} class. + */ + Calendar original = getTypicalCalendar(); + storageManager.saveCalendar(original); + ReadOnlyCalendar retrieved = storageManager.readCalendar().get(); + assertEquals(original, new Calendar(retrieved)); + } + + @Test + public void getUserPrefsFilePath() { + assertNotNull(storageManager.getUserPrefsFilePath()); + } + @Test public void getAddressBookFilePath() { assertNotNull(storageManager.getAddressBookFilePath()); } + @Test + public void getCalendarFilePath() { + assertNotNull(storageManager.getCalendarFilePath()); + } + } diff --git a/src/test/java/seedu/address/testutil/CalendarBuilder.java b/src/test/java/seedu/address/testutil/CalendarBuilder.java new file mode 100644 index 00000000000..85161280eb2 --- /dev/null +++ b/src/test/java/seedu/address/testutil/CalendarBuilder.java @@ -0,0 +1,35 @@ +package seedu.address.testutil; + +import seedu.address.model.calendar.Calendar; +import seedu.address.model.event.Event; + +/** + * A utility class to help with building Calendar objects. + * Example usage:
+ * {@code Calendar c = new CalendarBuilder().withEvent("Description", "2000-01-01 00:00", "2000-01-01 12:00") + * .build();} + */ +public class CalendarBuilder { + + private Calendar calendar; + + public CalendarBuilder() { + calendar = new Calendar(); + } + + public CalendarBuilder(Calendar calendar) { + this.calendar = calendar; + } + + /** + * Adds a new {@code Person} to the {@code Calendar} that we are building. + */ + public CalendarBuilder withEvent(Event event) { + calendar.addEvent(event); + return this; + } + + public Calendar build() { + return calendar; + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalEvents.java b/src/test/java/seedu/address/testutil/TypicalEvents.java new file mode 100644 index 00000000000..1ae7547ff53 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalEvents.java @@ -0,0 +1,69 @@ +package seedu.address.testutil; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_DATE_EARLIER; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_DATE_LATER; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_DATE_EARLIER; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_DATE_LATER; +import static seedu.address.logic.commands.CommandTestUtil.VALID_UNUSED_DESCRIPTION; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.calendar.Calendar; +import seedu.address.model.event.Event; + +/** + * A utility class containing a list of {@code Event} objects to be used in tests. + */ +public class TypicalEvents { + + public static final Event CONFERENCE = new EventBuilder().withDescription("Conference") + .withStartEndDate("2023-11-15 08:30", "2023-11-15 17:00").build(); + + public static final Event LAUNCH = new EventBuilder() + .withDescription("Launch and Marketing Strategy Discussion") + .withStartEndDate("2023-12-05 10:00", "2023-12-05 12:00").build(); + + public static final Event MEETING = new EventBuilder().withDescription("Team Meeting") + .withStartEndDate("2023-10-25 09:00", "2023-10-25 10:30").build(); + + public static final Event WEBINAR = new EventBuilder() + .withDescription("Webinar") + .withStartEndDate("2023-10-30 15:00", "2023-10-30 16:30").build(); + + public static final Event WORKSHOP = new EventBuilder() + .withDescription("Workshop") + .withStartEndDate("2023-11-10 14:00", "2023-11-10 16:30").build(); + + // Manually added + public static final Event TRAINING = new EventBuilder().withDescription("Customer Support Training") + .withStartEndDate("2023-11-20 09:00", "2023-11-20 12:00").build(); + public static final Event REVIEW = new EventBuilder().withDescription("Project Review") + .withStartEndDate("2023-10-29 15:00", "2023-10-29 16:30").build(); + + // Manually added - Event's details found in {@code CommandTestUtil} + public static final Event TEST_EVENT_A = new EventBuilder().withDescription(VALID_DESCRIPTION) + .withStartEndDate(VALID_START_DATE_EARLIER, VALID_END_DATE_EARLIER).build(); + + public static final Event TEST_EVENT_B = new EventBuilder().withDescription(VALID_UNUSED_DESCRIPTION) + .withStartEndDate(VALID_START_DATE_LATER, VALID_END_DATE_LATER).build(); + + private TypicalEvents() {} // prevents instantiation + + /** + * Returns an {@code Calendar} with all the typical events. + */ + public static Calendar getTypicalCalendar() { + Calendar calendar = new Calendar(); + for (Event event : getTypicalEvents()) { + calendar.addEvent(event); + } + return calendar; + } + + public static List getTypicalEvents() { + return new ArrayList<>(Arrays.asList(CONFERENCE, LAUNCH, MEETING, WEBINAR, WORKSHOP)); + } +}