diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..533faffe55a6 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 = "SocialCare"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 7f85c8b9258b..7d345ceb50f8 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,8 +3,11 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Objects; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; +import seedu.address.model.event.UniqueEventList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -15,6 +18,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueEventList events; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -25,6 +29,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + events = new UniqueEventList(); } public AddressBook() {} @@ -47,6 +52,14 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the event list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + this.events.setEvents(events); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -54,6 +67,7 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setEvents(newData.getEventList()); } //// person-level operations @@ -66,6 +80,14 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if an event with the same identity as {@code event} exists in the address book. + */ + public boolean hasEvent(Event event) { + requireNonNull(event); + return events.contains(event); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -74,6 +96,14 @@ public void addPerson(Person p) { persons.add(p); } + /** + * Adds an event to the address book. + * The event must not already exist in the address book. + */ + public void addEvent(Event e) { + events.add(e); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. @@ -85,6 +115,17 @@ public void updatePerson(Person target, Person editedPerson) { persons.setPerson(target, editedPerson); } + /** + * Replaces the given event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the address book. + * The event identity of {@code editedEvent} must not be the same as another existing event in the address book. + */ + public void updateEvent(Event target, Event editedEvent) { + requireNonNull(editedEvent); + + events.setEvent(target, editedEvent); + } + /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. @@ -93,11 +134,20 @@ public void removePerson(Person key) { persons.remove(key); } + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeEvent(Event key) { + events.remove(key); + } + //// util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; + return persons.asUnmodifiableObservableList().size() + " persons. " + + events.asUnmodifiableObservableList().size() + " events."; // TODO: refine later } @@ -106,15 +156,22 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getEventList() { + return events.asUnmodifiableObservableList(); + } + @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)); + && persons.equals(((AddressBook) other).persons) + && events.equals(((AddressBook) other).events)); } @Override public int hashCode() { - return persons.hashCode(); + return Objects.hash(persons, events); + // TODO: Improve way to hashCode AddressBook with multiple lists } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..78b371791271 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,6 +3,7 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -12,6 +13,9 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_EVENTS = unused -> true; + /** Clears existing backing model and replaces with the provided new data. */ void resetData(ReadOnlyAddressBook newData); @@ -42,6 +46,30 @@ public interface Model { */ void updatePerson(Person target, Person editedPerson); + /** + * Returns true if an event with the same identity as {@code event} exists in the address book. + */ + boolean hasEvent(Event event); + + /** + * Deletes the given event. + * The event must exist in the address book. + */ + void deleteEvent(Event target); + + /** + * Adds the given event. + * {@code event} must not already exist in the address book. + */ + void addEvent(Event event); + + /** + * Replaces the given event {@code target} with {@code editedEvent}. + * {@code target} must exist in the address book. + * The event identity of {@code editedEvent} must not be the same as another existing event in the address book. + */ + void updateEvent(Event target, Event editedEvent); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); @@ -51,6 +79,15 @@ public interface Model { */ void updateFilteredPersonList(Predicate predicate); + /** Returns an unmodifiable view of the filtered event list */ + ObservableList getFilteredEventList(); + + /** + * Updates the filter of the filtered event list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredEventList(Predicate predicate); + /** * Returns true if the model has previous address book states to restore. */ diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..a49a73888f8f 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -12,6 +12,7 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -22,6 +23,7 @@ public class ModelManager extends ComponentManager implements Model { private final VersionedAddressBook versionedAddressBook; private final FilteredList filteredPersons; + private final FilteredList filteredEvents; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,6 +36,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { versionedAddressBook = new VersionedAddressBook(addressBook); filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + filteredEvents = new FilteredList<>(versionedAddressBook.getEventList()); } public ModelManager() { @@ -83,7 +86,34 @@ public void updatePerson(Person target, Person editedPerson) { indicateAddressBookChanged(); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public boolean hasEvent(Event event) { + requireNonNull(event); + return versionedAddressBook.hasEvent(event); + } + + @Override + public void deleteEvent(Event target) { + versionedAddressBook.removeEvent(target); + indicateAddressBookChanged(); + } + + @Override + public void addEvent(Event event) { + versionedAddressBook.addEvent(event); + updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + indicateAddressBookChanged(); + } + + @Override + public void updateEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + versionedAddressBook.updateEvent(target, editedEvent); + indicateAddressBookChanged(); + } + + //=========== Filtered Person and Event List Accessors ============================================================= /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of @@ -100,6 +130,21 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + /** + * Returns an unmodifiable view of the list of {@code Event} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredEventList() { + return FXCollections.unmodifiableObservableList(filteredEvents); + } + + @Override + public void updateFilteredEventList(Predicate predicate) { + requireNonNull(predicate); + filteredEvents.setPredicate(predicate); + } + //=========== Undo/Redo ================================================================================= @Override @@ -144,7 +189,8 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); + && filteredPersons.equals(other.filteredPersons) + && filteredEvents.equals(other.filteredEvents); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a290..1b595f011762 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -14,4 +15,10 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the events list. + * This list will not contain any duplicate persons. + */ + ObservableList getEventList(); + } diff --git a/src/main/java/seedu/address/model/event/Date.java b/src/main/java/seedu/address/model/event/Date.java new file mode 100644 index 000000000000..48ff3045a21a --- /dev/null +++ b/src/main/java/seedu/address/model/event/Date.java @@ -0,0 +1,105 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.ParseException; +import java.text.SimpleDateFormat; + +/** + * Represents an Event's date in the application. + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)} + */ +public class Date { + public static final String MESSAGE_DATE_CONSTRAINTS = + "Event dates can take in DD-MM-YYYY input, and should not be blank"; + + /* + * First character of DD must be 0-3 + * First character of MM must be 0 or 1 + * Regex not enough to check for valid dates. Need to use a SimpleDateFormat parser as well. + */ + public static final String DATE_VALIDATION_REGEX = "[0-3]\\d-[01]\\d-\\d{4}"; + + public final String value; + + /** + * Constructs an {@code Date}. + * + * @param date A valid Date. + */ + public Date(String date) { + requireNonNull(date); + checkArgument(isValidDate(date), MESSAGE_DATE_CONSTRAINTS); + value = date; + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String test) { + if (!test.matches(DATE_VALIDATION_REGEX)) { + return false; + } + + SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy"); + df.setLenient(false); + + try { + df.parse(test); + return true; + } catch (ParseException ex) { + return false; + } + } + + /** + * Returns true if current date falls on an earlier date or on the same date as the other date. + */ + public boolean isLessThanOrEqualTo(Date otherDate) { + if (otherDate == this) { + return true; + } + + String[] dateParts = this.toString().split("-"); + //parseInt ignores leading zeros like 01 or 09 when converting from String to int + int year = Integer.parseInt(dateParts[2]); + int month = Integer.parseInt(dateParts[1]); + int day = Integer.parseInt(dateParts[0]); + + String[] otherDateParts = otherDate.toString().split("-"); + int otherYear = Integer.parseInt(otherDateParts[2]); + int otherMonth = Integer.parseInt(otherDateParts[1]); + int otherDay = Integer.parseInt(otherDateParts[0]); + + if (year > otherYear) { + //start year is more than end year + return false; + } else if (year == otherYear && month > otherMonth) { + //same year but start month is more than end month + return false; + } else if (year == otherYear && month == otherMonth && day > otherDay) { + //same year, same month but start day is more than end day + return false; + } + + return true; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Date // instanceof handles nulls + && value.equals(((Date) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/Description.java b/src/main/java/seedu/address/model/event/Description.java new file mode 100644 index 000000000000..d9929f79f81d --- /dev/null +++ b/src/main/java/seedu/address/model/event/Description.java @@ -0,0 +1,57 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's description in the application. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class Description { + public static final String MESSAGE_DESCRIPTION_CONSTRAINTS = + "Descriptions can take any values, and it should not be blank"; + + /* + * The first character of the name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String DESCRIPTION_VALIDATION_REGEX = "[^\\s].*"; + + public final String description; + + /** + * Constructs a {@code Description}. + * + * @param description A valid description. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_DESCRIPTION_CONSTRAINTS); + this.description = description; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidDescription(String test) { + return test.matches(DESCRIPTION_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Description // instanceof handles nulls + && description.equals(((Description) other).description)); // state check + } + + @Override + public int hashCode() { + return description.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/Event.java b/src/main/java/seedu/address/model/event/Event.java new file mode 100644 index 000000000000..7da26e9a4cbd --- /dev/null +++ b/src/main/java/seedu/address/model/event/Event.java @@ -0,0 +1,175 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.AppUtil.checkArgument; +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 an Event in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Event { + + public static final String MESSAGE_START_END_DATE_CONSTRAINTS = + "Start date should only be less than or equal to end date"; + + public static final String MESSAGE_START_END_TIME_CONSTRAINTS = + "Start time should only be less than or equal to end time"; + + // Identity fields + //private final Id id; + private final Name name; + private final Location location; + private final Date startDate; + private final Date endDate; + + // Data fields + private final Time startTime; + private final Time endTime; + private final Description description; + private final Set tags = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + //EventId to be added to constructor + public Event(Name name, Location location, Date startDate, Date endDate, + Time startTime, Time endTime, Description description, Set tags) { + //requireAllNonNull(id, name, location, startDate, endDate, description, tags); + requireAllNonNull(name, location, startDate, endDate, description, tags); + + //this.id = id; + this.name = name; + this.location = location; + + this.startDate = startDate; + this.endDate = endDate; + checkArgument(isValidStartAndEndDate(startDate, endDate), MESSAGE_START_END_DATE_CONSTRAINTS); + + this.startTime = startTime; + this.endTime = endTime; + checkArgument(isValidStartAndEndTime(startTime, endTime), MESSAGE_START_END_TIME_CONSTRAINTS); + + this.description = description; + this.tags.addAll(tags); + } + + /** + * Returns true if a given start date is less than or equal to end date. + */ + public static boolean isValidStartAndEndDate(Date startDate, Date endDate) { + return startDate.isLessThanOrEqualTo(endDate); + } + + /** + * Returns true if a given start time is less than or equal to end time. + */ + public static boolean isValidStartAndEndTime(Time startTime, Time endTime) { + return startTime.isLessThanOrEqualTo(endTime); + } + + public Name getName() { + return name; + } + + public Location getLocation() { + return location; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Time getStartTime() { + return startTime; + } + + public Time getEndTime() { + return endTime; + } + + public Description getDescription() { + return description; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + + /** + * Returns true if both Events of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two events. + */ + public boolean isSameEvent(Event otherEvent) { + if (otherEvent == this) { + return true; + } + + return otherEvent != null + && otherEvent.getName().equals(getName()) + && (otherEvent.getLocation().equals(getLocation()) || otherEvent.getStartDate().equals(getStartDate()) + || otherEvent.getEndDate().equals(getEndDate())); + } + + /** + * Returns true if both events have the same identity and data fields. + * This defines a stronger notion of equality between two events. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Event)) { + return false; + } + + Event otherEvent = (Event) other; + return otherEvent.getName().equals(getName()) + && otherEvent.getLocation().equals(getLocation()) + && otherEvent.getStartDate().equals(getStartDate()) + && otherEvent.getEndDate().equals(getEndDate()) + && otherEvent.getStartTime().equals(getStartTime()) + && otherEvent.getEndTime().equals(getEndTime()) + && otherEvent.getDescription().equals(getDescription()) + && otherEvent.getTags().equals(getTags()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, location, startDate, endDate, startTime, endTime, description, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Location: ").append(getLocation()) + .append(" Start Date: ").append(getStartDate()) + .append(" End Date: ").append(getEndDate()) + .append(" Start Time.java: ").append(getStartTime()) + .append(" End Time.java: ").append(getEndTime()) + .append(" Description: ").append(getDescription()) + .append(" Tags: "); + getTags().forEach(builder::append); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/event/Location.java b/src/main/java/seedu/address/model/event/Location.java new file mode 100644 index 000000000000..fa1d2691981d --- /dev/null +++ b/src/main/java/seedu/address/model/event/Location.java @@ -0,0 +1,56 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's location in the application. + * Guarantees: immutable; is valid as declared in {@link #isValidLocation(String)} + */ +public class Location { + public static final String MESSAGE_LOCATION_CONSTRAINTS = + "Locations can take any values, and it should not be blank"; + + /* + * The first character of the location must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String LOCATION_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Location}. + * + * @param location A valid location. + */ + public Location(String location) { + requireNonNull(location); + checkArgument(isValidLocation(location), MESSAGE_LOCATION_CONSTRAINTS); + value = location; + } + + /** + * Returns true if a given string is a valid location. + */ + public static boolean isValidLocation(String test) { + return test.matches(LOCATION_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Location // instanceof handles nulls + && value.equals(((Location) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/Name.java b/src/main/java/seedu/address/model/event/Name.java new file mode 100644 index 000000000000..fdb6d04de6af --- /dev/null +++ b/src/main/java/seedu/address/model/event/Name.java @@ -0,0 +1,59 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's name in the application. + * 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 name 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/event/Time.java b/src/main/java/seedu/address/model/event/Time.java new file mode 100644 index 000000000000..fa47ea810e02 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Time.java @@ -0,0 +1,84 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's time in the application. + * Guarantees: immutable; is valid as declared in {@link #isValidTime(String)} + */ +public class Time { + public static final String MESSAGE_TIME_CONSTRAINTS = + "Event times can take in HH:mm input, and should not be blank"; + + /* + * HH accepts 0-9, 1-9, 00-09, 10-19, 20-23 + * mm accepts 00-59 + */ + public static final String TIME_VALIDATION_REGEX = "([01]?[0-9]|2[0-3]):[0-5][0-9]"; + + public final String value; + + /** + * Constructs an {@code Time}. + * + * @param time A valid Time. + */ + public Time(String time) { + requireNonNull(time); + checkArgument(isValidTime(time), MESSAGE_TIME_CONSTRAINTS); + value = time; + } + + /** + * Returns true if a given string is a valid time. + */ + public static boolean isValidTime(String test) { + return test.matches(TIME_VALIDATION_REGEX); + } + + /** + * Returns true if current time occurs at an earlier period or at the same period as the other time. + */ + public boolean isLessThanOrEqualTo(Time otherTime) { + if (otherTime == this) { + return true; + } + + String[] timeParts = this.toString().split(":"); + //parseInt ignores leading zeros like 01 or 09 when converting from String to int + int minute = Integer.parseInt(timeParts[1]); + int hour = Integer.parseInt(timeParts[0]); + + String[] otherTimeParts = otherTime.toString().split(":"); + int otherMinute = Integer.parseInt(otherTimeParts[1]); + int otherHour = Integer.parseInt(otherTimeParts[0]); + + if (hour > otherHour) { + //start hour is more than end hour + return false; + } else if (hour == otherHour && minute > otherMinute) { + //same hour but start min is later than end min + return false; + } + + return true; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Time // instanceof handles nulls + && value.equals(((Time) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/UniqueEventList.java b/src/main/java/seedu/address/model/event/UniqueEventList.java new file mode 100644 index 000000000000..641c40c1a157 --- /dev/null +++ b/src/main/java/seedu/address/model/event/UniqueEventList.java @@ -0,0 +1,135 @@ +package seedu.address.model.event; + +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.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; + +/** + * A list of events that enforces uniqueness between its elements and does not allow nulls. + * An event is considered unique by comparing using {@code Event#isSameEvent(Event)}. As such, adding and updating of + * events uses Event#isSameEvent(Event) for equality so as to ensure that the event being added or updated is + * unique in terms of identity in the UniqueEventList. However, the removal of a event uses Event#equals(Object) so + * as to ensure that the event with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Event#isSameEvent(Event) + */ +public class UniqueEventList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent event as the given argument. + */ + public boolean contains(Event toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameEvent); + } + + /** + * Adds an event to the list. + * The event must not already exist in the list. + */ + public void add(Event toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateEventException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the list. + * The event identity of {@code editedEvent} must not be the same as another existing event in the list. + */ + public void setEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new EventNotFoundException(); + } + + if (!target.isSameEvent(editedEvent) && contains(editedEvent)) { + throw new DuplicateEventException(); + } + + internalList.set(index, editedEvent); + } + + /** + * Removes the equivalent event from the list. + * The event must exist in the list. + */ + public void remove(Event toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new EventNotFoundException(); + } + } + + public void setEvents(UniqueEventList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + requireAllNonNull(events); + if (!eventsAreUnique(events)) { + throw new DuplicateEventException(); + } + + internalList.setAll(events); + } + + /** + * 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 UniqueEventList // instanceof handles nulls + && internalList.equals(((UniqueEventList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code events} contains only unique events. + */ + private boolean eventsAreUnique(List events) { + for (int i = 0; i < events.size() - 1; i++) { + for (int j = i + 1; j < events.size(); j++) { + if (events.get(i).isSameEvent(events.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java new file mode 100644 index 000000000000..d213bedd5d25 --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java @@ -0,0 +1,11 @@ +package seedu.address.model.event.exceptions; + +/** + * Signals that the operation will result in duplicate Events (Events are considered duplicates if they have the same + * identity). + */ +public class DuplicateEventException extends RuntimeException { + public DuplicateEventException() { + super("Operation would result in duplicate event"); + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java new file mode 100644 index 000000000000..5117db006eaa --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.event.exceptions; + +/** + * Signals that the operation is unable to find the specified event. + */ +public class EventNotFoundException 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..dd0ee53e976b 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,6 +6,11 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Date; +import seedu.address.model.event.Description; +import seedu.address.model.event.Event; +import seedu.address.model.event.Location; +import seedu.address.model.event.Time; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -19,11 +24,11 @@ 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"), + 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"), + 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"), @@ -40,11 +45,28 @@ public static Person[] getSamplePersons() { }; } + public static Event[] getSampleEvents() { + return new Event[] { + new Event(new seedu.address.model.event.Name("Blood Donation Drive 2018"), + new Location("750E Chai Chee Road"), new Date("02-10-2018"), new Date("05-10-2018"), + new Time("11:30"), new Time("17:30"), new Description("Donation drive for blood."), + getTagSet("Public", "Donation")), + new Event(new seedu.address.model.event.Name("Youth Humanitarian Challenge"), + new Location("29 Havelock Road"), new Date("28-09-2018"), new Date("28-09-2018"), + new Time("10:00"), new Time("14:00"), new Description("To engage youths in humanitarianism."), + getTagSet("Competition")), + }; + } + public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + + for (Event sampleEvent : getSampleEvents()) { + sampleAb.addEvent(sampleEvent); + } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedEvent.java b/src/main/java/seedu/address/storage/XmlAdaptedEvent.java new file mode 100644 index 000000000000..c93bfdaa41e9 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedEvent.java @@ -0,0 +1,182 @@ +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.event.Date; +import seedu.address.model.event.Description; +import seedu.address.model.event.Event; +import seedu.address.model.event.Location; +import seedu.address.model.event.Name; +import seedu.address.model.event.Time; +import seedu.address.model.tag.Tag; + +/** + * JAXB-friendly version of the Event. + */ +public class XmlAdaptedEvent { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Event's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String location; + @XmlElement(required = true) + private String startDate; + @XmlElement(required = true) + private String endDate; + @XmlElement(required = true) + private String startTime; + @XmlElement(required = true) + private String endTime; + @XmlElement(required = true) + private String description; + + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedPerson. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedEvent() {} + + /** + * Constructs an {@code XmlAdaptedPerson} with the given event details. + */ + public XmlAdaptedEvent(String name, String location, String startDate, String endDate, + String startTime, String endTime, String description, List tagged) { + this.name = name; + this.location = location; + this.startDate = startDate; + this.endDate = endDate; + this.startTime = startTime; + this.endTime = endTime; + this.description = description; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + } + + /** + * Converts a given Event into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedEvent + */ + public XmlAdaptedEvent(Event source) { + name = source.getName().fullName; + location = source.getLocation().value; + startDate = source.getStartDate().value; + endDate = source.getEndDate().value; + startTime = source.getStartTime().value; + endTime = source.getEndTime().value; + description = source.getDescription().description; + + tagged = source.getTags().stream() + .map(XmlAdaptedTag::new) + .collect(Collectors.toList()); + } + + /** + * Converts this jaxb-friendly adapted event object into the model's Event object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event + */ + public Event toModelType() throws IllegalValueException { + final List eventTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + eventTags.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 (location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + if (!Location.isValidLocation(location)) { + throw new IllegalValueException(Location.MESSAGE_LOCATION_CONSTRAINTS); + } + final Location modelLocation = new Location(location); + + if (startDate == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName())); + } + if (!Date.isValidDate(startDate)) { + throw new IllegalValueException(Date.MESSAGE_DATE_CONSTRAINTS); + } + final Date modelStartDate = new Date(startDate); + + if (endDate == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName())); + } + if (!Date.isValidDate(endDate)) { + throw new IllegalValueException(Date.MESSAGE_DATE_CONSTRAINTS); + } + final Date modelEndDate = new Date(endDate); + + if (startTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Time.class.getSimpleName())); + } + if (!Time.isValidTime(startTime)) { + throw new IllegalValueException(Time.MESSAGE_TIME_CONSTRAINTS); + } + final Time modelStartTime = new Time(startTime); + + if (endTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Time.class.getSimpleName())); + } + if (!Time.isValidTime(endTime)) { + throw new IllegalValueException(Time.MESSAGE_TIME_CONSTRAINTS); + } + final Time modelEndTime = new Time(endTime); + + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_DESCRIPTION_CONSTRAINTS); + } + final Description modelDescription = new Description(description); + + + final Set modelTags = new HashSet<>(eventTags); + return new Event(modelName, modelLocation, modelStartDate, modelEndDate, modelStartTime, modelEndTime, + modelDescription, modelTags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedEvent)) { + return false; + } + + XmlAdaptedEvent otherPerson = (XmlAdaptedEvent) other; + return Objects.equals(name, otherPerson.name) + && Objects.equals(location, otherPerson.location) + && Objects.equals(startDate, otherPerson.startDate) + && Objects.equals(endDate, otherPerson.endDate) + && Objects.equals(startTime, otherPerson.startTime) + && Objects.equals(endTime, otherPerson.endTime) + && Objects.equals(description, otherPerson.description) + && tagged.equals(otherPerson.tagged); + } +} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index b85fa4a8f07e..8975a9543ff4 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -10,6 +10,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -20,15 +21,21 @@ public class XmlSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_EVENT = "Events list contains duplicate person(s)."; + @XmlElement private List persons; + @XmlElement + private List events; + /** * Creates an empty XmlSerializableAddressBook. * This empty constructor is required for marshalling. */ public XmlSerializableAddressBook() { persons = new ArrayList<>(); + events = new ArrayList<>(); } /** @@ -37,6 +44,7 @@ public XmlSerializableAddressBook() { public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + events.addAll(src.getEventList().stream().map(XmlAdaptedEvent::new).collect(Collectors.toList())); } /** @@ -54,6 +62,15 @@ public AddressBook toModelType() throws IllegalValueException { } addressBook.addPerson(person); } + + for (XmlAdaptedEvent e : events) { + Event event = e.toModelType(); + if (addressBook.hasEvent(event)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_EVENT); + } + addressBook.addEvent(event); + } + return addressBook; } @@ -66,6 +83,7 @@ public boolean equals(Object other) { if (!(other instanceof XmlSerializableAddressBook)) { return false; } - return persons.equals(((XmlSerializableAddressBook) other).persons); + return persons.equals(((XmlSerializableAddressBook) other).persons) + && events.equals(((XmlSerializableAddressBook) other).events); } } diff --git a/src/test/data/XmlSerializableAddressBookTest/duplicateEventAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/duplicateEventAddressBook.xml new file mode 100644 index 000000000000..72691feef6d7 --- /dev/null +++ b/src/test/data/XmlSerializableAddressBookTest/duplicateEventAddressBook.xml @@ -0,0 +1,27 @@ + + + + Blood Donation Drive 2018 + 750E Chai Chee Road + 02-10-2018 + 05-10-2018 + 11:30 + 17:30 + Donation drive for blood. + Donation + Public + + + + + Blood Donation Drive 2018 + 750E Chai Chee Road + 02-10-2018 + 05-10-2018 + 11:30 + 17:30 + Donation drive for blood. + Donation + Public + + diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidEventAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/invalidEventAddressBook.xml new file mode 100644 index 000000000000..9b2727f41915 --- /dev/null +++ b/src/test/data/XmlSerializableAddressBookTest/invalidEventAddressBook.xml @@ -0,0 +1,15 @@ + + + + + Blood Donation Drive 2018 + 750E Chai Chee Road + 1234 + 05-10-2018 + 11:30 + 17:30 + Donation drive for blood. + Donation + Public + + diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalEventsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalEventsAddressBook.xml new file mode 100644 index 000000000000..bd539b0d863e --- /dev/null +++ b/src/test/data/XmlSerializableAddressBookTest/typicalEventsAddressBook.xml @@ -0,0 +1,25 @@ + + + + + Blood Donation Drive 2018 + 750E, Chai Chee Road, #08-111 + 02-10-2018 + 05-10-2018 + 11:30 + 17:30 + Donation drive for blood. + Donation + Public + + + Youth Humanitarian Challenge + 29 Havelock Road + 28-09-2018 + 28-09-2018 + 10:00 + 14:00 + To engage youths in humanitarianism. + Competition + + diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/address/commons/core/ConfigTest.java index cc4bf567cb44..15a32f075fb0 100644 --- a/src/test/java/seedu/address/commons/core/ConfigTest.java +++ b/src/test/java/seedu/address/commons/core/ConfigTest.java @@ -14,7 +14,7 @@ public class ConfigTest { @Test public void toString_defaultObject_stringReturned() { - String defaultConfigAsString = "App title : Address App\n" + String defaultConfigAsString = "App title : SocialCare\n" + "Current log level : INFO\n" + "Preference file Location : preferences.json"; diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index c7077829f434..bbba7ef155bf 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -19,6 +19,7 @@ import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; import seedu.address.testutil.PersonBuilder; @@ -118,6 +119,26 @@ public void updatePerson(Person target, Person editedPerson) { throw new AssertionError("This method should not be called."); } + @Override + public boolean hasEvent(Event event) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteEvent(Event target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addEvent(Event event) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateEvent(Event target, Event editedEvent) { + throw new AssertionError("This method should not be called."); + } + @Override public ObservableList getFilteredPersonList() { throw new AssertionError("This method should not be called."); @@ -128,6 +149,16 @@ public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + @Override + public ObservableList getFilteredEventList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredEventList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + @Override public boolean canUndoAddressBook() { throw new AssertionError("This method should not be called."); diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index bf861fcb36c3..8a66d3b8157a 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -37,6 +37,17 @@ public class CommandTestUtil { public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_NAME_YOUTH = "Youth Humanitarian Challenge"; + public static final String VALID_LOCATION_YOUTH = "29 Havelock Road"; + public static final String VALID_START_DATE_YOUTH = "28-09-2018"; + public static final String VALID_END_DATE_YOUTH = "28-09-2018"; + public static final String VALID_START_TIME_YOUTH = "10:00"; + public static final String VALID_END_TIME_YOUTH = "14:00"; + public static final String VALID_DESCRIPTION_YOUTH = "To engage youths in humanitarianism."; + public static final String VALID_TAG_PUBLIC = "Public"; + public static final String VALID_TAG_DONATION = "Donation"; + public static final String VALID_TAG_COMPETITION = "Competition"; + public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY; diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 0d33cff49ab1..d5f747e84f9b 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -19,6 +19,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.testutil.PersonBuilder; @@ -96,6 +97,7 @@ public void getPersonList_modifyList_throwsUnsupportedOperationException() { */ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); + private final ObservableList events = FXCollections.observableArrayList(); AddressBookStub(Collection persons) { this.persons.setAll(persons); @@ -105,6 +107,11 @@ private static class AddressBookStub implements ReadOnlyAddressBook { public ObservableList getPersonList() { return persons; } + + @Override + public ObservableList getEventList() { + return events; + } } } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 7eab39d5de43..bf6b7e80b0fa 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -3,6 +3,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.testutil.TypicalEvents.BLOOD; +import static seedu.address.testutil.TypicalEvents.YOUTH; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BENSON; @@ -39,15 +41,40 @@ public void hasPerson_personInAddressBook_returnsTrue() { assertTrue(modelManager.hasPerson(ALICE)); } + @Test + public void hasEvent_nullEvent_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + modelManager.hasEvent(null); + } + + @Test + public void hasEvent_personNotInAddressBook_returnsFalse() { + assertFalse(modelManager.hasEvent(BLOOD)); + } + + @Test + public void hasEvent_personInAddressBook_returnsTrue() { + modelManager.addEvent(BLOOD); + assertTrue(modelManager.hasEvent(BLOOD)); + } + @Test public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { thrown.expect(UnsupportedOperationException.class); modelManager.getFilteredPersonList().remove(0); } + @Test + public void getFilteredEventList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + modelManager.getFilteredEventList().remove(0); + } + @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); + AddressBook addressBook = new AddressBookBuilder() + .withPerson(ALICE).withPerson(BENSON) + .withEvent(BLOOD).withEvent(YOUTH).build(); AddressBook differentAddressBook = new AddressBook(); UserPrefs userPrefs = new UserPrefs(); diff --git a/src/test/java/seedu/address/model/event/DateTest.java b/src/test/java/seedu/address/model/event/DateTest.java new file mode 100644 index 000000000000..4f1e8915e6f0 --- /dev/null +++ b/src/test/java/seedu/address/model/event/DateTest.java @@ -0,0 +1,60 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class DateTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Date(null)); + } + + @Test + public void constructor_invalidDate_throwsIllegalArgumentException() { + String invalidDate = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Date(invalidDate)); + } + + @Test + public void isValidDate() { + // null Date + Assert.assertThrows(NullPointerException.class, () -> Date.isValidDate(null)); + + // blank Date + assertFalse(Date.isValidDate("")); // empty string + assertFalse(Date.isValidDate(" ")); // spaces only + + // missing parts + assertFalse(Date.isValidDate("-12-2018")); // missing day + assertFalse(Date.isValidDate("31--2018")); // missing month + assertFalse(Date.isValidDate("02-05-")); // missing year + + // invalid parts + assertFalse(Date.isValidDate("123-08-2008")); // invalid day with 3 characters + assertFalse(Date.isValidDate("02-123-2008")); // invalid month with 3 characters + assertFalse(Date.isValidDate("02-12-20081")); // invalid year with 5 characters + assertFalse(Date.isValidDate("42-08-2008")); // invalid day with first character 4 + assertFalse(Date.isValidDate("39-08-2020")); // invalid day with first character 3 + assertFalse(Date.isValidDate("05-13-2020")); // invalid month more than 12 + assertFalse(Date.isValidDate("02--05-2018")); // double dash between day and month + assertFalse(Date.isValidDate("02-05--2018")); // double dash between month and year + assertFalse(Date.isValidDate("02/05/2018")); // invalid delimiter + assertFalse(Date.isValidDate("29-02-2018")); // non-leap year 2018 + assertFalse(Date.isValidDate("31-04-2018")); // invalid day for April + assertFalse(Date.isValidDate("31-06-2018")); // invalid day for June + assertFalse(Date.isValidDate("31-09-2018")); // invalid day for September + assertFalse(Date.isValidDate("31-11-2018")); // invalid day for September + + // valid Date + assertTrue(Date.isValidDate("05-08-2018")); + assertTrue(Date.isValidDate("01-01-2018")); // first day of January + assertTrue(Date.isValidDate("15-06-2018")); // middle of June + assertTrue(Date.isValidDate("31-12-2018")); // last day of December + assertTrue(Date.isValidDate("29-02-2020")); // leap year 2020 + assertTrue(Date.isValidDate("29-02-2024")); // leap year 2024 + } +} diff --git a/src/test/java/seedu/address/model/event/DescriptionTest.java b/src/test/java/seedu/address/model/event/DescriptionTest.java new file mode 100644 index 000000000000..667108eabbf4 --- /dev/null +++ b/src/test/java/seedu/address/model/event/DescriptionTest.java @@ -0,0 +1,37 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class DescriptionTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Description(null)); + } + + @Test + public void constructor_invalidDescription_throwsIllegalArgumentException() { + String invalidDescription = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Description(invalidDescription)); + } + + @Test + public void isValidDescription() { + // null description + Assert.assertThrows(NullPointerException.class, () -> Description.isValidDescription(null)); + + // invalid descriptions + assertFalse(Description.isValidDescription("")); // empty string + assertFalse(Description.isValidDescription(" ")); // spaces only + + // valid descriptions + assertTrue(Description.isValidDescription("To engage youths in humanitarianism.")); + assertTrue(Description.isValidDescription("-")); // one character + assertTrue(Description.isValidDescription("Singapore’s blood needs are currently shouldered by 1.8% of " + + "Singapore’s residential population. #BloodDonationDrive2018")); // long description + } +} diff --git a/src/test/java/seedu/address/model/event/EventTest.java b/src/test/java/seedu/address/model/event/EventTest.java new file mode 100644 index 000000000000..06664d493715 --- /dev/null +++ b/src/test/java/seedu/address/model/event/EventTest.java @@ -0,0 +1,144 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_DATE_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_LOCATION_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_DATE_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_TIME_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_COMPETITION; +import static seedu.address.testutil.TypicalEvents.BLOOD; +import static seedu.address.testutil.TypicalEvents.YOUTH; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.testutil.Assert; +import seedu.address.testutil.EventBuilder; + +public class EventTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + Event event = new EventBuilder().build(); + thrown.expect(UnsupportedOperationException.class); + event.getTags().remove(0); + } + + @Test + public void isValidStartAndEndDate() { + // BLOOD startDate - 02-10-2018 + // BLOOD endDate - 05-10-2018 + + // startDate before endDate -> returns true + assertTrue(BLOOD.isValidStartAndEndDate(BLOOD.getStartDate(), BLOOD.getEndDate())); + + // startDate same as endDate -> returns true + Event editedBlood = new EventBuilder(BLOOD).withStartDate(BLOOD.getEndDate().toString()).build(); + assertTrue(editedBlood.isValidStartAndEndDate(editedBlood.getStartDate(), editedBlood.getEndDate())); + + // startDate more than endDate -> returns IllegalArgumentException + Assert.assertThrows(IllegalArgumentException.class, () -> new EventBuilder(BLOOD) + .withStartDate("06-10-2018").build()); + } + + @Test + public void isValidStartAndEndTime() { + // BLOOD startTime - 11:30 + // BLOOD endTime - 17:30 + + // startTime before endTime -> returns true + assertTrue(BLOOD.isValidStartAndEndTime(BLOOD.getStartTime(), BLOOD.getEndTime())); + + // startTime same as endTime -> returns true + Event editedBlood = new EventBuilder(BLOOD).withStartTime(BLOOD.getEndTime().toString()).build(); + assertTrue(editedBlood.isValidStartAndEndTime(editedBlood.getStartTime(), editedBlood.getEndTime())); + + // startTime more than endTime -> returns IllegalArgumentException + Assert.assertThrows(IllegalArgumentException.class, () -> new EventBuilder(BLOOD) + .withStartTime("18:30").build()); + } + + @Test + public void isSameEvent() { + // same object -> returns true + assertTrue(BLOOD.isSameEvent(BLOOD)); + + // null -> returns false + assertFalse(BLOOD.isSameEvent(null)); + + // different location, start date and end date -> returns false + Event editedBlood = new EventBuilder(BLOOD).withLocation(VALID_LOCATION_YOUTH) + .withStartDate(VALID_START_DATE_YOUTH).withEndDate(VALID_END_DATE_YOUTH).build(); + assertFalse(BLOOD.isSameEvent(editedBlood)); + + // different name -> returns false + editedBlood = new EventBuilder(BLOOD).withName(VALID_NAME_YOUTH).build(); + assertFalse(BLOOD.isSameEvent(editedBlood)); + + // same name, same location, same start and end date, different attributes -> returns true + editedBlood = new EventBuilder(BLOOD).withDescription(VALID_DESCRIPTION_YOUTH) + .withStartTime(VALID_START_TIME_YOUTH).withEndTime(VALID_END_TIME_YOUTH) + .withTags(VALID_TAG_COMPETITION).build(); + assertTrue(BLOOD.isSameEvent(editedBlood)); + + // same name, same email, different attributes -> returns true + editedBlood = new EventBuilder(BLOOD).withLocation(VALID_LOCATION_YOUTH) + .withDescription(VALID_DESCRIPTION_YOUTH).withTags(VALID_TAG_COMPETITION).build(); + assertTrue(BLOOD.isSameEvent(editedBlood)); + } + + @Test + public void equals() { + // same values -> returns true + Event bloodCopy = new EventBuilder(BLOOD).build(); + assertTrue(BLOOD.equals(bloodCopy)); + + // same object -> returns true + assertTrue(BLOOD.equals(BLOOD)); + + // null -> returns false + assertFalse(BLOOD.equals(null)); + + // different type -> returns false + assertFalse(BLOOD.equals(5)); + + // different event -> returns false + assertFalse(BLOOD.equals(YOUTH)); + + // different name -> returns false + Event editedBlood = new EventBuilder(BLOOD).withName(VALID_NAME_YOUTH).build(); + assertFalse(BLOOD.equals(editedBlood)); + + // different location -> returns false + editedBlood = new EventBuilder(BLOOD).withLocation(VALID_LOCATION_YOUTH).build(); + assertFalse(BLOOD.equals(editedBlood)); + + // different startDate -> returns false + editedBlood = new EventBuilder(BLOOD).withStartDate(VALID_START_DATE_YOUTH).build(); + assertFalse(BLOOD.equals(editedBlood)); + + // different startTime -> returns false + editedBlood = new EventBuilder(BLOOD).withStartTime(VALID_START_TIME_YOUTH).build(); + assertFalse(BLOOD.equals(editedBlood)); + + // different endTime -> returns false + editedBlood = new EventBuilder(BLOOD).withEndTime(VALID_END_TIME_YOUTH).build(); + assertFalse(BLOOD.equals(editedBlood)); + + // different endTime -> returns false + editedBlood = new EventBuilder(BLOOD).withDescription(VALID_DESCRIPTION_YOUTH).build(); + assertFalse(BLOOD.equals(editedBlood)); + + // different tags -> returns false + editedBlood = new EventBuilder(BLOOD).withTags(VALID_TAG_COMPETITION).build(); + assertFalse(BLOOD.equals(editedBlood)); + } +} diff --git a/src/test/java/seedu/address/model/event/LocationTest.java b/src/test/java/seedu/address/model/event/LocationTest.java new file mode 100644 index 000000000000..7b391bbe7b74 --- /dev/null +++ b/src/test/java/seedu/address/model/event/LocationTest.java @@ -0,0 +1,36 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class LocationTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Location(null)); + } + + @Test + public void constructor_invalidLocation_throwsIllegalArgumentException() { + String invalidLocation = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Location(invalidLocation)); + } + + @Test + public void isValidLocation() { + // null location + Assert.assertThrows(NullPointerException.class, () -> Location.isValidLocation(null)); + + // invalid locations + assertFalse(Location.isValidLocation("")); // empty string + assertFalse(Location.isValidLocation(" ")); // spaces only + + // valid locations + assertTrue(Location.isValidLocation("19 Tanglin Road, #01-355")); + assertTrue(Location.isValidLocation("-")); // one character + assertTrue(Location.isValidLocation("5 Business Park IT BUILDING, 609914, Singapore")); // long location + } +} diff --git a/src/test/java/seedu/address/model/event/NameTest.java b/src/test/java/seedu/address/model/event/NameTest.java new file mode 100644 index 000000000000..6c11483348e3 --- /dev/null +++ b/src/test/java/seedu/address/model/event/NameTest.java @@ -0,0 +1,42 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class NameTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Name(null)); + } + + @Test + public void constructor_invalidName_throwsIllegalArgumentException() { + String invalidName = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Name(invalidName)); + } + + @Test + public void isValidName() { + // null name + Assert.assertThrows(NullPointerException.class, () -> Name.isValidName(null)); + + // invalid name + assertFalse(Name.isValidName("")); // empty string + assertFalse(Name.isValidName(" ")); // spaces only + assertFalse(Name.isValidName("^")); // only non-alphanumeric characters + assertFalse(Name.isValidName("charity*")); // contains non-alphanumeric characters + assertFalse(Name.isValidName("charity.")); // contains non-alphanumeric characters + + // valid name + assertTrue(Name.isValidName("donation")); // alphabets only + assertTrue(Name.isValidName("12345")); // numbers only + assertTrue(Name.isValidName("flag 2nd")); // alphanumeric characters + assertTrue(Name.isValidName("Flag Day")); // with capital letters + assertTrue(Name.isValidName("Blood Donation Drive 2018")); // long names + } +} diff --git a/src/test/java/seedu/address/model/event/TimeTest.java b/src/test/java/seedu/address/model/event/TimeTest.java new file mode 100644 index 000000000000..a0736151c6d0 --- /dev/null +++ b/src/test/java/seedu/address/model/event/TimeTest.java @@ -0,0 +1,50 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class TimeTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Time(null)); + } + + @Test + public void constructor_invalidTime_throwsIllegalArgumentException() { + String invalidTime = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Time(invalidTime)); + } + + @Test + public void isValidTime() { + // null Time + Assert.assertThrows(NullPointerException.class, () -> Time.isValidTime(null)); + + // blank Time + assertFalse(Time.isValidTime("")); // empty string + assertFalse(Time.isValidTime(" ")); // spaces only + + // missing parts + assertFalse(Time.isValidTime(":59")); // missing hour + assertFalse(Time.isValidTime("1:")); // missing minute + assertFalse(Time.isValidTime("12:")); // missing minute + + // invalid parts + assertFalse(Time.isValidTime("25:00")); // invalid hour over 24 + assertFalse(Time.isValidTime("13:60")); // invalid minute over 59 + assertFalse(Time.isValidTime("1122")); // no delimiter + assertFalse(Time.isValidTime("11-22")); // wrong delimiter + assertFalse(Time.isValidTime("13:6")); // invalid one character for minute + + + // valid Time + assertTrue(Time.isValidTime("15:15")); // 3:15 PM + assertTrue(Time.isValidTime("9:00")); // valid one character for hour + assertTrue(Time.isValidTime("0:00")); // 12:00 AM + assertTrue(Time.isValidTime("23:59")); // 11:59 PM + } +} diff --git a/src/test/java/seedu/address/model/event/UniqueEventListTest.java b/src/test/java/seedu/address/model/event/UniqueEventListTest.java new file mode 100644 index 000000000000..cb0b03146ce3 --- /dev/null +++ b/src/test/java/seedu/address/model/event/UniqueEventListTest.java @@ -0,0 +1,185 @@ +package seedu.address.model.event; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_COMPETITION; +import static seedu.address.testutil.TypicalEvents.BLOOD; +import static seedu.address.testutil.TypicalEvents.YOUTH; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; +import seedu.address.testutil.EventBuilder; + +public class UniqueEventListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final UniqueEventList uniqueEventList = new UniqueEventList(); + + @Test + public void contains_nullEvent_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.contains(null); + } + + @Test + public void contains_eventNotInList_returnsFalse() { + assertFalse(uniqueEventList.contains(BLOOD)); + } + + @Test + public void contains_eventInList_returnsTrue() { + uniqueEventList.add(BLOOD); + assertTrue(uniqueEventList.contains(BLOOD)); + } + + @Test + public void contains_eventWithSameIdentityFieldsInList_returnsTrue() { + uniqueEventList.add(BLOOD); + Event editedBlood = new EventBuilder(BLOOD).withDescription(VALID_DESCRIPTION_YOUTH) + .withTags(VALID_TAG_COMPETITION).build(); + assertTrue(uniqueEventList.contains(editedBlood)); + } + + @Test + public void add_nullEvent_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.add(null); + } + + @Test + public void add_duplicateEvent_throwsDuplicateEventException() { + uniqueEventList.add(BLOOD); + thrown.expect(DuplicateEventException.class); + uniqueEventList.add(BLOOD); + } + + @Test + public void setEvent_nullTargetEvent_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.setEvent(null, BLOOD); + } + + @Test + public void setEvent_nullEditedEvent_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.setEvent(BLOOD, null); + } + + @Test + public void setEvent_targetEventNotInList_throwsEventNotFoundException() { + thrown.expect(EventNotFoundException.class); + uniqueEventList.setEvent(BLOOD, BLOOD); + } + + @Test + public void setEvent_editedEventIsSameEvent_success() { + uniqueEventList.add(BLOOD); + uniqueEventList.setEvent(BLOOD, BLOOD); + UniqueEventList expectedUniqueEventList = new UniqueEventList(); + expectedUniqueEventList.add(BLOOD); + assertEquals(expectedUniqueEventList, uniqueEventList); + } + + @Test + public void setEvent_editedEventHasSameIdentity_success() { + uniqueEventList.add(BLOOD); + Event editedBlood = new EventBuilder(BLOOD).withDescription(VALID_DESCRIPTION_YOUTH) + .withTags(VALID_TAG_COMPETITION).build(); + uniqueEventList.setEvent(BLOOD, editedBlood); + UniqueEventList expectedUniqueEventList = new UniqueEventList(); + expectedUniqueEventList.add(editedBlood); + assertEquals(expectedUniqueEventList, uniqueEventList); + } + + @Test + public void setEvent_editedEventHasDifferentIdentity_success() { + uniqueEventList.add(BLOOD); + uniqueEventList.setEvent(BLOOD, YOUTH); + UniqueEventList expectedUniqueEventList = new UniqueEventList(); + expectedUniqueEventList.add(YOUTH); + assertEquals(expectedUniqueEventList, uniqueEventList); + } + + @Test + public void setEvent_editedEventHasNonUniqueIdentity_throwsDuplicateEventException() { + uniqueEventList.add(BLOOD); + uniqueEventList.add(YOUTH); + thrown.expect(DuplicateEventException.class); + uniqueEventList.setEvent(BLOOD, YOUTH); + } + + @Test + public void remove_nullEvent_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.remove(null); + } + + @Test + public void remove_eventDoesNotExist_throwsEventNotFoundException() { + thrown.expect(EventNotFoundException.class); + uniqueEventList.remove(BLOOD); + } + + @Test + public void remove_existingEvent_removesEvent() { + uniqueEventList.add(BLOOD); + uniqueEventList.remove(BLOOD); + UniqueEventList expectedUniqueEventList = new UniqueEventList(); + assertEquals(expectedUniqueEventList, uniqueEventList); + } + + @Test + public void setEvents_nullUniqueEventList_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.setEvents((UniqueEventList) null); + } + + @Test + public void setEvents_uniqueEventList_replacesOwnListWithProvidedUniqueEventList() { + uniqueEventList.add(BLOOD); + UniqueEventList expectedUniqueEventList = new UniqueEventList(); + expectedUniqueEventList.add(YOUTH); + uniqueEventList.setEvents(expectedUniqueEventList); + assertEquals(expectedUniqueEventList, uniqueEventList); + } + + @Test + public void setEvents_nullList_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + uniqueEventList.setEvents((List) null); + } + + @Test + public void setEvents_list_replacesOwnListWithProvidedList() { + uniqueEventList.add(BLOOD); + List eventList = Collections.singletonList(YOUTH); + uniqueEventList.setEvents(eventList); + UniqueEventList expectedUniqueEventList = new UniqueEventList(); + expectedUniqueEventList.add(YOUTH); + assertEquals(expectedUniqueEventList, uniqueEventList); + } + + @Test + public void setEvents_listWithDuplicateEvents_throwsDuplicateEventException() { + List listWithDuplicateEvents = Arrays.asList(BLOOD, BLOOD); + thrown.expect(DuplicateEventException.class); + uniqueEventList.setEvents(listWithDuplicateEvents); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + uniqueEventList.asUnmodifiableObservableList().remove(0); + } +} diff --git a/src/test/java/seedu/address/storage/XmlAdaptedEventTest.java b/src/test/java/seedu/address/storage/XmlAdaptedEventTest.java new file mode 100644 index 000000000000..0d9bdeaf5104 --- /dev/null +++ b/src/test/java/seedu/address/storage/XmlAdaptedEventTest.java @@ -0,0 +1,176 @@ +package seedu.address.storage; + +import static org.junit.Assert.assertEquals; +import static seedu.address.storage.XmlAdaptedEvent.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.TypicalEvents.YOUTH; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.Date; +import seedu.address.model.event.Description; +import seedu.address.model.event.Location; +import seedu.address.model.event.Name; +import seedu.address.model.event.Time; +import seedu.address.testutil.Assert; + +public class XmlAdaptedEventTest { + private static final String INVALID_NAME = " Bl@@d"; + private static final String INVALID_LOCATION = " "; + private static final String INVALID_START_DATE = "123"; + private static final String INVALID_END_DATE = "456"; + private static final String INVALID_START_TIME = "789"; + private static final String INVALID_END_TIME = "555"; + private static final String INVALID_DESCRIPTION = " "; + private static final String INVALID_TAG = "#friend"; + + private static final String VALID_NAME = YOUTH.getName().toString(); + private static final String VALID_LOCATION = YOUTH.getLocation().toString(); + private static final String VALID_START_DATE = YOUTH.getStartDate().toString(); + private static final String VALID_END_DATE = YOUTH.getEndDate().toString(); + private static final String VALID_START_TIME = YOUTH.getStartTime().toString(); + private static final String VALID_END_TIME = YOUTH.getEndTime().toString(); + private static final String VALID_DESCRIPTION = YOUTH.getDescription().toString(); + private static final List VALID_TAGS = YOUTH.getTags().stream() + .map(XmlAdaptedTag::new) + .collect(Collectors.toList()); + + @Test + public void toModelType_validEventDetails_returnsEvent() throws Exception { + XmlAdaptedEvent event = new XmlAdaptedEvent(YOUTH); + assertEquals(YOUTH, event.toModelType()); + } + + @Test + public void toModelType_invalidName_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(INVALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Name.MESSAGE_NAME_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullName_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(null, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidLocation_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, INVALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Location.MESSAGE_LOCATION_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullLocation_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(VALID_NAME, null, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Location.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidStartDate_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, INVALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Date.MESSAGE_DATE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullStartDate_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, null, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidEndDate_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, INVALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Date.MESSAGE_DATE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullEndDate_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, null, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidStartTime_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + INVALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Time.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullStartTime_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + null, VALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Time.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidEndTime_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, INVALID_END_TIME, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Time.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullEndTime_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, null, VALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Time.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidDescription_throwsIllegalValueException() { + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, INVALID_DESCRIPTION, VALID_TAGS); + String expectedMessage = Description.MESSAGE_DESCRIPTION_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + XmlAdaptedEvent event = new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, null, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Description.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidTags_throwsIllegalValueException() { + List invalidTags = new ArrayList<>(VALID_TAGS); + invalidTags.add(new XmlAdaptedTag(INVALID_TAG)); + XmlAdaptedEvent event = + new XmlAdaptedEvent(VALID_NAME, VALID_LOCATION, VALID_START_DATE, VALID_END_DATE, + VALID_START_TIME, VALID_END_TIME, VALID_DESCRIPTION, invalidTags); + Assert.assertThrows(IllegalValueException.class, event::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java index 55a4f10957b3..b9203599c301 100644 --- a/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java @@ -12,6 +12,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.XmlUtil; import seedu.address.model.AddressBook; +import seedu.address.testutil.TypicalEvents; import seedu.address.testutil.TypicalPersons; public class XmlSerializableAddressBookTest { @@ -21,6 +22,10 @@ public class XmlSerializableAddressBookTest { private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.xml"); private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.xml"); + private static final Path TYPICAL_EVENTS_FILE = TEST_DATA_FOLDER.resolve("typicalEventsAddressBook.xml"); + private static final Path INVALID_EVENT_FILE = TEST_DATA_FOLDER.resolve("invalidEventAddressBook.xml"); + private static final Path DUPLICATE_EVENT_FILE = TEST_DATA_FOLDER.resolve("duplicateEventAddressBook.xml"); + @Rule public ExpectedException thrown = ExpectedException.none(); @@ -50,4 +55,29 @@ public void toModelType_duplicatePersons_throwsIllegalValueException() throws Ex dataFromFile.toModelType(); } + @Test + public void toModelType_typicalEventsFile_success() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(TYPICAL_EVENTS_FILE, + XmlSerializableAddressBook.class); + AddressBook addressBookFromFile = dataFromFile.toModelType(); + AddressBook typicalEventsAddressBook = TypicalEvents.getTypicalAddressBook(); + assertEquals(addressBookFromFile, typicalEventsAddressBook); + } + + @Test + public void toModelType_invalidEventFile_throwsIllegalValueException() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_EVENT_FILE, + XmlSerializableAddressBook.class); + thrown.expect(IllegalValueException.class); + dataFromFile.toModelType(); + } + + @Test + public void toModelType_duplicateEvents_throwsIllegalValueException() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(DUPLICATE_EVENT_FILE, + XmlSerializableAddressBook.class); + thrown.expect(IllegalValueException.class); + thrown.expectMessage(XmlSerializableAddressBook.MESSAGE_DUPLICATE_EVENT); + dataFromFile.toModelType(); + } } diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index d53799fd1102..5835edb86aef 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -1,6 +1,7 @@ package seedu.address.testutil; import seedu.address.model.AddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -28,6 +29,15 @@ public AddressBookBuilder withPerson(Person person) { return this; } + /** + * Adds a new {@code Event} to the {@code AddressBook} that we are building. + */ + public AddressBookBuilder withEvent(Event event) { + addressBook.addEvent(event); + return this; + } + + public AddressBook build() { return addressBook; } diff --git a/src/test/java/seedu/address/testutil/EventBuilder.java b/src/test/java/seedu/address/testutil/EventBuilder.java new file mode 100644 index 000000000000..42bf6c9c9efc --- /dev/null +++ b/src/test/java/seedu/address/testutil/EventBuilder.java @@ -0,0 +1,128 @@ +package seedu.address.testutil; + +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.event.Date; +import seedu.address.model.event.Description; +import seedu.address.model.event.Event; +import seedu.address.model.event.Location; +import seedu.address.model.event.Name; +import seedu.address.model.event.Time; +import seedu.address.model.tag.Tag; +import seedu.address.model.util.SampleDataUtil; + +/** + * A utility class to help with building Event objects. + */ +public class EventBuilder { + public static final String DEFAULT_NAME = "Blood Donation Drive 2018"; + public static final String DEFAULT_LOCATION = "750E, Chai Chee Road, #08-111"; + public static final String DEFAULT_START_DATE = "02-10-2018"; + public static final String DEFAULT_END_DATE = "05-10-2018"; + public static final String DEFAULT_START_TIME = "11:30"; + public static final String DEFAULT_END_TIME = "17:30"; + public static final String DEFAULT_DESCRIPTION = "Donation drive for blood."; + + private Name name; + private Location location; + private Date startDate; + private Date endDate; + private Time startTime; + private Time endTime; + private Description description; + private Set tags; + + public EventBuilder() { + name = new Name(DEFAULT_NAME); + location = new Location(DEFAULT_LOCATION); + startDate = new Date(DEFAULT_START_DATE); + endDate = new Date(DEFAULT_END_DATE); + startTime = new Time(DEFAULT_START_TIME); + endTime = new Time(DEFAULT_END_TIME); + description = new Description(DEFAULT_DESCRIPTION); + tags = new HashSet<>(); + } + + /** + * Initializes the PersonBuilder with the data of {@code personToCopy}. + */ + public EventBuilder(Event eventToCopy) { + name = eventToCopy.getName(); + location = eventToCopy.getLocation(); + startDate = eventToCopy.getStartDate(); + endDate = eventToCopy.getEndDate(); + startTime = eventToCopy.getStartTime(); + endTime = eventToCopy.getEndTime(); + description = eventToCopy.getDescription(); + tags = new HashSet<>(eventToCopy.getTags()); + } + + /** + * Sets the {@code Name} of the {@code Event} that we are building. + */ + public EventBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Event} that we are building. + */ + public EventBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + return this; + } + + /** + * Sets the {@code Location} of the {@code Event} that we are building. + */ + public EventBuilder withLocation(String location) { + this.location = new Location(location); + return this; + } + + /** + * Sets the start {@code Date} of the {@code Event} that we are building. + */ + public EventBuilder withStartDate(String startDate) { + this.startDate = new Date(startDate); + return this; + } + /** + * Sets the end {@code Date} of the {@code Event} that we are building. + */ + public EventBuilder withEndDate(String endDate) { + this.endDate = new Date(endDate); + return this; + } + + /** + * Sets the start {@code Time} of the {@code Event} that we are building. + */ + public EventBuilder withStartTime(String startTime) { + this.startTime = new Time(startTime); + return this; + } + + /** + * Sets the end {@code Time} of the {@code Event} that we are building. + */ + public EventBuilder withEndTime(String endTime) { + this.endTime = new Time(endTime); + return this; + } + + /** + * Sets the {@code Description} of the {@code Event} that we are building. + */ + public EventBuilder withDescription(String description) { + this.description = new Description(description); + return this; + } + + public Event build() { + return new Event(name, location, startDate, endDate, startTime, endTime, description, tags); + } + +} 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 000000000000..0a4cfce94dc1 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalEvents.java @@ -0,0 +1,51 @@ +package seedu.address.testutil; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_DATE_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_LOCATION_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_DATE_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_TIME_YOUTH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_COMPETITION; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +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 BLOOD = new EventBuilder().withName("Blood Donation Drive 2018") + .withLocation("750E, Chai Chee Road, #08-111").withStartDate("02-10-2018").withEndDate("05-10-2018") + .withStartTime("11:30").withEndTime("17:30").withDescription("Donation drive for blood.") + .withTags("Public", "Donation").build(); + + // Manually added - Event's details found in {@code CommandTestUtil} + public static final Event YOUTH = new EventBuilder().withName(VALID_NAME_YOUTH) + .withLocation(VALID_LOCATION_YOUTH).withStartDate(VALID_START_DATE_YOUTH).withEndDate(VALID_END_DATE_YOUTH) + .withStartTime(VALID_START_TIME_YOUTH).withEndTime(VALID_END_TIME_YOUTH) + .withDescription(VALID_DESCRIPTION_YOUTH).withTags(VALID_TAG_COMPETITION).build(); + + private TypicalEvents() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical events. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (Event event : getTypicalEvents()) { + ab.addEvent(event); + } + return ab; + } + + public static List getTypicalEvents() { + return new ArrayList<>(Arrays.asList(BLOOD, YOUTH)); + } +}