diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000000..a97fcbfb8abb --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,36 @@ +# CircleCI configuration file +# For more details see https://circleci.com/docs/2.0/ + +version: 2 +jobs: + build: + docker: + - image: openjdk:8-jdk + working_directory: ~/project + steps: + - checkout + - run: + name: Setup environment variables + command: echo 'export TERM=dumb' >>"$BASH_ENV" + - restore_cache: + keys: + # Increment the version number e.g. v2-gradle-cache-... + # to force the caches to be "invalidated". + - v1-gradle-cache-{{ checksum "build.gradle" }} + - v1-gradle-cache- + - run: + name: Build documentation + command: ./gradlew clean asciidoctor + - store_artifacts: + path: ~/project/build/docs/html5 + destination: docs + - run: + name: Cleanup for save_cache + command: >- + rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + - save_cache: + key: v1-gradle-cache-{{ checksum "build.gradle" }} + paths: + - ~/.gradle/caches + - ~/.gradle/wrapper diff --git a/README.adoc b/README.adoc index 03eff3a4d191..bacae1f82026 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 4) += Medeina ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103JAN2018-F14-B2/main[image:https://travis-ci.org/CS2103JAN2018-F14-B2/main.svg?branch=master[Build Status]] +https://coveralls.io/github/CS2103JAN2018-F14-B2/main?branch=master[image:https://coveralls.io/repos/github/CS2103JAN2018-F14-B2/main/badge.svg?branch=master[Coverage Status]] +https://circleci.com/gh/CS2103JAN2018-F14-B2/main[image:https://circleci.com/gh/CS2103JAN2018-F14-B2/main.svg?style=svg[CircleCi]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,26 +13,35 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. + +Medeina is a desktop application developed for **veterinarians and their assistants** to manage contacts, pet patient list and appointments seamlessly. + +It is a specialized application that provides **dual functionality** of an address book and a task manager, to assist veterinarians and their assistants in their day-to-day practices. + +Medeina is written in the Java programming language and conforms to the Object-Oriented Programming (OOP) style. This is to ensure that future upgrades can be integrated easily. + +== Key Features + +Customized appointment tracking:: Medeina offers a clean and organized user interface to track your appointments easily. You can expect an overview of upcoming appointments, to detailed information on pet patient involved and point of contact for an appointment. + +Easily manage dependencies amongst contacts, pet patient list and appointments:: You no longer need to worry about mistyping a pet's name and/or owner's information when making a new appointment. Medeina will check for you. + +Simple and intuitive user commands:: Medeina keeps to simple and intuitive commands to make things easy for you. Be it adding an appointment, new contact or new patient, you just need to remember one command: `add`. For users familiar with Command Line Interface (CLI) or Windows command prompt, rejoice! You will find much familiarity in Medeina's list of commands. + +Convenient tagging:: Use tags to easily distinguish different types of contacts, pet patients and appointments at a glance. + +Runs on all mainstream OS:: Hassle-free. Get Medeina now and use it today. == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. -* Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4] +* Original source: https://github.com/se-edu/addressbook-level4[Address Book Level 4] by https://github.com/se-edu/[SE-EDU] +* Application icon: Made by http://www.freepik.com[Freepik] from http://www.flaticon.com[FLATICON] == Licence : link:LICENSE[MIT] diff --git a/Unused/CalendarViewCommand.java b/Unused/CalendarViewCommand.java new file mode 100644 index 000000000000..fb8bb59b75c2 --- /dev/null +++ b/Unused/CalendarViewCommand.java @@ -0,0 +1,38 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ChangeCalendarViewEvent; +import seedu.address.logic.commands.exceptions.CommandException; + +//@@author Robert-Peng-unused +/**code unused as the function is integrated into listappt command + + * Command to switch between calendar views such as day, week, month and year + + */ +public class CalendarViewCommand extends Command { + + public static final String COMMAND_WORD = "calendar"; + public static final String COMMAND_ALIAS = "cal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": To change the calendar view between Day, " + + "Week, Month, and Year \n" + + COMMAND_ALIAS + ": Short for calendar. \n" + + "Parameter: \n" + + "Day view: d\n" + + "Week view: w\n" + + "Month view: m\n" + + "Year view: y\n"; + + public static final String MESSAGE_SUCCESS = "View changed."; + + private Character arg; + + public CalendarViewCommand(Character c) { + this.arg = c; + } + + @Override + public CommandResult execute() throws CommandException { + EventsCenter.getInstance().post(new ChangeCalendarViewEvent(arg)); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/Unused/CalendarViewCommandParser.java b/Unused/CalendarViewCommandParser.java new file mode 100644 index 000000000000..a597ae452d43 --- /dev/null +++ b/Unused/CalendarViewCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.CalendarViewCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author Robert-Peng-unused +/**code unused as the function is integrated into listappt command + + * Parser for CalendarViewCommand + + */ +public class CalendarViewCommandParser implements Parser { + + + @Override + public Command parse(String userInput) throws ParseException { + userInput = userInput.trim(); + if (userInput.length() != 1 || !isValidCommand(userInput)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CalendarViewCommand.MESSAGE_USAGE)); + } + return new CalendarViewCommand(userInput.charAt(0)); + } + + /** + * Check if the parameters is either w,d,y or m. + */ + private boolean isValidCommand(String str) { + + assert(str.length() == 1); + switch (str.charAt(0)) { + case('w'): + case('d'): + case('y'): + case('m'): + return true; + default: + return false; + } + } +} + + diff --git a/Unused/CalendarViewCommandSystemTest.java b/Unused/CalendarViewCommandSystemTest.java new file mode 100644 index 000000000000..9e78db69a9bd --- /dev/null +++ b/Unused/CalendarViewCommandSystemTest.java @@ -0,0 +1,34 @@ +package systemtests; + +import static org.junit.Assert.assertEquals; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import org.junit.Test; + +import seedu.address.logic.commands.CalendarViewCommand; + +//@@author Robert-Peng-unused + +/** + * code unused as the function is integrated into listappt command + */ +public class CalendarViewCommandSystemTest extends AddressBookSystemTest { + @Test + public void changeCalendarView() { + assertCommandSuccess(CalendarViewCommand.COMMAND_WORD + " d", CalendarViewCommand.MESSAGE_SUCCESS); + assertCommandSuccess(CalendarViewCommand.COMMAND_WORD + " w", CalendarViewCommand.MESSAGE_SUCCESS); + assertCommandSuccess(CalendarViewCommand.COMMAND_WORD + " m", CalendarViewCommand.MESSAGE_SUCCESS); + assertCommandSuccess(CalendarViewCommand.COMMAND_WORD + " y", CalendarViewCommand.MESSAGE_SUCCESS); + assertCommandSuccess(CalendarViewCommand.COMMAND_WORD + " q", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CalendarViewCommand.MESSAGE_USAGE)); + } + + /** + * Performs verification for command to calendarView + */ + private void assertCommandSuccess(String command, String message) { + executeCommand(command); + assertEquals(getResultDisplay().getText() , message); + } +} diff --git a/Unused/ChangeCalendarViewEvent.java b/Unused/ChangeCalendarViewEvent.java new file mode 100644 index 000000000000..3b6508c081b7 --- /dev/null +++ b/Unused/ChangeCalendarViewEvent.java @@ -0,0 +1,22 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +//@@author Robert-Peng-unused +/** code unused as the function is integrated into listappt command + + * Indicates a request to change calendar view + + */ +public class ChangeCalendarViewEvent extends BaseEvent { + + public final Character character; + + public ChangeCalendarViewEvent(Character character) { + this.character = character; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..d4aea885e221 100644 --- a/build.gradle +++ b/build.gradle @@ -40,13 +40,14 @@ jacocoTestReport { dependencies { String testFxVersion = '4.0.7-alpha' - + compile fileTree(dir: 'lib', include: '*.jar') compile group: 'org.fxmisc.easybind', name: 'easybind', version: '1.0.3' compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' compile group: 'com.google.guava', name: 'guava', version: '19.0' - + //compile 'com.calendarfx:calendar:8.4.2' + //compile "org.jfxtras:jfxtras-all:8.0-r6-SNAPSHOT" testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.testfx', name: 'testfx-core', version: testFxVersion testCompile group: 'org.testfx', name: 'testfx-junit', version: testFxVersion @@ -57,7 +58,7 @@ dependencies { } shadowJar { - archiveName = "addressbook.jar" + archiveName = "medeina.jar" destinationDir = file("${buildDir}/jar/") } diff --git a/collated/functional/Robert-Peng.md b/collated/functional/Robert-Peng.md new file mode 100644 index 000000000000..306849689b94 --- /dev/null +++ b/collated/functional/Robert-Peng.md @@ -0,0 +1,740 @@ +# Robert-Peng +###### \java\seedu\address\logic\commands\ListCommand.java +``` java +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PET_PATIENTS; + +/** + * Lists all persons and petpatients in Medeina to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "ls"; + + public static final String MESSAGE_SUCCESS = "Listed all contacts and pet patients"; + + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredPetPatientList(PREDICATE_SHOW_ALL_PET_PATIENTS); + return new CommandResult(MESSAGE_SUCCESS); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String nric} into a {@code NRIC}. + * Leading and trailing whitespaces will be trimmed. + * @param nric + * @return + * @throws IllegalValueException + */ + public static Nric parseNric(String nric) throws IllegalValueException { + requireNonNull(nric); + String trimmedNric = nric.trim(); + if (!Nric.isValidNric(trimmedNric)) { + throw new IllegalValueException(Nric.MESSAGE_NRIC_CONSTRAINTS); + } + return new Nric(trimmedNric); + } + + /** + * Parses a {@code Optional nric} into an {@code Optional} if {@code nric} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + * @param nric + * @return + * @throws IllegalValueException + */ + public static Optional parseNric(Optional nric) throws IllegalValueException { + requireNonNull(nric); + return nric.isPresent() ? Optional.of(parseNric(nric.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\model\appointment\Appointment.java +``` java + /** + * Returns a list of tags as a string + */ + public String getTagString() { + StringBuilder tagString = new StringBuilder(); + Set tagSet = Collections.unmodifiableSet(appointmentTags.toSet()); + Iterator iterator = tagSet.iterator(); + Tag tag = (Tag) iterator.next(); + while (iterator.hasNext()) { + tagString.append(tag.tagName); + tagString.append(", "); + tag = (Tag) iterator.next(); + } + tagString.append(tag.tagName); + return tagString.toString().trim(); + } +} +``` +###### \java\seedu\address\model\appointment\exceptions\ConcurrentAppointmentException.java +``` java + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Represents an error message when user attempt to add new appointment that + * interfere with other appointment time interval + */ +public class ConcurrentAppointmentException extends IllegalValueException { + + public ConcurrentAppointmentException () { + super("Medeina should not add appointments to on-going appointment slots"); + } + +} + +``` +###### \java\seedu\address\model\appointment\exceptions\PastAppointmentException.java +``` java + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Represents an error message when adding appointment with a past date + */ +public class PastAppointmentException extends IllegalValueException { + + public PastAppointmentException() { + super("Medeina should not add appointments with past DateTime"); + } +} + +``` +###### \java\seedu\address\model\person\Nric.java +``` java +/** + * Represents a Person's email in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidNRIC(String)} + */ +public class Nric { + public static final String MESSAGE_NRIC_CONSTRAINTS = "Contact NRIC should be of the format #0000000@ " + + "where # is a letter that can be S T F or G,\n" + + "0000000 represents 7 digits which can be any number from 0-9,\n" + + "@ can be any alphabet A-Z.\n" + + "Both # and @ must be in upper case."; + + private static final String FIRST_CHAR_REGEX = "[STFG]"; + private static final String MIDDLE_NUM_REGEX = "[0-9][0-9][0-9][0-9][0-9][0-9][0-9]"; + private static final String LAST_CHAR_REGEX = "[A-Z]"; + public static final String NRIC_VALIDATION_REGEX = FIRST_CHAR_REGEX + MIDDLE_NUM_REGEX + + LAST_CHAR_REGEX; + public final String value; + + /** + * Constructs a NRIC. + * + * @param nric A valid NRIC number + */ + public Nric(String nric) { + requireNonNull(nric); + checkArgument(isValidNric(nric), MESSAGE_NRIC_CONSTRAINTS); + this.value = nric; + } + + /** + * Returns if a given String is a valid NRIC + * @param test + * @return + */ + public static boolean isValidNric(String test) { + return test.matches(NRIC_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Nric // instanceof handles nulls + && this.value.equals(((Nric) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\ui\CalendarWindow.java +``` java +/** + * Implement CalendarView from CalendarFX to show appointments + */ +public class CalendarWindow extends UiPart { + + public static final String DEFAULT_PAGE = "CalendarPanel.fxml"; + + private ObservableList appointmentList; + private Calendar calendar; + + @FXML + private CalendarView calendarView; + private DayView dayView; + private WeekView weekView; + + /** + * + * @param OwnerList + */ + public CalendarWindow(ObservableList appointmentList) { + super(DEFAULT_PAGE); + + this.appointmentList = appointmentList; + calendarView = new CalendarView(); + + + setView(); + setTime(); + setCalendar(); + disableViews(); + registerAsAnEventHandler(this); + + } + + private void setView() { + this.dayView = calendarView.getDayPage().getDetailedDayView().getDayView(); + dayView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); + dayView.setHourHeight(250); + + this.weekView = calendarView.getWeekPage().getDetailedWeekView().getWeekView(); + weekView.setHoursLayoutStrategy(DayViewBase.HoursLayoutStrategy.FIXED_HOUR_HEIGHT); + weekView.setHourHeight(250); + } + + private void setTime() { + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + } + + /** + * Creates a new a calendar + */ + private void setCalendar() { + setTime(); + calendarView.getCalendarSources().clear(); + CalendarSource calendarSource = new CalendarSource("Appointments"); + int styleNumber = 0; + int appointmentCounter = 0; + + for (Appointment appointment : appointmentList) { + + Calendar calendar = createCalendar(styleNumber, appointment); + calendar.setReadOnly(true); + calendarSource.getCalendars().add(calendar); + + LocalDateTime ldt = appointment.getDateTime(); + appointmentCounter++; + + Entry entry = new Entry (buildAppointment(appointment, appointmentCounter).toString()); + + entry.setInterval(new Interval(ldt, ldt.plusMinutes(30))); + + styleNumber++; + styleNumber = styleNumber % 7; + + calendar.addEntry(entry); + + } + calendarView.getCalendarSources().add(calendarSource); + } + + /** + * + * @param appointment + * @param appointmentCounter + * @return + */ + private StringBuilder buildAppointment (Appointment appointment, int appointmentCounter) { + final StringBuilder builder = new StringBuilder(); + builder.append(appointmentCounter) + .append(". ") + .append(appointment.getPetPatientName().toString() + "\n") + .append("Contact Nric: " + appointment.getOwnerNric() + "\n") + .append("Appointment type: " + appointment.getTagString()); + + builder.append("\n"); + builder.append("Remarks: " + appointment.getRemark().toString()); + return builder; + } + + /** + * + * @param styleNumber + * @param appointment + * @return a calendar with given info and corresponding style + */ + private Calendar createCalendar(int styleNumber, Appointment appointment) { + Calendar calendar = new Calendar(appointment.getPetPatientName().toString()); + calendar.setStyle(Calendar.Style.getStyle(styleNumber)); + calendar.setLookAheadDuration(Duration.ofDays(365)); + calendar.setLookBackDuration(Duration.ofDays(365)); + return calendar; + } + + /** + * close unwanted UI components + */ + + private void disableViews() { + calendarView.setShowAddCalendarButton(false); + calendarView.setShowSearchField(false); + calendarView.setShowSearchResultsTray(false); + calendarView.setShowPrintButton(false); + calendarView.setShowSourceTrayButton(false); + calendarView.setShowPageSwitcher(false); + calendarView.setShowToolBar(false); + calendarView.showDayPage(); + calendarView.setShowSourceTray(false); + calendarView.setShowPageToolBarControls(false); + } + + public CalendarView getRoot() { + return this.calendarView; + } + + @Subscribe + private void handleNewAppointmentEvent(AddressBookChangedEvent event) { + appointmentList = event.data.getAppointmentList(); + Platform.runLater( + this::setCalendar + ); + + } + +``` +###### \java\seedu\address\ui\MainWindow.java +``` java + calendarWindow = new CalendarWindow(logic.getFilteredAppointmentList()); + this.calendarPlaceholder.getChildren().add(calendarWindow.getRoot()); + + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + petPatientListPanel = new PetPatientListPanel(logic.getFilteredPetPatientList()); + petPatientListPanelPlaceholder.getChildren().add(petPatientListPanel.getRoot()); +``` +###### \java\seedu\address\ui\PersonCard.java +``` java + /** + * Returns the color style for {@code tagName}'s label. + * Solution below adopted from : + * https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + */ + private String getTagColorStyleFor(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between different runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + /** + * Creates the tag labels for {@code person}. + */ + private void createTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColorStyleFor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); + } +``` +###### \java\seedu\address\ui\PetPatientCard.java +``` java +/** + * AN UI component that displays the information of a {@code PetPatient} + */ +public class PetPatientCard extends UiPart { + private static final String FXML = "PetPatientListCard.fxml"; + + private static final String[] TAG_COLOR_STYLES = + {"teal", "red", "yellow", "blue", "orange", "brown", "green", "pink", + "black", "grey"}; + + public final PetPatient petPatient; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label species; + @FXML + private Label breed; + @FXML + private Label colour; + @FXML + private Label bloodType; + @FXML + private Label ownerNric; + @FXML + private FlowPane tags; + + public PetPatientCard(PetPatient petPatient, int displayedIndex) { + super(FXML); + this.petPatient = petPatient; + id.setText(displayedIndex + ". "); + name.setText(petPatient.getName().toString()); + species.setText("Species:\t\t" + petPatient.getSpecies().toString()); + breed.setText("Breed:\t\t" + petPatient.getBreed().toString()); + colour.setText("Colour:\t\t" + petPatient.getColour().toString()); + bloodType.setText("Blood Type:\t" + petPatient.getBloodType().toString()); + ownerNric.setText("Owner NRIC:\t" + petPatient.getOwner().toString()); + createTags(petPatient); + } + + /** + * Returns the color style for {@code tagName}'s label. + * Solution below adopted from : + * https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + */ + private String getTagColorStyleFor(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between different runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + /** + * Creates the tag labels for {@code PetPatient}. + */ + private void createTags(PetPatient petPatient) { + petPatient.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColorStyleFor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PetPatientCard)) { + return false; + } + + // state check + PetPatientCard card = (PetPatientCard) other; + return id.getText().equals(card.id.getText()) + && petPatient.equals(card.petPatient); + } +} +``` +###### \java\seedu\address\ui\PetPatientListPanel.java +``` java +/** + * Panel containing list of PetPatients + */ +public class PetPatientListPanel extends UiPart { + private static final String FXML = "PetPatientListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PetPatientListPanel.class); + + @FXML + private ListView petPatientListView; + + public PetPatientListPanel(ObservableList petPatientList) { + super(FXML); + setConnections(petPatientList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList petPatientList) { + ObservableList mappedList = EasyBind.map( + petPatientList, (petPatient) -> new PetPatientCard(petPatient, petPatientList.indexOf(petPatient) + 1)); + petPatientListView.setItems(mappedList); + petPatientListView.setCellFactory(listView -> new PetPatientListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + petPatientListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in petPatient list panel changed to : '" + newValue + "'"); + raise(new PetPatientPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code PetPatientCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + petPatientListView.scrollTo(index); + petPatientListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code PetPatientCard}. + */ + class PetPatientListViewCell extends ListCell { + + @Override + protected void updateItem(PetPatientCard petPatient, boolean empty) { + super.updateItem(petPatient, empty); + + if (empty || petPatient == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(petPatient.getRoot()); + } + } + } + +} +``` +###### \resources\view\CalendarPanel.fxml +``` fxml + + + + + + + + + + + + + + +``` +###### \resources\view\DarkTheme.css +``` css + +.tab-pane .tab-header-area .tab-background{ +-fx-opacity: 0; +} + +#owner-Tab { + -fx-background-color: A9A9A9; + -fx-text-base-color: black; +} + +#petPatient-Tab { + -fx-background-color: A9A9A9; + -fx-text-base-color: black; +} + +#owner-pane { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-base: null; + -fx-border-color: null; + -fx-background-color: derive(#1d1d1d, 20%); + +} + +#petPatient-pane { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-base: null; + -fx-border-color: null; + -fx-background-color: derive(#1d1d1d, 20%); +} + +``` +###### \resources\view\DarkTheme.css +``` css + +#personListView { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: white; +} + +#personListView .list-cell:filled:odd { + -fx-background-color: #515658; +} + +#petPatientListView { + -fx-background-color: derive(#1d1d1d, 20%); +} + +#petPatientListView .list-cell:filled:odd { + -fx-background-color: #515658 +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: transparent; +} + +.list-cell:filled:odd { + -fx-background-color: transparent; +} +``` +###### \resources\view\LightTheme.css +``` css + +#personListView { + -fx-background-color: derive(white, 20%); +} + + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: white; +} + +#personListView .list-cell:filled:odd { + -fx-background-color: white; +} + +#personListView .list-cell:filled:even { + -fx-background-color: #f7f1dc; +} + +#petPatientListView { + -fx-background-color: derive(white, 20%); +} + +#petPatientListView .list-cell:filled:odd { + -fx-background-color: white; +} + +#petPatientListView .list-cell:filled:even { + -fx-background-color: #f7f1dc; +} + + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: white; +} + +.list-cell:filled:odd { + -fx-background-color: white; +} + +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + + + + + + + + + + +``` +###### \resources\view\PersonListPanel.fxml +``` fxml + + + + + + + + + + + + + + + + + + +``` +###### \resources\view\PetPatientListCard.fxml +``` fxml + + + + + + + + + + + + + + + + +``` +###### \resources\view\PetPatientListPanel.fxml +``` fxml + + + + + + + + + + + + + + + + + + +``` diff --git a/collated/functional/aquarinte-reused.md b/collated/functional/aquarinte-reused.md new file mode 100644 index 000000000000..91ec229b0e17 --- /dev/null +++ b/collated/functional/aquarinte-reused.md @@ -0,0 +1,29 @@ +# aquarinte-reused +###### \java\seedu\address\commons\util\StringUtil.java +``` java + /** + * Returns a string with trailing whitespaces on the left removed. + * + * Reused from: http://tutorial4java.blogspot.sg/2013/05/trim-ltrim-and-rtrim-in-java.html + */ + public static String leftTrim(String s) { + return STRING_LEFT_TRIM.matcher(s).replaceAll(""); + } +} +``` +###### \java\seedu\address\ui\CommandBox.java +``` java + /** Caret position bug fix from https://bugs.openjdk.java.net/browse/JDK-8088614 */ + autocompleteListener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, String oldValue, String newValue) { + Platform.runLater(new Runnable() { + public void run() { + triggerAutocomplete(newValue); + } + }); + } + }; + commandTextField.textProperty().addListener(autocompleteListener); + isAutocompleting = true; +``` diff --git a/collated/functional/aquarinte.md b/collated/functional/aquarinte.md new file mode 100644 index 000000000000..d82acf59c69a --- /dev/null +++ b/collated/functional/aquarinte.md @@ -0,0 +1,1664 @@ +# aquarinte +###### \java\seedu\address\commons\events\ui\ChangeThemeRequestEvent.java +``` java +/** + * Indicates a request to change Medeina's theme + */ +public class ChangeThemeRequestEvent extends BaseEvent { + public final Theme theme; + + public ChangeThemeRequestEvent(Theme theme) { + this.theme = theme; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\commons\util\StringUtil.java +``` java + /** + * Returns the actual required value of a String. Removes description (starts with "\t:"). + */ + public static String removeDescription(String s) { + int descriptionStart = s.indexOf("\t:"); + + if (descriptionStart > 0) { + return s.substring(0, descriptionStart); + } + + return s; + } + +``` +###### \java\seedu\address\logic\commands\AddCommand.java +``` java +/** + * Adds a Person, Petpatient and/or Appointment to Medeina. + */ +public class AddCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "add"; + public static final String COMMAND_ALIAS = "a"; + + public static final String MESSAGE_USAGE = "To add a new contact: " + + COMMAND_WORD + " " + OPTION_OWNER + " " + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_NRIC + "NRIC " + + "[" + PREFIX_TAG + "TAG]...\n" + + "To add a new pet patient: " + + COMMAND_WORD + " " + OPTION_PETPATIENT + " " + PREFIX_NAME + "PET_NAME " + + PREFIX_SPECIES + "SPECIES " + + PREFIX_BREED + "BREED " + + PREFIX_COLOUR + "COLOUR " + + PREFIX_BLOODTYPE + "BLOOD_TYPE " + + "[" + PREFIX_TAG + "TAG]... " + OPTION_OWNER + " " + PREFIX_NRIC + "NRIC\n" + + "To add a new appointment: " + + COMMAND_WORD + " " + OPTION_APPOINTMENT + " " + PREFIX_DATE + "DATE " + + PREFIX_REMARK + "REMARK " + + PREFIX_TAG + "TYPE OF APPOINTMENT... " + OPTION_OWNER + " " + PREFIX_NRIC + "NRIC " + + OPTION_PETPATIENT + " " + PREFIX_NAME + " PET_NAME\n" + + "To add all new: " + COMMAND_WORD + " " + OPTION_OWNER + " " + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_NRIC + "NRIC " + + "[" + PREFIX_TAG + "TAG]... " + OPTION_PETPATIENT + " " + PREFIX_NAME + "PET_NAME " + + PREFIX_SPECIES + "SPECIES " + + PREFIX_BREED + "BREED " + + PREFIX_COLOUR + "COLOUR " + + PREFIX_BLOODTYPE + "BLOOD_TYPE " + + "[" + PREFIX_TAG + "TAG]... " + OPTION_APPOINTMENT + " " + PREFIX_DATE + "DATE " + + PREFIX_REMARK + "REMARK " + + PREFIX_TAG + "TYPE OF APPOINTMENT..."; + + public static final String MESSAGE_ERROR_PERSON = "option " + OPTION_OWNER + "\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_NRIC + "NRIC " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + OPTION_OWNER + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_NRIC + "S1234567Q " + + PREFIX_TAG + "supplier"; + + public static final String MESSAGE_ERROR_APPOINTMENT = "option " + OPTION_APPOINTMENT + "\n" + + "Parameters: " + + PREFIX_DATE + "DATE " + + PREFIX_REMARK + "REMARK " + + PREFIX_TAG + "TYPE OF APPOINTMENT...\n" + + "Example: " + OPTION_APPOINTMENT + " " + + PREFIX_DATE + "2018-12-31 12:30 " + + PREFIX_REMARK + "nil " + + PREFIX_TAG + "checkup " + + PREFIX_TAG + "vaccination"; + + public static final String MESSAGE_ERROR_PETPATIENT = "option " + OPTION_PETPATIENT + "\n" + + "Parameters: " + + PREFIX_NAME + "PET_NAME " + + PREFIX_SPECIES + "SPECIES " + + PREFIX_BREED + "BREED " + + PREFIX_COLOUR + "COLOUR " + + PREFIX_BLOODTYPE + "BLOOD_TYPE " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + OPTION_PETPATIENT + " " + + PREFIX_NAME + "Jewel " + + PREFIX_SPECIES + "Cat " + + PREFIX_BREED + "Persian Ragdoll " + + PREFIX_COLOUR + "calico " + + PREFIX_BLOODTYPE + "AB " + + PREFIX_TAG + "underweight"; + + public static final String MESSAGE_SUCCESS_PERSON = "New contact added: %1$s\n"; + public static final String MESSAGE_SUCCESS_PETPATIENT = "New pet patient added: %1$s \n" + + "under contact: %2$s"; + public static final String MESSAGE_SUCCESS_APPOINTMENT = "New appointment made: %1$s\n" + + "under contact: %2$s\n" + + "for pet patient: %3$s"; + public static final String MESSAGE_SUCCESS_EVERYTHING = MESSAGE_SUCCESS_PERSON + + "New pet patient added: %2$s\n" + + "New appointment made: %3$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This contact already exists in Medeina."; + public static final String MESSAGE_DUPLICATE_NRIC = "There is already a contact with this NRIC."; + public static final String MESSAGE_DUPLICATE_APPOINTMENT = "This particular appointment already exists in Medeina."; + public static final String MESSAGE_DUPLICATE_DATETIME = "This date time is already taken by another appointment."; + public static final String MESSAGE_DUPLICATE_PET_PATIENT = "This pet patient already exists in Medeina"; + public static final String MESSAGE_INVALID_NRIC = "The specified NRIC does not belong to anyone in Medeina." + + " Please add a new contact."; + public static final String MESSAGE_INVALID_PET_PATIENT = "The specified pet cannot be found under the specified " + + "contact in Medeina. Please add a new pet patient."; + public static final String MESSAGE_MISSING_NRIC_PREFIX = "option -o\n" + + "Missing prefix nr/ for NRIC."; + public static final String MESSAGE_MISSING_PET_PATIENT_NAME_PREFIX = "option -p\n" + + "Missing prefix n/ for pet patient name."; + public static final String MESSAGE_CONCURRENT_APPOINTMENT = "Appointment cannot be concurrent with other " + + "appointments."; + public static final String MESSAGE_PAST_APPOINTMENT = "Appointment cannot be created with past DateTime."; + + private Person person; + private PetPatient petPatient; + private Appointment appt; + private Nric ownerNric; + private PetPatientName petPatientName; + private int type; + + /** + * Creates an AddCommand to add the specified {@code Person} and {@code PetPatient} and {@code Appointment}. + */ + public AddCommand(Person person, PetPatient petPatient, Appointment appt) { + requireNonNull(person); + requireNonNull(petPatient); + requireNonNull(appt); + this.person = person; + this.petPatient = petPatient; + this.appt = appt; + type = 1; + } + + /** + * Creates an AddCommand to add the specified {@code Appointment} if an existing Person object getNric() is + * equivalent to {@code Nric}, and an existing PetPatient object getOwner() is equivalent to {@code ownerNric} + * and getName() equivalent to {@code PetPatientName}. + */ + public AddCommand(Appointment appt, Nric ownerNric, PetPatientName petPatientName) { + requireNonNull(appt); + requireNonNull(ownerNric); + requireNonNull(petPatientName); + this.appt = appt; + this.ownerNric = ownerNric; + this.petPatientName = petPatientName; + type = 2; + } + + /** + * Creates an AddCommand to add the specified {@code PetPatient} if an existing Person object getNric() is + * equivalent to {@code Nric}. + */ + public AddCommand(PetPatient petPatient, Nric ownerNric) { + requireNonNull(petPatient); + requireNonNull(ownerNric); + this.petPatient = petPatient; + this.ownerNric = ownerNric; + type = 3; + } + + /** + * Creates an AddCommand to add the specified {@code Person}. + */ + public AddCommand(Person owner) { + requireNonNull(owner); + person = owner; + type = 4; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + switch (type) { + case 1: return addAllNew(); + case 2: return addNewAppointment(); + case 3: return addNewPetPatient(); + case 4: return addNewPerson(); + default: throw new CommandException(MESSAGE_USAGE); + } + + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (DuplicateNricException e) { + throw new CommandException(MESSAGE_DUPLICATE_NRIC); + } catch (DuplicatePetPatientException e) { + throw new CommandException(MESSAGE_DUPLICATE_PET_PATIENT); + } catch (DuplicateAppointmentException e) { + throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT); + } catch (DuplicateDateTimeException e) { + throw new CommandException(MESSAGE_DUPLICATE_DATETIME); + } catch (ConcurrentAppointmentException e) { + throw new CommandException(MESSAGE_CONCURRENT_APPOINTMENT); + } catch (PastAppointmentException e) { + throw new CommandException(MESSAGE_PAST_APPOINTMENT); + } + } + + private CommandResult addNewPerson() throws DuplicatePersonException, DuplicateNricException { + model.addPerson(person); + return new CommandResult(String.format(MESSAGE_SUCCESS_PERSON, person)); + } + + /** + * Adds a new pet patient under an existing person. + */ + private CommandResult addNewPetPatient() throws DuplicatePetPatientException, CommandException { + person = getValidOwner(ownerNric); + model.addPetPatient(petPatient); + return new CommandResult(String.format(MESSAGE_SUCCESS_PETPATIENT, petPatient, person)); + } + + /** + * Adds a new appointment for an existing pet patient, under an existing person. + */ + private CommandResult addNewAppointment() throws CommandException, DuplicateAppointmentException, + DuplicateDateTimeException, ConcurrentAppointmentException, PastAppointmentException { + person = getValidOwner(ownerNric); + petPatient = getValidPetPatient(ownerNric, petPatientName); + model.addAppointment(appt); + return new CommandResult(String.format(MESSAGE_SUCCESS_APPOINTMENT, appt, person, petPatient)); + } + + /** + * Adds a new appointment for a new pet patient under a new person. + */ + private CommandResult addAllNew() throws DuplicatePersonException, DuplicateNricException, + DuplicatePetPatientException, DuplicateAppointmentException, DuplicateDateTimeException, + ConcurrentAppointmentException, PastAppointmentException { + model.addPerson(person); + model.addPetPatient(petPatient); + model.addAppointment(appt); + return new CommandResult(String.format(MESSAGE_SUCCESS_EVERYTHING, person, petPatient, appt)); + } + + /** + * Returns a person object that exists in Medeina. + */ + private Person getValidOwner(Nric ownerNric) throws CommandException { + Person validOwner = model.getPersonWithNric(ownerNric); + if (validOwner == null) { + throw new CommandException(MESSAGE_INVALID_NRIC); + } + return validOwner; + } + + /** + * Returns a petpatient object that exists in Medeina. + */ + private PetPatient getValidPetPatient(Nric ownerNric, PetPatientName petPatientName) throws CommandException { + PetPatient validPatient = model.getPetPatientWithNricAndName(ownerNric, petPatientName); + if (validPatient == null) { + throw new CommandException(MESSAGE_INVALID_PET_PATIENT); + } + return validPatient; + } + + /** + * Checks if two objects are the same for equals() method. + * + * Returns true if both objects are equivalent. + * Returns true if both objects are null. + */ + public boolean isTheSame(Object one, Object two) { + if (one != null && two != null) { + if (one.equals(two)) { + return true; + } + } + + if (one == null && two == null) { + return true; + } + + return false; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof AddCommand)) { + return false; + } + + AddCommand otherAddCommand = (AddCommand) other; + + boolean personSame = isTheSame(person, otherAddCommand.person); + boolean petPatientSame = isTheSame(petPatient, otherAddCommand.petPatient); + boolean appointmentSame = isTheSame(appt, otherAddCommand.appt); + + if (personSame && petPatientSame && appointmentSame) { + return true; + } else { + return false; + } + } +} +``` +###### \java\seedu\address\logic\commands\ChangeThemeCommand.java +``` java +/** + * Changes the theme of Medeina. + */ +public class ChangeThemeCommand extends Command { + public static final String COMMAND_WORD = "theme"; + public static final String COMMAND_ALIAS = "t"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Change Medeina's theme to the" + + "specified theme name (case-insensitive)\n" + + "Parameters: THEME NAME\n" + + "Example: " + COMMAND_WORD + " light"; + + private String result; + + private final Theme theme; + + public ChangeThemeCommand(Theme theme) { + requireNonNull(theme); + this.theme = theme; + result = "Current theme: " + theme.getThemeName(); + } + + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ChangeThemeRequestEvent(theme)); + return new CommandResult(result); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangeThemeCommand // instanceof handles nulls + && theme.equals(((ChangeThemeCommand) other).theme)); + } +} +``` +###### \java\seedu\address\logic\LogicManager.java +``` java + @Override + public Set getAllCommandWords() { + return cliSyntax.getCommandWords(); + } + + @Override + public Set getCommandWordsWithOptionPrefix() { + return cliSyntax.getCommandWordsWithOptionPrefix(); + } + + @Override + public Set getAllPrefixes() { + return cliSyntax.getPrefixes(); + } + + @Override + public Set getAllOptions() { + return cliSyntax.getOptions(); + } + + @Override + public Set getAllNric() { + return nricInModel; + } + + @Override + public Set getAllPersonTags() { + Set personTags = personTagsInModel.stream() + .map(pt -> pt.tagName) + .collect(Collectors.toSet()); + return personTags; + } + + @Override + public Set getAllPetPatientNames() { + return petPatientNamesInModel; + } + + @Override + public Set getAllPetPatientSpecies() { + return speciesInModel; + } + + @Override + public Set getAllPetPatientBreeds() { + return breedsInModel; + } + + @Override + public Set getAllPetPatientColours() { + return coloursInModel; + } + + @Override + public Set getAllPetPatientBloodTypes() { + return bloodTypesInModel; + } + + @Override + public Set getAllPetPatientTags() { + Set petPatientTags = petPatientTagsInModel.stream() + .map(ppt -> ppt.tagName) + .collect(Collectors.toSet()); + return petPatientTags; + } + + @Override + public Set getAllAppointmentTags() { + Set appointmentTags = appointmentTagsInModel.stream() + .map(a -> a.tagName) + .collect(Collectors.toSet()); + return appointmentTags; + } + + @Override + public void setAttributesForPersonObjects() { + nricInModel = new HashSet<>(); + personTagsInModel = new HashSet<>(); + + for (Person p : model.getAddressBook().getPersonList()) { + nricInModel.add(p.getNric().toString()); + personTagsInModel.addAll(p.getTags()); + } + } + + @Override + public void setAttributesForPetPatientObjects() { + petPatientNamesInModel = new HashSet<>(); + speciesInModel = new HashSet<>(); + breedsInModel = new HashSet<>(); + coloursInModel = new HashSet<>(); + bloodTypesInModel = new HashSet<>(); + petPatientTagsInModel = new HashSet<>(); + + for (PetPatient p : model.getAddressBook().getPetPatientList()) { + petPatientNamesInModel.add(p.getName().toString()); + speciesInModel.add(p.getSpecies().toString()); + breedsInModel.add(p.getBreed().toString()); + coloursInModel.add(p.getColour().toString()); + bloodTypesInModel.add(p.getBloodType().toString()); + petPatientTagsInModel.addAll(p.getTags()); + } + } + + @Override + public void setAttributesForAppointmentObjects() { + appointmentTagsInModel = new HashSet<>(); + for (Appointment a : model.getAddressBook().getAppointmentList()) { + appointmentTagsInModel.addAll(a.getTag()); + } + } + + @Override + public Set getAllTagsInModel() { + Set tagsInModel = new HashSet<>(); + for (Tag t : model.getTagList()) { + tagsInModel.add(t.tagName); + } + return tagsInModel; + } +} +``` +###### \java\seedu\address\logic\parser\AddCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddCommand object. + */ +public class AddCommandParser implements Parser { + + private static final Pattern ADD_COMMAND_FORMAT_ALL_NEW = Pattern.compile("-(o)+(?.*)" + + "-(p)+(?.*)-(a)+(?.*)"); + private static final Pattern ADD_COMMAND_FORMAT_OWNER_ONLY = Pattern.compile("-(o)+(?.*)"); + private static final Pattern ADD_COMMAND_FORMAT_NEW_PET_EXISTING_OWNER = Pattern.compile("-(p)+(?.*)" + + "-(o)+(?.*)"); + private static final Pattern ADD_COMMAND_FORMAT_NEW_APPT_EXISTING_OWNER_PET = Pattern.compile("-(a)+(?.*)" + + "-(o)+(?.*)" + "-(p)+(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the Person class + * and returns an Person object. + * @throws ParseException if the user input does not conform the expected format. + */ + public Person parsePerson(String ownerInfo) throws ParseException { + ArgumentMultimap argMultimapOwner = + ArgumentTokenizer.tokenize(ownerInfo, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_NRIC, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimapOwner, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NRIC) + || !argMultimapOwner.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_PARAMETER_FORMAT, AddCommand.MESSAGE_ERROR_PERSON)); + } + + try { + Name ownerName = ParserUtil.parseName(argMultimapOwner.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimapOwner.getValue(PREFIX_PHONE)).get(); + Email email = ParserUtil.parseEmail(argMultimapOwner.getValue(PREFIX_EMAIL)).get(); + Address address = ParserUtil.parseAddress(argMultimapOwner.getValue(PREFIX_ADDRESS)).get(); + Nric nric = ParserUtil.parseNric(argMultimapOwner.getValue(PREFIX_NRIC)).get(); + Set ownerTagList = ParserUtil.parseTags(argMultimapOwner.getAllValues(PREFIX_TAG)); + + Person owner = new Person(ownerName, phone, email, address, nric, ownerTagList); + return owner; + + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Parses the given {@code String} of arguments in the context of the Appointment class + * and returns an Appointment object. + * @throws ParseException if the user input does not conform the expected format. + */ + public Appointment parseAppointment(String apptInfo) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(apptInfo, PREFIX_DATE, PREFIX_REMARK, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_DATE, PREFIX_REMARK, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_PARAMETER_FORMAT, AddCommand.MESSAGE_ERROR_APPOINTMENT)); + } + + try { + LocalDateTime localDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE)).get(); + if (localDateTime.isBefore(LocalDateTime.now())) { + throw new PastAppointmentException(); + } + + Remark remark = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK)).get(); + Set type = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + Appointment appointment = new Appointment(remark, localDateTime, type); + return appointment; + + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + + } + } + /** + * Parses the given {@code String} of arguments in the context of the PetPatient class + * and returns an PetPatient object. + * @throws ParseException if the user input does not conform the expected format. + */ + public PetPatient parsePetPatient(String petInfo) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(petInfo, PREFIX_NAME, PREFIX_SPECIES, PREFIX_BREED, PREFIX_COLOUR, + PREFIX_BLOODTYPE, PREFIX_TAG); + + if (!arePrefixesPresent( + argMultimap, PREFIX_NAME, PREFIX_BREED, PREFIX_SPECIES, PREFIX_COLOUR, PREFIX_BLOODTYPE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_PARAMETER_FORMAT, + AddCommand.MESSAGE_ERROR_PETPATIENT)); + } + + try { + PetPatientName name = ParserUtil.parsePetPatientName(argMultimap.getValue(PREFIX_NAME)).get(); + Species species = ParserUtil.parseSpecies(argMultimap.getValue(PREFIX_SPECIES)).get(); + Breed breed = ParserUtil.parseBreed(argMultimap.getValue(PREFIX_BREED)).get(); + Colour color = ParserUtil.parseColour(argMultimap.getValue(PREFIX_COLOUR)).get(); + BloodType bloodType = ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE)).get(); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + PetPatient petPatient = new PetPatient(name, species, breed, color, bloodType, tagList); + + return petPatient; + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Parses the given {@code String} of arguments in the context of the Nric class + * and returns a Nric object. + * @throws ParseException if the user input does not conform the expected format. + */ + public Nric parseNric(String nric) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(nric, PREFIX_NRIC); + + if (!arePrefixesPresent(argMultimap, PREFIX_NRIC) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_PARAMETER_FORMAT, + AddCommand.MESSAGE_MISSING_NRIC_PREFIX)); + } + + try { + Nric validNric = ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC)).get(); + return validNric; + + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Parses the given {@code String} of arguments in the context of the PetPatientName class + * and returns a PetPatientName object. + * @throws ParseException if the user input does not conform the expected format. + */ + public PetPatientName parsePetPatientName(String petName) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(petName, PREFIX_NAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_PARAMETER_FORMAT, + AddCommand.MESSAGE_MISSING_PET_PATIENT_NAME_PREFIX)); + } + + try { + PetPatientName petPatientName = ParserUtil.parsePetPatientName(argMultimap.getValue(PREFIX_NAME)).get(); + return petPatientName; + + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Parses the given {@code String} of arguments in the context of AddCommand + * and returns an AddCommand object. + * @throws ParseException if the user input does not conform the expected format. + */ + private AddCommand createNewOwnerPetAppt(String ownerInfo, String petInfo, String apptInfo) + throws ParseException { + Person owner = parsePerson(ownerInfo); + + PetPatient petPatient = parsePetPatient(petInfo); + petPatient.setOwnerNric(owner.getNric()); + + Appointment appt = parseAppointment(apptInfo); + appt.setOwnerNric(owner.getNric()); + appt.setPetPatientName(petPatient.getName()); + + return new AddCommand(owner, petPatient, appt); + } + + /** + * Parses the given {@code String} of arguments in the context of AddCommand + * and returns an AddCommand object. + * @throws ParseException if the user input does not conform the expected format. + */ + private AddCommand createNewApptforExistingOwnerAndPet(String apptInfo, String ownerNric, String petName) + throws ParseException { + Appointment appt = parseAppointment(apptInfo); + Nric nric = parseNric(ownerNric); + PetPatientName petPatientName = parsePetPatientName(petName); + + appt.setOwnerNric(nric); + appt.setPetPatientName(petPatientName); + + return new AddCommand(appt, nric, petPatientName); + } + + /** + * Parses the given {@code String} of arguments in the context of AddCommand + * and returns an AddCommand object. + * @throws ParseException if the user input does not conform the expected format. + */ + private AddCommand createNewPetForExistingPerson(String petInfo, String ownerNric) throws ParseException { + PetPatient petPatient = parsePetPatient(petInfo); + Nric nric = parseNric(ownerNric); + + petPatient.setOwnerNric(nric); + + return new AddCommand(petPatient, nric); + } + + /** + * Parses the given {@code String} of arguments in the context of AddCommand + * and returns an AddCommand object. + * @throws ParseException if the user input does not conform the expected format. + */ + private AddCommand parseNewOwnerOnly(String ownerInfo) throws ParseException { + Person owner = parsePerson(ownerInfo); + return new AddCommand(owner); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of AddCommand + * and returns an AddCommand object. + * @throws ParseException if the user input does not conform the expected format. + */ + public AddCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + + //to add a new person (owner), new pet patient, and a new appointment + final Matcher matcherForAllNew = ADD_COMMAND_FORMAT_ALL_NEW.matcher(trimmedArgs); + if (matcherForAllNew.matches()) { + String ownerInfo = matcherForAllNew.group("ownerInfo"); + String petInfo = matcherForAllNew.group("petInfo"); + String apptInfo = matcherForAllNew.group("apptInfo"); + return createNewOwnerPetAppt(ownerInfo, petInfo, apptInfo); + } + + //add a new appointment for existing person and pet patient + final Matcher matcherForNewAppt = ADD_COMMAND_FORMAT_NEW_APPT_EXISTING_OWNER_PET.matcher(trimmedArgs); + if (matcherForNewAppt.matches()) { + String apptInfo = matcherForNewAppt.group("apptInfo"); + String ownerNric = matcherForNewAppt.group("ownerNric"); + String petName = matcherForNewAppt.group("petName"); + return createNewApptforExistingOwnerAndPet(apptInfo, ownerNric, petName); + } + + //add a new patient to an existing owner + final Matcher matcherForNewPet = ADD_COMMAND_FORMAT_NEW_PET_EXISTING_OWNER.matcher(trimmedArgs); + if (matcherForNewPet.matches()) { + String petInfo = matcherForNewPet.group("petInfo"); + String ownerNric = matcherForNewPet.group("ownerNric"); + return createNewPetForExistingPerson(petInfo, ownerNric); + } + + //add a new person + final Matcher matcherForNewPerson = ADD_COMMAND_FORMAT_OWNER_ONLY.matcher(trimmedArgs); + if (matcherForNewPerson.matches()) { + String ownerInfo = matcherForNewPerson.group("ownerInfo"); + return parseNewOwnerOnly(ownerInfo); + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + } + +} +``` +###### \java\seedu\address\logic\parser\ChangeThemeCommandParser.java +``` java +/** + * Parses input arguments and creates a new ChangeThemeCommand object + */ +public class ChangeThemeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ChangeThemeCommand + * and returns a ChangeThemeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ChangeThemeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeThemeCommand.MESSAGE_USAGE)); + } + + if (hasMoreThanOneArgument(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeThemeCommand.MESSAGE_USAGE)); + } + + if (!Theme.hasValidThemeName(trimmedArgs.toLowerCase())) { + throw new ParseException(Theme.MESSAGE_THEME_CONSTRAINTS); + } + + return new ChangeThemeCommand(new Theme(trimmedArgs.toLowerCase())); + } + + /** + * Returns true if {@code trimmedArgs} contains more than 1 argument/word (separated by space). + */ + private boolean hasMoreThanOneArgument(String trimmedArgs) { + String[] splitArgs = trimmedArgs.split(" "); + if (splitArgs.length > 1) { + return true; + } + return false; + } + +} +``` +###### \java\seedu\address\logic\parser\CliSyntax.java +``` java + /* Prefix with description */ + public static final String PREFIX_NAME_DESC = PREFIX_NAME.toString() + "\t: name"; + public static final String PREFIX_PHONE_DESC = PREFIX_PHONE.toString() + "\t: phone"; + public static final String PREFIX_EMAIL_DESC = PREFIX_EMAIL.toString() + "\t: email"; + public static final String PREFIX_ADDRESS_DESC = PREFIX_ADDRESS.toString() + "\t: address"; + public static final String PREFIX_NRIC_DESC = PREFIX_NRIC.toString() + "\t: NRIC"; + public static final String PREFIX_TAG_DESC = PREFIX_TAG.toString() + "\t: tag"; + public static final String PREFIX_REMARK_DESC = PREFIX_REMARK.toString() + "\t: remark"; + public static final String PREFIX_DATE_DESC = PREFIX_DATE.toString() + "\t: yyyy-mm-dd hh:mm"; + public static final String PREFIX_SPECIES_DESC = PREFIX_SPECIES.toString() + "\t: species"; + public static final String PREFIX_BREED_DESC = PREFIX_BREED.toString() + "\t: breed"; + public static final String PREFIX_COLOUR_DESC = PREFIX_COLOUR.toString() + "\t: colour"; + public static final String PREFIX_BLOODTYPE_DESC = PREFIX_BLOODTYPE.toString() + "\t: blood type"; + + /* Option definitions */ + public static final String OPTION_OWNER = "-o"; + public static final String OPTION_PETPATIENT = "-p"; + public static final String OPTION_APPOINTMENT = "-a"; + public static final String OPTIONFORCE_OWNER = "-fo"; + public static final String OPTIONFORCE_PETPATIENT = "-fp"; + public static final String OPTION_YEAR = "-y"; + public static final String OPTION_MONTH = "-m"; + public static final String OPTION_WEEK = "-w"; + public static final String OPTION_DAY = "-d"; + + /* Option with description */ + public static final String OPTION_OWNER_DESC = OPTION_OWNER + "\t: person/owner"; + public static final String OPTION_PETPATIENT_DESC = OPTION_PETPATIENT + "\t: pet patient"; + public static final String OPTION_APPOINTMENT_DESC = OPTION_APPOINTMENT + "\t: appointment"; + public static final String OPTIONFORCE_OWNER_DESC = OPTIONFORCE_OWNER + "\t: force delete person/owner"; + public static final String OPTIONFORCE_PETPATIENT_DESC = OPTIONFORCE_PETPATIENT + "\t: force delete pet patient"; + public static final String OPTION_YEAR_DESC = OPTION_YEAR + "\t: calendar year view"; + public static final String OPTION_MONTH_DESC = OPTION_MONTH + "\t: calendar month view"; + public static final String OPTION_WEEK_DESC = OPTION_WEEK + "\t: calendar week view"; + public static final String OPTION_DAY_DESC = OPTION_DAY + "\t: calendar day view"; + + private static CliSyntax instance; + + private static final Set prefixes = Stream.of( + PREFIX_NAME_DESC, PREFIX_PHONE_DESC, PREFIX_EMAIL_DESC, PREFIX_ADDRESS_DESC, PREFIX_NRIC_DESC, + PREFIX_BREED_DESC, PREFIX_SPECIES_DESC, PREFIX_COLOUR_DESC, PREFIX_BLOODTYPE_DESC, PREFIX_DATE_DESC, + PREFIX_REMARK_DESC, PREFIX_TAG_DESC) + .collect(Collectors.toSet()); + + private static final Set commandWordsWithOptionPrefix = Stream.of( + AddCommand.COMMAND_WORD, EditCommand.COMMAND_WORD, DeleteCommand.COMMAND_WORD, FindCommand.COMMAND_WORD, + ListAppointmentCommand.COMMAND_WORD).collect(Collectors.toSet()); + + private static final Set commandWords = Stream.of( + AddCommand.COMMAND_WORD, EditCommand.COMMAND_WORD, DeleteCommand.COMMAND_WORD, ListCommand.COMMAND_WORD, + FindCommand.COMMAND_WORD, ChangeThemeCommand.COMMAND_WORD, ClearCommand.COMMAND_WORD, + HelpCommand.COMMAND_WORD, ExitCommand.COMMAND_WORD, RedoCommand.COMMAND_WORD, UndoCommand.COMMAND_WORD, + HistoryCommand.COMMAND_WORD, ListAppointmentCommand.COMMAND_WORD).collect(Collectors.toSet()); + + private static final Set options = Stream.of(OPTION_OWNER_DESC, OPTION_PETPATIENT_DESC, + OPTION_APPOINTMENT_DESC, OPTIONFORCE_OWNER_DESC, OPTIONFORCE_PETPATIENT_DESC, + OPTION_YEAR_DESC, OPTION_MONTH_DESC, OPTION_WEEK_DESC, OPTION_DAY_DESC) + .collect(Collectors.toSet()); + + public static final int MAX_SYNTAX_SIZE = Math.max(commandWords.size(), Math.max(prefixes.size(), options.size())); + + public static CliSyntax getInstance() { + if (instance == null) { + instance = new CliSyntax(); + } + return instance; + } + + public Set getCommandWords() { + return CliSyntax.commandWords; + } + + public Set getCommandWordsWithOptionPrefix() { + return CliSyntax.commandWordsWithOptionPrefix; + } + + public Set getPrefixes() { + return CliSyntax.prefixes; + } + + public Set getOptions() { + return CliSyntax.options; + } + +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * First character of each word will be set to upper case. + * All other characters will be set to lower case. + * @throws IllegalValueException if the given {@code name} is invalid. + */ + public static Name parseName(String name) throws IllegalValueException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!Name.isValidName(trimmedName)) { + throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + } + String[] wordsInName = trimmedName.split(" "); + StringBuilder formattedName = new StringBuilder(); + for (String n : wordsInName) { + formattedName = formattedName.append(n.substring(0, 1).toUpperCase()) + .append(n.substring(1).toLowerCase()).append(" "); + } + return new Name(formattedName.toString().trim()); + } +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String name} into a {@code PetPatientName}. + * Leading and trailing whitespaces will be trimmed. + * First character of each word will be set to upper case. + * All other characters will be set to lower case. + * @throws IllegalValueException if the given {@code name} is invalid. + */ + public static PetPatientName parsePetPatientName(String name) throws IllegalValueException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!PetPatientName.isValidName(trimmedName)) { + throw new IllegalValueException(PetPatientName.MESSAGE_PET_NAME_CONSTRAINTS); + } + String[] wordsInName = trimmedName.split(" "); + StringBuilder formattedName = new StringBuilder(); + for (String n : wordsInName) { + formattedName = formattedName.append(n.substring(0, 1).toUpperCase()) + .append(n.substring(1).toLowerCase()).append(" "); + } + return new PetPatientName(formattedName.toString().trim()); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public Person getPersonWithNric(Nric ownerNric) { + for (Person p : addressBook.getPersonList()) { + if (p.getNric().equals(ownerNric)) { + return p; + } + } + return null; + } + + @Override + public PetPatient getPetPatientWithNricAndName(Nric ownerNric, PetPatientName petPatientName) { + for (PetPatient p : addressBook.getPetPatientList()) { + if (p.getOwner().equals(ownerNric) && p.getName().equals(petPatientName)) { + return p; + } + } + return null; + } + +``` +###### \java\seedu\address\model\theme\Theme.java +``` java +/** + * Represents a Theme in the address book. + */ +public class Theme { + + private static String[] themes = {"dark", "light"}; + private static String[] themesLocation = {"/view/DarkTheme.css", "/view/LightTheme.css"}; + + public static final String MESSAGE_THEME_CONSTRAINTS = "Please specify one of the following themes:\n" + + Arrays.stream(themes).collect(Collectors.joining(", ")); + + public final String selectedThemePath; + + /** + * Constructs a {@code Theme}. + * + * @param themeName A valid theme name. + */ + public Theme(String themeName) { + requireNonNull(themeName); + checkArgument(hasValidThemeName(themeName.toLowerCase()), MESSAGE_THEME_CONSTRAINTS); + selectedThemePath = themesLocation[Arrays.asList(themes).indexOf(themeName.toLowerCase())]; + } + + /** + * Returns true if a given string is a valid theme name. + */ + public static boolean hasValidThemeName(String themeName) { + boolean isValid = Arrays.stream(themes).anyMatch(themeName::equals); + return isValid; + } + + public String getThemeName() { + return themes[Arrays.asList(themesLocation).indexOf(selectedThemePath)]; + } + + public String getThemePath() { + return selectedThemePath; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Theme // instanceof handles nulls + && this.selectedThemePath.equals(((Theme) other).selectedThemePath)); // state check + } + + @Override + public int hashCode() { + return selectedThemePath.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + selectedThemePath + ']'; + } +} +``` +###### \java\seedu\address\ui\Autocomplete.java +``` java +/** + * Handles case-insensitive autocompletion of command line syntax, + * and also some user input parameters: Nric, pet patient name, species, tags etc. + */ +public class Autocomplete { + + private static final Logger logger = LogsCenter.getLogger(Autocomplete.class); + private static final int MAX_SUGGESTION_COUNT = CliSyntax.MAX_SYNTAX_SIZE; + private static Autocomplete instance; + private Logic logic; + private String trimmedCommandInput; + private String[] trimmedCommandInputArray; + private String commandWord; + private String option; + private String targetWord; + private Set tagSet; + + public static Autocomplete getInstance() { + if (instance == null) { + instance = new Autocomplete(); + EventsCenter.getInstance().registerHandler(instance); + } + return instance; + } + + /** + * Initalizes or updates data required for autocomplete. + */ + public void init(Logic logic) { + this.logic = logic; + logic.setAttributesForPersonObjects(); + logic.setAttributesForPetPatientObjects(); + logic.setAttributesForAppointmentObjects(); + } + + /** + * Returns a list of suggestions for autocomplete based on user input (up to current caret position). + * + * @param commandTextField Command box that holds user input. + */ + public List getSuggestions(TextField commandTextField) { + int cursorPosition = commandTextField.getCaretPosition(); + trimmedCommandInput = StringUtil.leftTrim(commandTextField.getText(0, cursorPosition)); + + // split string, but retain all whitespaces in array "trimmedCommandInputArray" + trimmedCommandInputArray = trimmedCommandInput.split("((?<= )|(?= ))", -1); + + commandWord = trimmedCommandInputArray[0]; + targetWord = trimmedCommandInputArray[trimmedCommandInputArray.length - 1].toLowerCase(); + setOption(); + + if (trimmedCommandInputArray.length <= 2) { + return getCommandWordSuggestions(); + } + + if (!targetWord.equals("") && hasOptionsAndPrefixes()) { + if (hasAddCommandReferNric() || hasEditCommandReferNric() || hasFindCommandReferNric()) { + return getNricSuggestions(); + } + + if (hasReferenceToExistingPetPatientNames()) { + return getPetPatientNameSuggestions(); + } + + if (targetWord.startsWith(PREFIX_SPECIES.toString())) { + return getPetPatientSpeciesSuggestions(); + } + + if (targetWord.startsWith(PREFIX_BREED.toString())) { + return getPetPatientBreedSuggestions(); + } + + if (targetWord.startsWith(PREFIX_COLOUR.toString())) { + return getPetPatientColourSuggestions(); + } + + if (targetWord.startsWith(PREFIX_BLOODTYPE.toString())) { + return getPetPatientBloodTypeSuggestions(); + } + + if (targetWord.startsWith(PREFIX_TAG.toString())) { + return getTagSuggestions(); + } + + if (targetWord.startsWith("-")) { + return getOptionSuggestions(); + } + + return getPrefixSuggestions(); + + } else { + + if (hasOptionsAndPrefixes()) { + return getPrefixSuggestions(); + } + } + + return new ArrayList(); + } + + /** + * Returns false if the command is one that does not require any options or prefixes in its syntax. + */ + private boolean hasOptionsAndPrefixes() { + if (logic.getCommandWordsWithOptionPrefix().contains(commandWord)) { + return true; + } + + return false; + } + + /** + * Checks if command input {@code trimmedCommandInputArray} contains the "add" command with reference to existing + * persons' Nric, and determine if autocomplete for persons' Nric is necessary. + * + * Returns false if the command input is to add a new person. + */ + private boolean hasAddCommandReferNric() { + // adding a new owner will not have autocomplete for Nric + if (commandWord.equals(AddCommand.COMMAND_WORD) + && trimmedCommandInputArray[2].equals(OPTION_OWNER)) { + return false; + } + + if (commandWord.equals(AddCommand.COMMAND_WORD) + && trimmedCommandInputArray[trimmedCommandInputArray.length - 3].equals(OPTION_OWNER) + && targetWord.startsWith(PREFIX_NRIC.toString())) { + return true; + } + return false; + } + + /** + * Checks if command input {@code trimmedCommandInputArray} contains the "edit" command with reference to existing + * persons' Nric, and determine if autocomplete for persons' Nric is necessary. + * + * Returns true if editing the owner's nric of a pet patient. + */ + private boolean hasEditCommandReferNric() { + if (commandWord.equals(EditCommand.COMMAND_WORD) + && option.equals(OPTION_PETPATIENT) + && targetWord.startsWith(PREFIX_NRIC.toString())) { + return true; + } + return false; + } + + /** + * Checks if command input {@code trimmedCommandInputArray} contains the "find" command with reference to existing + * persons' Nric, and determine if autocomplete for persons' Nric is necessary. + * + * Returns true if finding a person by nric. + */ + private boolean hasFindCommandReferNric() { + if (commandWord.equals(FindCommand.COMMAND_WORD) + && option.equals(OPTION_OWNER) && targetWord.startsWith(PREFIX_NRIC.toString())) { + return true; + } + return false; + } + + /** + *Returns true if command input {@code trimmedCommandInput} is the syntax for adding a new appointment. + */ + private boolean hasReferenceToExistingPetPatientNames() { + final Pattern addNewAppointment = Pattern.compile(AddCommand.COMMAND_WORD + " -(a)+(?.*)" + + "-(o)+(?.*)" + "-(p)+(?.*)"); + final Matcher matcherForNewAppt = addNewAppointment.matcher(trimmedCommandInput); + + if (matcherForNewAppt.matches()) { + return true; + } + + return false; + } + + /** + * Sets {@code option} based on the last option found in {@code trimmedCommandInput}. + */ + private void setOption() { + option = "nil"; + + int o = trimmedCommandInput.lastIndexOf(OPTION_OWNER); + int p = trimmedCommandInput.lastIndexOf(OPTION_PETPATIENT); + int a = trimmedCommandInput.lastIndexOf(OPTION_APPOINTMENT); + + int index = (a > p) ? a : p; + index = (index > o) ? index : o; + + if (index > -1 && (trimmedCommandInput.length() >= index + 2)) { + option = trimmedCommandInput.substring(index, index + 2); // (inclusive, exclusive) + } + } + + /** + * Returns a string that contains the parameter part of {@code targetWord}. + */ + private String getParameter() { + String[] splitByPrefix = targetWord.split("/"); + String parameter = splitByPrefix[1]; + return parameter; + } + + /** + * Returns a sorted list of suggestions for tags. + * List size conforms to max size {@code MAX_SUGGESTION_COUNT}. + */ + private List getTagSuggestions() { + setTagListBasedOnOption(); + if (targetWord.equals(PREFIX_TAG.toString())) { + List suggestions = tagSet + .stream() + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetTag = getParameter(); + List suggestions = tagSet + .stream() + .filter(t -> t.toLowerCase().startsWith(targetTag) && !t.toLowerCase().equals(targetTag)) + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Sets {@code tagList} based on {@code option}. + * -o option will set elements of {@code tagList} to be persons' tags. + * -p option will set elements of {@code tagList} to be pet patients' tags. + * -a option will set elements of {@code tagList} to be appointments' tags. + */ + private void setTagListBasedOnOption() { + switch(option) { + + case OPTION_OWNER: + tagSet = logic.getAllPersonTags(); + break; + + case OPTION_PETPATIENT: + tagSet = logic.getAllPetPatientTags(); + break; + + case OPTION_APPOINTMENT: + tagSet = logic.getAllAppointmentTags(); + break; + + default: + tagSet = logic.getAllTagsInModel(); + } + } + + /** + * Returns a sorted list of suggestions for pet patient names. + * List size conforms to max size {@code MAX_SUGGESTION_COUNT}. + */ + private List getPetPatientNameSuggestions() { + if (targetWord.equals(PREFIX_NAME.toString())) { + List suggestions = logic.getAllPetPatientNames() + .stream() + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetPetName = getParameter(); + List suggestions = logic.getAllPetPatientNames() + .stream() + .filter(pn -> pn.toLowerCase().startsWith(targetPetName) && !pn.toLowerCase().equals(targetPetName)) + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Returns a sorted list of suggestions for pet patient species. + */ + private List getPetPatientSpeciesSuggestions() { + if (targetWord.equals(PREFIX_SPECIES.toString())) { + List suggestions = logic.getAllPetPatientSpecies() + .stream() + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetSpecies = getParameter(); + List suggestions = logic.getAllPetPatientSpecies() + .stream() + .filter(s -> s.toLowerCase().startsWith(targetSpecies) && !s.toLowerCase().equals(targetSpecies)) + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Returns a sorted list of suggestions for pet patient breeds. + * List size conforms to max size {@code MAX_SUGGESTION_COUNT}. + */ + private List getPetPatientBreedSuggestions() { + if (targetWord.equals(PREFIX_BREED.toString())) { + List suggestions = logic.getAllPetPatientBreeds() + .stream() + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetBreed = getParameter(); + List suggestions = logic.getAllPetPatientBreeds() + .stream() + .filter(b -> b.toLowerCase().startsWith(targetBreed) && !b.toLowerCase().equals(targetBreed)) + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Returns a sorted list of suggestions for pet patient colours. + * List size conforms to max size {@code MAX_SUGGESTION_COUNT}. + */ + private List getPetPatientColourSuggestions() { + if (targetWord.equals(PREFIX_COLOUR.toString())) { + List suggestions = logic.getAllPetPatientColours() + .stream() + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetPetColour = getParameter(); + List suggestions = logic.getAllPetPatientColours() + .stream() + .filter(c -> c.toLowerCase().startsWith(targetPetColour) + && !c.toLowerCase().equals(targetPetColour)) + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Returns a sorted list of suggestions for pet patient blood types. + * List size conforms to max size {@code MAX_SUGGESTION_COUNT}. + */ + private List getPetPatientBloodTypeSuggestions() { + if (targetWord.equals(PREFIX_BLOODTYPE.toString())) { + List suggestions = logic.getAllPetPatientBloodTypes() + .stream() + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetPetBloodType = getParameter(); + List suggestions = logic.getAllPetPatientBloodTypes() + .stream() + .filter(bt -> bt.toLowerCase().startsWith(targetPetBloodType) + && !bt.toLowerCase().equals(targetPetBloodType)) + .sorted(String::compareToIgnoreCase) + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Returns a sorted list of suggestions for Nric. + * List size conforms to max size {@code MAX_SUGGESTION_COUNT}. + */ + private List getNricSuggestions() { + if (targetWord.equals(PREFIX_NRIC.toString())) { + List suggestions = logic.getAllNric() + .stream() + .sorted() + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + + } else { + String targetNric = getParameter().toUpperCase(); + List suggestions = logic.getAllNric() + .stream() + .filter(n -> n.startsWith(targetNric) && !n.equals(targetNric)) + .sorted() + .limit(MAX_SUGGESTION_COUNT) + .collect(Collectors.toList()); + return suggestions; + } + } + + /** + * Returns a sorted list of suggestions for prefixes. + */ + private List getPrefixSuggestions() { + List suggestions = logic.getAllPrefixes() + .stream() + .filter(p -> p.startsWith(targetWord) && !(StringUtil.removeDescription(p).equals(targetWord))) + .sorted() + .collect(Collectors.toList()); + return suggestions; + } + + /** + * Returns a sorted list of suggestions for options. + */ + private List getOptionSuggestions() { + List suggestions = logic.getAllOptions() + .stream() + .filter(o -> o.startsWith(targetWord) && !(StringUtil.removeDescription(o).equals(targetWord))) + .sorted() + .collect(Collectors.toList()); + return suggestions; + } + + /** + * Returns a sorted list of suggestions for command words. + */ + private List getCommandWordSuggestions() { + List suggestions = logic.getAllCommandWords() + .stream() + .filter(c -> c.startsWith(targetWord) && !c.equals(targetWord)) + .sorted() + .collect(Collectors.toList()); + return suggestions; + } + + @Subscribe + public void handleAddressBookChangedEvent(AddressBookChangedEvent a) { + init(this.logic); + logger.info(LogsCenter.getEventHandlingLogMessage(a, "Local data has changed," + + " update autocomplete data")); + } + +} +``` +###### \java\seedu\address\ui\CommandBox.java +``` java + /** + * Toggles autocomplete on or off. + */ + private void toggleAutocomplete() { + if (isAutocompleting) { + commandTextField.textProperty().removeListener(getAutocompleteListener()); + isAutocompleting = false; + hideSuggestionBox(); + logger.info("Autocomplete has been toggled [OFF]"); + } else { + commandTextField.textProperty().addListener(getAutocompleteListener()); + isAutocompleting = true; + logger.info("Autocomplete has been toggled [ON]"); + } + } + + private void hideSuggestionBox() { + if (suggestionBox.isShowing()) { + suggestionBox.hide(); + } + } + + /** + * Calls Autocomplete class to process commandTextField's content. + * + * @param newValue New user input. + */ + private void triggerAutocomplete(String newValue) { + suggestionBox.getItems().clear(); + + if (!newValue.equals("")) { + + List suggestions = autocompleteLogic.getSuggestions(commandTextField); + + if (!suggestions.isEmpty()) { + setContextMenu(suggestions); + } + } + } + + /** + * Sets the context menu {@code suggestionBox} with autocomplete suggestions. + */ + private void setContextMenu(List suggestions) { + for (String s : suggestions) { + MenuItem m = new MenuItem(s); + String autocompleteValue = StringUtil.removeDescription(s); + m.setOnAction(event -> handleAutocompleteSelection(autocompleteValue)); + suggestionBox.getItems().add(m); + } + suggestionBox.show(commandTextField, Side.BOTTOM, 0, 0); + } + + /** + * Updates text in commandTextField with autocomplete selection {@code toAdd}. + * + * Supports insertion of autocomplete selection in the middle of commandTextField. + * user input: 'a', selected autocomplete 'add' --> commandTextField will show 'add' and not 'aadd'. + * user input: 'nr/F012', selected autocomplete 'F0123456B' --> commandTextField will show 'nr/F0123456B' + * and not 'nr/F012F0123456B'. + */ + private void handleAutocompleteSelection(String toAdd) { + int cursorPosition = commandTextField.getCaretPosition(); + int userInputLength = commandTextField.getText().length(); + + // .split() retains all whitespaces in array. + String[] words = commandTextField.getText(0, cursorPosition).split("((?<= )|(?= ))", -1); + String targetWord = words[words.length - 1]; + String restOfInput = getRemainingInput(cursorPosition, userInputLength); + + if (containsPrefix(targetWord)) { + String[] splitByPrefix = targetWord.split("/"); + words[words.length - 1] = splitByPrefix[0] + "/" + toAdd; + } else { + words[words.length - 1] = toAdd; + } + + String updatedInput = String.join("", words); + int newCursorPosition = updatedInput.length(); + commandTextField.setText(updatedInput + restOfInput); + commandTextField.positionCaret(newCursorPosition); + } + + /** + * Returns remaining text in {@code commandTextField} after {@code cursorPosition}, if any. + */ + private String getRemainingInput(int cursorPosition, int userInputLength) { + String restOfInput = ""; + if (userInputLength > cursorPosition + 1) { + restOfInput = commandTextField.getText(cursorPosition, commandTextField.getText().length()); + } + return restOfInput; + } + + /** + * Returns true if {@code toCheck} contains a prefix or is a prefix. + */ + private boolean containsPrefix(String toCheck) { + if (toCheck.contains("/")) { + return true; + } else if (toCheck.length() > 0 && toCheck.substring(toCheck.length() - 1).equals("/")) { + return true; + } else { + return false; + } + } + + public static ChangeListener getAutocompleteListener() { + return autocompleteListener; + } +} +``` +###### \java\seedu\address\ui\MainWindow.java +``` java + /** + * Sets the default theme based on user preferences. + */ + private void setWindowDefaultTheme(UserPrefs prefs) { + getRoot().getScene().getStylesheets().add(prefs.getGuiSettings().getCurrentTheme()); + } + + /** + * Returns the current size, position, and theme of the main Window. + */ + GuiSettings getCurrentGuiSetting() { + ObservableList cssFiles = getRoot().getScene().getStylesheets(); + assert cssFiles.size() == 2 : "There should only be 2 stylesheets used in main Window."; + + String theme = cssFiles.stream().filter(c -> !c.contains("/view/Extensions.css")).findFirst().get(); + return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY(), theme); + } + +``` +###### \java\seedu\address\ui\MainWindow.java +``` java + /** + * Changes the theme of Medeina. + */ + @Subscribe + public void handleChangeThemeEvent(ChangeThemeRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + String userSelectedTheme = event.theme.getThemePath(); + String userSelectedStyleSheet = this.getClass().getResource(userSelectedTheme).toExternalForm(); + if (!hasStyleSheet(userSelectedStyleSheet)) { + changeStyleSheet(userSelectedStyleSheet); + } + } + + /** + * Checks whether {@code theme} is already in use by the application. + */ + public Boolean hasStyleSheet(String theme) { + List styleSheetsInUsed = getRoot().getScene().getStylesheets(); + if (styleSheetsInUsed.contains(theme)) { + return true; + } + return false; + } + + /** + * Removes all existing stylesheets and add the given {@code theme} to style sheets. + * Re-adds Extensions.css to style sheets. + */ + public void changeStyleSheet(String theme) { + String extensions = this.getClass().getResource("/view/Extensions.css").toExternalForm(); + getRoot().getScene().getStylesheets().clear(); + getRoot().getScene().getStylesheets().add(extensions); //re-add Extensions.css + boolean isChanged = getRoot().getScene().getStylesheets().add(theme); + assert isChanged == true : "Medeina's theme is not successfully changed."; + } +} +``` diff --git a/collated/functional/chialejing-reused.md b/collated/functional/chialejing-reused.md new file mode 100644 index 000000000000..11de3b9a27e3 --- /dev/null +++ b/collated/functional/chialejing-reused.md @@ -0,0 +1,119 @@ +# chialejing-reused +###### \java\seedu\address\logic\descriptors\EditPersonDescriptor.java +``` java +/** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ +public class EditPersonDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private Nric nric; + private Set tags; + + public EditPersonDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPersonDescriptor(EditPersonDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setNric(toCopy.nric); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, + this.address, this.nric, this.tags); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setNric(Nric nric) { + this.nric = nric; + } + + public Optional getNric() { + return Optional.ofNullable(nric); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPersonDescriptor)) { + return false; + } + + // state check + EditPersonDescriptor e = (EditPersonDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getAddress().equals(e.getAddress()) + && getNric().equals(e.getNric()) + && getTags().equals(e.getTags()); + } +} +``` diff --git a/collated/functional/chialejing.md b/collated/functional/chialejing.md new file mode 100644 index 000000000000..71243baea7ab --- /dev/null +++ b/collated/functional/chialejing.md @@ -0,0 +1,1941 @@ +# chialejing +###### \java\seedu\address\logic\commands\EditCommand.java +``` java +/** + * Edits the details of an existing person, pet patient or appointment in the address book. + */ +public class EditCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "ed"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits the details of the contact / owner / appointment identified by their respective index numbers. " + + "Existing values will be overwritten by the input values. " + + "Note that INDEX must be a positive integer.\n" + + "To edit the details of an existing contact: " + + COMMAND_WORD + " -o " + "INDEX " + + "[" + PREFIX_NAME + "CONTACT_NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_NRIC + "NRIC] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "To edit the details of an existing pet patient: " + + COMMAND_WORD + " -p " + "INDEX " + + "[" + PREFIX_NAME + "PET_PATIENT_NAME] " + + "[" + PREFIX_SPECIES + "SPECIES] " + + "[" + PREFIX_BREED + "BREED] " + + "[" + PREFIX_COLOUR + "COLOUR] " + + "[" + PREFIX_BLOODTYPE + "BLOOD_TYPE] " + + "[" + PREFIX_NRIC + "OWNER_NRIC] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "To edit the details of an existing appointment: " + + COMMAND_WORD + " -a " + "INDEX " + + "[" + PREFIX_DATE + "YYYY-MM-DD HH:MM] " + + "[" + PREFIX_REMARK + "REMARK] " + // + "[" + PREFIX_NRIC + "OWNER_NRIC] " + // + "[" + PREFIX_NAME + "PET_PATIENT_NAME] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "\n"; + + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Contact: %1$s"; + public static final String MESSAGE_EDIT_PET_PATIENT_SUCCESS = "Edited Pet Patient: %1$s"; + public static final String MESSAGE_EDIT_APPOINTMENT_SUCCESS = "Edited Appointment: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_PERSON = "This contact already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PET_PATIENT = "This pet patient already exists in the address book."; + public static final String MESSAGE_DUPLICATE_APPOINTMENT = "This appointment already exists in the address book."; + public static final String MESSAGE_MISSING_PERSON = "The target contact cannot be missing."; + public static final String MESSAGE_MISSING_PET_PATIENT = "The target pet patient cannot be missing"; + public static final String MESSAGE_MISSING_APPOINTMENT = "The target appointment cannot be missing."; + public static final String MESSAGE_DUPLICATE_APPOINTMENT_TIMING = "Duplicate in appointment timing."; + public static final String MESSAGE_PAST_APPOINTMENT = "Appointment cannot be in the past."; + public static final String MESSAGE_CONCURRENT_APPOINTMENT = "Appointment cannot be concurrent " + + "with other appointments."; + /** + * Enum to support the type of edit command that the user wishes to execute. + */ + public enum Type { EDIT_PERSON, EDIT_PET_PATIENT, EDIT_APPOINTMENT }; + + private Index index; + private EditPersonDescriptor editPersonDescriptor; + private EditPetPatientDescriptor editPetPatientDescriptor; + private EditAppointmentDescriptor editAppointmentDescriptor; + private Type type; + + private Person personToEdit; // original + private Person editedPerson; // edited + private PetPatient petPatientToEdit; + private PetPatient editedPetPatient; + private Appointment appointmentToEdit; + private Appointment editedAppointment; + + /** + * @param index of the person in the filtered person list to edit + * @param editPersonDescriptor details to edit the person with + */ + public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + requireNonNull(index); + requireNonNull(editPersonDescriptor); + + this.index = index; + this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.type = EDIT_PERSON; + } + + /** + * @param index of the pet patient in the filtered pet patient list to edit + * @param editPetPatientDescriptor details to edit the pet patient with + */ + public EditCommand(Index index, EditPetPatientDescriptor editPetPatientDescriptor) { + requireNonNull(index); + requireNonNull(editPetPatientDescriptor); + + this.index = index; + this.editPetPatientDescriptor = new EditPetPatientDescriptor(editPetPatientDescriptor); + this.type = EDIT_PET_PATIENT; + } + + /** + * @param index of the appointment in the filtered appointment list to edit + * @param editAppointmentDescriptor details to edit the appointment with + */ + public EditCommand(Index index, EditAppointmentDescriptor editAppointmentDescriptor) { + requireNonNull(index); + requireNonNull(editAppointmentDescriptor); + + this.index = index; + this.editAppointmentDescriptor = new EditAppointmentDescriptor(editAppointmentDescriptor); + this.type = EDIT_APPOINTMENT; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + switch (type) { + case EDIT_PERSON: + resolvePersonDependencies(); + model.updatePerson(personToEdit, editedPerson); + break; + case EDIT_PET_PATIENT: + resolvePetPatientDependencies(); + model.updatePetPatient(petPatientToEdit, editedPetPatient); + break; + case EDIT_APPOINTMENT: + checkForClashes(); + // checkForSameAppointmentTiming(); + checkForConcurrentAppointments(); + checkForPastAppointment(); + model.updateAppointment(appointmentToEdit, editedAppointment); + break; + default: + break; + } + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (PersonNotFoundException pnfe) { + throw new CommandException(MESSAGE_MISSING_PERSON); + } catch (DuplicatePetPatientException dppe) { + throw new CommandException(MESSAGE_DUPLICATE_PET_PATIENT); + } catch (PetPatientNotFoundException ppnfe) { + throw new CommandException(MESSAGE_MISSING_PET_PATIENT); + } catch (DuplicateDateTimeException ddte) { + throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT_TIMING); + } catch (DuplicateAppointmentException dae) { + throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT); + } catch (AppointmentNotFoundException anfe) { + throw new CommandException(MESSAGE_MISSING_APPOINTMENT); + } catch (PastAppointmentException pae) { + throw new CommandException(MESSAGE_PAST_APPOINTMENT); + } catch (ConcurrentAppointmentException cae) { + throw new CommandException(MESSAGE_CONCURRENT_APPOINTMENT); + } + switch (type) { + case EDIT_PERSON: + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + case EDIT_PET_PATIENT: + model.updateFilteredPetPatientList(PREDICATE_SHOW_ALL_PET_PATIENTS); + return new CommandResult(String.format(MESSAGE_EDIT_PET_PATIENT_SUCCESS, editedPetPatient)); + case EDIT_APPOINTMENT: + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + return new CommandResult(String.format(MESSAGE_EDIT_APPOINTMENT_SUCCESS, editedAppointment)); + default: + return null; + } + } + + /** + * Checks whether person's NRIC has been modified + * If yes, update all other relevant pet patients and appointments under the same person + * If no, do nothing + */ + private void resolvePersonDependencies() throws DuplicatePetPatientException, PetPatientNotFoundException, + DuplicateAppointmentException, AppointmentNotFoundException { + + Nric oldNric = personToEdit.getNric(); + Nric newNric = editedPerson.getNric(); + + if (!oldNric.equals(newNric)) { + updatePetPatientsByOwnerNric(oldNric, newNric); + updateAppointmentByOwnerNric(oldNric, newNric); + } + } + + /** + * Checks whether pet patient's name or owner NRIC has been modified + * If yes, update all other relevant appointments and also the update the new owner for the pet + */ + private void resolvePetPatientDependencies() throws CommandException, + AppointmentNotFoundException, DuplicateAppointmentException { + + Nric oldNric = petPatientToEdit.getOwner(); + Nric newNric = editedPetPatient.getOwner(); + PetPatientName oldPetName = petPatientToEdit.getName(); + PetPatientName newPetName = editedPetPatient.getName(); + + if (!oldNric.equals(newNric)) { // nric edited, I want to change owner + Person newOwner = model.getPersonWithNric(newNric); // new owner must exist + if (newOwner == null) { + throw new CommandException("New owner must exist first before updating pet patient's owner NRIC!"); + } + // we only update nric for appointments for that specific pet patient! + // this is because it might be an owner transfer. If there are some other pets under the previous owner, + // he/she may still be holding on to these pets. + updateAppointmentByOwnerNricForSpecificPetName(oldNric, newNric, oldPetName); + } + if (!oldPetName.equals(newPetName)) { // name edited + updateAppointmentByPetPatientName(newNric, oldPetName, newPetName); + } + } + + /** + * Checks whether there are clashes in appointment date and time (i.e. same timing with another appointment) + */ + private void checkForClashes() throws DuplicateDateTimeException { + LocalDateTime oldDateTime = appointmentToEdit.getDateTime(); + LocalDateTime newDateTime = editedAppointment.getDateTime(); + + if (!oldDateTime.equals(newDateTime)) { + Appointment appointmentWithClash = model.getClashingAppointment(newDateTime); + if (appointmentWithClash != null) { + throw new DuplicateDateTimeException(); + } + } + } + + /** + * Checks whether there are clashes in appointment date and time (concurrent appointments) + */ + private void checkForConcurrentAppointments() throws ConcurrentAppointmentException { + LocalDateTime oldDateTime = appointmentToEdit.getDateTime(); + LocalDateTime newDateTime = editedAppointment.getDateTime(); + + if (model.hasConcurrentAppointment(oldDateTime, newDateTime)) { + throw new ConcurrentAppointmentException(); + } + } + + /** + * Checks whether appointment datetime given is in the past + */ + private void checkForPastAppointment() throws PastAppointmentException { + LocalDateTime newDateTime = editedAppointment.getDateTime(); + + if (newDateTime.isBefore(LocalDateTime.now())) { + throw new PastAppointmentException(); + } + } + + /** + * Checks whether the new timing for the appointment is equivalent to the old one + */ + private void checkForSameAppointmentTiming() throws CommandException { + LocalDateTime oldDateTime = appointmentToEdit.getDateTime(); + LocalDateTime newDateTime = editedAppointment.getDateTime(); + + if (newDateTime.equals(oldDateTime)) { + throw new CommandException("Appointment timing has not changed."); + } + } + + /** + * Helper function to update pet patient's owner from an old nric to new nric + */ + private void updatePetPatientsByOwnerNric(Nric oldNric, Nric newNric) throws + PetPatientNotFoundException, DuplicatePetPatientException { + + ArrayList petPatientArrayList = model.getPetPatientsWithNric(oldNric); + EditPetPatientDescriptor eppd = new EditPetPatientDescriptor(); + eppd.setOwnerNric(newNric); + + for (PetPatient currPetPatient : petPatientArrayList) { + PetPatient modifiedPetPatient = createEditedPetPatient(currPetPatient, eppd); + model.updatePetPatient(currPetPatient, modifiedPetPatient); + model.updateFilteredPetPatientList(PREDICATE_SHOW_ALL_PET_PATIENTS); + } + } + + /** + * Helper function to update appointment's owner from an old nric to new nric + */ + private void updateAppointmentByOwnerNric(Nric oldNric, Nric newNric) throws + AppointmentNotFoundException, DuplicateAppointmentException { + + ArrayList appointmentArrayList = model.getAppointmentsWithNric(oldNric); + EditAppointmentDescriptor ead = new EditAppointmentDescriptor(); + ead.setOwnerNric(newNric); + + for (Appointment currAppointment : appointmentArrayList) { + Appointment modifiedAppointment = createEditedAppointment(currAppointment, ead); + model.updateAppointment(currAppointment, modifiedAppointment); + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + } + } + + /** + * Helper function to update pet patient name in appointment + */ + private void updateAppointmentByPetPatientName(Nric ownerNric, PetPatientName oldPetName, + PetPatientName newPetName) throws + DuplicateAppointmentException, AppointmentNotFoundException { + + ArrayList appointmentArrayList = + model.getAppointmentsWithNricAndPetName(ownerNric, oldPetName); + EditAppointmentDescriptor ead = new EditAppointmentDescriptor(); + ead.setPetPatientName(newPetName); + + for (Appointment currAppointment : appointmentArrayList) { + Appointment modifiedAppointment = createEditedAppointment(currAppointment, ead); + model.updateAppointment(currAppointment, modifiedAppointment); + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + } + } + + /** + * Helper function to update the pet patient owner's NRIC for all its appointment + */ + private void updateAppointmentByOwnerNricForSpecificPetName(Nric oldNric, Nric newNric, PetPatientName oldPetName) + throws DuplicateAppointmentException, AppointmentNotFoundException { + + ArrayList appointmentArrayList = + model.getAppointmentsWithNricAndPetName(oldNric, oldPetName); + EditAppointmentDescriptor ead = new EditAppointmentDescriptor(); + ead.setOwnerNric(newNric); + + for (Appointment currAppointment : appointmentArrayList) { + Appointment modifiedAppointment = createEditedAppointment(currAppointment, ead); + model.updateAppointment(currAppointment, modifiedAppointment); + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + switch (type) { + case EDIT_PERSON: + preprocessUndoableCommandForPerson(); + break; + case EDIT_PET_PATIENT: + preprocessUndoableCommandForPetPatient(); + break; + case EDIT_APPOINTMENT: + preprocessUndoableCommandForAppointment(); + break; + default: + break; + } + } + /** + * Obtains the last shown person list. + */ + protected void preprocessUndoableCommandForPerson() throws CommandException { + List lastShownPersonList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + } + + personToEdit = lastShownPersonList.get(index.getZeroBased()); + editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + } + + /** + * Obtains the last shown pet patient list. + */ + protected void preprocessUndoableCommandForPetPatient() throws CommandException { + List lastShownPetPatientList = model.getFilteredPetPatientList(); + + if (index.getZeroBased() >= lastShownPetPatientList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PET_PATIENT_DISPLAYED_INDEX); + } + + petPatientToEdit = lastShownPetPatientList.get(index.getZeroBased()); + editedPetPatient = createEditedPetPatient(petPatientToEdit, editPetPatientDescriptor); + } + + /** + * Obtains the last shown appointment list. + */ + protected void preprocessUndoableCommandForAppointment() throws CommandException { + List lastShownAppointmentList = model.getFilteredAppointmentList(); + + if (index.getZeroBased() >= lastShownAppointmentList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_APPOINTMENT_DISPLAYED_INDEX); + } + + appointmentToEdit = lastShownAppointmentList.get(index.getZeroBased()); + editedAppointment = createEditedAppointment(appointmentToEdit, editAppointmentDescriptor); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + assert personToEdit != null; + + Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); + Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); + Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Nric updatedNric = editPersonDescriptor.getNric().orElse(personToEdit.getNric()); + Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedNric, updatedTags); + } + + /** + * Creates and returns a {@code PetPatient} with the details of {@code petPatientToEdit} + * edited with {@code editPetPatientDescriptor}. + */ + private static PetPatient createEditedPetPatient(PetPatient petPatientToEdit, + EditPetPatientDescriptor editPetPatientDescriptor) { + assert petPatientToEdit != null; + + PetPatientName updatedName = editPetPatientDescriptor.getName().orElse(petPatientToEdit.getName()); + Species updatedSpecies = editPetPatientDescriptor.getSpecies().orElse(petPatientToEdit.getSpecies()); + Breed updatedBreed = editPetPatientDescriptor.getBreed().orElse(petPatientToEdit.getBreed()); + Colour updatedColour = editPetPatientDescriptor.getColour().orElse(petPatientToEdit.getColour()); + BloodType updatedBloodType = editPetPatientDescriptor.getBloodType().orElse(petPatientToEdit.getBloodType()); + Nric updatedOwnerNric = editPetPatientDescriptor.getOwnerNric().orElse(petPatientToEdit.getOwner()); + Set updatedTags = editPetPatientDescriptor.getTags().orElse(petPatientToEdit.getTags()); + + return new PetPatient( + updatedName, + updatedSpecies, + updatedBreed, + updatedColour, + updatedBloodType, + updatedOwnerNric, + updatedTags + ); + } + + /** + * Creates and returns a {@code Appointment} with the details of {@code appointmentToEdit} + * edited with {@code editAppointmentDescriptor}. + */ + private static Appointment createEditedAppointment(Appointment appointmentToEdit, + EditAppointmentDescriptor editAppointmentDescriptor) { + assert appointmentToEdit != null; + + Nric updatedOwnerNric = editAppointmentDescriptor.getOwnerNric() + .orElse(appointmentToEdit.getOwnerNric()); + PetPatientName updatedPetPatientName = editAppointmentDescriptor.getPetPatientName() + .orElse(appointmentToEdit.getPetPatientName()); + Remark updatedRemark = editAppointmentDescriptor.getRemark() + .orElse(appointmentToEdit.getRemark()); + LocalDateTime updatedLocalDateTime = editAppointmentDescriptor.getLocalDateTime() + .orElse(appointmentToEdit.getDateTime()); + Set updatedTags = editAppointmentDescriptor.getTags() + .orElse(appointmentToEdit.getAppointmentTags()); + + return new Appointment( + updatedOwnerNric, + updatedPetPatientName, + updatedRemark, + updatedLocalDateTime, + updatedTags + ); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCommand)) { + return false; + } + + // state check + EditCommand e = (EditCommand) other; + switch (type) { + case EDIT_PERSON: + return index.equals(e.index) + && editPersonDescriptor.equals(e.editPersonDescriptor) + && Objects.equals(personToEdit, e.personToEdit); + case EDIT_PET_PATIENT: + return index.equals(e.index) + && editPetPatientDescriptor.equals(e.editPetPatientDescriptor) + && Objects.equals(petPatientToEdit, e.petPatientToEdit); + case EDIT_APPOINTMENT: + return index.equals(e.index) + && editAppointmentDescriptor.equals(e.editAppointmentDescriptor) + && Objects.equals(appointmentToEdit, e.appointmentToEdit); + default: + return false; + } + } +} +``` +###### \java\seedu\address\logic\descriptors\EditAppointmentDescriptor.java +``` java +/** + * Stores the details to edit the appointment with. Each non-empty field value will replace the + * corresponding field value of the appointment. + */ +public class EditAppointmentDescriptor { + private Nric ownerNric; + private PetPatientName petPatientName; + private Remark remark; + private LocalDateTime localDateTime; + private Set tags; + + public EditAppointmentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditAppointmentDescriptor(EditAppointmentDescriptor toCopy) { + setOwnerNric(toCopy.ownerNric); + setPetPatientName(toCopy.petPatientName); + setRemark(toCopy.remark); + setLocalDateTime(toCopy.localDateTime); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.ownerNric, this.petPatientName, this.remark, + this.localDateTime, this.tags); + } + + public void setOwnerNric(Nric ownerNric) { + this.ownerNric = ownerNric; + } + + public Optional getOwnerNric() { + return Optional.ofNullable(ownerNric); + } + + public void setPetPatientName(PetPatientName petPatientName) { + this.petPatientName = petPatientName; + } + + public Optional getPetPatientName() { + return Optional.ofNullable(petPatientName); + } + + public void setRemark(Remark remark) { + this.remark = remark; + } + + public Optional getRemark() { + return Optional.ofNullable(remark); + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public Optional getLocalDateTime() { + return Optional.ofNullable(localDateTime); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditAppointmentDescriptor)) { + return false; + } + + // state check + EditAppointmentDescriptor e = (EditAppointmentDescriptor) other; + + return getOwnerNric().equals(e.getOwnerNric()) + && getPetPatientName().equals(e.getPetPatientName()) + && getRemark().equals(e.getRemark()) + && getLocalDateTime().equals(e.getLocalDateTime()) + && getTags().equals(e.getTags()); + } +} +``` +###### \java\seedu\address\logic\descriptors\EditPetPatientDescriptor.java +``` java +/** + * Stores the details to edit the pet patient with. Each non-empty field value will replace the + * corresponding field value of the pet patient. + */ +public class EditPetPatientDescriptor { + private PetPatientName name; + private Species species; + private Breed breed; + private Colour colour; + private BloodType bloodType; + private Nric ownerNric; + private Set tags; + + public EditPetPatientDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPetPatientDescriptor(EditPetPatientDescriptor toCopy) { + setName(toCopy.name); + setSpecies(toCopy.species); + setBreed(toCopy.breed); + setColour(toCopy.colour); + setBloodType(toCopy.bloodType); + setOwnerNric(toCopy.ownerNric); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.name, this.species, this.breed, + this.colour, this.bloodType, this.ownerNric, this.tags); + } + + public void setName(PetPatientName name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setSpecies(Species species) { + this.species = species; + } + + public Optional getSpecies() { + return Optional.ofNullable(species); + } + + public void setBreed(Breed breed) { + this.breed = breed; + } + + public Optional getBreed() { + return Optional.ofNullable(breed); + } + + public void setColour(Colour colour) { + this.colour = colour; + } + + public Optional getColour() { + return Optional.ofNullable(colour); + } + + public void setBloodType(BloodType bloodType) { + this.bloodType = bloodType; + } + + public Optional getBloodType() { + return Optional.ofNullable(bloodType); + } + + public void setOwnerNric(Nric nric) { + this.ownerNric = nric; + } + + public Optional getOwnerNric() { + return Optional.ofNullable(ownerNric); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPetPatientDescriptor)) { + return false; + } + + // state check + EditPetPatientDescriptor e = (EditPetPatientDescriptor) other; + + return getName().equals(e.getName()) + && getSpecies().equals(e.getSpecies()) + && getBreed().equals(e.getBreed()) + && getColour().equals(e.getColour()) + && getBloodType().equals(e.getBloodType()) + && getOwnerNric().equals(e.getOwnerNric()) + && getTags().equals(e.getTags()); + } +} +``` +###### \java\seedu\address\logic\parser\EditCommandParser.java +``` java +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditCommandParser implements Parser { + + private static final Pattern EDIT_COMMAND_FORMAT_PERSON = Pattern.compile("-(o)+(?.*)"); + private static final Pattern EDIT_COMMAND_FORMAT_PET_PATIENT = Pattern.compile("-(p)+(?.*)"); + private static final Pattern EDIT_COMMAND_FORMAT_APPOINTMENT = Pattern.compile("-(a)+(?.*)"); + + /** + * Parses the different types of Objects (Person, PetPatient or Appointment) based on whether user has + * provided "-o", "-p" or "-a" in the command + * @param args String to parse + * @return EditCommand object to edit the object + * @throws ParseException if invalid format is detected + */ + public EditCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + + // Edit existing person + final Matcher matcherForPerson = EDIT_COMMAND_FORMAT_PERSON.matcher(trimmedArgs); + if (matcherForPerson.matches()) { + String personInfo = matcherForPerson.group("personInfo"); + return parsePerson(personInfo); + } + + // Edit existing pet patient + final Matcher matcherForPetPatient = EDIT_COMMAND_FORMAT_PET_PATIENT.matcher(trimmedArgs); + if (matcherForPetPatient.matches()) { + String petPatientInfo = matcherForPetPatient.group("petPatientInfo"); + return parsePetPatient(petPatientInfo); + } + + // Edit existing appointment + final Matcher matcherForAppointment = EDIT_COMMAND_FORMAT_APPOINTMENT.matcher(trimmedArgs); + if (matcherForAppointment.matches()) { + String appointmentInfo = matcherForAppointment.group("appointmentInfo"); + return parseAppointment(appointmentInfo); + } + + // throws exception for invalid format + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + +``` +###### \java\seedu\address\logic\parser\EditCommandParser.java +``` java + /** + * Parses the given {@code petPatientInfo} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditCommand parsePetPatient(String petPatientInfo) throws ParseException { + requireNonNull(petPatientInfo); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(petPatientInfo, PREFIX_NAME, PREFIX_SPECIES, PREFIX_BREED, + PREFIX_COLOUR, PREFIX_BLOODTYPE, PREFIX_NRIC, PREFIX_TAG); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + EditPetPatientDescriptor editPetPatientDescriptor = new EditPetPatientDescriptor(); + try { + ParserUtil.parsePetPatientName(argMultimap.getValue(PREFIX_NAME)) + .ifPresent(editPetPatientDescriptor::setName); + ParserUtil.parseSpecies(argMultimap.getValue(PREFIX_SPECIES)) + .ifPresent(editPetPatientDescriptor::setSpecies); + ParserUtil.parseBreed(argMultimap.getValue(PREFIX_BREED)) + .ifPresent(editPetPatientDescriptor::setBreed); + ParserUtil.parseColour(argMultimap.getValue(PREFIX_COLOUR)) + .ifPresent(editPetPatientDescriptor::setColour); + ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE)) + .ifPresent(editPetPatientDescriptor::setBloodType); + ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC)) + .ifPresent(editPetPatientDescriptor::setOwnerNric); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)) + .ifPresent(editPetPatientDescriptor::setTags); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editPetPatientDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editPetPatientDescriptor); + } + + /** + * Parses the given {@code appointmentInfo} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditCommand parseAppointment(String appointmentInfo) throws ParseException { + requireNonNull(appointmentInfo); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(appointmentInfo, PREFIX_DATE, PREFIX_REMARK, PREFIX_TAG); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + EditAppointmentDescriptor editAppointmentDescriptor = new EditAppointmentDescriptor(); + try { + // ParserUtil.parsePetPatientName(argMultimap.getValue(PREFIX_NAME)) + // .ifPresent(editAppointmentDescriptor::setPetPatientName); + ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE)) + .ifPresent(editAppointmentDescriptor::setLocalDateTime); + ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK)) + .ifPresent(editAppointmentDescriptor::setRemark); + //ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC)) + // .ifPresent(editAppointmentDescriptor::setOwnerNric); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)) + .ifPresent(editAppointmentDescriptor::setTags); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editAppointmentDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editAppointmentDescriptor); + + } + +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code Optional name} into an {@code Optional} if {@code name} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parsePetPatientName(Optional name) throws IllegalValueException { + requireNonNull(name); + return name.isPresent() ? Optional.of(parsePetPatientName(name.get())) : Optional.empty(); + } + + /** + * Parses a {@code String species} into a {@code Species}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Species parseSpecies(String species) throws IllegalValueException { + requireNonNull(species); + String trimmedSpecies = species.trim(); + if (!Species.isValidSpecies(trimmedSpecies)) { + throw new IllegalValueException(Species.MESSAGE_PET_SPECIES_CONSTRAINTS); + } + String[] wordsInSpecies = trimmedSpecies.split(" "); + StringBuilder formattedSpecies = new StringBuilder(); + for (String s : wordsInSpecies) { + formattedSpecies = formattedSpecies + .append(s.substring(0, 1).toUpperCase()) + .append(s.substring(1).toLowerCase()) + .append(" "); + } + return new Species(formattedSpecies.toString().trim()); + } + + /** + * Parses a {@code Optional species} into an {@code Optional} if {@code species} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSpecies(Optional species) throws IllegalValueException { + requireNonNull(species); + return species.isPresent() ? Optional.of(parseSpecies(species.get())) : Optional.empty(); + } + + /** + * Parses a {@code String breed} into a {@code Breed}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Breed parseBreed(String breed) throws IllegalValueException { + requireNonNull(breed); + String trimmedBreed = breed.trim(); + if (!Breed.isValidBreed(trimmedBreed)) { + throw new IllegalValueException(Breed.MESSAGE_PET_BREED_CONSTRAINTS); + } + String[] wordsInBreed = trimmedBreed.split(" "); + StringBuilder formattedBreed = new StringBuilder(); + for (String s : wordsInBreed) { + formattedBreed = formattedBreed + .append(s.substring(0, 1).toUpperCase()) + .append(s.substring(1).toLowerCase()) + .append(" "); + } + return new Breed(formattedBreed.toString().trim()); + } + + /** + * Parses a {@code Optional breed} into an {@code Optional} if {@code breed} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseBreed(Optional breed) throws IllegalValueException { + requireNonNull(breed); + return breed.isPresent() ? Optional.of(parseBreed(breed.get())) : Optional.empty(); + } + + /** + * Parses a {@code String colour} into a {@code Colour}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Colour parseColour(String colour) throws IllegalValueException { + requireNonNull(colour); + String trimmedColour = colour.trim(); + if (!Colour.isValidColour(trimmedColour)) { + throw new IllegalValueException(Colour.MESSAGE_PET_COLOUR_CONSTRAINTS); + } + String[] wordsInColour = trimmedColour.split(" "); + StringBuilder formattedColour = new StringBuilder(); + for (String s : wordsInColour) { + formattedColour = formattedColour + .append(s.substring(0).toLowerCase()) + .append(" "); + } + return new Colour(formattedColour.toString().trim()); + } + + /** + * Parses a {@code Optional colour} into an {@code Optional} if {@code colour} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseColour(Optional colour) throws IllegalValueException { + requireNonNull(colour); + return colour.isPresent() ? Optional.of(parseColour(colour.get())) : Optional.empty(); + } + + /** + * Parses a {@code String bloodType} into a {@code String}. + * Leading and trailing whitespaces will be trimmed. + */ + public static BloodType parseBloodType(String bloodType) throws IllegalValueException { + requireNonNull(bloodType); + String trimmedBloodType = bloodType.trim(); + if (!BloodType.isValidBloodType(trimmedBloodType)) { + throw new IllegalValueException(BloodType.MESSAGE_PET_BLOODTYPE_CONSTRAINTS); + } + String[] wordsInBloodType = trimmedBloodType.split(" "); + StringBuilder formattedBloodType = new StringBuilder(); + for (String s : wordsInBloodType) { + formattedBloodType = formattedBloodType + .append(s.substring(0).toUpperCase()) + .append(" "); + } + return new BloodType(formattedBloodType.toString().trim()); + } + + /** + * Parses a {@code Optional bloodType} into an {@code Optional} if {@code bloodType} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseBloodType(Optional bloodType) throws IllegalValueException { + requireNonNull(bloodType); + return bloodType.isPresent() ? Optional.of(parseBloodType(bloodType.get())) : Optional.empty(); + } +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Replaces the given pet patient {@code target} in the list with {@code editedPetPatient}. + * {@code AddressBook}'s tag list will be updated with the tags of {@code editedPetPatient}. + * + * @throws DuplicatePetPatientException if updating the pet patient's details causes the pet patient to be + * equivalent to another existing pet patient in the list. + * @throws PetPatientNotFoundException if {@code target} could not be found in the list. + * @see #syncWithMasterTagList(PetPatient) + */ + public void updatePetPatient(PetPatient target, PetPatient editedPetPatient) + throws DuplicatePetPatientException, PetPatientNotFoundException { + requireNonNull(editedPetPatient); + + PetPatient syncEditedPetPatient = syncWithMasterTagList(editedPetPatient); + petPatients.setPetPatient(target, syncEditedPetPatient); + removeUselessTags(); + } + + /** + * Replaces the given appointment {@code target} in the list with {@code editedAppointment}. + * {@code AddressBook}'s tag list will be updated with the tags of {@code editedAppointment}. + * + * @throws DuplicateAppointmentException if updating the appointment's details causes the appointment to be + * equivalent to another existing appointment in the list. + * @throws AppointmentNotFoundException if {@code target} could not be found in the list. + * @see #syncWithMasterTagList(Appointment) + */ + public void updateAppointment(Appointment target, Appointment editedAppointment) + throws DuplicateAppointmentException, AppointmentNotFoundException { + requireNonNull(editedAppointment); + + Appointment syncEditedPetPatient = syncWithMasterTagList(editedAppointment); + appointments.setAppointment(target, syncEditedPetPatient); + removeUselessTags(); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Updates the master tag list to include tags in {@code petPatient} that are not in the list. + * + * @return a copy of this {@code petPatient} such that every tag in this pet patient points to a Tag object in the + * master list. + */ + private PetPatient syncWithMasterTagList (PetPatient petPatient) { + final UniqueTagList currentPetPatientTags = new UniqueTagList(petPatient.getTags()); + tags.mergeFrom(currentPetPatientTags); + + // Create map with values = tag object references in the master list + // used for checking person tag references + final Map masterTagObjects = new HashMap<>(); + tags.forEach(tag -> masterTagObjects.put(tag, tag)); + + // Rebuild the list of person tags to point to the relevant tags in the master tag list. + final Set correctTagReferences = new HashSet<>(); + currentPetPatientTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); + return new PetPatient( + petPatient.getName(), + petPatient.getSpecies(), + petPatient.getBreed(), + petPatient.getColour(), + petPatient.getBloodType(), + petPatient.getOwner(), + correctTagReferences); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public synchronized void addPetPatient(PetPatient petPatient) throws DuplicatePetPatientException { + addressBook.addPetPatient(petPatient); + updateFilteredPetPatientList(PREDICATE_SHOW_ALL_PET_PATIENTS); + indicateAddressBookChanged(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public ArrayList getPetPatientsWithNric(Nric ownerNric) { + ArrayList petPatientArrayList = new ArrayList<>(); + for (PetPatient p : addressBook.getPetPatientList()) { + if (p.getOwner().equals(ownerNric)) { + petPatientArrayList.add(p); + } + } + return petPatientArrayList; + } + + @Override + public ArrayList getAppointmentsWithNric(Nric ownerNric) { + ArrayList appointmentArrayList = new ArrayList<>(); + for (Appointment a : addressBook.getAppointmentList()) { + if (a.getOwnerNric().equals(ownerNric)) { + appointmentArrayList.add(a); + } + } + return appointmentArrayList; + } + + @Override + public ArrayList getAppointmentsWithNricAndPetName(Nric ownerNric, PetPatientName petPatientName) { + ArrayList appointmentArrayList = new ArrayList<>(); + for (Appointment a : addressBook.getAppointmentList()) { + if (a.getOwnerNric().equals(ownerNric) && a.getPetPatientName().equals(petPatientName)) { + appointmentArrayList.add(a); + } + } + return appointmentArrayList; + } + + @Override + public Appointment getClashingAppointment(LocalDateTime dateTime) { + for (Appointment a : addressBook.getAppointmentList()) { + if (a.getDateTime().equals(dateTime)) { + return a; + } + } + return null; + } + + @Override + public boolean hasConcurrentAppointment(LocalDateTime oldDateTime, LocalDateTime newDateTime) { + for (Appointment a : addressBook.getAppointmentList()) { + LocalDateTime dateTime = a.getDateTime(); + if (newDateTime.isAfter(dateTime) + && newDateTime.isBefore(dateTime.plusMinutes(30)) + && !dateTime.equals(oldDateTime)) { + return true; + } + if (newDateTime.isBefore(dateTime) + && newDateTime.plusMinutes(30).isAfter(dateTime) + && !dateTime.equals(oldDateTime)) { + return true; + } + } + return false; + } + + @Override + public void updatePetPatient(PetPatient target, PetPatient editedPetPatient) + throws DuplicatePetPatientException, PetPatientNotFoundException { + requireAllNonNull(target, editedPetPatient); + + addressBook.updatePetPatient(target, editedPetPatient); + indicateAddressBookChanged(); + } + + @Override + public void updateAppointment(Appointment target, Appointment editedAppointment) + throws DuplicateAppointmentException, AppointmentNotFoundException { + requireAllNonNull(target, editedAppointment); + + addressBook.updateAppointment(target, editedAppointment); + indicateAddressBookChanged(); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Returns an unmodifiable view of the list of {@code PetPatient} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredPetPatientList() { + return FXCollections.unmodifiableObservableList(filteredPetPatients); + } + + @Override + public void updateFilteredPetPatientList(Predicate predicate) { + requireNonNull(predicate); + filteredPetPatients.setPredicate(predicate); + } + +``` +###### \java\seedu\address\model\petpatient\BloodType.java +``` java +/** + * Represents a PetPatient's blood type in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidBloodType(String)} + */ +public class BloodType { + + public static final String MESSAGE_PET_BLOODTYPE_CONSTRAINTS = + "Pet Patient blood type should only contain alphabetic characters, punctuations and spaces, " + + "and it should not be blank."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String BLOODTYPE_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum}\\p{Punct}\\p{Blank}]*"; + + public final String bloodType; + + /** + * Constructs a {@code BloodType}. + * + * @param bloodType A valid bloodType. + */ + public BloodType(String bloodType) { + requireNonNull(bloodType); + checkArgument(isValidBloodType(bloodType), MESSAGE_PET_BLOODTYPE_CONSTRAINTS); + this.bloodType = bloodType; + } + + /** + * Returns true if a given string is a valid bloodType. + */ + public static boolean isValidBloodType(String test) { + return test.matches(BLOODTYPE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return bloodType; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof BloodType // instanceof handles nulls + && this.bloodType.equals(((BloodType) other).bloodType)); // state check + } + + @Override + public int hashCode() { + return bloodType.hashCode(); + } +} +``` +###### \java\seedu\address\model\petpatient\Breed.java +``` java +/** + * Represents a PetPatient's breed in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidBreed(String)} + */ +public class Breed { + + public static final String MESSAGE_PET_BREED_CONSTRAINTS = + "Pet Patient breed should only contain alphabetic characters and spaces, and it should not be blank."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String BREED_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String breed; + + /** + * Constructs a {@code Breed}. + * + * @param breed A valid breed. + */ + public Breed(String breed) { + requireNonNull(breed); + checkArgument(isValidBreed(breed), MESSAGE_PET_BREED_CONSTRAINTS); + this.breed = breed; + } + + /** + * Returns true if a given string is a valid breed. + */ + public static boolean isValidBreed(String test) { + return test.matches(BREED_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return breed; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Breed // instanceof handles nulls + && this.breed.equals(((Breed) other).breed)); // state check + } + + @Override + public int hashCode() { + return breed.hashCode(); + } +} +``` +###### \java\seedu\address\model\petpatient\Colour.java +``` java +/** + * Represents a PetPatient's colour in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidColour(String)} + */ +public class Colour { + + public static final String MESSAGE_PET_COLOUR_CONSTRAINTS = + "Pet Patient colour should only contain alphabetic characters and spaces, and it should not be blank."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String COLOUR_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String colour; + + /** + * Constructs a {@code Colour}. + * + * @param colour A valid colour. + */ + public Colour(String colour) { + requireNonNull(colour); + checkArgument(isValidColour(colour), MESSAGE_PET_COLOUR_CONSTRAINTS); + this.colour = colour; + } + + /** + * Returns true if a given string is a valid colour. + */ + public static boolean isValidColour(String test) { + return test.matches(COLOUR_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return colour; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Colour // instanceof handles nulls + && this.colour.equals(((Colour) other).colour)); // state check + } + + @Override + public int hashCode() { + return colour.hashCode(); + } +} +``` +###### \java\seedu\address\model\petpatient\exceptions\DuplicatePetPatientException.java +``` java +/** + * Signals that the operation will result in duplicate PetPatient objects. + */ +public class DuplicatePetPatientException extends DuplicateDataException { + public DuplicatePetPatientException() { + super("Operation would result in duplicate pet patients"); + } +} +``` +###### \java\seedu\address\model\petpatient\exceptions\PetPatientNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified pet patient. + */ +public class PetPatientNotFoundException extends Exception { +} +``` +###### \java\seedu\address\model\petpatient\PetPatient.java +``` java +/** + * Represents a PetPatient in the address book. + * Guarantees: details are present, field values are validated. + */ +public class PetPatient { + private final PetPatientName name; + private final Species species; // e.g. dogs, cats, birds, etc. + private final Breed breed; // different varieties of the same species + private final Colour colour; + private final BloodType bloodType; + + private final UniqueTagList tags; + + private final Optional dateOfBirth; // can be null + private Nric ownerNric; // can be null (initially) + private StringBuilder medicalHistory; // can be null (initially) + + //keep this constructor, as owner NRIC can be null initially when adding a new PetPatient + public PetPatient(PetPatientName name, + Species species, + Breed breed, + Colour colour, + BloodType bloodType, + Set tags) { + requireAllNonNull(name, species, breed, colour, bloodType, tags); + this.name = name; + this.species = species; + this.breed = breed; + this.colour = colour; + this.bloodType = bloodType; + this.tags = new UniqueTagList(tags); + + this.ownerNric = null; + this.dateOfBirth = null; + this.medicalHistory = new StringBuilder(); + } + + public PetPatient(PetPatientName name, + Species species, + Breed breed, + Colour colour, + BloodType bloodType, + Nric ownerNric, + Set tags) { + requireAllNonNull(name, species, breed, colour, bloodType, tags); + this.name = name; + this.species = species; + this.breed = breed; + this.colour = colour; + this.bloodType = bloodType; + this.tags = new UniqueTagList(tags); + this.ownerNric = ownerNric; + this.dateOfBirth = null; + this.medicalHistory = new StringBuilder(); + } + + //keep this constructor + public PetPatient(PetPatientName name, + Species species, + Breed breed, + Colour colour, + BloodType bloodType, + Nric ownerNric, + Optional dateOfBirth, + Set tags) { + requireAllNonNull(name, species, breed, colour, bloodType, ownerNric, dateOfBirth, tags); + this.name = name; + this.species = species; + this.breed = breed; + this.colour = colour; + this.bloodType = bloodType; + this.ownerNric = ownerNric; + this.dateOfBirth = dateOfBirth; + this.tags = new UniqueTagList(tags); + this.medicalHistory = new StringBuilder(); + } + + public PetPatientName getName() { + return name; + } + + public Optional getDateOfBirth() { + return dateOfBirth; + } + + public Species getSpecies() { + return species; + } + + public Breed getBreed() { + return breed; + } + + public Colour getColour() { + return colour; + } + + public BloodType getBloodType() { + return bloodType; + } + + public Nric getOwner() { + return ownerNric; + } + + public void setOwnerNric(Nric ownerNric) { + this.ownerNric = ownerNric; + } + + public StringBuilder getMedicalHistory() { + return medicalHistory; + } + + public void updateMedicalHistory(String newContent) { + this.medicalHistory.append(newContent); + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags.toSet()); + } + + /** + * Returns a list of tags as a string, for find command. + */ + public String getTagString() { + StringBuilder tagString = new StringBuilder(); + Set tagSet = Collections.unmodifiableSet(tags.toSet()); + for (Tag tag : tagSet) { + tagString.append(tag.tagName); + tagString.append(" "); + } + return tagString.toString().trim(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof PetPatient)) { + return false; + } + + PetPatient otherPetPatient = (PetPatient) other; + return otherPetPatient.getName().equals(this.getName()) + && otherPetPatient.getSpecies().equals(this.getSpecies()) + && otherPetPatient.getBreed().equals(this.getBreed()) + && otherPetPatient.getColour().equals(this.getColour()) + && otherPetPatient.getBloodType().equals(this.getBloodType()) + && otherPetPatient.getOwner().equals(this.getOwner()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, species, breed, colour, bloodType, tags, ownerNric); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("\t") + .append(getName()) + .append("\tSpecies: ") + .append(getSpecies()) + .append("\tBreed: ") + .append(getBreed()) + .append("\tColor: ") + .append(getColour()) + .append("\tBlood Type: ") + .append(getBloodType()) + .append("\t\tOwner's NRIC: ") + .append(getOwner()) + .append("\tTags: "); + getTags().forEach(builder::append); + return builder.toString(); + } +} +``` +###### \java\seedu\address\model\petpatient\PetPatientName.java +``` java +/** + * Represents a PetPatient's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class PetPatientName { + + public static final String MESSAGE_PET_NAME_CONSTRAINTS = + "Pet Patient names should only contain alphanumeric characters and spaces, and it should not be blank."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String fullName; + + /** + * Constructs a {@code PetPatientName}. + * + * @param name A valid name. + */ + public PetPatientName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_PET_NAME_CONSTRAINTS); + this.fullName = name; + } + + /** + * Returns true if a given string is a valid pet patient 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 PetPatientName // instanceof handles nulls + && this.fullName.equals(((PetPatientName) other).fullName)); // state check + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} +``` +###### \java\seedu\address\model\petpatient\Species.java +``` java +/** + * Represents a PetPatient's species in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidSpecies(String)} + */ +public class Species { + + public static final String MESSAGE_PET_SPECIES_CONSTRAINTS = + "Pet Patient species should only contain alphabetic characters and spaces, and it should not be blank."; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String SPECIES_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String species; + + /** + * Constructs a {@code Species}. + * + * @param species A valid species. + */ + public Species(String species) { + requireNonNull(species); + checkArgument(isValidSpecies(species), MESSAGE_PET_SPECIES_CONSTRAINTS); + this.species = species; + } + + /** + * Returns true if a given string is a valid species. + */ + public static boolean isValidSpecies(String test) { + return test.matches(SPECIES_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return species; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Species // instanceof handles nulls + && this.species.equals(((Species) other).species)); // state check + } + + @Override + public int hashCode() { + return species.hashCode(); + } +} +``` +###### \java\seedu\address\model\petpatient\UniquePetPatientList.java +``` java +/** + * A list of pet patients that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see PetPatient#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniquePetPatientList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent pet patient as the given argument. + */ + public boolean contains(PetPatient toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a pet patient to the list. + * + * @throws DuplicatePetPatientException if the pet patient to add is a duplicate of an existing pet patient + * in the list. + */ + public void add(PetPatient toAdd) throws DuplicatePetPatientException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePetPatientException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the pet patient {@code target} in the list with {@code editedPetPatient}. + * + * @throws DuplicatePetPatientException if the replacement is equivalent to another existing pet patient + * in the list. + * @throws PetPatientNotFoundException if {@code target} could not be found in the list. + */ + public void setPetPatient(PetPatient target, PetPatient editedPetPatient) + throws DuplicatePetPatientException, PetPatientNotFoundException { + requireNonNull(editedPetPatient); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PetPatientNotFoundException(); + } + + if (!target.equals(editedPetPatient) && internalList.contains(editedPetPatient)) { + throw new DuplicatePetPatientException(); + } + + internalList.set(index, editedPetPatient); + } + + /** + * Removes the equivalent pet patient from the list. + * + * @throws PetPatientNotFoundException if no such pet patient could be found in the list. + */ + public boolean remove(PetPatient toRemove) throws PetPatientNotFoundException { + requireNonNull(toRemove); + final boolean petPatientFoundAndDeleted = internalList.remove(toRemove); + if (!petPatientFoundAndDeleted) { + throw new PetPatientNotFoundException(); + } + return petPatientFoundAndDeleted; + } + + public void setPetPatients(UniquePetPatientList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setPetPatients(List petPatients) throws DuplicatePetPatientException { + requireAllNonNull(petPatients); + final UniquePetPatientList replacement = new UniquePetPatientList(); + for (final PetPatient petPatient : petPatients) { + replacement.add(petPatient); + } + setPetPatients(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + 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 UniquePetPatientList // instanceof handles nulls + && this.internalList.equals(((UniquePetPatientList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedPetPatient.java +``` java +/** + * JAXB-friendly version of the PetPatient. + */ +public class XmlAdaptedPetPatient { + public static final String MISSING_NAME_FIELD_MESSAGE_FORMAT = "Pet patient's name field is missing!"; + public static final String MISSING_SPECIES_FIELD_MESSAGE_FORMAT = "Pet patient's species field is missing!"; + public static final String MISSING_BREED_FIELD_MESSAGE_FORMAT = "Pet patient's breed field is missing!"; + public static final String MISSING_COLOUR_FIELD_MESSAGE_FORMAT = "Pet patient's colour field is missing!"; + public static final String MISSING_BLOODTYPE_FIELD_MESSAGE_FORMAT = "Pet patient's blood type field is missing!"; + public static final String MISSING_OWNER_FIELD_MESSAGE_FORMAT = "Pet patient's owner field is missing!"; + + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String species; + @XmlElement(required = true) + private String breed; + @XmlElement(required = true) + private String colour; + @XmlElement(required = true) + private String bloodType; + @XmlElement(required = true) + private String ownerNric; + + @XmlElement + private String dateOfBirth; + @XmlElement + private List tagged = new ArrayList<>(); + @XmlElement + private String medicalHistory; + + /** + * Constructs an XmlAdaptedPetPatient. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedPetPatient() {} + + /** + * Constructs an {@code XmlAdaptedPetPatient} with the given pet patient details. + */ + public XmlAdaptedPetPatient(String name, String species, String breed, String colour, + String bloodType, String ownerNric, List tagged) { + this.name = name; + this.species = species; + this.breed = breed; + this.colour = colour; + this.bloodType = bloodType; + this.ownerNric = ownerNric; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + } + + /** + * Converts a given PetPatient into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedPetPatient + */ + public XmlAdaptedPetPatient(PetPatient source) { + name = source.getName().fullName; + species = source.getSpecies().species; + breed = source.getBreed().breed; + colour = source.getColour().colour; + bloodType = source.getBloodType().bloodType; + ownerNric = source.getOwner().toString(); + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted pet patient object into the model's PetPatient object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted pet patient + */ + public PetPatient toModelType() throws IllegalValueException { + final List petPatientTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + petPatientTags.add(tag.toModelType()); + } + + if (this.name == null) { + throw new IllegalValueException( + String.format(MISSING_NAME_FIELD_MESSAGE_FORMAT, PetPatientName.class.getSimpleName())); + } + if (!PetPatientName.isValidName(this.name)) { + throw new IllegalValueException(PetPatientName.MESSAGE_PET_NAME_CONSTRAINTS); + } + final PetPatientName name = new PetPatientName(this.name); + + if (this.species == null) { + throw new IllegalValueException( + String.format(MISSING_SPECIES_FIELD_MESSAGE_FORMAT, Species.class.getSimpleName())); + } + if (!Species.isValidSpecies(this.species)) { + throw new IllegalValueException(Species.MESSAGE_PET_SPECIES_CONSTRAINTS); + } + final Species species = new Species(this.species); + + if (this.breed == null) { + throw new IllegalValueException( + String.format(MISSING_BREED_FIELD_MESSAGE_FORMAT, Breed.class.getSimpleName())); + } + if (!Breed.isValidBreed(this.breed)) { + throw new IllegalValueException(Breed.MESSAGE_PET_BREED_CONSTRAINTS); + } + final Breed breed = new Breed(this.breed); + + if (this.colour == null) { + throw new IllegalValueException( + String.format(MISSING_COLOUR_FIELD_MESSAGE_FORMAT, Colour.class.getSimpleName())); + } + if (!Colour.isValidColour(this.colour)) { + throw new IllegalValueException(Colour.MESSAGE_PET_COLOUR_CONSTRAINTS); + } + final Colour colour = new Colour(this.colour); + + if (this.bloodType == null) { + throw new IllegalValueException(String.format(MISSING_BLOODTYPE_FIELD_MESSAGE_FORMAT)); + } + if (!BloodType.isValidBloodType(this.bloodType)) { + throw new IllegalValueException(BloodType.MESSAGE_PET_BLOODTYPE_CONSTRAINTS); + } + final BloodType bloodType = new BloodType(this.bloodType); + + if (this.ownerNric == null) { + throw new IllegalValueException( + String.format(MISSING_OWNER_FIELD_MESSAGE_FORMAT, PetPatientName.class.getSimpleName())); + } + if (!Nric.isValidNric(this.ownerNric)) { + throw new IllegalValueException(Nric.MESSAGE_NRIC_CONSTRAINTS); + } + final Nric ownerNric = new Nric(this.ownerNric); + + final Set tags = new HashSet<>(petPatientTags); + return new PetPatient(name, species, breed, colour, bloodType, ownerNric, tags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedPetPatient)) { + return false; + } + + XmlAdaptedPetPatient otherPetPatient = (XmlAdaptedPetPatient) other; + return Objects.equals(name, otherPetPatient.name) + && Objects.equals(species, otherPetPatient.species) + && Objects.equals(breed, otherPetPatient.breed) + && Objects.equals(colour, otherPetPatient.colour) + && Objects.equals(bloodType, otherPetPatient.bloodType) + && Objects.equals(ownerNric, otherPetPatient.ownerNric) + && tagged.equals(otherPetPatient.tagged); + } +} +``` diff --git a/collated/functional/wynonaK-reused.md b/collated/functional/wynonaK-reused.md new file mode 100644 index 000000000000..dd4684cbc80a --- /dev/null +++ b/collated/functional/wynonaK-reused.md @@ -0,0 +1,74 @@ +# wynonaK-reused +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes all {@code tag}s not used by anyone in this {@code AddressBook}. + * Reused from https://github.com/se-edu/addressbook-level4/pull/790/files with minor modifications + */ + private void removeUselessTags() { + Set personTags = + persons.asObservableList() + .stream() + .map(Person::getTags) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + Set appointmentTags = + appointments.asObservableList() + .stream() + .map(Appointment::getAppointmentTags) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + Set petPatientTags = + petPatients.asObservableList() + .stream() + .map(PetPatient::getTags) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + + personTags.addAll(appointmentTags); + personTags.addAll(petPatientTags); + tags.setTags(personTags); + } + + /** + * Removes {@code tag} from {@code person} with that tag this {@code AddressBook}. + * + * @throws PersonNotFoundException if {@code person} is not found in this {@code AddressBook}. + * Reused from https://github.com/se-edu/addressbook-level4/ + * pull/790/files with minor modifications + */ + private void removeTagParticular(Tag tag, Person person) throws PersonNotFoundException { + Set tagList = new HashSet<>(person.getTags()); //gets all the tags from a person + + if (tagList.remove(tag)) { + Person updatedPerson = + new Person(person.getName(), person.getPhone(), person.getEmail(), + person.getAddress(), person.getNric(), tagList); + + try { + updatePerson(person, updatedPerson); + } catch (DuplicatePersonException dpe) { + throw new AssertionError("Modifying tag only should not result in duplicate contact."); + } + } else { + return; + } + } + + /** + * Removes {@code tag} from all person with that tag this {@code AddressBook}. + * Reused from https://github.com/se-edu/addressbook-level4/pull/790/files with minor modifications + */ + public void removeTag(Tag tag) { + try { + for (Person currPerson : persons) { + removeTagParticular(tag, currPerson); + } + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("Impossible as obtained from Medeina."); + } + } + + //// util methods + +``` diff --git a/collated/functional/wynonaK.md b/collated/functional/wynonaK.md new file mode 100644 index 000000000000..e536f98cd609 --- /dev/null +++ b/collated/functional/wynonaK.md @@ -0,0 +1,2132 @@ +# wynonaK +###### \java\seedu\address\commons\events\ui\ChangeDayViewRequestEvent.java +``` java +/** + * Indicates a request to change to the day view of the date requested. + */ +public class ChangeDayViewRequestEvent extends BaseEvent { + + public final LocalDate date; + + public ChangeDayViewRequestEvent(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\commons\events\ui\ChangeMonthViewRequestEvent.java +``` java +/** + * Indicates a request to change to the yearmonth view of the yearmonth requested. + */ +public class ChangeMonthViewRequestEvent extends BaseEvent { + + public final YearMonth yearMonth; + + public ChangeMonthViewRequestEvent(YearMonth yearMonth) { + this.yearMonth = yearMonth; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} + +``` +###### \java\seedu\address\commons\events\ui\ChangeWeekViewRequestEvent.java +``` java +/** + * Indicates a request to change to the week view of the date requested. + */ +public class ChangeWeekViewRequestEvent extends BaseEvent { + + public final LocalDate date; + + public ChangeWeekViewRequestEvent(LocalDate date) { + this.date = date; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\commons\events\ui\ChangeYearViewRequestEvent.java +``` java +/** + * Indicates a request to change to the year view of the year requested. + */ +public class ChangeYearViewRequestEvent extends BaseEvent { + + public final Year year; + + public ChangeYearViewRequestEvent(Year year) { + this.year = year; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\logic\commands\DeleteCommand.java +``` java +/** + * Deletes a person, pet patient or appointment from the address book. + */ +public class DeleteCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + " -[f]o/-[f]p/-a" + + ": Deletes the contact/pet/appointment identified by the index number used in the latest listing.\n" + + "Additional -[f] options indicates forcefully deleting object all of its related dependencies.\n" + + "Parameters: INDEX (must be a positive integer, must not be invalid)\n" + + "Example: " + COMMAND_WORD + " -o 1"; + + public static final String MESSAGE_USAGE_OWNER = COMMAND_WORD + + " -o" + + ": Deletes the contact identified by the index number used in the latest contact listing.\n" + + "Parameters: INDEX (must be a positive integer, must not be invalid)\n" + + "Example: " + COMMAND_WORD + " -o 1"; + + public static final String MESSAGE_USAGE_PET_PATIENT = COMMAND_WORD + + " -p" + + ": Deletes the pet patient identified by the index number used in the latest pet patient listing.\n" + + "Parameters: INDEX (must be a positive integer, must not be invalid)\n" + + "Example: " + COMMAND_WORD + " -p 1"; + + public static final String MESSAGE_USAGE_APPOINTMENT = COMMAND_WORD + + " -a" + + ": Deletes the appointment identified by the index number used in the latest appointment listing.\n" + + "Parameters: INDEX (must be a positive integer, must not be invalid)\n" + + "Example: " + COMMAND_WORD + " -a 1"; + + public static final String MESSAGE_USAGE_FORCE_OWNER = COMMAND_WORD + + " -fo" + + ": Forcefully deletes a contact and all related dependencies " + + "identified by the index number used in the latest contact listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " -fo 1"; + + public static final String MESSAGE_USAGE_FORCE_PET_PATIENT = COMMAND_WORD + + " -fp" + + ": Forcefully deletes a pet patient and all related dependencies " + + "identified by the index number used in the latest pet patient listing.\n" + + "Parameters: INDEX (must be a positive integer, must not be invalid)\n" + + "Example: " + COMMAND_WORD + " -fp 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Contact: %1$s"; + public static final String MESSAGE_DELETE_PET_PATIENT_SUCCESS = "Deleted Pet Patient: %1$s"; + public static final String MESSAGE_DELETE_APPOINTMENT_SUCCESS = "Deleted Appointment: %1$s"; + + private final Index targetIndex; + private final int type; + + private Person personToDelete; + private PetPatient petPatientToDelete; + private Appointment appointmentToDelete; + + public DeleteCommand(int type, Index targetIndex) { + this.type = type; + this.targetIndex = targetIndex; + } + + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + switch (type) { + case 1: return deletePerson(); + case 2: return deletePetPatient(); + case 3: return deleteAppointment(); + case 4: return deleteForcePerson(); + case 5: return deleteForcePetPatient(); + default: + throw new CommandException(MESSAGE_USAGE); + } + } catch (PetDependencyNotEmptyException e) { + throw new CommandException(Messages.MESSAGE_DEPENDENCIES_EXIST); + } catch (AppointmentDependencyNotEmptyException e) { + throw new CommandException(Messages.MESSAGE_DEPENDENCIES_EXIST); + } + } + /** + * Deletes {@code personToDelete} from the address book. + */ + private CommandResult deletePerson() throws PetDependencyNotEmptyException { + try { + requireNonNull(personToDelete); + model.deletePerson(personToDelete); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target contact cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + } + + private void getPersonToDelete() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DISPLAYED_INDEX); + } + + personToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Deletes the pet patient {@code petPatientToDelete} from the address book. + */ + private CommandResult deletePetPatient() throws AppointmentDependencyNotEmptyException { + try { + requireNonNull(petPatientToDelete); + model.deletePetPatient(petPatientToDelete); + } catch (PetPatientNotFoundException ppnfe) { + throw new AssertionError("The target pet patient cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PET_PATIENT_SUCCESS, petPatientToDelete)); + } + + private void getPetPatientToDelete() throws CommandException { + List lastShownList = model.getFilteredPetPatientList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DISPLAYED_INDEX); + } + + petPatientToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + /** + * Deletes the appointment {@code appointmentToDelete} from the address book. + */ + private CommandResult deleteAppointment() { + try { + requireNonNull(appointmentToDelete); + model.deleteAppointment(appointmentToDelete); + } catch (AppointmentNotFoundException anfe) { + throw new AssertionError("The target appointment cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_APPOINTMENT_SUCCESS, appointmentToDelete)); + } + + private void getAppointmentToDelete() throws CommandException { + List lastShownList = model.getFilteredAppointmentList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DISPLAYED_INDEX); + } + + appointmentToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Forcefully deletes {@code personToDelete} from the address book. + * All related dependencies (pet patients, appointments) will be deleted as well. + */ + private CommandResult deleteForcePerson() { + String deleteDependenciesList = ""; + + try { + requireNonNull(personToDelete); + List petPatientsDeleted = model.deletePetPatientDependencies(personToDelete); + List appointmentsDeleted = new ArrayList<>(); + for (PetPatient pp : petPatientsDeleted) { + appointmentsDeleted.addAll(model.deleteAppointmentDependencies(pp)); + deleteDependenciesList += "\n" + (String.format(MESSAGE_DELETE_PET_PATIENT_SUCCESS, pp)); + } + for (Appointment appointment : appointmentsDeleted) { + deleteDependenciesList += "\n" + (String.format(MESSAGE_DELETE_APPOINTMENT_SUCCESS, appointment)); + } + model.deletePerson(personToDelete); + } catch (PersonNotFoundException e) { + throw new AssertionError("The target contact cannot be missing"); + } catch (PetDependencyNotEmptyException e) { + throw new AssertionError("Pet dependencies still exist!"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete) + + deleteDependenciesList); + } + + /** + * Forcefully deletes {@code petPatientToDelete} from the address book. + * All related dependencies (appointments) will be deleted as well. + */ + private CommandResult deleteForcePetPatient() { + String deleteDependenciesList = ""; + + try { + requireNonNull(petPatientToDelete); + List appointmentDependenciesDeleted = model.deleteAppointmentDependencies(petPatientToDelete); + for (Appointment appointment : appointmentDependenciesDeleted) { + deleteDependenciesList += "\n" + (String.format(MESSAGE_DELETE_APPOINTMENT_SUCCESS, appointment)); + } + model.deletePetPatient(petPatientToDelete); + } catch (PetPatientNotFoundException ppnfe) { + throw new AssertionError("The target pet patient cannot be missing"); + } catch (AppointmentDependencyNotEmptyException e) { + throw new AssertionError("Appointment dependencies still exist!"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PET_PATIENT_SUCCESS, petPatientToDelete) + + deleteDependenciesList); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + try { + switch (type) { + case 1: getPersonToDelete(); + break; + case 2: getPetPatientToDelete(); + break; + case 3: getAppointmentToDelete(); + break; + case 4: getPersonToDelete(); + break; + case 5: getPetPatientToDelete(); + break; + default: + throw new CommandException(MESSAGE_USAGE); + } + } catch (CommandException e) { + throw new CommandException(Messages.MESSAGE_INVALID_DISPLAYED_INDEX); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteCommand) other).targetIndex) // state check + && Objects.equals(this.personToDelete, ((DeleteCommand) other).personToDelete)); + } +} +``` +###### \java\seedu\address\logic\commands\FindCommand.java +``` java +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindCommand extends Command { + + public static final String COMMAND_WORD = "find"; + public static final String COMMAND_ALIAS = "f"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all fields that matches any of" + + " the specified option, prefixes & keywords (case-sensitive)" + + " and displays them as a list with index numbers.\n" + + "Parameters: OPTION PREFIX/KEYWORD [MORE_PREFIX/MORE_KEYWORDS]...\n" + + "Accepted Options: -o (CONTACT-RELATED), -p (PET-PATIENT-RELATED)\n" + + "Accepted Prefixes for Contacts: n/NAME, nr/NRIC, t/TAG\n" + + "Accepted Prefixes for Pet Patient: n/NAME, s/SPECIES, b/BREED, c/COLOUR, bt/BLOODTYPE, t/TAG\n" + + "Example: " + COMMAND_WORD + " -o n/alice bob charlie"; + + private HashMap hashMap; + private int type = 0; + + public FindCommand(HashMap hashMap) { + this.hashMap = hashMap; + if (hashMap.containsKey("ownerName") + || hashMap.containsKey("ownerNric") + || hashMap.containsKey("ownerTag")) { + type = 1; + } else if (hashMap.containsKey("petName") + || hashMap.containsKey("petSpecies") + || hashMap.containsKey("petBreed") + || hashMap.containsKey("petColour") + || hashMap.containsKey("petBloodType") + || hashMap.containsKey("petTag")) { + type = 2; + } + } + + @Override + public CommandResult execute() throws CommandException { + switch (type) { + case 1: + return findOwner(); + case 2: + return findPetPatient(); + default: + throw new CommandException(MESSAGE_USAGE); + } + } + + /** + * Finds owners with given {@code predicate} in this {@code addressbook}. + */ + private CommandResult findOwner() { + Predicate finalPredicate = null; + + if (hashMap.containsKey("ownerName")) { + String[] nameKeywords = hashMap.get("ownerName"); + Predicate namePredicate = person -> Arrays.stream(nameKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + finalPredicate = namePredicate; + } + + if (hashMap.containsKey("ownerNric")) { + String[] nricKeywords = hashMap.get("ownerNric"); + Predicate nricPredicate = person -> Arrays.stream(nricKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getNric().toString(), keyword)); + if (finalPredicate == null) { + finalPredicate = nricPredicate; + } else { + finalPredicate = finalPredicate.and(nricPredicate); + } + } + + if (hashMap.containsKey("ownerTag")) { + String[] tagKeywords = hashMap.get("ownerTag"); + Predicate tagPredicate = person -> Arrays.stream(tagKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getTagString(), keyword)); + if (finalPredicate == null) { + finalPredicate = tagPredicate; + } else { + finalPredicate = finalPredicate.and(tagPredicate); + } + } + + model.updateFilteredPersonList(finalPredicate); + updatePetListForOwner(); + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size()) + + "\n" + + getMessageForPetPatientListShownSummary(model.getFilteredPetPatientList().size())); + } + + /** + * Finds owners with given {@code predicate} in this {@code addressbook}. + */ + private CommandResult findPetPatient() { + Predicate finalPredicate = null; + + if (hashMap.containsKey("petName")) { + String[] nameKeywords = hashMap.get("petName"); + Predicate namePredicate = petPatient -> Arrays.stream(nameKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(petPatient.getName().fullName, keyword)); + finalPredicate = namePredicate; + } + + if (hashMap.containsKey("petSpecies")) { + String[] speciesKeywords = hashMap.get("petSpecies"); + Predicate speciesPredicate = petPatient -> Arrays.stream(speciesKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(petPatient.getSpecies().species, keyword)); + if (finalPredicate == null) { + finalPredicate = speciesPredicate; + } else { + finalPredicate = finalPredicate.and(speciesPredicate); + } + } + + if (hashMap.containsKey("petBreed")) { + String[] breedKeywords = hashMap.get("petBreed"); + Predicate breedPredicate = petPatient -> Arrays.stream(breedKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(petPatient.getBreed().breed, keyword)); + if (finalPredicate == null) { + finalPredicate = breedPredicate; + } else { + finalPredicate = finalPredicate.and(breedPredicate); + } + } + + if (hashMap.containsKey("petColour")) { + String[] colourKeywords = hashMap.get("petColour"); + Predicate colourPredicate = petPatient -> Arrays.stream(colourKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(petPatient.getColour().colour, keyword)); + if (finalPredicate == null) { + finalPredicate = colourPredicate; + } else { + finalPredicate = finalPredicate.and(colourPredicate); + } + } + + if (hashMap.containsKey("petBloodType")) { + String[] bloodTypeKeywords = hashMap.get("petBloodType"); + Predicate bloodTypePredicate = petPatient -> Arrays.stream(bloodTypeKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase( + petPatient.getBloodType().bloodType, keyword)); + if (finalPredicate == null) { + finalPredicate = bloodTypePredicate; + } else { + finalPredicate = finalPredicate.and(bloodTypePredicate); + } + } + + if (hashMap.containsKey("petTag")) { + String[] tagKeywords = hashMap.get("petTag"); + Predicate tagPredicate = petPatient -> Arrays.stream(tagKeywords) + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(petPatient.getTagString(), keyword)); + if (finalPredicate == null) { + finalPredicate = tagPredicate; + } else { + finalPredicate = finalPredicate.and(tagPredicate); + } + } + + model.updateFilteredPetPatientList(finalPredicate); + updateOwnerListForPets(); + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size()) + + "\n" + + getMessageForPetPatientListShownSummary(model.getFilteredPetPatientList().size())); + } + + /** + * Updates the filtered pet list with the changed owners in this {@code addressbook}. + */ + private void updatePetListForOwner() { + List nricKeywordsForPets = new ArrayList<>(); + for (Person person : model.getFilteredPersonList()) { + nricKeywordsForPets.add(person.getNric().toString()); + } + Predicate petPatientNricPredicate = petPatient -> nricKeywordsForPets.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(petPatient.getOwner().toString(), keyword)); + model.updateFilteredPetPatientList(petPatientNricPredicate); + } + + /** + * Updates the filtered person list with the changed pets in this {@code addressbook}. + */ + private void updateOwnerListForPets() { + List nricKeywordsForOwner = new ArrayList<>(); + for (PetPatient petPatient : model.getFilteredPetPatientList()) { + if (!nricKeywordsForOwner.contains(petPatient.getOwner().toString())) { + nricKeywordsForOwner.add(petPatient.getOwner().toString()); + } + } + Predicate ownerNricPredicate = person -> nricKeywordsForOwner.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getNric().toString(), keyword)); + model.updateFilteredPersonList(ownerNricPredicate); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.hashMap.equals(((FindCommand) other).hashMap)); // state check + } +} +``` +###### \java\seedu\address\logic\commands\ListAppointmentCommand.java +``` java +/** + * Lists appointments based on the specified year, month, week or day. + */ +public class ListAppointmentCommand extends Command { + public static final String COMMAND_WORD = "listappt"; + public static final String COMMAND_ALIAS = "la"; + + public static final String MESSAGE_SUCCESS = "Successfully listed appointments in the %1$s view requested."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": To handle all appointment related listings.\n" + + "Parameters: OPTION FIELD\n" + + "Accepted Options: -y (YEAR VIEW), -m (MONTH VIEW), -w (WEEK VIEW), -d (DAY VIEW)\n" + + "YEAR VIEW accepts a year field in the format of yyyy.\n" + + "MONTH VIEW accepts a year and month field in the format of yyyy-MM" + + " or just a month field in the format of MM, of which the year will be defaulted to this current year.\n" + + "WEEK VIEW accepts a date field in the format of yyyy-MM-dd.\n" + + "DAY VIEW accepts a date field in the format of yyyy-MM-dd.\n" + + "If nothing is given as a FIELD, it will return the specified view of the current date.\n" + + "You can only list past appointments if you had an appointment in the year of the specified field."; + + private int type = 0; //year = 1, month = 2, week = 3, day = 4. + private Year year = null; + private YearMonth yearMonth = null; + private LocalDate date = null; + + public ListAppointmentCommand(int type, Year year) { + this.type = type; + this.year = year; + } + + public ListAppointmentCommand(int type, YearMonth yearMonth) { + this.type = type; + this.yearMonth = yearMonth; + } + + public ListAppointmentCommand(int type, LocalDate date) { + this.type = type; + this.date = date; + } + + + private CommandResult getYearView() throws NoAppointmentInYearException { + if (year.isBefore(Year.now())) { + if (!checkPastAppointment(year.getValue())) { + throw new NoAppointmentInYearException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + } + + EventsCenter.getInstance().post(new ChangeYearViewRequestEvent(year)); + return new CommandResult(String.format(MESSAGE_SUCCESS, "year")); + } + + private CommandResult getMonthView() throws NoAppointmentInYearException { + if (yearMonth.isBefore(YearMonth.now())) { + if (!checkPastAppointment(yearMonth.getYear())) { + throw new NoAppointmentInYearException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + } + + EventsCenter.getInstance().post(new ChangeMonthViewRequestEvent(yearMonth)); + return new CommandResult(String.format(MESSAGE_SUCCESS, "month")); + } + + private CommandResult getWeekView() throws NoAppointmentInYearException { + if (date.isBefore(LocalDate.now())) { + if (!checkPastAppointment(date.getYear())) { + throw new NoAppointmentInYearException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + } + + EventsCenter.getInstance().post(new ChangeWeekViewRequestEvent(date)); + return new CommandResult(String.format(MESSAGE_SUCCESS, "week")); + } + + private CommandResult getDayView() throws NoAppointmentInYearException { + if (date.isBefore(LocalDate.now())) { + if (!checkPastAppointment(date.getYear())) { + throw new NoAppointmentInYearException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + } + + EventsCenter.getInstance().post(new ChangeDayViewRequestEvent(date)); + return new CommandResult(String.format(MESSAGE_SUCCESS, "day")); + } + + /** + * Check if there exists a past appointment with in the {@code model} with the {@code year} specified. + */ + private boolean checkPastAppointment(int year) { + model.updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + List appointmentList = model.getFilteredAppointmentList(); + for (Appointment appointment : appointmentList) { + if (appointment.getDateTime().getYear() == year) { + return true; + } + } + return false; + } + + @Override + public CommandResult execute() throws CommandException { + try { + switch (type) { + case 1: + return getYearView(); + case 2: + return getMonthView(); + case 3: + return getWeekView(); + case 4: + return getDayView(); + default: + throw new CommandException(MESSAGE_USAGE); + } + } catch (NoAppointmentInYearException e) { + throw new CommandException("You can only list past appointments if you had an appointment" + + " in the year of the specified field!"); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ListAppointmentCommand)) { + return false; + } + + ListAppointmentCommand otherListAppointmentCommand = (ListAppointmentCommand) other; + + boolean yearSame = isTheSame(year, otherListAppointmentCommand.year); + boolean yearMonthSame = isTheSame(yearMonth, otherListAppointmentCommand.yearMonth); + boolean dateSame = isTheSame(date, otherListAppointmentCommand.date); + + if (yearSame || yearMonthSame || dateSame) { + return true; + } else { + return false; + } + } + + /** + * Checks if both objects are the same. + * Returns true if both objects are equivalent. + * Returns true if both objects are null. + */ + public boolean isTheSame(Object one, Object two) { + if (one != null && two != null) { + if (one.equals(two)) { + return true; + } + } + + if (one == null && two == null) { + return true; + } + + return false; + } +} +``` +###### \java\seedu\address\logic\parser\DeleteCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteCommandParser implements Parser { + + private static final Pattern DELETE_COMMAND_FORMAT_OWNER = Pattern.compile("-(o)+(?.*)"); + private static final Pattern DELETE_COMMAND_FORMAT_PET_PATIENT = Pattern.compile("-(p)+(?.*)"); + private static final Pattern DELETE_COMMAND_FORMAT_APPOINTMENT = Pattern.compile("-(a)+(?.*)"); + private static final Pattern DELETE_COMMAND_FORMAT_FORCE_OWNER = Pattern.compile("-(fo)+(?.*)"); + private static final Pattern DELETE_COMMAND_FORMAT_FORCE_PET_PATIENT = Pattern.compile("-(fp)+(?.*)"); + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns an DeleteCommand object for execution. + * type changes depending on what pattern it matches + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + + final Matcher matcherForOwner = DELETE_COMMAND_FORMAT_OWNER.matcher(trimmedArgs); + if (matcherForOwner.matches()) { + try { + int type = 1; + Index index = ParserUtil.parseIndex(matcherForOwner.group("index")); + return new DeleteCommand(type, index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE_OWNER)); + } + } + + final Matcher matcherForPetPatient = DELETE_COMMAND_FORMAT_PET_PATIENT.matcher(trimmedArgs); + if (matcherForPetPatient.matches()) { + try { + int type = 2; + Index index = ParserUtil.parseIndex(matcherForPetPatient.group("index")); + return new DeleteCommand(type, index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE_PET_PATIENT)); + } + } + + final Matcher matcherForAppointment = DELETE_COMMAND_FORMAT_APPOINTMENT.matcher(trimmedArgs); + if (matcherForAppointment.matches()) { + try { + int type = 3; + Index index = ParserUtil.parseIndex(matcherForAppointment.group("index")); + return new DeleteCommand(type, index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE_APPOINTMENT)); + } + } + + final Matcher matcherForForceOwner = DELETE_COMMAND_FORMAT_FORCE_OWNER.matcher(trimmedArgs); + if (matcherForForceOwner.matches()) { + try { + int type = 4; + Index index = ParserUtil.parseIndex(matcherForForceOwner.group("index")); + return new DeleteCommand(type, index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE_FORCE_OWNER)); + } + } + + final Matcher matcherForForcePetPatient = DELETE_COMMAND_FORMAT_FORCE_PET_PATIENT.matcher(trimmedArgs); + if (matcherForForcePetPatient.matches()) { + try { + int type = 5; + Index index = ParserUtil.parseIndex(matcherForForcePetPatient.group("index")); + return new DeleteCommand(type, index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE_FORCE_PET_PATIENT)); + } + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\ListAppointmentCommandParser.java +``` java +/** + * Parses input arguments and creates a new ListAppointmentCommand object + */ +public class ListAppointmentCommandParser implements Parser { + + private static final Pattern LIST_APPOINTMENT_COMMAND_FORMAT_YEAR = Pattern.compile("-(y)+(?.*)"); + private static final Pattern LIST_APPOINTMENT_COMMAND_FORMAT_MONTH = Pattern.compile("-(m)+(?.*)"); + private static final Pattern LIST_APPOINTMENT_COMMAND_FORMAT_WEEK = Pattern.compile("-(w)+(?.*)"); + private static final Pattern LIST_APPOINTMENT_COMMAND_FORMAT_DAY = Pattern.compile("-(d)+(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the ListAppointmentCommand + * and returns an ListAppointmentCommand object for execution. + * type changes depending on what pattern it matches + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ListAppointmentCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + + final Matcher matcherForYear = LIST_APPOINTMENT_COMMAND_FORMAT_YEAR.matcher(trimmedArgs); + if (matcherForYear.matches()) { + int type = 1; + + try { + Year year = ParserUtil.parseYear(matcherForYear.group("info")); + return new ListAppointmentCommand(type, year); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListAppointmentCommand.MESSAGE_USAGE)); + } + } + + final Matcher matcherForMonth = LIST_APPOINTMENT_COMMAND_FORMAT_MONTH.matcher(trimmedArgs); + if (matcherForMonth.matches()) { + try { + int type = 2; + YearMonth yearMonth = ParserUtil.parseMonth(matcherForMonth.group("info")); + return new ListAppointmentCommand(type, yearMonth); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListAppointmentCommand.MESSAGE_USAGE)); + } + } + + final Matcher matcherForWeek = LIST_APPOINTMENT_COMMAND_FORMAT_WEEK.matcher(trimmedArgs); + if (matcherForWeek.matches()) { + try { + int type = 3; + LocalDate date = ParserUtil.parseDate(matcherForWeek.group("info")); + return new ListAppointmentCommand(type, date); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListAppointmentCommand.MESSAGE_USAGE)); + } + } + + final Matcher matcherForDay = LIST_APPOINTMENT_COMMAND_FORMAT_DAY.matcher(trimmedArgs); + if (matcherForDay.matches()) { + try { + int type = 4; + LocalDate date = ParserUtil.parseDate(matcherForDay.group("info")); + return new ListAppointmentCommand(type, date); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListAppointmentCommand.MESSAGE_USAGE)); + } + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListAppointmentCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String remark} into an {@code Remark}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code remark} is invalid. + */ + public static Remark parseRemark(String remark) throws IllegalValueException { + requireNonNull(remark); + String trimmedRemark = remark.trim(); + if (!Remark.isValidRemark(trimmedRemark)) { + throw new IllegalValueException(Remark.MESSAGE_REMARK_CONSTRAINTS); + } + return new Remark(trimmedRemark); + } + + /** + * Parses a {@code Optional remark} into an {@code Optional} if {@code remark} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseRemark(Optional remark) throws IllegalValueException { + requireNonNull(remark); + return remark.isPresent() ? Optional.of(parseRemark(remark.get())) : Optional.empty(); + } + + /** + * Parses a {@code String dateTime} into an {@code LocalDateTime} object. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code dateTime} is invalid. + */ + public static LocalDateTime parseDateTime(String dateTime) throws IllegalValueException { + requireNonNull(dateTime); + + dateTime = dateTime.trim(); + + try { + String[] dateTimeArray = dateTime.split("\\s+"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + df.setLenient(false); + df.parse(dateTimeArray[0]); + } catch (ParseException e) { + throw new IllegalValueException("Please give a valid date and time based on the format yyyy-MM-dd HH:mm!"); + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime localDateTime = null; + try { + localDateTime = LocalDateTime.parse(dateTime, formatter); + } catch (DateTimeParseException e) { + throw new IllegalValueException("Please give a valid date and time based on the format yyyy-MM-dd HH:mm!"); + } + + return localDateTime; + } + + /** + * Parses {@code Optional dateTime} into an {@code Optional} if {@code dateTime} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseDateTime(Optional dateTime) throws IllegalValueException { + requireNonNull(dateTime); + return dateTime.isPresent() ? Optional.of(parseDateTime(dateTime.get())) : Optional.empty(); + } + + /** + * Parses a {@code String date} into an {@code LocalDate} object. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code dateTime} is invalid. + */ + public static LocalDate parseDate(String date) throws IllegalValueException { + LocalDate localDate = null; + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + date = date.trim(); + + if (date.isEmpty()) { + localDate = LocalDate.now(); + return localDate; + } + + try { + df.setLenient(false); + df.parse(date); + localDate = LocalDate.parse(date, formatter); + } catch (ParseException | DateTimeParseException e) { + throw new IllegalValueException("Please give a valid date based on the format yyyy-MM-dd!"); + } + + return localDate; + } + + /** + * Parses {@code Optional date} into an {@code Optional} if {@code date} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseDate(Optional date) throws IllegalValueException { + requireNonNull(date); + return date.isPresent() ? Optional.of(parseDate(date.get())) : Optional.empty(); + } + + /** + * Parses a {@code String stringYear} into an {@code Year} object. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code stringYear} is invalid. + */ + public static Year parseYear(String stringYear) throws IllegalValueException { + Year year = null; + DateFormat df = new SimpleDateFormat("yyyy"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy"); + stringYear = stringYear.trim(); + + if (stringYear.isEmpty()) { + year = Year.now(); + return year; + } + + try { + df.setLenient(false); + df.parse(stringYear); + year = Year.parse(stringYear, formatter); + } catch (ParseException | DateTimeParseException e) { + throw new IllegalValueException("Please give a valid year based on the format yyyy!"); + } + + return year; + } + + /** + * Parses {@code Optional month} into an {@code Optional} if {@code year} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseYear(Optional year) throws IllegalValueException { + requireNonNull(year); + return year.isPresent() ? Optional.of(parseYear(year.get())) : Optional.empty(); + } + + /** + * Parses a {@code String stringMonth} into an {@code YearMonth} object. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code stringMonth} is invalid. + */ + public static YearMonth parseMonth(String stringMonth) throws IllegalValueException { + YearMonth yearMonth = null; + DateFormat df = new SimpleDateFormat("yyyy-MM"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM"); + stringMonth = stringMonth.trim(); + + if (stringMonth.isEmpty()) { + yearMonth = YearMonth.now(); + return yearMonth; + } + + try { + if (stringMonth.length() == 2) { + int month = Integer.parseInt(stringMonth); + yearMonth = YearMonth.now().withMonth(month); + return yearMonth; + } + + df.setLenient(false); + df.parse(stringMonth); + yearMonth = YearMonth.parse(stringMonth, formatter); + } catch (ParseException e) { + throw new IllegalValueException("Please give a valid year and month based on the format yyyy-MM!"); + } catch (NumberFormatException nfe) { + throw new IllegalValueException("Please input integer for month in the format MM!"); + } catch (DateTimeException dte) { + throw new IllegalValueException("Please give a valid month based on the format MM!"); + } + + return yearMonth; + } + + /** + * Parses {@code Optional month} into an {@code Optional} if {@code month} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseMonth(Optional month) throws IllegalValueException { + requireNonNull(month); + return month.isPresent() ? Optional.of(parseMonth(month.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Updates the master tag list to include tags in {@code appointment} that are not in the list. + * + * @return a copy of this {@code appointment} such that every tag in this appointment + * points to a Tag object in the master list. + */ + private Appointment syncWithMasterTagList(Appointment appointment) { + final UniqueTagList appointmentTags = new UniqueTagList(appointment.getAppointmentTags()); + tags.mergeFrom(appointmentTags); + + // Create map with values = tag object references in the master list + // used for checking person tag references + final Map masterTagObjects = new HashMap<>(); + tags.forEach(tag -> masterTagObjects.put(tag, tag)); + + // Rebuild the list of person tags to point to the relevant tags in the master tag list. + final Set correctTagReferences = new HashSet<>(); + appointmentTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); + return new Appointment( + appointment.getOwnerNric(), + appointment.getPetPatientName(), + appointment.getRemark(), + appointment.getDateTime(), + correctTagReferences); + } + + /** + * Adds an appointment. + * Also checks the new appointment's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the appointment to point to those in {@link #tags}. + * + * @throws DuplicateAppointmentException if an equivalent person already exists. + */ + public void addAppointment(Appointment a) throws DuplicateAppointmentException, DuplicateDateTimeException, + ConcurrentAppointmentException, PastAppointmentException { + Appointment appointment = syncWithMasterTagList(a); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any appointment + // in the appointment list. + appointments.add(appointment); + } + + ////Delete operations + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes {@code key} from this {@code AddressBook}. + * + * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. + * @throws PetDependencyNotEmptyException if the {@code key} still contains pet patients it is tied to. + */ + public boolean removePerson(Person key) throws PersonNotFoundException, PetDependencyNotEmptyException { + petPatientDependenciesExist(key); + + if (persons.remove(key)) { + removeUselessTags(); + return true; + } else { + throw new PersonNotFoundException(); + } + } + + /** + * Removes pet patient {@code key} from this {@code AddressBook}. + * + * @throws PetPatientNotFoundException if the {@code key} is not in this {@code AddressBook}. + * @throws AppointmentDependencyNotEmptyException if the {@code key} still contains appointments it is tied to. + */ + public boolean removePetPatient(PetPatient key) + throws PetPatientNotFoundException, AppointmentDependencyNotEmptyException { + appointmentDependenciesExist(key); + + if (petPatients.remove(key)) { + removeUselessTags(); + return true; + } else { + throw new PetPatientNotFoundException(); + } + } + + /** + * Forcefully removes all pet patients dependencies on {@code key} from this {@code AddressBook}. + */ + + public List removeAllPetPatientDependencies(Person key) { + Iterator petPatientIterator = petPatients.iterator(); + List petPatientsDeleted = new ArrayList<>(); + while (petPatientIterator.hasNext()) { + PetPatient petPatient = petPatientIterator.next(); + + if (petPatient.getOwner().equals(key.getNric())) { + petPatientsDeleted.add(petPatient); + petPatientIterator.remove(); + } + } + return petPatientsDeleted; + } + + /** + * @throws AppointmentDependencyNotEmptyException if appointment dependencies of {@code key} + * still exists in {@code AddressBook}. + */ + private void appointmentDependenciesExist(PetPatient key) throws AppointmentDependencyNotEmptyException { + for (Appointment appointment : appointments) { + if (appointment.getPetPatientName().equals(key.getName()) + && appointment.getOwnerNric().equals(key.getOwner())) { + throw new AppointmentDependencyNotEmptyException("Appointment dependency still exist!"); + } + } + } + + /** + * @throws PetDependencyNotEmptyException if pet dependencies of {@code key} still exists in {@code AddressBook}. + */ + private void petPatientDependenciesExist(Person key) throws PetDependencyNotEmptyException { + for (PetPatient petPatient : petPatients) { + if (petPatient.getOwner().equals(key.getNric())) { + throw new PetDependencyNotEmptyException("Pet Patient dependency still exist!"); + } + } + } + + /** + * Forcefully removes all dependencies relying on pet patient {@code key} from this {@code AddressBook}. + * + */ + public List removeAllAppointmentDependencies(PetPatient key) { + List appointmentsDeleted = new ArrayList<>(); + Iterator appointmentIterator = appointments.iterator(); + + while (appointmentIterator.hasNext()) { + Appointment appointment = appointmentIterator.next(); + + if (appointment.getPetPatientName().equals(key.getName()) + && appointment.getOwnerNric().equals(key.getOwner())) { + appointmentsDeleted.add(appointment); + appointmentIterator.remove(); + } + } + + return appointmentsDeleted; + } + + /** + * Removes appointment {@code key} from this {@code AddressBook}. + * + * @throws AppointmentNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeAppointment(Appointment key) throws AppointmentNotFoundException { + if (appointments.remove(key)) { + removeUselessTags(); + return true; + } else { + throw new AppointmentNotFoundException(); + } + } + + //// tag operations + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + @Override + public ObservableList getAppointmentList() { + return appointments.asObservableList(); + } + +``` +###### \java\seedu\address\model\appointment\Appointment.java +``` java +/** + * Represents an Appointment. + * Guarantees: details are present and not null, field values are validated. + */ +public class Appointment { + private Nric ownerNric; + private PetPatientName petPatientName; + private Remark remark; //remarks + private LocalDateTime localDateTime; //date of appointment + + private UniqueTagList appointmentTags; //type of appointment + + /** + * Every field must be present and not null. + */ + public Appointment(Nric ownerNric, PetPatientName petPatientName, Remark remark, + LocalDateTime localDateTime, Set appointmentTags) { + requireAllNonNull(ownerNric, petPatientName, remark, localDateTime, appointmentTags); + this.ownerNric = ownerNric; + this.petPatientName = petPatientName; + this.remark = remark; + this.localDateTime = localDateTime; + // protect internal tags from changes in the arg list + this.appointmentTags = new UniqueTagList(appointmentTags); + } + + /** + * ownerNric and petName can be set later using setter methods. + */ + public Appointment(Remark remark, LocalDateTime localDateTime, Set type) { + requireAllNonNull(remark, localDateTime, type); + this.remark = remark; + this.localDateTime = localDateTime; + // protect internal tags from changes in the arg list + this.appointmentTags = new UniqueTagList(type); + } + + public Nric getOwnerNric() { + return ownerNric; + } + + public void setOwnerNric(Nric ownerNric) { + this.ownerNric = ownerNric; + } + + public PetPatientName getPetPatientName() { + return petPatientName; + } + + public void setPetPatientName(PetPatientName petPatientName) { + this.petPatientName = petPatientName; + } + + public Remark getRemark() { + return remark; + } + + public LocalDateTime getDateTime() { + return localDateTime; + } + + public String getFormattedLocalDateTime() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return localDateTime.format(formatter); + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getAppointmentTags() { + return Collections.unmodifiableSet(appointmentTags.toSet()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Appointment)) { + return false; + } + + Appointment otherAppointment = (Appointment) other; + return otherAppointment.getOwnerNric().equals(this.getOwnerNric()) + && otherAppointment.getPetPatientName().equals((this.getPetPatientName())) + && otherAppointment.getRemark().equals(this.getRemark()) + && otherAppointment.getDateTime().equals(this.getDateTime()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(ownerNric, petPatientName, remark, localDateTime, appointmentTags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" ") + .append(getFormattedLocalDateTime()) + .append(" Remarks: ") + .append(getRemark()) + .append(" Type(s): "); + getAppointmentTags().forEach(builder::append); + return builder.toString(); + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTag() { + return Collections.unmodifiableSet(appointmentTags.toSet()); + } + +``` +###### \java\seedu\address\model\appointment\exceptions\AppointmentDependencyNotEmptyException.java +``` java +/** + * Signals that the operation is unable to continue because there are still appointments dependent. + */ + +public class AppointmentDependencyNotEmptyException extends Exception { + public AppointmentDependencyNotEmptyException(String message) { + super(message); + } +} +``` +###### \java\seedu\address\model\appointment\exceptions\AppointmentNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified appointment. + */ +public class AppointmentNotFoundException extends Exception { +} +``` +###### \java\seedu\address\model\appointment\exceptions\DuplicateAppointmentException.java +``` java +/** + * Signals that the operation will result in duplicate Appointment objects. + */ +public class DuplicateAppointmentException extends DuplicateDataException { + public DuplicateAppointmentException() { + super("Operation would result in duplicate appointments"); + } +} +``` +###### \java\seedu\address\model\appointment\exceptions\DuplicateDateTimeException.java +``` java +/** + * Signals that the operation will result in double booking. + */ +public class DuplicateDateTimeException extends DuplicateDataException { + public DuplicateDateTimeException() { + super("Operation would result in multiple bookings in the same time slot"); + } +} +``` +###### \java\seedu\address\model\appointment\exceptions\NoAppointmentInYearException.java +``` java +/** + * Signals that the operation cannot be done as there is no appointments in said year. + */ +public class NoAppointmentInYearException extends Exception { + public NoAppointmentInYearException(String message) { + super(message); + } +} +``` +###### \java\seedu\address\model\appointment\Remark.java +``` java +/** + * Represents a Appointment's remarks. + * Guarantees: is valid as declared in {@link #isValidRemark(String)} + */ +public class Remark { + + public static final String MESSAGE_REMARK_CONSTRAINTS = + "Remarks can take any values, and it should not be blank. Leave \"nil\" for no remarks."; + + /* + * The first character of the remark must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String REMARK_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Remark}. + * + * @param remark A valid address. + */ + public Remark(String remark) { + requireNonNull(remark); + checkArgument(isValidRemark(remark), MESSAGE_REMARK_CONSTRAINTS); + this.value = remark; + } + + /** + * Returns true if a given string is a valid remark. + */ + public static boolean isValidRemark(String test) { + return test.matches(REMARK_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Remark // instanceof handles nulls + && this.value.equals(((Remark) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\appointment\UniqueAppointmentList.java +``` java +/** + * A list of appointments that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Appointment#equals(Object) + */ +public class UniqueAppointmentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent appointment as the given argument. + */ + public boolean contains(Appointment toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds an appointment to the list. + * + * @throws DuplicateAppointmentException if the person to add is a duplicate of an existing person in the list. + */ + public void add(Appointment toAdd) throws DuplicateAppointmentException, DuplicateDateTimeException, + PastAppointmentException, ConcurrentAppointmentException { + requireNonNull(toAdd); + + if (contains(toAdd)) { + throw new DuplicateAppointmentException(); + } + + ArrayList timeList = new ArrayList<>(); + + for (Appointment a : internalList) { + timeList.add(a.getDateTime()); + if (a.getDateTime().equals(toAdd.getDateTime())) { + throw new DuplicateDateTimeException(); + } + } + + for (LocalDateTime dateTime : timeList) { + if (toAdd.getDateTime().isAfter(dateTime) && toAdd.getDateTime().isBefore(dateTime.plusMinutes(30))) { + throw new ConcurrentAppointmentException(); + } + if (toAdd.getDateTime().isBefore(dateTime) && toAdd.getDateTime().plusMinutes(30).isAfter(dateTime)) { + throw new ConcurrentAppointmentException(); + } + } + internalList.add(toAdd); + } + + /** + * Replaces the appointment {@code target} in the list with {@code editedAppointment}. + * + * @throws DuplicateAppointmentException if the replacement is equivalent to + * another existing appointment in the list. + * @throws AppointmentNotFoundException if {@code target} could not be found in the list. + */ + public void setAppointment(Appointment target, Appointment editedAppointment) + throws DuplicateAppointmentException, AppointmentNotFoundException { + requireNonNull(editedAppointment); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new AppointmentNotFoundException(); + } + + if (!target.equals(editedAppointment) && internalList.contains(editedAppointment)) { + throw new DuplicateAppointmentException(); + } + + internalList.set(index, editedAppointment); + } + + /** + * Removes the equivalent pet patient from the list. + * + * @throws AppointmentNotFoundException if no such pet patient could be found in the list. + */ + public boolean remove(Appointment toRemove) throws AppointmentNotFoundException { + requireNonNull(toRemove); + final boolean appointmentFoundAndDeleted = internalList.remove(toRemove); + if (!appointmentFoundAndDeleted) { + throw new AppointmentNotFoundException(); + } + return appointmentFoundAndDeleted; + } + + public void setAppointments(UniqueAppointmentList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setAppointments(List appointments) + throws DuplicateAppointmentException, DuplicateDateTimeException, + ConcurrentAppointmentException, PastAppointmentException { + requireAllNonNull(appointments); + final UniqueAppointmentList replacement = new UniqueAppointmentList(); + for (final Appointment appointment : appointments) { + replacement.add(appointment); + } + setAppointments(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + 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 UniqueAppointmentList // instanceof handles nulls + && this.internalList.equals(((UniqueAppointmentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public synchronized void deletePetPatient(PetPatient target) + throws PetPatientNotFoundException, AppointmentDependencyNotEmptyException { + addressBook.removePetPatient(target); + indicateAddressBookChanged(); + } + + @Override + public synchronized List deletePetPatientDependencies(Person target) { + List petPatients = addressBook.removeAllPetPatientDependencies(target); + indicateAddressBookChanged(); + return petPatients; + } + + @Override + public synchronized List deleteAppointmentDependencies(PetPatient target) { + List dependenciesDeleted = addressBook.removeAllAppointmentDependencies(target); + indicateAddressBookChanged(); + return dependenciesDeleted; + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public synchronized void deleteAppointment(Appointment target) throws AppointmentNotFoundException { + addressBook.removeAppointment(target); + indicateAddressBookChanged(); + } + + @Override + public synchronized void addAppointment(Appointment appointment) + throws DuplicateAppointmentException, DuplicateDateTimeException, + ConcurrentAppointmentException, PastAppointmentException { + addressBook.addAppointment(appointment); + updateFilteredAppointmentList(PREDICATE_SHOW_ALL_APPOINTMENTS); + indicateAddressBookChanged(); + } + + + @Override + public void deleteTag(Tag tag) { + addressBook.removeTag(tag); + } + + //=========== Filtered Person List Accessors ============================================================= + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /** + * Returns an unmodifiable view of the list of {@code Appointment} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredAppointmentList() { + return FXCollections.unmodifiableObservableList(filteredAppointments); + } + + @Override + public void updateFilteredAppointmentList(Predicate predicate) { + requireNonNull(predicate); + filteredAppointments.setPredicate(predicate); + } + + //=========== Filtered Pet Patient List Accessors ============================================================= + +``` +###### \java\seedu\address\model\petpatient\exceptions\PetDependencyNotEmptyException.java +``` java +/** + * Signals that the operation is unable to continue because there are still pets dependent. + */ +public class PetDependencyNotEmptyException extends Exception { + public PetDependencyNotEmptyException(String message) { + super(message); + } +} +``` +###### \java\seedu\address\model\util\SampleDataUtil.java +``` java +/** + * Contains utility methods for populating {@code AddressBook} with sample data. + */ +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 Nric("S0123456B"), + getTagSet("owner")), + 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 Nric("T0123456C"), + getTagSet("owner")), + 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"), new Nric("G0123456A"), + getTagSet("owner")), + new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Nric("F0123456B"), + getTagSet("owner")), + new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), new Nric("S0163456E"), + getTagSet("owner")), + new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), new Nric("F0123056T"), + getTagSet("owner")), + new Person(new Name("Alexia Tan"), new Phone("67321372"), new Email("alexia@example.com"), + new Address("260 Orchard Road, The Heeren ,04-30/31 238855, Singapore"), new Nric("S1199380Z"), + getTagSet("owner")), + new Person(new Name("Bernard Yeong"), new Phone("65457582"), new Email("bernardyeong@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Nric("S8267808E"), + getTagSet("owner")), + new Person(new Name("John Cena"), new Phone("93282203"), new Email("johncena@example.com"), + new Address("5 Airport Cargo Road #452A Core H 4th Storey, 819462, Singapore"), new Nric("S6654649G"), + getTagSet("owner")), + new Person(new Name("Rick Sanchez"), new Phone("62653105"), new Email("ricksanchez@example.com"), + new Address("15 Kian Teck Road 628770, Singapore"), new Nric("S5985945E"), + getTagSet("owner")), + new Person(new Name("Lee Tze Ting"), new Phone("63392060"), new Email("tzeting@example.com"), + new Address("73 Bras Basah, 07-01 470765, Singapore"), new Nric("S1209036F"), + getTagSet("volunteer")), + new Person(new Name("Lee Yan Hwa"), new Phone("68845060"), new Email("yanhwa@example.com"), + new Address("69 Mohamed Sultan Raod, 239015, Singapore"), new Nric("S3643153I"), + getTagSet("volunteer")), + new Person(new Name("Yuuri Katsuki"), new Phone("63353388"), new Email("yuuriviktor@example.com"), + new Address("180 Clemenceau Avenue #06-01 Haw Par Centre, 239922, Singapore"), new Nric("S4176809F"), + getTagSet("supplier")), + new Person(new Name("Lu Li Ming"), new Phone("62255154"), new Email("liming@example.com"), + new Address("69 Choa Chu Kang Loop #02-12, 689672, Singapore"), new Nric("S2557566J"), + getTagSet("owner", "spca")), + new Person(new Name("Eileen Yeo"), new Phone("67797976"), new Email("eileen@example.com"), + new Address("Block 51 Ayer Rajah Crescent 02-15/16 Singapore 139948, Singapore"), new Nric("S9408343E"), + getTagSet("volunteer", "owner")), + new Person(new Name("Liew Chin Chuan"), new Phone("63921480"), new Email("chinchuan@example.com"), + new Address("71 Sultan Gate, 198496, Singapore"), new Nric("S2330718I"), + getTagSet("owner", "volunteer")), + new Person(new Name("Samson Yeow"), new Phone("63488686"), new Email("samson@example.com"), + new Address("86 East Coast Road, 428788, Singapore"), new Nric("S7165937B"), + getTagSet("owner", "spca")), + new Person(new Name("Codee Ong"), new Phone("63488686"), new Email("codeeo@example.com"), + new Address("35 Changi North Crescent, 499641, Singapore"), new Nric("S1317219F"), + getTagSet("owner")), + new Person(new Name("Fuji Syuusuke"), new Phone("90245123"), new Email("fujis@example.com"), + new Address("Blk 106 Bukit Purmei Street 10, #20-20"), new Nric("S9015638A"), + getTagSet("supplier", "owner")), + new Person(new Name("Tezuka Kunimitsu"), new Phone("92247377"), new Email("teuzkak@example.com"), + new Address("Blk 106 Bukit Purmei Street 10, #20-20"), new Nric("S2012044D"), + getTagSet("supplier", "owner")) + }; + } + + public static PetPatient[] getSamplePetPatients() { + return new PetPatient[] { + new PetPatient(new PetPatientName("Ane"), new Species("Cat"), new Breed("Siamese"), + new Colour("brown"), new BloodType("A"), new Nric("S0123456B"), + getTagSet("hostile")), + new PetPatient(new PetPatientName("Bei"), new Species("Cat"), new Breed("British Shorthair"), + new Colour("grey"), new BloodType("B"), new Nric("T0123456C"), + getTagSet("depression")), + new PetPatient(new PetPatientName("Nei"), new Species("Cat"), new Breed("Maine Coon"), + new Colour("black"), new BloodType("AB"), new Nric("T0123456C"), + getTagSet("aggressive")), + new PetPatient(new PetPatientName("Chae"), new Species("Cat"), new Breed("Russian Blue"), + new Colour("grey"), new BloodType("A"), new Nric("G0123456A"), + getTagSet("fiv")), + new PetPatient(new PetPatientName("Don"), new Species("Dog"), new Breed("German Shepherd"), + new Colour("brown"), new BloodType("DEA 4+"), new Nric("F0123456B"), + getTagSet("aggressive")), + new PetPatient(new PetPatientName("Este"), new Species("Dog"), new Breed("Golden Retriever"), + new Colour("golden"), new BloodType("DEA 6+"), new Nric("S0163456E"), + getTagSet("microchipped")), + new PetPatient(new PetPatientName("Famm"), new Species("Dog"), new Breed("Pug"), + new Colour("golden"), new BloodType("DEA 1.1-"), new Nric("F0123056T"), + getTagSet("3legged")), + new PetPatient(new PetPatientName("Plan"), new Species("Dog"), new Breed("Siberian Husky"), + new Colour("white"), new BloodType("DEA 1.1+"), new Nric("F0123056T"), + getTagSet("hostile", "newborn")), + new PetPatient(new PetPatientName("Blu"), new Species("Cat"), new Breed("Burmese"), + new Colour("brown"), new BloodType("A"), new Nric("S1199380Z"), + getTagSet("hostile", "fiv")), + new PetPatient(new PetPatientName("Red"), new Species("Cat"), new Breed("Cornish Rex"), + new Colour("white"), new BloodType("B"), new Nric("S8267808E"), + getTagSet("fiv")), + new PetPatient(new PetPatientName("Fluffy"), new Species("Cat"), new Breed("Birman"), + new Colour("white"), new BloodType("AB"), new Nric("S6654649G"), + getTagSet("aggressive")), + new PetPatient(new PetPatientName("Scooby"), new Species("Cat"), new Breed("Ocicat"), + new Colour("white"), new BloodType("A"), new Nric("S5985945E"), + getTagSet("hostile", "newborn")), + new PetPatient(new PetPatientName("Snowball"), new Species("Dog"), new Breed("Rottweiler"), + new Colour("brown and black"), new BloodType("DEA 4+"), new Nric("S2557566J"), + getTagSet("aggressive", "microchipped")), + new PetPatient(new PetPatientName("Wabbit"), new Species("Dog"), new Breed("Beagle"), + new Colour("brown and white"), new BloodType("DEA 6+"), new Nric("S2557566J"), + getTagSet("microchipped")), + new PetPatient(new PetPatientName("Oreo"), new Species("Dog"), new Breed("Dalmation"), + new Colour("black and white"), new BloodType("DEA 1.1+"), new Nric("S9408343E"), + getTagSet("3legged")), + new PetPatient(new PetPatientName("Milkshake"), new Species("Bird"), new Breed("Black Throated Sparrow"), + new Colour("black and white"), new BloodType("NIL"), new Nric("S2330718I"), + getTagSet("newborn", "missing")), + new PetPatient(new PetPatientName("Ginger"), new Species("Bird"), new Breed("Amazon Parrot"), + new Colour("green"), new BloodType("NIL"), new Nric("S2330718I"), + getTagSet("hostile")), + new PetPatient(new PetPatientName("Juniper"), new Species("Chinchilla"), new Breed("Lanigera Chinchilla"), + new Colour("grey"), new BloodType("NIL"), new Nric("S2330718I"), + getTagSet("newborn")), + new PetPatient(new PetPatientName("Baron"), new Species("Chinchilla"), new Breed("Brevicaudata Chinchilla"), + new Colour("black"), new BloodType("NIL"), new Nric("S7165937B"), + getTagSet("microchipped", "allergy")), + new PetPatient(new PetPatientName("Sting"), new Species("Guinea Pig"), new Breed("Abyssinian Guinea Pig"), + new Colour("white"), new BloodType("B RH-"), new Nric("S7165937B"), + getTagSet("newborn", "hostile")), + new PetPatient(new PetPatientName("Riddle"), new Species("Guinea Pig"), new Breed("Skinny Pig"), + new Colour("black and white"), new BloodType("A RH+"), new Nric("S7165937B"), + getTagSet("aggressive", "allergy")), + new PetPatient(new PetPatientName("Tiki"), new Species("Guinea Pig"), new Breed("Teddy Guinea Pig"), + new Colour("golden"), new BloodType("AB RH+"), new Nric("S0163456E"), + getTagSet("drooling", "newborn")), + new PetPatient(new PetPatientName("Hero"), new Species("Dog"), new Breed("German Shepherd"), + new Colour("black"), new BloodType("DEA 1.1+"), new Nric("S2012044D"), + getTagSet("newborn")), + new PetPatient(new PetPatientName("Thorn"), new Species("Cat"), new Breed("Chinchilla Persian"), + new Colour("white"), new BloodType("AB"), new Nric("S9015638A"), + getTagSet("newborn")), + new PetPatient(new PetPatientName("Alpha"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("black and white"), new BloodType("DEA 4+"), new Nric("S1317219F"), + getTagSet("aggressive", "newborn")), + new PetPatient(new PetPatientName("Beta"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("brown and white"), new BloodType("DEA 6+"), new Nric("S1317219F"), + getTagSet("microchipped", "hostile")), + new PetPatient(new PetPatientName("Gamma"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("red and white"), new BloodType("DEA 1.1-"), new Nric("S1317219F"), + getTagSet("microchipped")), + new PetPatient(new PetPatientName("Delta"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("brown and white"), new BloodType("DEA 1.1+"), new Nric("S1317219F"), + getTagSet("microchipped")), + new PetPatient(new PetPatientName("Epsilon"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("seal and white"), new BloodType("DEA 4+"), new Nric("S1317219F"), + getTagSet("microchipped", "aggressive")), + new PetPatient(new PetPatientName("Zeta"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("sable and white"), new BloodType("DEA 4-"), new Nric("F0123056T"), + getTagSet("microchipped", "senior")), + new PetPatient(new PetPatientName("Eta"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("brown and white"), new BloodType("DEA 6+"), new Nric("S1317219F"), + getTagSet("microchipped")), + new PetPatient(new PetPatientName("Theta"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("gray and white"), new BloodType("DEA 1.1+"), new Nric("S1317219F"), + getTagSet("microchipped", "senior")), + new PetPatient(new PetPatientName("Iota"), new Species("Dog"), new Breed("Alaskan Malamute"), + new Colour("black and white"), new BloodType("DEA 1.1+"), new Nric("S1317219F"), + getTagSet("microchipped", "arthritis")) + }; + } + + + public static Appointment[] getSampleAppointments() { + return new Appointment[] { + new Appointment(new Nric("S0123456B"), new PetPatientName("Ane"), new Remark("nil"), + getLocalDateTime("2018-10-01 10:30"), getTagSet("checkup")), + new Appointment(new Nric("T0123456C"), new PetPatientName("Bei"), new Remark("nil"), + getLocalDateTime("2018-10-02 10:30"), getTagSet("presurgery")), + new Appointment(new Nric("F0123056T"), new PetPatientName("Famm"), new Remark("Home visit"), + getLocalDateTime("2018-10-03 10:30"), getTagSet("vaccination")), + new Appointment(new Nric("F0123056T"), new PetPatientName("Plan"), new Remark("Home visit"), + getLocalDateTime("2018-10-03 11:00"), getTagSet("vaccination")), + new Appointment(new Nric("T0123456C"), new PetPatientName("Bei"), new Remark("nil"), + getLocalDateTime("2018-10-06 10:30"), getTagSet("surgery")), + new Appointment(new Nric("G0123456A"), new PetPatientName("Chae"), new Remark("nil"), + getLocalDateTime("2018-10-07 09:30"), getTagSet("checkup")), + new Appointment(new Nric("F0123456B"), new PetPatientName("Don"), new Remark("nil"), + getLocalDateTime("2018-10-07 15:30"), getTagSet("microchipping")), + new Appointment(new Nric("T0123456C"), new PetPatientName("Bei"), new Remark("nil"), + getLocalDateTime("2018-10-09 15:30"), getTagSet("postsurgery")), + new Appointment(new Nric("T0123456C"), new PetPatientName("Nei"), new Remark("nil"), + getLocalDateTime("2018-10-09 16:00"), getTagSet("checkup")), + new Appointment(new Nric("S1199380Z"), new PetPatientName("Blu"), new Remark("nil"), + getLocalDateTime("2018-06-01 10:30"), getTagSet("vaccination")), + new Appointment(new Nric("S8267808E"), new PetPatientName("Red"), new Remark("Home visit"), + getLocalDateTime("2018-06-01 11:30"), getTagSet("checkup")), + new Appointment(new Nric("S6654649G"), new PetPatientName("Fluffy"), new Remark("nil"), + getLocalDateTime("2018-06-02 10:30"), getTagSet("vaccination")), + new Appointment(new Nric("S9408343E"), new PetPatientName("Oreo"), new Remark("nil"), + getLocalDateTime("2018-06-02 11:00"), getTagSet("microchipping")), + new Appointment(new Nric("S2557566J"), new PetPatientName("Wabbit"), new Remark("nil"), + getLocalDateTime("2018-06-03 10:30"), getTagSet("sterilisation")), + new Appointment(new Nric("S2330718I"), new PetPatientName("Ginger"), new Remark("nil"), + getLocalDateTime("2018-06-03 09:30"), getTagSet("checkup")), + new Appointment(new Nric("F0123456B"), new PetPatientName("Juniper"), new Remark("Might require stay"), + getLocalDateTime("2018-06-04 15:30"), getTagSet("sterilisation")), + new Appointment(new Nric("S7165937B"), new PetPatientName("Baron"), new Remark("nil"), + getLocalDateTime("2018-06-04 16:30"), getTagSet("checkup")), + new Appointment(new Nric("S7165937B"), new PetPatientName("Sting"), new Remark("Home visit"), + getLocalDateTime("2018-06-05 16:00"), getTagSet("vaccination")), + new Appointment(new Nric("S7165937B"), new PetPatientName("Riddle"), new Remark("Might require stay"), + getLocalDateTime("2018-06-06 15:30"), getTagSet("sterilisation")), + new Appointment(new Nric("S0163456E"), new PetPatientName("Tiki"), new Remark("nil"), + getLocalDateTime("2018-06-07 16:30"), getTagSet("checkup")), + new Appointment(new Nric("S2012044D"), new PetPatientName("Hero"), new Remark("Might require stay"), + getLocalDateTime("2018-06-08 16:00"), getTagSet("sterilisation")), + new Appointment(new Nric("S9015638A"), new PetPatientName("Thorn"), new Remark("Might require stay"), + getLocalDateTime("2018-06-08 18:00"), getTagSet("sterilisation")), + }; + } + + public static ReadOnlyAddressBook getSampleAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Person samplePerson : getSamplePersons()) { + sampleAb.addPerson(samplePerson); + } + for (PetPatient petPatient : getSamplePetPatients()) { + sampleAb.addPetPatient(petPatient); + } + for (Appointment appointment : getSampleAppointments()) { + sampleAb.addAppointment(appointment); + } + return sampleAb; + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (DuplicateNricException e) { + throw new AssertionError("sample data cannot contain duplicate NRIC values", e); + } catch (DuplicatePetPatientException e) { + throw new AssertionError("sample data cannot contain duplicate pet patients", e); + } catch (DuplicateDateTimeException e) { + throw new AssertionError("sample data cannot contain double booked appointments", e); + } catch (DuplicateAppointmentException e) { + throw new AssertionError("sample data cannot contain duplicate appointments", e); + } catch (ConcurrentAppointmentException cae) { + throw new AssertionError("AddressBook should not add appointments to on-going appointment slots"); + } catch (PastAppointmentException pae) { + throw new AssertionError("AddressBook should not add appointments with past DateTime"); + } + } + + /** + * Returns a tag set containing the list of strings given. + */ + public static Set getTagSet(String... strings) { + HashSet tags = new HashSet<>(); + for (String s : strings) { + tags.add(new Tag(s)); + } + + return tags; + } + + /** + * Returns a LocalDateTime object of the given string. + */ + private static LocalDateTime getLocalDateTime(String string) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + return LocalDateTime.parse(string, formatter); + } + +} +``` +###### \java\seedu\address\storage\XmlAdaptedAppointment.java +``` java +/** + * JAXB-friendly version of an Appointment. + */ +public class XmlAdaptedAppointment { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Appointment %s field is missing!"; + + @XmlElement(required = true) + private String ownerNric; + @XmlElement(required = true) + private String petPatientName; + @XmlElement(required = true) + private String remark; + @XmlElement(required = true) + private String dateTime; + + @XmlElement + private List appointmentTagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedAppointment. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAppointment() {} + + /** + * Constructs an {@code XmlAdaptedAppointment} with the given appointment details. + */ + public XmlAdaptedAppointment(String ownerNric, String petPatientName, String remark, + String dateTime, List appointmentTagged) { + this.ownerNric = ownerNric; + this.petPatientName = petPatientName; + this.remark = remark; + this.dateTime = dateTime; + if (appointmentTagged != null) { + this.appointmentTagged = new ArrayList<>(appointmentTagged); + } + } + + /** + * Converts a given Appointment into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedAppointment + */ + public XmlAdaptedAppointment(Appointment source) { + ownerNric = source.getOwnerNric().toString(); + petPatientName = source.getPetPatientName().toString(); + remark = source.getRemark().value; + dateTime = source.getFormattedLocalDateTime(); + appointmentTagged = new ArrayList<>(); + for (Tag tag : source.getAppointmentTags()) { + appointmentTagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted appointment object into the model's Appointment object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted appointment + */ + public Appointment toModelType() throws IllegalValueException { + final List appointmentTags = new ArrayList<>(); + for (XmlAdaptedTag tag : appointmentTagged) { + appointmentTags.add(tag.toModelType()); + } + + if (this.ownerNric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(this.ownerNric)) { + throw new IllegalValueException(Nric.MESSAGE_NRIC_CONSTRAINTS); + } + final Nric ownerNric = new Nric(this.ownerNric); + + if (this.petPatientName == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, PetPatientName.class.getSimpleName())); + } + if (!PetPatientName.isValidName(this.petPatientName)) { + throw new IllegalValueException(PetPatientName.MESSAGE_PET_NAME_CONSTRAINTS); + } + final PetPatientName petPatientName = new PetPatientName(this.petPatientName); + + if (this.remark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); + } + if (!Remark.isValidRemark(this.remark)) { + throw new IllegalValueException(Remark.MESSAGE_REMARK_CONSTRAINTS); + } + + final Remark remark = new Remark(this.remark); + + if (this.dateTime == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, LocalDateTime.class.getSimpleName())); + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime localDateTime = null; + + try { + localDateTime = LocalDateTime.parse(dateTime, formatter); + } catch (DateTimeParseException e) { + throw new IllegalValueException("Please follow the format of yyyy-MM-dd HH:mm"); + } + + final LocalDateTime dateTime = localDateTime; + + + final Set thisAppointmentTags = new HashSet<>(appointmentTags); + return new Appointment(ownerNric, petPatientName, remark, dateTime, thisAppointmentTags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedAppointment)) { + return false; + } + + XmlAdaptedAppointment otherAppointment = (XmlAdaptedAppointment) other; + return Objects.equals(ownerNric, otherAppointment.ownerNric) + && Objects.equals(petPatientName, otherAppointment.petPatientName) + && Objects.equals(remark, otherAppointment.remark) + && Objects.equals(dateTime, otherAppointment.dateTime) + && appointmentTagged.equals(otherAppointment.appointmentTagged); + } +} +``` +###### \java\seedu\address\ui\CalendarWindow.java +``` java + private void changeYearView(Year year) { + calendarView.showYear(year); + } + + private void changeMonthView(YearMonth yearMonth) { + calendarView.showYearMonth(yearMonth); + } + + /** + * changes the week view based on {@code date}. + * Does some particular checks (as weekFields give diff result from calendarFX), + * to ensure that it runs smoothly. + */ + private void changeWeekView(LocalDate date) { + WeekFields weekFields = WeekFields.SUNDAY_START; + int week = date.get(weekFields.weekOfWeekBasedYear()) - 1; + + if (week == 0 && date.getMonthValue() == 12) { + //wraparound + week = 52; + calendarView.showWeek(Year.of(date.getYear()), week); + } else if (week == 0 && date.getMonthValue() == 1) { + //wraparound + LocalDate dateOfFirstJan = LocalDate.of(date.getYear(), 1, 1); + if (dateOfFirstJan.getDayOfWeek().getValue() != 7) { + week = 52; + calendarView.showWeek(Year.of(date.getYear() - 1), week); + } else { + week = 53; + calendarView.showWeek(Year.of(date.getYear() - 1), week); + } + } else { + calendarView.showWeek(Year.of(date.getYear()), week); + } + } + + private void changeDayView(LocalDate date) { + calendarView.showDate(date); + } + + @Subscribe + private void handleChangeYearView(ChangeYearViewRequestEvent event) { + Year year = event.year; + Platform.runLater(() -> changeYearView(year)); + } + + @Subscribe + private void handleChangeMonthView(ChangeMonthViewRequestEvent event) { + YearMonth yearMonth = event.yearMonth; + Platform.runLater(() -> changeMonthView(yearMonth)); + } + + @Subscribe + private void handleChangeWeekView(ChangeWeekViewRequestEvent event) { + LocalDate date = event.date; + Platform.runLater(() -> changeWeekView(date)); + } + + @Subscribe + private void handleChangeDayView(ChangeDayViewRequestEvent event) { + LocalDate date = event.date; + Platform.runLater(() -> changeDayView(date)); + } + +} + + +``` diff --git a/collated/test/Robert-Peng.md b/collated/test/Robert-Peng.md new file mode 100644 index 000000000000..01afdd3a671d --- /dev/null +++ b/collated/test/Robert-Peng.md @@ -0,0 +1,425 @@ +# Robert-Peng +###### \java\guitests\guihandles\CalendarPanelHandle.java +``` java +/** + * + */ +public class CalendarPanelHandle extends NodeHandle { + + public static final String CALENDARPANEL_ID = "#calendarPlaceholder"; + + protected CalendarPanelHandle(Node calendarPanelNode) { + super(calendarPanelNode); + } +} +``` +###### \java\guitests\guihandles\MainWindowHandle.java +``` java + public CalendarPanelHandle getCalendarPanel() { + return calendarPanel; + } + + + public PetPatientListPanelHandle getPetPatientListPanel() { + return petPatientListPanel; + } +``` +###### \java\guitests\guihandles\PetPatientCardHandle.java +``` java +/** + * Provides a handle to a petPatient card in the petPatient list panel + */ + +public class PetPatientCardHandle extends NodeHandle { + private static final String ID_FIELD_ID = "#id"; + private static final String NAME_FIELD_ID = "#name"; + private static final String SPECIES_FIELD_ID = "#species"; + private static final String BREED_FIELD_ID = "#breed"; + private static final String COLOUR_FIELD_ID = "#colour"; + private static final String BLOODTYPE_FIELD_ID = "#bloodType"; + private static final String OWNERNRIC_FIELD_ID = "#ownerNric"; + private static final String TAGS_FIELD_ID = "#tags"; + + private final Label idLabel; + private final Label nameLabel; + private final Label speciesLabel; + private final Label breedLabel; + private final Label colourLabel; + private final Label bloodTypeLabel; + private final Label ownerNricLabel; + private final List