diff --git a/Collate-TUI.jar b/Collate-TUI.jar new file mode 100644 index 000000000000..2b9e032106dd Binary files /dev/null and b/Collate-TUI.jar differ diff --git a/README.adoc b/README.adoc index 03eff3a4d191..ba634de18613 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,8 @@ -= Address Book (Level 4) += PrisonBook 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-T11-B2[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/sarahgoh97/main[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,24 +12,22 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop Prison Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). + +== Target Users + +* Private prison owners or wardens have to keep track of what is happening in their own prisons. +* Guards who want to check schedules of prisoners or when they are on duty == Site Map * <> * <> -* <> * <> -* <> == Acknowledgements +* The original source code of this application is from the AddressBook-Level4 project created by SE-EDU initiative. * 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] diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..80d49f454573 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,9 @@ dependencies { 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.google.api-client:google-api-client:1.23.0' + compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0' + compile 'com.google.apis:google-api-services-calendar:v3-rev301-1.23.0' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.testfx', name: 'testfx-core', version: testFxVersion @@ -63,7 +66,7 @@ shadowJar { } task wrapper(type: Wrapper) { - gradleVersion = '2.12' + gradleVersion = '4.6' } task coverage(type: JacocoReport) { @@ -205,3 +208,5 @@ deployOfflineDocs.dependsOn asciidoctor processResources.dependsOn deployOfflineDocs defaultTasks 'clean', 'headless', 'allTests', 'coverage', 'asciidoctor' + + diff --git a/client_secret.json b/client_secret.json new file mode 100644 index 000000000000..f857c266e44a --- /dev/null +++ b/client_secret.json @@ -0,0 +1 @@ +{"installed":{"client_id":"824981534536-7k6u7vm63qpv15valckvlg7n4d4hkecq.apps.googleusercontent.com","project_id":"fiery-topic-199411","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"vIpAveyVpUqN90Ckf892Xdi6","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} diff --git a/collated/functional/philos22-unused.md b/collated/functional/philos22-unused.md new file mode 100644 index 000000000000..451211abc491 --- /dev/null +++ b/collated/functional/philos22-unused.md @@ -0,0 +1,127 @@ +# philos22-unused +###### \java\seedu\address\model\person\Prisoner.java +``` java +package seedu.address.model.person; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; + + +/** + * Prisoner class is child class of Person: but each instance has a release date + */ +public class Prisoner extends Person { + + private final ReleaseDate releaseDate; + + public Prisoner(Name name, Phone phone, Email email, Address address, Role role, Set tags, + ReleaseDate releaseDate) { + super(name, phone, email, address, role, tags); + requireAllNonNull(name, phone, email, address, tags, releaseDate); + this.releaseDate = releaseDate; + } + + public ReleaseDate getRelease_date() { + return releaseDate; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Prisoner)) { + return false; + } + + Prisoner otherPerson = (Prisoner) other; + return otherPerson.getName().equals(this.getName()) + && otherPerson.getPhone().equals(this.getPhone()) + && otherPerson.getEmail().equals(this.getEmail()) + && otherPerson.getAddress().equals(this.getAddress()) + && otherPerson.getRelease_date().equals(this.getRelease_date()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(this.getName(), this.getPhone(), this.getEmail(), this.getAddress(), this.getTags(), + this.getRelease_date()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Address: ") + .append(getAddress()) + .append(" Tags: "); + getTags().forEach(builder::append); + builder.append(" Release Date: ").append(getRelease_date()); + return builder.toString(); + } +} +``` +###### \java\seedu\address\model\person\ReleaseDate.java +``` java +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the release date of the prisoner in date time format + */ +public class ReleaseDate { + + public static final String MESSAGE_RELEASE_DATE_CONSTRAINTS = "The date format: yyyy-MM-dd"; + // credit of the regex goes to - Ofir Luzon - on Stack Overflow + public static final String RELEASE_DATE_VALIDATION_REGEX = "^(?:(?:31(-)(?:0?[13578]|1[02]))\\1|" + + + "(?:(?:29|30)(-)(?:0?[1,3-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:29(-)0?2\\3" + + + "(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))" + + + "$|^(?:0?[1-9]|1\\d|2[0-8])(-)(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$"; + public final String value; + + + // Constructs valid release date with valid parameter + public ReleaseDate(String releaseDate) { + requireNonNull(releaseDate); + checkArgument(isValidReleaseDate(releaseDate), MESSAGE_RELEASE_DATE_CONSTRAINTS); + this.value = releaseDate; + } + + // Returns true if release date is valid + public static boolean isValidReleaseDate(String test) { + return test.matches(RELEASE_DATE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReleaseDate // instanceof handles nulls + && this.value.equals(((ReleaseDate) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` diff --git a/collated/functional/philos22.md b/collated/functional/philos22.md new file mode 100644 index 000000000000..784f582ea2d4 --- /dev/null +++ b/collated/functional/philos22.md @@ -0,0 +1,564 @@ +# philos22 +###### \java\seedu\address\Calendar.java +``` java + + /** + * iterate through events and list them out with their respective start times + * @return a list of upcoming events + * @throws IOException + */ + public static String listEvents() throws IOException { + + com.google.api.services.calendar.Calendar service = + getCalendarService(); + + // List the next 10 events from the primary calendar. + DateTime now = new DateTime(System.currentTimeMillis()); + Events events = service.events().list("primary") + .setMaxResults(10) + .setTimeMin(now) + .setOrderBy("startTime") + .setSingleEvents(true) + .execute(); + List items = events.getItems(); + StringBuilder result = new StringBuilder(); + + if (items.size() == 0) { + result.append("No upcoming events found."); + } else { + Integer eventNumber = 1; + for (Event event : items) { + String eventId = event.getId(); + addEventIDs(eventId); + result.append(String.format("[Event %s] \t %s \t [%s] to [%s] \tLocation: %s\n", + eventNumber, event.getSummary(), event.getStart().getDateTime(), + event.getEnd().getDateTime(), event.getLocation())); + eventNumber++; + } + } + return result.toString(); + } + + /** + * Adds event to the calendar - specifying Name, Location, StartTime, EndTime + * @return success code + * @throws IOException + */ + public static String addEvent(String eventName, String eventLocation, DateTime startDateTime, + DateTime endDateTime) throws IOException { + + String successAddedMessage = "Event added successfully"; + + com.google.api.services.calendar.Calendar service = + getCalendarService(); + + Event event = new Event() + .setSummary(eventName) + .setLocation(eventLocation); + + EventDateTime start = new EventDateTime() + .setDateTime(startDateTime) + .setTimeZone(""); + event.setStart(start); + + EventDateTime end = new EventDateTime() + .setDateTime(endDateTime) + .setTimeZone(""); + event.setEnd(end); + + String calendarId = "primary"; + event = service.events().insert(calendarId, event).execute(); + System.out.printf("Event created: %s\n", event.getHtmlLink()); + + return successAddedMessage; + } + + /** + * Delete event from the calendar - specifying EventID + * @return success code + * @throws IOException + */ + public static String delEvent(String eventArrayId) throws IOException { + + String successDeletedMessage = "Event successfully deleted."; + + int eventArrayIdInt = Integer.parseInt(eventArrayId) - 1; + String eventId = eventIDs.get(eventArrayIdInt); + + // Build a new authorized API client service. + com.google.api.services.calendar.Calendar service = getCalendarService(); + + service.events().delete("primary", eventId).execute(); + + eventIDs.clear(); + + String reList = listEvents(); // to regenerate the EventIDs array + + return successDeletedMessage; + } + +} +``` +###### \java\seedu\address\logic\commands\CalendarAddCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START; + +import java.io.IOException; + +import com.google.api.client.util.DateTime; + +import seedu.address.Calendar; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Adds an event to the Google Calendar. + */ +public class CalendarAddCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "calAdd"; + public static final String COMMAND_ALIAS = "calA"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an event to the calendar. \n" + + "Parameters: " + + PREFIX_EVENT + "EVENT NAME " + + PREFIX_LOCATION + "EVENT LOCATION " + + PREFIX_START + "EVENT_START_TIME " + + PREFIX_END + "EVENT_END_TIME \n" + + "Example: \n" + COMMAND_WORD + " " + + PREFIX_EVENT + "Prison Security Sweep " + + PREFIX_LOCATION + "NUS Prison " + + PREFIX_START + "2020-04-01 15:00:00 " + + PREFIX_END + "2020-04-01 17:00:00"; + + private final String calEventName; + private final String calEventLocation; + private final DateTime calStartDateTime; + private final DateTime calEndDateTime; + private final String toAdd; + + + /** + * Creates an CalendarAddCommand to add the specified {@code Event} + */ + public CalendarAddCommand(String eventName, String eventLocation, DateTime startDateTime, DateTime endDateTime) { + requireNonNull(eventName); + requireNonNull(eventLocation); + requireNonNull(startDateTime); + requireNonNull(endDateTime); + calEventName = eventName; + calEventLocation = eventLocation; + calStartDateTime = startDateTime; + calEndDateTime = endDateTime; + toAdd = calEventName + " " + calEventLocation + " " + calStartDateTime.toString() + " " + + calEndDateTime.toString(); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + String successMessage = new Calendar().addEvent(calEventName, calEventLocation, calStartDateTime, + calEndDateTime); + return new CommandResult(String.format(successMessage, toAdd)); + } catch (IOException e) { + throw new CommandException(e.toString()); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CalendarAddCommand // instanceof handles nulls + && toAdd.equals(((CalendarAddCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\CalendarCommand.java +``` java +package seedu.address.logic.commands; + +import java.io.IOException; + +import seedu.address.Calendar; + +/** + * Lists all the commands entered by user from the start of app launch. + */ +public class CalendarCommand extends Command { + + public static final String COMMAND_WORD = "calendar"; + public static final String COMMAND_ALIAS = "cal"; + public static final String ERROR_MESSAGE = "calendar execution failed"; + public static final int MIN_SECURITY_LEVEL = 1; + + @Override + public CommandResult execute() { + try { + String messagesuccess = new Calendar().listEvents(); + return new CommandResult(messagesuccess); + } catch (IOException e) { + e.printStackTrace(); + } + return new CommandResult(ERROR_MESSAGE); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + +} +``` +###### \java\seedu\address\logic\commands\CalendarDeleteCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; + +import seedu.address.Calendar; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Deletes an event from the Google Calendar. + */ +public class CalendarDeleteCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "calDel"; + public static final String COMMAND_ALIAS = "calD"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes an event from the calendar. \n" + + "Parameter: EVENT NUMBER (from the event list in Calendar command)" + + "Example: \n" + COMMAND_WORD + " 2\n" + + "Deletes the second event listed in cal command"; + + private final String toDel; + + /** + * Creates an CalendarAddCommand to add the specified {@code Event} + */ + public CalendarDeleteCommand(String eventId) { + requireNonNull(eventId); + toDel = eventId; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + String successMessage = new Calendar().delEvent(toDel); + return new CommandResult(String.format(successMessage, toDel)); + } catch (IOException e) { + throw new CommandException(e.toString()); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CalendarDeleteCommand // instanceof handles nulls + && toDel.equals(((CalendarDeleteCommand) other).toDel)); + } +} +``` +###### \java\seedu\address\logic\commands\FindCommand.java +``` java + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names or tags contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: n/NAME_KEYWORDS t/TAG_KEYWORDS...\n" + + "Example: " + COMMAND_WORD + " n/alice t/tag1"; + + private final Predicate predicate; + + public FindCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicate.equals(((FindCommand) other).predicate)); // state check + } + +} +``` +###### \java\seedu\address\logic\parser\AddressBookParser.java +``` java + case CalendarCommand.COMMAND_WORD: + case CalendarCommand.COMMAND_ALIAS: + return new CalendarCommand(); + + case CalendarAddCommand.COMMAND_WORD: + case CalendarAddCommand.COMMAND_ALIAS: + return new CalendarAddCommandParser().parse(arguments); + + case CalendarDeleteCommand.COMMAND_WORD: + case CalendarDeleteCommand.COMMAND_ALIAS: + return new CalendarDeleteCommandParser().parse(arguments); + +``` +###### \java\seedu\address\logic\parser\CalendarAddCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START; + +import java.time.format.DateTimeParseException; +import java.util.stream.Stream; + +import com.google.api.client.util.DateTime; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CalendarAddCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new CalendarAddCommandParser object + */ +public class CalendarAddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CalendarAddCommand + * and returns the message of whether execution was successful or not. + * @throws ParseException if the user input does not conform the expected format + */ + public CalendarAddCommand parse(String args) throws ParseException, DateTimeParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_EVENT, PREFIX_LOCATION, PREFIX_START, PREFIX_END); + + if (!arePrefixesPresent(argMultimap, PREFIX_EVENT, PREFIX_LOCATION, PREFIX_START, PREFIX_END)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CalendarAddCommand.MESSAGE_USAGE)); + } + + try { + String eventName = ParserUtil.parseName(argMultimap.getValue(PREFIX_EVENT)).get().toString(); + String eventLocation = ParserUtil.parseName(argMultimap.getValue(PREFIX_LOCATION)).get().toString(); + DateTime startDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_START).toString()); + DateTime endDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_END).toString()); + return new CalendarAddCommand(eventName, eventLocation, startDateTime, endDateTime); + + } catch (IllegalValueException | DateTimeParseException e) { + throw new ParseException(e.getMessage(), e); + } + } + + /** + * 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()); + } + +} +``` +###### \java\seedu\address\logic\parser\CalendarDeleteCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.CalendarDeleteCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new CalendarDeleteCommandParser object + */ +public class CalendarDeleteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CalendarDeleteCommand + * and returns the message of whether execution was successful or not. + * @throws ParseException if the user input does not conform the expected format + */ + public CalendarDeleteCommand parse(String args) throws ParseException { + try { + int intCheck = Integer.parseInt(args); + } catch (Exception e) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CalendarDeleteCommand.MESSAGE_USAGE)); + } + + return new CalendarDeleteCommand(args.trim()); + } +} +``` +###### \java\seedu\address\logic\parser\CliSyntax.java +``` java + public static final Prefix PREFIX_EVENT = new Prefix("event/"); + public static final Prefix PREFIX_LOCATION = new Prefix("loc/"); + public static final Prefix PREFIX_START = new Prefix("start/"); + public static final Prefix PREFIX_END = new Prefix("end/"); +``` +###### \java\seedu\address\logic\parser\FindCommandParser.java +``` java + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TAG); + + if (!(arePrefixesPresent(argMultimap, PREFIX_NAME) || arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty())) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + if ((arePrefixesPresent(argMultimap, PREFIX_NAME)) && (arePrefixesPresent(argMultimap, PREFIX_TAG))) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().split("\\s+"); + return new FindCommand(new ContainsKeywordsPredicate(Arrays.asList(nameKeywords), + Arrays.asList(tagKeywords))); + } else if (arePrefixesPresent(argMultimap, PREFIX_NAME)) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } else if (arePrefixesPresent(argMultimap, PREFIX_TAG)) { + String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().split("\\s+"); + return new FindCommand(new TagContainsKeywordsPredicate(Arrays.asList(tagKeywords))); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code Optional DateTime} if present. + */ + public static DateTime parseDateTime(String dateTime) throws DateTimeParseException { + + String theDateTime = dateTime.replaceAll("[\\[\\]]", "").replaceAll("Optional", ""); + + try { + TemporalAccessor ta = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").parse(theDateTime); + String strDateTime = LocalDateTime.from(ta).toString() + ":00+08:00"; + return new DateTime(strDateTime); + } catch (DateTimeParseException e) { + throw new DateTimeParseException(MESSAGE_INVALID_DATETIME_FORMAT, theDateTime, 0, e); + } + } + +} +``` +###### \java\seedu\address\model\person\ContainsKeywordsPredicate.java +``` java +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests that a {@code Person}'s fields matches any of the keywords given. + */ +public class ContainsKeywordsPredicate implements Predicate { + private final Predicate namePredicate; + private final Predicate tagPredicate; + + public ContainsKeywordsPredicate(List nameKeywords, List tagKeywords) { + this.namePredicate = new NameContainsKeywordsPredicate(nameKeywords); + this.tagPredicate = new TagContainsKeywordsPredicate(tagKeywords); + } + + + @Override + public boolean test(Person person) { + return namePredicate.test(person) && tagPredicate.test(person); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof ContainsKeywordsPredicate // Used to handle Nulls + && this.namePredicate.equals(((ContainsKeywordsPredicate) other).namePredicate) + && this.tagPredicate.equals(((ContainsKeywordsPredicate) other).tagPredicate)); // state check + } + +} +``` +###### \java\seedu\address\model\person\TagContainsKeywordsPredicate.java +``` java +package seedu.address.model.person; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class TagContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + // Making a string of all tags + if (person.getTags().size() == 0) { + return false; + } + Iterator tagIteration = person.getTags().iterator(); + StringBuilder strBuild = new StringBuilder(); + strBuild.append(tagIteration.next()); + while (tagIteration.hasNext()) { + strBuild.append(" " + tagIteration.next()); + } + String tagList = strBuild.toString().replace("[", "").replace("]", ""); + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tagList, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + } + +} +``` diff --git a/collated/functional/sarahgoh97.md b/collated/functional/sarahgoh97.md new file mode 100644 index 000000000000..1d5f20e3db70 --- /dev/null +++ b/collated/functional/sarahgoh97.md @@ -0,0 +1,1386 @@ +# sarahgoh97 +###### \java\seedu\address\commons\events\ui\HideMapEvent.java +``` java +package seedu.address.commons.events.ui; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates an event to hide the map. + */ +public class HideMapEvent extends BaseEvent { + + public final ObservableList list = FXCollections.observableArrayList(); + + public HideMapEvent() { + for (int i = 0; i < 15; i++) { + list.add("Insufficient Access"); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\logic\commands\AddCellCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.cell.exceptions.AlreadyInCellException; +import seedu.address.model.cell.exceptions.FullCellException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.cell.exceptions.NotPrisonerException; +import seedu.address.model.person.Person; + +/** + * Adds a prisoner to a cell in the address book. + */ +public class AddCellCommand extends UndoableCommand { + public static final String COMMAND_WORD = "addcell"; + public static final String COMMAND_ALIAS = "ac"; + public static final int MIN_SECURITY_LEVEL = 2; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a prisoner to the specified cell." + + "by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer) " + + "CELLADDRESS (block-unit)\n" + + "Example: " + COMMAND_WORD + " 1 1-1"; + + public static final String MESSAGE_ADD_CELL_SUCCESS = "Prisoner %s added to %s."; + public static final String MESSAGE_FULL_CELL = "Cell %s is already full. Below is the map."; + public static final String MESSAGE_NON_EXISTENT_CELL = "This cell %s does not exist. Below is the map."; + public static final String MESSAGE_NOT_PRISONER = "%s is not a prisoner."; + public static final String MESSAGE_ALREADY_IN_CELL = "%s is already in cell %s"; + + public final Index index; + + private Person prisonerToAdd; + private String cellAddress; + + /** + * @param index of the person in the filtered person list to edit + * @param cellAddress cell to be added to + */ + public AddCellCommand(Index index, String cellAddress) { + requireNonNull(index); + requireNonNull(cellAddress); + this.index = index; + this.cellAddress = cellAddress; + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + requireNonNull(prisonerToAdd); + try { + model.addPrisonerToCell(prisonerToAdd, cellAddress); + } catch (FullCellException fce) { + throw new CommandException(String.format(MESSAGE_FULL_CELL, + cellAddress, new ShowCellsCommand().getMapString( + model.getAddressBook().getCellList().toString()))); + } catch (NonExistentCellException nece) { + throw new CommandException(String.format(MESSAGE_NON_EXISTENT_CELL, cellAddress)); + } catch (NotPrisonerException npe) { + throw new CommandException(String.format(MESSAGE_NOT_PRISONER, prisonerToAdd.getName())); + } catch (AlreadyInCellException aice) { + throw new CommandException(String.format(MESSAGE_ALREADY_IN_CELL, + prisonerToAdd.getName(), prisonerToAdd.getCellAddress())); + } + return new CommandResult(String.format(MESSAGE_ADD_CELL_SUCCESS, prisonerToAdd.getName(), cellAddress)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + prisonerToAdd = lastShownList.get(index.getZeroBased()); + } + + public Person getPrisonerToAdd() { + return prisonerToAdd; + } + + public String getCellAddress() { + return cellAddress; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCellCommand // instanceof handles nulls + && this.index.equals(((AddCellCommand) other).index) // state check + && Objects.equals(this.prisonerToAdd, ((AddCellCommand) other).prisonerToAdd) //prisoner check + && Objects.equals(this.cellAddress, ((AddCellCommand) other).cellAddress)); //celladdress check + } +} +``` +###### \java\seedu\address\logic\commands\DeleteCellCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.cell.exceptions.NotImprisonedException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +/** + * Deletes a prisoner from a cell in the address book. + */ +public class DeleteCellCommand extends UndoableCommand { + public static final String COMMAND_WORD = "deletecell"; + public static final String COMMAND_ALIAS = "dc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes prisoners in specified cell.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + "1"; + + public static final String MESSAGE_DELETE_CELL_SUCCESS = "Prisoner %s has been released."; + public static final String MESSAGE_NOT_IMPRISONED = "The target person is not imprisoned here"; + + public final Index targetIndex; + + private Person prisonerToDelete; + private String cellAddress; + + /** + * Creates a deleteCellCommand object + * @param targetIndex of the person being deleted + */ + public DeleteCellCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(prisonerToDelete); + try { + model.deletePrisonerFromCell(prisonerToDelete); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } catch (NotImprisonedException nie) { + throw new CommandException(MESSAGE_NOT_IMPRISONED); + } + + return new CommandResult(String.format(MESSAGE_DELETE_CELL_SUCCESS, prisonerToDelete.getName().toString())); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + prisonerToDelete = lastShownList.get(targetIndex.getZeroBased()); + if (prisonerToDelete.getIsInCell()) { + cellAddress = prisonerToDelete.getCellAddress().toString(); + } + } + + public Person getPrisonerToDelete() { + return prisonerToDelete; + } + + public String getCellAddress() { + return cellAddress; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCellCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteCellCommand) other).targetIndex) // state check + && Objects.equals(this.prisonerToDelete, ((DeleteCellCommand) other).prisonerToDelete)); + } +} +``` +###### \java\seedu\address\logic\commands\EditCommand.java +``` java + private static Address getUpdatedAddress(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + if (!updatedAddress.equals(personToEdit.getAddress()) && personToEdit.getIsInCell()) { + //if address has changed and imprisoned person + String original = personToEdit.getAddress().toString(); + String newAddress = original.substring(0, original.indexOf("s: ") + 3) + updatedAddress.toString() + "]"; + updatedAddress = new Address(newAddress); + } + return updatedAddress; + } + +``` +###### \java\seedu\address\logic\commands\ListCellCommand.java +``` java +package seedu.address.logic.commands; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.person.Person; + +/** + * Lists all person in specified cell + */ +public class ListCellCommand extends Command { + public static final String COMMAND_WORD = "listcell"; + public static final String COMMAND_ALIAS = "lc"; + public static final int MIN_SECURITY_LEVEL = 1; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all people in the specified cell." + + " Parameters: CELLADDRESS\n" + + "Example: " + COMMAND_WORD + " 1-1"; + + public static final String MESSAGE_LISTCELL_SUCCESS = "Listed persons in cell %s"; + public static final String MESSAGE_NON_EXISTENT_CELL = "This cell %s does not exist"; + private final Predicate predicate; + private final String cellAddress; + + public ListCellCommand(Predicate predicate, String cellAddress) { + this.predicate = predicate; + this.cellAddress = cellAddress; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.updateFilteredPersonListForCell(predicate, cellAddress); + } catch (NonExistentCellException nece) { + throw new CommandException(String.format(MESSAGE_NON_EXISTENT_CELL, cellAddress)); + } + return new CommandResult(String.format(MESSAGE_LISTCELL_SUCCESS, cellAddress)); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListCellCommand // instanceof handles nulls + && this.predicate.equals(((ListCellCommand) other).predicate)); // state check + } +} +``` +###### \java\seedu\address\logic\commands\ShowCellsCommand.java +``` java +package seedu.address.logic.commands; + +import seedu.address.model.cell.CellMap; + +/** + * Shows all cells and number of prisoners in each of them to the user. + */ +public class ShowCellsCommand extends Command { + + public static final String COMMAND_WORD = "map"; + public static final String COMMAND_ALIAS = "m"; + public static final int MIN_SECURITY_LEVEL = 1; + + public static final String MESSAGE_SUCCESS = "%s\nShown cells with number of people in them."; + + + @Override + public CommandResult execute() { + String cells = model.getAddressBook().getCellList().toString(); + String map = getMapString(cells); + return new CommandResult(String.format(MESSAGE_SUCCESS, map)); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + public String getMapString(String cells) { + for (int i = 1; i <= CellMap.MAX_ROW; i++) { + cells = cells.replace(", " + i + "-1", i + "-1"); + } + cells = cells.substring(1, cells.length() - 1); + + return cells; + } +} +``` +###### \java\seedu\address\logic\parser\AddCellCommandParser.java +``` java +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddCellCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCellCommand object + */ +public class AddCellCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddCellCommand + * and returns an AddCellCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCellCommand parse (String args) throws ParseException { + try { + requireNonNull(args); + String removePrefix = null; + String unparsedIndex = null; + if (args.contains(" ") && args.length() > args.indexOf(" ")) { + removePrefix = args.substring(args.indexOf(" ") + 1); + if (removePrefix.contains(" ") && removePrefix.length() > removePrefix.indexOf(" ")) { + unparsedIndex = removePrefix.substring(0, removePrefix.indexOf(" ")); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCellCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCellCommand.MESSAGE_USAGE)); + } + if (removePrefix != null && removePrefix.contains(" ") && unparsedIndex != null) { + String cellAddress = removePrefix.substring(removePrefix.indexOf(" ") + 1); + Index index = ParserUtil.parseIndex(unparsedIndex); + if (cellAddress.matches("\\d+-\\d+")) { + return new AddCellCommand(index, cellAddress); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCellCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCellCommand.MESSAGE_USAGE)); + } + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCellCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\address\logic\parser\AddressBookParser.java +``` java + case ShowCellsCommand.COMMAND_WORD: + case ShowCellsCommand.COMMAND_ALIAS: + return new ShowCellsCommand(); + + case AddCellCommand.COMMAND_WORD: + case AddCellCommand.COMMAND_ALIAS: + return new AddCellCommandParser().parse(arguments); + + case DeleteCellCommand.COMMAND_WORD: + case DeleteCellCommand.COMMAND_ALIAS: + return new DeleteCellCommandParser().parse(arguments); + + case ListCellCommand.COMMAND_WORD: + case ListCellCommand.COMMAND_ALIAS: + return new ListCellCommandParser().parse(arguments); +``` +###### \java\seedu\address\logic\parser\DeleteCellCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.DeleteCellCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses the given input and gives a new DeleteCellCommand object. + */ +public class DeleteCellCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of DeleteCellCommand + * and returns a DeleteCellCommand object for execution + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public DeleteCellCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteCellCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCellCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\address\logic\parser\EditCommandParser.java +``` java + if (args.contains(PREFIX_ROLE.toString())) { + throw new ParseException(String.format(EditCommand.MESSAGE_CANNOT_CHANGE_ROLE)); + } +``` +###### \java\seedu\address\logic\parser\ListCellCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ListCellCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.AddressContainsCellPredicate; + +/** + * Parses the given input and gives a new ListCellCommand object. + */ +public class ListCellCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of ListCellCommand + * and returns a ListCellCommand object for execution + * @throws ParseException if the user input does not conform the expected format + */ + + @Override + public ListCellCommand parse(String args) throws ParseException { + args = args.trim(); + if (args.matches("\\d+-\\d+")) { + return new ListCellCommand(new AddressContainsCellPredicate(args), args); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCellCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java + if (target.getIsInCell()) { + cells.setPrisonerToCell(target, syncedEditedPerson); + } + } + + /** + * Replaces the given person {@code changed} in the list with {@code original} in the cellMap. + * This is only done from undo. + */ + public void updatePrisonerFromUndo(Person changed, Person original) { + requireAllNonNull(original, changed); + + cells.setPrisonerToCell(changed, original); + } +``` +###### \java\seedu\address\model\AddressBook.java +``` java + if (key.getIsInCell() == true) { + String cellAddress = key.getCellAddress().toString(); + cells.deletePrisonerFromCell(key, cellAddress); + } +``` +###### \java\seedu\address\model\AddressBook.java +``` java + //// cell-level operations + + /** + * + * @param c is the cell to add to the map + */ + public void addCell(Cell c) { + String cellAddress = c.getCellAddress(); + cells.setCell(c, cellAddress); + } + + /** + * Adds a prisoner to a cell + * @param cellAddress to get the correct cell + * @param prisoner to be added into the cell + * @throws FullCellException if the cell already has the maximum number of prisoners + * @throws NonExistentCellException if the cell address is invalid + * @throws NotPrisonerException if the Person passed is not a prisoner + */ + public void addPrisonerToCell(String cellAddress, Person prisoner) throws FullCellException, + NonExistentCellException, NotPrisonerException, AlreadyInCellException { + requireAllNonNull(prisoner, cellAddress); + if (!Cell.isValidCellAddress(cellAddress)) { + throw new NonExistentCellException(); + } else if (!prisoner.getRole().equals(Role.PRISONER)) { + throw new NotPrisonerException(); + } else if (prisoner.getIsInCell()) { + throw new AlreadyInCellException(); + } else if (cells.getCell(cellAddress).getNumberOfPrisoners() >= Cell.MAX_SIZE) { + throw new FullCellException(); + } else { + Person updatedPrisoner = new Person(prisoner, true, cellAddress); + updatePrisoner(prisoner, updatedPrisoner); + addPrisonerToCellPermitted(updatedPrisoner, cellAddress); + } + } + + /** + * Adding prisoner to CellMap once exceptions cleared + * @param prisoner is the correct person without requiring editing + * @param cellAddress is the String corresponding to the cell shown on map + */ + public void addPrisonerToCellPermitted(Person prisoner, String cellAddress) { + cells.addPrisonerToCell(prisoner, cellAddress); + } + + /** + * Deletes prisoner from a specified cell + */ + public void deletePrisonerFromCell(Person prisoner, String cellAddress) { + cells.deletePrisonerFromCell(prisoner, cellAddress); + } + + /** + * Replaces the given person {@code target} in the list with {@code updatedPrisoner}. + */ + public void updatePrisoner(Person target, Person updatedPrisoner) { + persons.setPrisoner(target, updatedPrisoner); + } + + //// util methods + + @Override + public String toString() { + return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags\n" + + cells.getCellList() + users.getUserList(); + // TODO: refine later + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + @Override + public ObservableList getCellList() { + return cells.getCellList(); + } +``` +###### \java\seedu\address\model\cell\Cell.java +``` java +package seedu.address.model.cell; + +import java.util.ArrayList; + +import seedu.address.model.person.Person; + +/** + * Represents a cell in the prison. + * Guarantees: Cells created are all part of the CellMap + */ +public class Cell { + + public static final int MAX_SIZE = 2; + private final ArrayList prisoners; + private String cellAddress; + private boolean isLast; + + /** + * Represents a cell in the Prison. + */ + public Cell(int row, int column) { + prisoners = new ArrayList(MAX_SIZE); + cellAddress = row + "-" + column; + if (column == CellMap.MAX_COL) { + isLast = true; + } else { + isLast = false; + } + } + + /** + * A copied cell + */ + public Cell(ArrayList prisoners, String cellAddress, boolean isLast) { + this.prisoners = new ArrayList(prisoners); + this.cellAddress = cellAddress; + this.isLast = isLast; + } + + public static int getCol(String cellAddress) { + return Integer.parseInt(cellAddress.substring(cellAddress.indexOf("-") + 1)); + } + + public static int getRow(String cellAddress) { + return Integer.parseInt(cellAddress.substring(0, cellAddress.indexOf("-"))); + } + + public void addPrisoner(Person prisoner) { + prisoners.add(prisoner); + } + + public void deletePrisoner(Person prisoner) { + prisoners.remove(prisoner); + } + + public ArrayList getPrisoners() { + return prisoners; + } + + public String getCellAddress() { + return cellAddress; + } + + public int getNumberOfPrisoners() { + return prisoners.size(); + } + + public boolean getIsLast() { + return isLast; + } + + /** + * Returns true if a given string is a valid cell. + */ + public static boolean isValidCellAddress(String test) { + if (test.matches("\\d+-\\d+")) { + int row = getRow(test); + int col = getCol(test); + return row <= CellMap.MAX_ROW && row > 0 + && col <= CellMap.MAX_COL && col > 0; + } else { + return false; + } + } + + @Override + public String toString() { + String string = getCellAddress() + " [" + getNumberOfPrisoners() + "]"; + if (isLast) { + return string + "\n"; + } + return string; + } + +} +``` +###### \java\seedu\address\model\cell\CellMap.java +``` java +package seedu.address.model.cell; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.person.Person; + +/** + * Contains the cells of the prison for the PrisonBook + * Cells are not allowed to exceed the maximum fixed size of the prison + */ +public class CellMap { + public static final int MAX_ROW = 3; + public static final int MAX_COL = 5; + private Cell[][] cellMap; + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Represents a fixed-sized map of the cells in the prison. + * Initialised at start of program. + */ + public CellMap() { + resetData(); + } + + /** + * @param cellAddress has to be within boundaries + * @return Cell from cellAddress + */ + public Cell getCell(String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + int index = getIndex(row, col); + return internalList.get(index); + } + + public void setCells(ObservableList cells) { + for (Cell c: cells) { + setCell(c, c.getCellAddress()); + } + internalList.clear(); + internalList.setAll(cells); + } + + public void setCell(Cell cell, String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + cellMap[row][col] = cell; + int num = getIndex(row, col); + if (num >= internalList.size()) { + internalList.add(cell); + } else { + internalList.set(num, cell); + } + } + + private int getCellMapRow(String cellAddress) { + return Cell.getRow(cellAddress) - 1; + } + + private int getCellMapCol(String cellAddress) { + return Cell.getCol(cellAddress) - 1; + } + + /** + * Adds a prisoner to a specified cell. + */ + public void addPrisonerToCell(Person prisoner, String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + addPrisonerToCell(prisoner, row, col); + } + + /** + * private method called from public method above + */ + private void addPrisonerToCell(Person prisoner, int row, int col) { + Cell cell = cellMap[row][col]; + cellMap[row][col].addPrisoner(prisoner); + int index = getIndex(row, col); + internalList.set(index, cell); + } + + private int getIndex(int row, int col) { + return row * MAX_COL + col; + } + + /** + * Removes a prisoner from a specified cell + */ + public void deletePrisonerFromCell(Person prisoner, String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + deletePrisonerFromCell(prisoner, row, col); + } + + /** + * private method called from public method above + */ + private void deletePrisonerFromCell(Person prisoner, int row, int col) { + Cell cell = cellMap[row][col]; + cell.deletePrisoner(prisoner); + int index = getIndex(row, col); + internalList.set(index, cell); + } + + /** + * Sets new edited person to original cell + * Precondition: target must be imprisoned + */ + public void setPrisonerToCell(Person target, Person updatedPrisoner) { + assert(target.getIsInCell()); + assert(updatedPrisoner.getIsInCell()); + String cellAddress = target.getCellAddress().toString(); + deletePrisonerFromCell(target, cellAddress); + addPrisonerToCell(updatedPrisoner, cellAddress); + } + + /** + * For storage purposes + */ + public ObservableList getCellList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Resets the existing data of this {@code CellMap}. + */ + public void resetData() { + cellMap = new Cell[MAX_ROW][MAX_COL]; + internalList.clear(); + for (int currRow = 0; currRow < MAX_ROW; currRow++) { + for (int currCol = 0; currCol < MAX_COL; currCol++) { + Cell cell = new Cell(currRow + 1, currCol + 1); + cellMap[currRow][currCol] = cell; + internalList.add(cell); + } + } + } + + /** + * Returns a map of the cells and their addresses + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Cell[] cArray: cellMap) { + for (Cell c: cArray) { + sb.append(c.getCellAddress() + " [" + c.getNumberOfPrisoners() + "] "); + } + sb.delete(sb.length() - 1, sb.length()); + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CellMap // instanceof handles nulls + && this.toString().equals((other.toString()))); + } +} +``` +###### \java\seedu\address\model\cell\exceptions\AlreadyInCellException.java +``` java +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the prisoner being added is already in a cell in the prison. + */ +public class AlreadyInCellException extends IllegalValueException { + public AlreadyInCellException() { + super("Prisoner already in prison."); + } +} +``` +###### \java\seedu\address\model\cell\exceptions\FullCellException.java +``` java +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the operation will be adding a prisoner to a full cell. + */ +public class FullCellException extends IllegalValueException { + public FullCellException() { + super("Invalid cell. Cell is already full."); + } +} +``` +###### \java\seedu\address\model\cell\exceptions\NonExistentCellException.java +``` java +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the cell indicated is not within boundaries + */ +public class NonExistentCellException extends IllegalValueException { + public NonExistentCellException() { + super("No such cell exists."); + } +} +``` +###### \java\seedu\address\model\cell\exceptions\NotImprisonedException.java +``` java +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the operation will be removing a person not in jail from the jail. + */ +public class NotImprisonedException extends IllegalValueException { + public NotImprisonedException() { + super("This person is not imprisoned in this prison."); + } +} +``` +###### \java\seedu\address\model\cell\exceptions\NotPrisonerException.java +``` java +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the person added to a cell is not a prisoner + */ +public class NotPrisonerException extends IllegalValueException { + public NotPrisonerException() { + super("Invalid person to add, not a prisoner."); + } +} +``` +###### \java\seedu\address\model\Model.java +``` java + /** Adds given prisoner into a cell */ + void addPrisonerToCell(Person prisoner, String cellAddress) + throws FullCellException, NonExistentCellException, + NotPrisonerException, AlreadyInCellException; + + /**Deletes given prisoner from a cell from DeleteCellCommand */ + void deletePrisonerFromCell(Person prisoner) throws PersonNotFoundException, NotImprisonedException; + + /**Adds given prisoner back into a cell from undo command */ + void addPrisonerToCellFromUndo(Person prisoner, String cellAddress); + + /** Deletes given prisoner from a cell from undo command*/ + void deletePrisonerFromCellFromUndo(Person prisoner, String cellAddress); + + /** Updates given prisoner who changed from undo command*/ + void updatePrisonerFromUndo(Person changed, Person original); +``` +###### \java\seedu\address\model\Model.java +``` java + /** + * Updates the filter of the filtered person list to show only people in the cell. + */ + void updateFilteredPersonListForCell(Predicate predicate, String cellAddress) + throws NonExistentCellException; + +} +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void updatePrisonerFromUndo(Person changed, Person original) { + addressBook.updatePrisonerFromUndo(changed, original); + indicateAddressBookChanged(); + } + + @Override + public void addPrisonerToCell(Person prisoner, String cellAddress) + throws FullCellException, NonExistentCellException, + NotPrisonerException, AlreadyInCellException { + requireAllNonNull(prisoner, cellAddress); + addressBook.addPrisonerToCell(cellAddress, prisoner); + indicateAddressBookChanged(); + } + + /** + * Deletes a prisoner from a cell from DeleteCellCommand + * @param prisoner to be removed from cell + * @throws PersonNotFoundException if prisoner does not exist + * @throws NotImprisonedException if person chosen is not imprisoned here + */ + @Override + public void deletePrisonerFromCell(Person prisoner) throws PersonNotFoundException, NotImprisonedException { + requireNonNull(prisoner); + if (!filteredPersons.contains(prisoner)) { + throw new PersonNotFoundException(); + } else { + String cellAddress = prisoner.getAddress().toString(); + if (prisoner.getIsInCell()) { + cellAddress = cellAddress.substring(0, cellAddress.indexOf(" ")); + addressBook.deletePrisonerFromCell(prisoner, cellAddress); + Person freedPrisoner = new Person(prisoner, false); + addressBook.updatePrisoner(prisoner, freedPrisoner); + } else { + throw new NotImprisonedException(); + } + } + indicateAddressBookChanged(); + } + + /* this is to undo AddCellCommand*/ + @Override + public void deletePrisonerFromCellFromUndo(Person prisoner, String cellAddress) { + requireAllNonNull(prisoner, cellAddress); + Person updatedPrisoner = new Person(prisoner, true, cellAddress); + addressBook.deletePrisonerFromCell(updatedPrisoner, cellAddress); + indicateAddressBookChanged(); + } + + /* this is to undo DeleteCellCommand*/ + @Override + public void addPrisonerToCellFromUndo(Person prisoner, String cellAddress) { + requireAllNonNull(prisoner, cellAddress); + addressBook.addPrisonerToCellPermitted(prisoner, cellAddress); + indicateAddressBookChanged(); + } +``` +###### \java\seedu\address\model\ModelManager.java +``` java + /* This is for ListCellCommand */ + @Override + public void updateFilteredPersonListForCell(Predicate predicate, String cellAddress) + throws NonExistentCellException { + requireNonNull(predicate); + requireNonNull(cellAddress); + if (Cell.isValidCellAddress(cellAddress)) { + filteredPersons.setPredicate(predicate); + } else { + throw new NonExistentCellException(); + } + } +``` +###### \java\seedu\address\model\person\AddressContainsCellPredicate.java +``` java +package seedu.address.model.person; + +import java.util.function.Predicate; + +/** + * Tests that the person is in the cell + */ +public class AddressContainsCellPredicate implements Predicate { + private final String cellAddress; + + public AddressContainsCellPredicate(String cellAddress) { + this.cellAddress = cellAddress; + } + + @Override + public boolean test(Person person) { + if (person.getIsInCell()) { + if (person.getCellAddress().toString().equals(cellAddress)) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsCellPredicate // instanceof handles nulls + && this.cellAddress.equals(((AddressContainsCellPredicate) other).cellAddress)); // state check + } +} +``` +###### \java\seedu\address\model\person\Person.java +``` java + //only called if prisoner isInCell + public Address getCellAddress() { + assert(isInCell); + String cellAddress = address.value.substring(0, address.value.indexOf("[") - 1); + return new Address(cellAddress); + } +``` +###### \java\seedu\address\model\person\UniquePersonList.java +``` java + /** + * Replaces the person {@code target} in the list with {@code updatedPrisoner}. + */ + public void setPrisoner(Person target, Person updatedPrisoner) { + int index = internalList.indexOf(target); + internalList.set(index, updatedPrisoner); + } +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the cells list. + * This list will not contain any duplicate cells. + */ + ObservableList getCellList(); + +``` +###### \java\seedu\address\model\util\SampleDataUtil.java +``` java + public static Cell[] getSampleCells() { + Person alex = new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("1-1 [Old address: Blk 30 Geylang Street 29, #06-40]"), new Role("p"), + getTagSet("ChickenAllergy"), true); + Person charlotte = new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), + new Address("1-1 [Old address: Blk 11 Ang Mo Kio Street 74, #11-04]"), new Role("p"), + getTagSet("ViolentTendencies"), true); + Person sarah = new Person(new Name("Sarah Goh"), new Phone("90288173"), new Email("gohst@example.com"), + new Address("3-5 [Old address: Tung Ann Association Building 141 Cecil Street #03-02]"), + new Role("p"), getTagSet(), true); + + return new Cell[] { + new Cell(getPrisoners(alex, charlotte), "1-1", false), + new Cell(1, 2), new Cell(1, 3), new Cell(1, 4), new Cell(1, 5), + new Cell(2, 1), new Cell(2, 2), new Cell(2, 3), new Cell(2, 4), new Cell(2, 5), + new Cell(3, 1), new Cell(3, 2), new Cell(3, 3), new Cell(3, 4), + new Cell(getPrisoners(sarah), "3-5", true) + }; + } + + public static ArrayList getPrisoners(Person... prisoners) { + ArrayList list = new ArrayList(); + for (Person p: prisoners) { + list.add(p); + } + return list; + } + +``` +###### \java\seedu\address\storage\XmlAdaptedCell.java +``` java +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.cell.Cell; +import seedu.address.model.person.Person; + +/** + * JAXB-friendly version of the Cell. + */ +public class XmlAdaptedCell { + + public static final String INVALID_CELL = "This cell does not exist!"; + + @XmlElement (required = true) + private String cellAddress; + @XmlElement (required = true) + private List prisoners = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedCell. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedCell() {} + + /** + * Constructs a {@code XmlAdaptedCell} with the given Cell. + */ + public XmlAdaptedCell(String cellAddress, List prisoners) { + this.cellAddress = cellAddress; + if (prisoners != null) { + this.prisoners = new ArrayList<>(prisoners); + } + } + + /** + * Converts a given Cell into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedCell(Cell source) { + cellAddress = source.getCellAddress(); + for (Person person: source.getPrisoners()) { + prisoners.add(new XmlAdaptedPerson(person)); + } + } + + /** + * Converts this jaxb-friendly adapted Cell object into the model's Cell object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted cell + */ + public Cell toModelType() throws IllegalValueException { + if (this.cellAddress == null) { + throw new IllegalValueException(INVALID_CELL); + } + if (!Cell.isValidCellAddress(this.cellAddress)) { + throw new IllegalValueException(INVALID_CELL); + } + int row = Cell.getRow(cellAddress); + int col = Cell.getCol(cellAddress); + Cell cell = new Cell(row, col); + + for (XmlAdaptedPerson person : prisoners) { + cell.addPrisoner(person.toModelType()); + } + return cell; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedCell)) { + return false; + } + + XmlAdaptedCell otherCell = (XmlAdaptedCell) other; + return Objects.equals(cellAddress, otherCell.cellAddress) + && prisoners.equals(otherCell.prisoners); + } +} +``` +###### \java\seedu\address\ui\MainWindow.java +``` java + mapPanel = new MapPanel(); + mapPanelPlaceholder.getChildren().add(mapPanel.getRoot()); +``` +###### \java\seedu\address\ui\MapPanel.java +``` java +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.ui.HideMapEvent; +import seedu.address.model.cell.Cell; + + +/** + * The cellMap of the App. + */ +public class MapPanel extends UiPart { + + private static final String FXML = "MapPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(MapPanel.class); + + @FXML + private Label cellAddress11; + @FXML + private Label cellAddress12; + @FXML + private Label cellAddress13; + @FXML + private Label cellAddress14; + @FXML + private Label cellAddress15; + @FXML + private Label cellAddress21; + @FXML + private Label cellAddress22; + @FXML + private Label cellAddress23; + @FXML + private Label cellAddress24; + @FXML + private Label cellAddress25; + @FXML + private Label cellAddress31; + @FXML + private Label cellAddress32; + @FXML + private Label cellAddress33; + @FXML + private Label cellAddress34; + @FXML + private Label cellAddress35; + + public MapPanel() { + super(FXML); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList list) { + cellAddress11.setText(list.get(0)); + cellAddress12.setText(list.get(1)); + cellAddress13.setText(list.get(2)); + cellAddress14.setText(list.get(3)); + cellAddress15.setText(list.get(4)); + cellAddress21.setText(list.get(5)); + cellAddress22.setText(list.get(6)); + cellAddress23.setText(list.get(7)); + cellAddress24.setText(list.get(8)); + cellAddress25.setText(list.get(9)); + cellAddress31.setText(list.get(10)); + cellAddress32.setText(list.get(11)); + cellAddress33.setText(list.get(12)); + cellAddress34.setText(list.get(13)); + cellAddress35.setText(list.get(14)); + } + + private void setStrings(ObservableList cellList) { + ObservableList list = FXCollections.observableArrayList(); + for (int i = 0; i < 15; i++) { + list.add(Integer.toString(cellList.get(i).getNumberOfPrisoners())); + } + setConnections(list); + } + + @Subscribe + public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Changing map\n")); + setStrings(abce.data.getCellList()); + } + + @Subscribe + private void handleHideMapRequestEvent(HideMapEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setConnections(event.list); + } + +} +``` +###### \resources\view\MapPanel.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + +``` diff --git a/collated/functional/zacci.md b/collated/functional/zacci.md new file mode 100644 index 000000000000..f5e76e68f940 --- /dev/null +++ b/collated/functional/zacci.md @@ -0,0 +1,1148 @@ +# zacci +###### \java\seedu\address\logic\commands\AddUserCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; + +/** + * Adds a new user to the PrisonBook + */ +public class AddUserCommand extends Command { + public static final String COMMAND_WORD = "adduser"; + public static final String COMMAND_ALIAS = "au"; + public static final int MIN_SECURITY_LEVEL = 3; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a new user to the PrisonBook.\n" + + "Parameters: user/NEW_USERNAME pw/NEW_PASSWORD sl/SECURITY_LEVEL (integer from 0 tp 3)...\n" + + "Example: " + COMMAND_WORD + " user/newuser1 pw/password1 sl/2"; + + public static final String MESSAGE_ADD_USER_SUCCESS = "New user %s added to PrisonBook"; + public static final String MESSAGE_ALREADY_EXISTING_USER = "%s is already a user in PrisonBook"; + private final String username; + + private User userToAdd; + + /** + * @param username of the new user to be added to the PrisonBook + * @param password of the new user to be added to the PrisonBook + * @param securityLevel of the new user to be added to the PrisonBook + */ + public AddUserCommand(String username, String password, int securityLevel) { + requireNonNull(username); + requireNonNull(password); + this.username = username; + userToAdd = new User(username, password, securityLevel); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public CommandResult execute() throws CommandException { + requireNonNull(model); + requireNonNull(userToAdd); + try { + model.addUser(userToAdd); + } catch (UserAlreadyExistsException uaee) { + throw new CommandException(String.format(MESSAGE_ALREADY_EXISTING_USER, username)); + } + return new CommandResult(String.format(MESSAGE_ADD_USER_SUCCESS, username)); + } + + public User getUserToAdd() { + return userToAdd; + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddUserCommand // instanceof handles nulls + && Objects.equals(this.userToAdd, ((AddUserCommand) other).userToAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\CheckStatusCommand.java +``` java +package seedu.address.logic.commands; + +/** + * Displays the status of the current session + */ +public class CheckStatusCommand extends Command { + + public static final String COMMAND_WORD = "status"; + + public static final String MESSAGE_USER_NOT_LOGGED_IN = "You are not currently logged in"; + + /** + * Checks the status of current session + * @return details of the status + */ + public CommandResult execute() { + if (!model.checkIsLoggedIn()) { + return new CommandResult(MESSAGE_USER_NOT_LOGGED_IN); + } + String details = (model.getSessionDetails()); + return new CommandResult(details); + } + +} +``` +###### \java\seedu\address\logic\commands\Command.java +``` java + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } +``` +###### \java\seedu\address\logic\commands\DeleteUserCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; + +/** + * Deletes a user from the PrisonBook. + */ +public class DeleteUserCommand extends UndoableCommand { + public static final String COMMAND_WORD = "deleteuser"; + public static final String COMMAND_ALIAS = "du"; + public static final int MIN_SECURITY_LEVEL = 2; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes specified user.\n" + + "Parameters: user/USERNAME_TO_BE_DELETED\n" + + "Example: " + COMMAND_WORD + " user/prisonguard"; + + public static final String MESSAGE_DELETE_CELL_SUCCESS = "User has been successfully deleted"; + + private String userToDelete; + + /** + * Creates a deleteUserCommand object + * @param userToDelete username to be deleted + */ + public DeleteUserCommand(String userToDelete) { + this.userToDelete = userToDelete; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(userToDelete); + try { + model.deleteUser(userToDelete); + } catch (CannotDeleteSelfException cdse) { + throw new CommandException(cdse.getMessage()); + } catch (MustHaveAtLeastOneSecurityLevelThreeUserException mhalosltue) { + throw new CommandException(mhalosltue.getMessage()); + } catch (UserDoesNotExistException udnee) { + throw new CommandException(udnee.getMessage()); + } catch (NotEnoughAuthorityToDeleteException neatde) { + throw new CommandException(neatde.getMessage()); + } + + return new CommandResult(MESSAGE_DELETE_CELL_SUCCESS); + } + + @Override + protected void preprocessUndoableCommand() { + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteUserCommand // instanceof handles nulls + && this.userToDelete.equals(((DeleteUserCommand) other).userToDelete)); // state check + } +} +``` +###### \java\seedu\address\logic\commands\EditCommand.java +``` java + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } +``` +###### \java\seedu\address\logic\commands\LoginCommand.java +``` java +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Attempts to log in user with given Username and Password + */ +public class LoginCommand extends Command { + + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_LOGIN_FAILURE = "Login failed. Username and/or Password entered incorrectly."; + public static final String MESSAGE_LOGIN_SUCCESS = "Login Success"; + public static final String MESSAGE_ALREADY_LOGGED_IN = "You are already logged in. Please logout before " + + "attempting to login with another account."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logs in with your username and password to gain " + + "access to the Prison Book.\n" + + "Parameters: user/YOUR_USERNAME pw/YOUR_PASSWORD...\n" + + "Example: " + COMMAND_WORD + " user/prisonwarden99 pw/password1"; + + private final String username; + private final String password; + + public LoginCommand(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public CommandResult execute() throws CommandException { + if (model.checkIsLoggedIn()) { + throw new CommandException(MESSAGE_ALREADY_LOGGED_IN); + } + if (!model.attemptLogin(username, password)) { + throw new CommandException(MESSAGE_LOGIN_FAILURE); + } else { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_LOGIN_SUCCESS); + } + } + +} +``` +###### \java\seedu\address\logic\commands\LogoutCommand.java +``` java +package seedu.address.logic.commands; + +import java.util.ArrayList; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.HideMapEvent; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.NameContainsKeywordsPredicate; + +/** + * Logs the user out of the current session + */ +public class LogoutCommand extends Command { + + public static final String COMMAND_WORD = "logout"; + + public static final String MESSAGE_SUCCESS = "Successfully logged out"; + public static final String MESSAGE_USER_NOT_LOGGED_IN = "You are not currently logged in"; + + @Override + public CommandResult execute() throws CommandException { + undoRedoStack.clearStack(); + if (!model.checkIsLoggedIn()) { + throw new CommandException(MESSAGE_USER_NOT_LOGGED_IN); + } + model.logout(); + model.updateFilteredPersonList(new NameContainsKeywordsPredicate(new ArrayList())); + EventsCenter.getInstance().post(new HideMapEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} +``` +###### \java\seedu\address\logic\commands\UndoableCommand.java +``` java + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } +``` +###### \java\seedu\address\logic\LogicManager.java +``` java + /** + * Executes the received command if the logged in user's security level meets the MIN_SECURITY_LEVEL for the command + */ + private CommandResult restrictedExecute (Command command) throws CommandException { + logger.info("Command MIN_SECURITY_LEVEL: " + command.getMinSecurityLevel()); + if (command.getMinSecurityLevel() <= model.getSecurityLevel()) { + try { + CommandResult result = command.execute(); + undoRedoStack.push(command); + return result; + } finally { + } + } else { + CommandResult result = new CommandResult(MESSAGE_INSUFFICIENT_SECURITY_CLEARANCE); + return result; + } + } +``` +###### \java\seedu\address\logic\LogicManager.java +``` java + @Override + public ObservableList getUserList() { + return model.getAddressBook().getUserList(); + } +} +``` +###### \java\seedu\address\logic\parser\AddressBookParser.java +``` java + case LoginCommand.COMMAND_WORD: + return new LoginCommandParser().parse(arguments); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommand(); + + case CheckStatusCommand.COMMAND_WORD: + return new CheckStatusCommand(); + + case ShowUsersCommand.COMMAND_WORD: + return new ShowUsersCommand(); + + case AddUserCommand.COMMAND_WORD: + return new AddUserCommandParser().parse(arguments); + + case DeleteUserCommand.COMMAND_WORD: + return new DeleteUserCommandParser().parse(arguments); +``` +###### \java\seedu\address\logic\parser\AddUserCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SECURITY_LEVEL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddUserCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddUserCommand object + */ +public class AddUserCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddUserCommand + * and returns an AddUserCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddUserCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_SECURITY_LEVEL); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_SECURITY_LEVEL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddUserCommand.MESSAGE_USAGE)); + } + + String username = ""; + String password = ""; + int securityLevel = -1; + try { + username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)); + password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD)); + securityLevel = ParserUtil.parseSecurityLevel(argMultimap.getValue(PREFIX_SECURITY_LEVEL)); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + + return new AddUserCommand(username, password, securityLevel); + + } + + /** + * 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()); + } + +} +``` +###### \java\seedu\address\logic\parser\CliSyntax.java +``` java + public static final Prefix PREFIX_ROLE = new Prefix("r/"); + public static final Prefix PREFIX_USERNAME = new Prefix("user/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("pw/"); + public static final Prefix PREFIX_SECURITY_LEVEL = new Prefix("sl/"); +``` +###### \java\seedu\address\logic\parser\LoginCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new LoginCommand object + */ +public class LoginCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the LoginCommand + * and returns an LoginCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public LoginCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + String username = ""; + String password = ""; + try { + username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)); + password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD)); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + + return new LoginCommand(username, password); + + } + + /** + * 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()); + } + +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a @code String username + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code password} is invalid. + */ + public static String parseUsername(String username) throws IllegalValueException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!trimmedUsername.matches("[\\p{Alnum}]*")) { + throw new IllegalValueException("Username can only consist of alphanumeric characters"); + } + return trimmedUsername; + } + + /** + * Parses a {@code Optional username} into an {@code Optional} if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static String parseUsername(Optional username) throws IllegalValueException { + //requireNonNull(username); null accepted for now + return username.isPresent() ? parseUsername(username.get()) : ""; + } + + /** + * Parses a @code String password + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code password} is invalid. + */ + public static String parsePassword(String password) throws IllegalValueException { + requireNonNull(password); + String trimmedPassword = password.trim(); + + if (!trimmedPassword.matches("[\\p{Alnum}]*")) { + throw new IllegalValueException("Password can only consist of alphanumeric characters"); + } + return trimmedPassword; + } + + /** + * Parses a {@code Optional password} into an {@code Optional} if {@code password} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static String parsePassword(Optional password) throws IllegalValueException { + requireNonNull(password); + return password.isPresent() ? parsePassword(password.get()) : ""; + } + + /** + * Parses a @code String securityLevel + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code securityLevel} is invalid. + */ + public static int parseSecurityLevel(String securityLevel) throws NumberFormatException, IllegalValueException { + requireNonNull(securityLevel); + String trimmedSecurityLevel = securityLevel.trim(); + + if (!trimmedSecurityLevel.matches("[123]")) { + throw new IllegalValueException("Security Level can only take integer values 1, 2 or 3"); + } + + int intSecurityLevel = Integer.parseInt(trimmedSecurityLevel); + return intSecurityLevel; + } + + /** + * Parses a {@code Optional securityLevel} into an {@code Optional} + * if {@code securityLevel} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static int parseSecurityLevel (Optional password) + throws NumberFormatException, IllegalValueException { + requireNonNull(password); + return password.isPresent() ? parseSecurityLevel(password.get()) : -1; + } +``` +###### \java\seedu\address\model\AddressBook.java +``` java + //// user-level operations + + /** + * + * @param u is the user to add to the HashMap + */ + public void addUser(User u) throws UserAlreadyExistsException { + users.addUser(u); + } + + /** + * Attempt to log in with the entered username and password + */ + public int attemptLogin(String username, String password) { + return users.verify(username, password); + } + + @Override + public ObservableList getUserList() { + return users.getUserList(); + } + + public void setUsers(ObservableList users) { + this.users.setUsers(users); + } + + public void deleteUser(String userToDelete, String deleterUsername) throws CannotDeleteSelfException, + MustHaveAtLeastOneSecurityLevelThreeUserException, UserDoesNotExistException, + NotEnoughAuthorityToDeleteException { + users.deleteUser(userToDelete, deleterUsername); + } + +``` +###### \java\seedu\address\model\Model.java +``` java + /** Returns the session */ + Session getSession(); + + /** Checks if there is a user logged in to the current session*/ + boolean checkIsLoggedIn(); + + /** Clears existing session*/ + void logout(); + + /** Logs in verified user and assigns security level to the session */ + void login(String username, int securityLevel); + + /** Attempts to login user with entered username and password */ + boolean attemptLogin(String username, String password); + + /** Returns Session details to caller */ + String getSessionDetails(); + + /** Returns Session security level to caller */ + int getSecurityLevel(); + + /** Adds given user to the PrisonBook */ + void addUser(User user) throws UserAlreadyExistsException; + + /** Adds given user to the PrisonBook */ + void deleteUser(String user) throws CannotDeleteSelfException, MustHaveAtLeastOneSecurityLevelThreeUserException, + UserDoesNotExistException, NotEnoughAuthorityToDeleteException; +``` +###### \java\seedu\address\model\ModelManager.java +``` java + logger.info("Initialising session"); + session = new Session(); + logger.info("Initialised session"); + //@@ author + } + + public ModelManager() { + this(new AddressBook(), new UserPrefs()); + } + + @Override + public void resetData(ReadOnlyAddressBook newData) { + addressBook.resetData(newData); + indicateAddressBookChanged(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return addressBook; + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public Session getSession() { + return session; + } + + @Override + public void logout() { + session.logout(); + logger.info("User logged out"); + } + + @Override + public boolean checkIsLoggedIn() { + return session.checkIsLoggedIn(); + } + + @Override + public void login(String username, int securityLevel) { + session.login(username, securityLevel); + logger.info("User logged in with: u/" + username + " slevel/" + securityLevel); + } + + @Override + public boolean attemptLogin(String username, String password) { + logger.info("Current session: " + getSessionDetails()); + int securityLevel = addressBook.attemptLogin(username, password); + if (securityLevel < 0) { + return false; + } else { + login(username, securityLevel); + indicateAddressBookChanged(); + return true; + } + } + + @Override + public String getSessionDetails() { + return ("Username: " + session.getUsername() + " Security Level: " + session.getSecurityLevel()); + } + + @Override + public int getSecurityLevel() { + return session.getSecurityLevel(); + } + + @Override + public void addUser(User userToAdd) throws UserAlreadyExistsException { + addressBook.addUser(userToAdd); + indicateAddressBookChanged(); + logger.info("New user added: " + userToAdd.getUsername()); + } + + @Override + public void deleteUser (String userToDelete) throws CannotDeleteSelfException, + MustHaveAtLeastOneSecurityLevelThreeUserException, UserDoesNotExistException, + NotEnoughAuthorityToDeleteException { + addressBook.deleteUser(userToDelete, session.getUsername()); + indicateAddressBookChanged(); + logger.info("User deleted: " + userToDelete); + } +``` +###### \java\seedu\address\model\person\Role.java +``` java +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's role in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidRole(String)} + */ +public class Role { + + + public static final String MESSAGE_ROLE_CONSTRAINTS = + "Role can only take on the values 'g' or 'p', which represents Guard or Prisoner respectively"; + public static final Role PRISONER = new Role("p"); + public static final Role GUARD = new Role("g"); + public final String value; + + /** + * Constructs a {@code Role}. + * + * @param role 'g' or 'p' to represent Guard or Prisoner respectively. + */ + public Role(String role) { + requireNonNull(role); + checkArgument(isValidRole(role), MESSAGE_ROLE_CONSTRAINTS); + this.value = role; + } + + /** + * Returns true if a given string is a valid person phone number. + */ + public static boolean isValidRole(String test) { + return test.equals("g") || test.equals("p"); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Role // instanceof handles nulls + && this.value.equals(((Role) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the users list. + * This list will not contain any duplicate users. + */ + ObservableList getUserList(); +``` +###### \java\seedu\address\model\Session.java +``` java +package seedu.address.model; + +/** + * Represents User's Session. + */ +public class Session { + + private String username; + private int securityLevel; + private boolean isLoggedIn = false; + + public Session() { + resetSession(); + } + + /** + * Sets session details for user upon successful login + */ + public void login(String username, int securityLevel) { + this.username = username; + this.securityLevel = securityLevel; + this.isLoggedIn = true; + } + + public void logout() { + resetSession(); + } + + private void resetSession() { + username = ""; + securityLevel = 0; + isLoggedIn = false; + } + + public String getUsername() { + return username; + } + + public int getSecurityLevel() { + return securityLevel; + } + + public boolean checkIsLoggedIn () { + return isLoggedIn; + } + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof Session)) { + return false; + } + + // state check + Session other = (Session) obj; + return username.equals(other.username) + && securityLevel == other.securityLevel; + } +} +``` +###### \java\seedu\address\model\user\exceptions\UserAlreadyExistsException.java +``` java +package seedu.address.model.user.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the username that is being added already exists. + */ +public class UserAlreadyExistsException extends IllegalValueException { + public UserAlreadyExistsException() { + super("This username is already used"); + } +} +``` +###### \java\seedu\address\model\user\UniqueUserMap.java +``` java +package seedu.address.model.user; + +import java.util.HashMap; + +import java.util.Iterator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; + +/** + * Contains the users of the PrisonBook + */ +public class UniqueUserMap { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + private HashMap userMap; + + private int numberOfSecurityLevelThree = 0; + + private final User defaultUser1 = new User("prisonguard", "password1", 1); + private final User defaultUser2 = new User("prisonleader", "password2", 2); + private final User defaultUser3 = new User("prisonwarden", "password3", 3); + + public UniqueUserMap() { + resetData(); + } + + /** + * Resets the existing data of this {@code userMap}. + */ + public void resetData() { + userMap = new HashMap(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Returns user's securityLevel if username and matching password is found in database, else return -1 + * @param username + * @param password + * @return + */ + public int verify(String username, String password) { + if (contains(username)) { + return userMap.get(username).checkPassword(password); + } else { + return -1; + } + } + + public boolean contains(String username) { + return userMap.containsKey(username); + } + + /** + * For storage purposes + */ + public ObservableList getUserList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Adds user to the userMap and the internalList + * @param user must be valid User + * @return true if added successfully and false if failed to add + */ + public boolean addUser(User user) throws UserAlreadyExistsException { + if (contains(user.getUsername())) { + throw new UserAlreadyExistsException(); + } else { + userMap.put(user.getUsername(), user); + internalList.add(user); + if (user.getSecurityLevel() == 3) { + numberOfSecurityLevelThree++; + } + return true; + } + } + + /** + * Delete user from the userMap and the internalList + * @param userToDelete must be an existing user + * @return true if added successfully and false if failed to add + */ + public boolean deleteUser(String userToDelete, String deleterUsername) throws UserDoesNotExistException, + NotEnoughAuthorityToDeleteException, CannotDeleteSelfException, + MustHaveAtLeastOneSecurityLevelThreeUserException { + int deleterSecurityLevel = userMap.get(deleterUsername).getSecurityLevel(); + if (!contains(userToDelete)) { + throw new UserDoesNotExistException(); + } else if (userToDelete.equals(deleterUsername)) { + throw new CannotDeleteSelfException(); + } else if (deleterSecurityLevel != 3 && userMap.get(userToDelete).getSecurityLevel() >= deleterSecurityLevel) { + throw new NotEnoughAuthorityToDeleteException(); + } else if (userMap.get(userToDelete).getSecurityLevel() == 3 && numberOfSecurityLevelThree <= 1) { + throw new MustHaveAtLeastOneSecurityLevelThreeUserException(); + } else { + userMap.remove(userToDelete); + Iterator iter = internalList.listIterator(); + for (int i = 0; i < internalList.size(); i++) { + User curr = iter.next(); + if (userToDelete.equals(curr.getUsername())) { + iter.remove(); + break; + } + } + return true; + } + } + + public void setUsers(ObservableList users) { + for (User u: users) { + try { + addUser(u); + } catch (UserAlreadyExistsException e) { + int dummy = 0; + } + } + internalList.clear(); + internalList.setAll(users); + } + + /** + * Checks if two UniqueUserMaps are equal + * @param obj any object + * @return return true if the userMap and internalList are equal + */ + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof UniqueUserMap)) { + return false; + } + + // state check + UniqueUserMap other = (UniqueUserMap) obj; + return userMap.equals(other.userMap) && internalList.equals(other.internalList); + } +} +``` +###### \java\seedu\address\model\user\User.java +``` java +package seedu.address.model.user; + +/** + * Represents a user of the PrisonBook + */ +public class User { + + public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + private final String username; + private final String password; + private final int securityLevel; + + public User (String username, String password, int securityLevel) { + this.username = username; + this.password = password; + this.securityLevel = securityLevel; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public int getSecurityLevel() { + return securityLevel; + } + + /** + * Returns true if a given string is a valid username. + */ + public static boolean isValidName(String test) { + return test.matches(NAME_VALIDATION_REGEX); + } + + /** + * Checks if given string matches password, returns user securityLevel if password matches, else returns -1 + */ + public int checkPassword(String enteredPassword) { + if (enteredPassword.equals(password)) { + return securityLevel; + } else { + return -1; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof User // instanceof handles nulls + && username.equals(((User) other).username)); + } + + @Override + public String toString() { + return ("Username: " + username + " securityLevel: " + securityLevel); + } + +} + +``` +###### \java\seedu\address\model\util\SampleDataUtil.java +``` java + public static User[] getSampleUsers() { + return new User[] { + new User("prisonguard", "password1", 1), + new User("prisonleader", "password2", 2), + new User("prisonwarden", "password3", 3), + new User("prisonguard2", "password1", 1), + new User("prisonguard3", "password1", 1), + new User("prisonleader2", "password2", 2), + new User("prisonleader3", "password2", 2), + new User("prisonwarden2", "password3", 3) + }; + } +``` +###### \java\seedu\address\storage\XmlAdaptedUser.java +``` java +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.model.user.User; + +/** + * JAXB-friendly version of the Person. + */ +public class XmlAdaptedUser { + + @XmlElement(required = true) + private String username; + @XmlElement(required = true) + private String password; + @XmlElement(required = true) + private int securityLevel; + + /** + * Constructs an XmlAdaptedUser. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedUser() {} + + /** + * Constructs an {@code XmlAdaptedPerson} with the given person details. + */ + public XmlAdaptedUser(String username, String password, int securityLevel) { + this.username = username; + this.password = password; + this.securityLevel = securityLevel; + } + + /** + * Converts a given User into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedUser + */ + public XmlAdaptedUser(User source) { + username = source.getUsername(); + password = source.getPassword(); + securityLevel = source.getSecurityLevel(); + } + + /** + * Converts this jaxb-friendly adapted user object into the model's User object. + * + */ + public User toModelType() { + return new User(username, password, securityLevel); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedUser)) { + return false; + } + + XmlAdaptedUser otherUser = (XmlAdaptedUser) other; + return this.username.equals(((XmlAdaptedUser) other).username) + && this.password.equals(((XmlAdaptedUser) other).password) + && this.securityLevel == (((XmlAdaptedUser) other).securityLevel); + } +} +``` diff --git a/collated/test/philos22.md b/collated/test/philos22.md new file mode 100644 index 000000000000..aea829e4a33e --- /dev/null +++ b/collated/test/philos22.md @@ -0,0 +1,396 @@ +# philos22 +###### \java\seedu\address\CalendarTest.java +``` java + +package seedu.address; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import com.google.api.client.util.DateTime; +import com.google.api.services.calendar.model.Event; +import com.google.api.services.calendar.model.EventDateTime; + +/** + * This class is meant to override some properties of Calendar so that it will be suited for + * testing + */ +public class CalendarTest extends Calendar { + + private static ArrayList eventIDs = new ArrayList<>(); + + static { + addEventIDs("one1"); + addEventIDs("two2"); + addEventIDs("three3"); + addEventIDs("four4"); + addEventIDs("five5"); + addEventIDs("six6"); + addEventIDs("seven7"); + addEventIDs("eight8"); + } + + private static List pseudoEventList = new ArrayList<>(); + + static { + pseudoEventList.add("[Event 1] \t EventName1 \t [EventStart1] to [EventEnd1] \tLocation: EventLocation1\n"); + pseudoEventList.add("[Event 2] \t EventName2 \t [EventStart2] to [EventEnd2] \tLocation: EventLocation2\n"); + pseudoEventList.add("[Event 3] \t EventName3 \t [EventStart3] to [EventEnd3] \tLocation: EventLocation3\n"); + pseudoEventList.add("[Event 4] \t EventName4 \t [EventStart4] to [EventEnd4] \tLocation: EventLocation4\n"); + pseudoEventList.add("[Event 5] \t EventName5 \t [EventStart5] to [EventEnd5] \tLocation: EventLocation5\n"); + pseudoEventList.add("[Event 6] \t EventName6 \t [EventStart6] to [EventEnd6] \tLocation: EventLocation6\n"); + pseudoEventList.add("[Event 7] \t EventName7 \t [EventStart7] to [EventEnd7] \tLocation: EventLocation7\n"); + pseudoEventList.add("[Event 8] \t EventName8 \t [EventStart8] to [EventEnd8] \tLocation: EventLocation8\n"); + } + + public static void addEventIDs(String event) { + eventIDs.add(event); + } + + /** + * Manually creating a list of events for testing + */ + public static String listEventsTest() { + StringBuilder result = new StringBuilder(); + for (String eventLine : pseudoEventList) { + result.append(eventLine); + } + return result.toString(); + } + + @Test + public static void addEventTest(String eventName, String eventLocation, DateTime startDateTime, + DateTime endDateTime) { + + Event event = new Event() + .setSummary(eventName) + .setLocation(eventLocation); + + EventDateTime start = new EventDateTime() + .setDateTime(startDateTime) + .setTimeZone(""); + event.setStart(start); + + EventDateTime end = new EventDateTime() + .setDateTime(endDateTime) + .setTimeZone(""); + event.setEnd(end); + + //EventName matches + assertEquals(event.getSummary(), eventName); + + //Location matches + assertEquals(event.getLocation(), eventLocation); + + //Start matches + assertEquals(event.getStart(), startDateTime); + + //End matches + assertEquals(event.getEnd(), endDateTime); + } + +} +``` +###### \java\seedu\address\logic\commands\CalendarCommandTest.java +``` java + +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import seedu.address.CalendarTest; + +/** + * Ensures that the events are listed in the same way + */ +public class CalendarCommandTest { + + private static List pseudoEventList = new ArrayList<>(); + + @Before + public void setUp() { + pseudoEventList.add("[Event 1] \t EventName1 \t [EventStart1] to [EventEnd1] \tLocation: EventLocation1\n"); + pseudoEventList.add("[Event 2] \t EventName2 \t [EventStart2] to [EventEnd2] \tLocation: EventLocation2\n"); + pseudoEventList.add("[Event 3] \t EventName3 \t [EventStart3] to [EventEnd3] \tLocation: EventLocation3\n"); + pseudoEventList.add("[Event 4] \t EventName4 \t [EventStart4] to [EventEnd4] \tLocation: EventLocation4\n"); + pseudoEventList.add("[Event 5] \t EventName5 \t [EventStart5] to [EventEnd5] \tLocation: EventLocation5\n"); + pseudoEventList.add("[Event 6] \t EventName6 \t [EventStart6] to [EventEnd6] \tLocation: EventLocation6\n"); + pseudoEventList.add("[Event 7] \t EventName7 \t [EventStart7] to [EventEnd7] \tLocation: EventLocation7\n"); + pseudoEventList.add("[Event 8] \t EventName8 \t [EventStart8] to [EventEnd8] \tLocation: EventLocation8\n"); + } + + @Test + public void calendarListEvents_showsSameList() { + StringBuilder expectedOutput = new StringBuilder(); + for (String eventLine : pseudoEventList) { + expectedOutput.append(eventLine); + } + assertEquals(CalendarTest.listEventsTest().toString(), expectedOutput.toString()); + } + +} +``` +###### \java\seedu\address\logic\commands\FindCommandTest.java +``` java + private FindCommand prepareCommand(String userInput, String tagInput) { + FindCommand command = + new FindCommand(new ContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")), + Arrays.asList(tagInput.split("\\s+")))); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +``` +###### \java\seedu\address\logic\parser\CalendarAddCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATETIME_FORMAT; +import static seedu.address.logic.commands.CalendarAddCommand.COMMAND_WORD; +import static seedu.address.logic.commands.CommandTestUtil.EVENT_END_DESC; +import static seedu.address.logic.commands.CommandTestUtil.EVENT_LOCATION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.EVENT_NAME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.EVENT_START_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EVENT_START; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EVENT_END; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EVENT_LOCATION; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EVENT_NAME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EVENT_START; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.CalendarAddCommand; + +/** + * Testing for CalendarAddCommand + */ +public class CalendarAddCommandParserTest { + + private CalendarAddCommandParser parser = new CalendarAddCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CalendarAddCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsCalendarAddCommand() { + // no leading and trailing whitespaces + String eventName = "CalendarTest"; + String location = "CalendarLocation"; + String startTime = "2019-01-01 12:00:00"; + String endTime = "2019-01-01 13:00:00"; + CalendarAddCommand expectedCalendarAddCommand = + new CalendarAddCommand(eventName, location, ParserUtil.parseDateTime(startTime), + ParserUtil.parseDateTime(endTime)); + + assertParseSuccess(parser, + "calAdd event/CalendarTest loc/CalendarLocation start/2019-01-01 12:00:00 end/2019-01-01 13:00:00", + expectedCalendarAddCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, + "calAdd \n event/CalendarTest \t \n loc/CalendarLocation \t start/2019-01-01 12:00:00 \n " + + "end/2019-01-01 13:00:00", expectedCalendarAddCommand); + } + + @Test + public void parse_missingPrefix_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, CalendarAddCommand.MESSAGE_USAGE); + + //missing event prefix + assertParseFailure(parser, COMMAND_WORD + " " + VALID_EVENT_NAME + EVENT_LOCATION_DESC + + EVENT_START_DESC + EVENT_END_DESC, expectedMessage); + + //missing location prefix + assertParseFailure(parser, COMMAND_WORD + " " + EVENT_NAME_DESC+ VALID_EVENT_LOCATION + EVENT_START_DESC + + EVENT_END_DESC, expectedMessage); + + //missing startDateTime prefix + assertParseFailure(parser, COMMAND_WORD + " " + EVENT_NAME_DESC + EVENT_LOCATION_DESC + VALID_EVENT_START + + EVENT_END_DESC, expectedMessage); + + //missing endDateTime prefix + assertParseFailure(parser, COMMAND_WORD + " " + EVENT_NAME_DESC + EVENT_LOCATION_DESC + EVENT_START_DESC + + VALID_EVENT_END, expectedMessage); + } + + @Test + public void parse_invalidArgs_failure() { + //invalid date format - start and end dates are the same scenario + assertParseFailure(parser, COMMAND_WORD + " " + EVENT_NAME_DESC + EVENT_LOCATION_DESC + INVALID_EVENT_START + + EVENT_END_DESC, MESSAGE_INVALID_DATETIME_FORMAT); + } + +} +``` +###### \java\seedu\address\logic\parser\CalendarDeleteCommandParserTest.java +``` java + +package seedu.address.logic.parser; + +import static org.junit.Assert.assertEquals; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; + +import java.util.ArrayList; + +import org.junit.Test; + +import seedu.address.logic.commands.CalendarDeleteCommand; + +/** + * Testing for CalendarDeleteCommand + */ +public class CalendarDeleteCommandParserTest { + + private CalendarDeleteCommandParser parser = new CalendarDeleteCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + ArrayList eventIdsTest = new ArrayList<>(); //Same logic as delete event command + eventIdsTest.add("a1b2c3d4e5f6g7"); + assertEquals(eventIdsTest.get(1 - 1), "a1b2c3d4e5f6g7"); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CalendarDeleteCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\FindCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import seedu.address.logic.commands.FindCommand; +import seedu.address.model.person.ContainsKeywordsPredicate; + +public class FindCommandParserTest { + + private FindCommandParser parser = new FindCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + List nameList = new ArrayList<>(); + nameList.add("Alice"); + nameList.add("Bob"); + List tagList = new ArrayList<>(); + tagList.add("Tag1"); + tagList.add("Tag2"); + FindCommand expectedFindCommand = + new FindCommand(new ContainsKeywordsPredicate(nameList, tagList)); + + assertParseSuccess(parser, "find n/Alice Bob t/Tag1 Tag2", expectedFindCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, "find \n n/Alice \n \t Bob \t t/Tag1 \n Tag2", expectedFindCommand); + } + +} +``` +###### \java\seedu\address\model\person\TagContainsKeywordsPredicateTest.java +``` java +package seedu.address.model.person; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.testutil.PersonBuilder; + +public class TagContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + TagContainsKeywordsPredicate firstPredicate = new TagContainsKeywordsPredicate(firstPredicateKeywordList); + TagContainsKeywordsPredicate secondPredicate = new TagContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + TagContainsKeywordsPredicate firstPredicateCopy = new TagContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_tagContainsKeywords_returnsTrue() { + // One keyword + TagContainsKeywordsPredicate predicate = new TagContainsKeywordsPredicate(Collections.singletonList("Tag1")); + assertTrue(predicate.test(new PersonBuilder().withTags("Tag1").build())); + + // Multiple keywords + predicate = new TagContainsKeywordsPredicate(Arrays.asList("Tag1", "Tag2")); + assertTrue(predicate.test(new PersonBuilder().withTags("Tag1").build())); + + // Mixed-case keyword + predicate = new TagContainsKeywordsPredicate(Arrays.asList("tAg1")); + assertTrue(predicate.test(new PersonBuilder().withTags("Tag1").build())); + } + + @Test + public void test_tagDoesNotContainKeywords_returnsFalse() { + // Zero keywords + TagContainsKeywordsPredicate predicate = new TagContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withTags("Tag1").build())); + + // Non-matching keyword + predicate = new TagContainsKeywordsPredicate(Collections.singletonList("Tag2")); + assertFalse(predicate.test(new PersonBuilder().withTags("Tag1").build())); + + // Keywords match phone, email and address, but does not match tag + predicate = new TagContainsKeywordsPredicate(Arrays.asList("Alice", "12345", + "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new PersonBuilder().withTags("Tag1").withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withAddress("Main Street").build())); + } +} +``` diff --git a/collated/test/sarahgoh97.md b/collated/test/sarahgoh97.md new file mode 100644 index 000000000000..33a9d5efc4ca --- /dev/null +++ b/collated/test/sarahgoh97.md @@ -0,0 +1,740 @@ +# sarahgoh97 +###### \java\seedu\address\logic\commands\AddCellCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; + +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.AddCellCommand.MESSAGE_ALREADY_IN_CELL; +import static seedu.address.logic.commands.AddCellCommand.MESSAGE_FULL_CELL; +import static seedu.address.logic.commands.AddCellCommand.MESSAGE_NOT_PRISONER; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; +import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.testutil.TypicalCells.FIRST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalCells.FULL_CELL; +import static seedu.address.testutil.TypicalCells.INVALID_FIRST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalCells.INVALID_SECOND_CELL_ADDRESS; +import static seedu.address.testutil.TypicalCells.LAST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +public class AddCellCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + public AddCellCommandTest() { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Test + public void execute_validIndexUnfilteredListValidCellAddress_success() throws Exception { + Person prisonerToAdd = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + String cellAddress = LAST_CELL_ADDRESS; + AddCellCommand addCellCommand = prepareCommand(INDEX_FIRST_PERSON, cellAddress); + + String expectedMessage = String.format(AddCellCommand.MESSAGE_ADD_CELL_SUCCESS, + prisonerToAdd.getName().toString(), cellAddress); + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + expectedModel.addPrisonerToCell(prisonerToAdd, cellAddress); + + assertCommandSuccess(addCellCommand, model, expectedMessage, expectedModel); + addCellCommand.undo(); + } + + @Test + public void execute_inValidIndexUnfilteredListValidCellAddress_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + String cellAddress = FIRST_CELL_ADDRESS; + AddCellCommand addCellCommand = prepareCommand(outOfBoundIndex, cellAddress); + + assertCommandFailure(addCellCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + } + + @Test + public void execute_validIndexUnfilteredListInvalidFirstDigitCellAddress_failure() { + String cellAddress = INVALID_FIRST_CELL_ADDRESS; + AddCellCommand addCellCommand = prepareCommand(INDEX_FIRST_PERSON, cellAddress); + + assertCommandFailure(addCellCommand, model, String.format(AddCellCommand.MESSAGE_NON_EXISTENT_CELL, + cellAddress)); + + } + + @Test + public void execute_validIndexUnfilteredListInvalidSecondDigitCellAddress_failure() { + String cellAddress = INVALID_SECOND_CELL_ADDRESS; + AddCellCommand addCellCommand = prepareCommand(INDEX_FIRST_PERSON, cellAddress); + + assertCommandFailure(addCellCommand, model, String.format(AddCellCommand.MESSAGE_NON_EXISTENT_CELL, + cellAddress)); + + } + + @Test + public void execute_fullCell_failure() { + String cellAddress = FULL_CELL.getCellAddress(); + AddCellCommand addCellCommand = prepareCommand(INDEX_FIRST_PERSON, cellAddress); + + assertCommandFailure(addCellCommand, model, + String.format(MESSAGE_FULL_CELL, cellAddress)); + + } + + @Test + public void execute_personNotPrisoner_failure() { + Person prisonerToAdd = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + String cellAddress = LAST_CELL_ADDRESS; + AddCellCommand addCellCommand = prepareCommand(INDEX_SECOND_PERSON, cellAddress); + + assertCommandFailure(addCellCommand, model, + String.format(MESSAGE_NOT_PRISONER, prisonerToAdd.getName().toString())); + + } + + @Test + public void execute_personInCell_failure() { + Person prisonerToAdd = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + String cellAddress = LAST_CELL_ADDRESS; + AddCellCommand addCellCommand = prepareCommand(INDEX_THIRD_PERSON, cellAddress); + + assertCommandFailure(addCellCommand, model, + String.format(MESSAGE_ALREADY_IN_CELL, + prisonerToAdd.getName().toString(), prisonerToAdd.getCellAddress())); + + } + + @Test + public void equals() { + AddCellCommand acFirstCommand = prepareCommand(INDEX_FIRST_PERSON, FIRST_CELL_ADDRESS); + AddCellCommand acSecondCommand = prepareCommand(INDEX_FIRST_PERSON, LAST_CELL_ADDRESS); + AddCellCommand acThirdCommand = prepareCommand(INDEX_SECOND_PERSON, FIRST_CELL_ADDRESS); + + + // same object -> returns true + assertTrue(acFirstCommand.equals(acFirstCommand)); + + // same values -> returns true + AddCellCommand copy = prepareCommand(INDEX_FIRST_PERSON, FIRST_CELL_ADDRESS); + assertTrue(acFirstCommand.equals(copy)); + + // different types -> returns false + assertFalse(acFirstCommand.equals(1)); + + // null -> returns false + assertFalse(acFirstCommand.equals(null)); + + // different cell -> returns false + assertFalse(acFirstCommand.equals(acSecondCommand)); + + //different person/index -> returns false + assertFalse(acFirstCommand.equals(acThirdCommand)); + } + + @Test + public void executeUndoRedo() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + AddCellCommand addCellCommand = prepareCommand(INDEX_FIRST_PERSON, FIRST_CELL_ADDRESS); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // add cell -> first person added to a cell + Person prisonerToAdd = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + addCellCommand.preprocessUndoableCommand(); + addCellCommand.execute(); + undoRedoStack.push(addCellCommand); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.addPrisonerToCell(prisonerToAdd, FIRST_CELL_ADDRESS); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + addCellCommand.undo(); + } + + private AddCellCommand prepareCommand(Index index, String cellAddress) { + AddCellCommand addCellCommand = new AddCellCommand(index, cellAddress); + addCellCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return addCellCommand; + } +} +``` +###### \java\seedu\address\logic\commands\AddCommandTest.java +``` java + @Override + public void addPrisonerToCell(Person prisoner, String cellAddress) { + fail("This method should not be called."); + } + + @Override + public void deletePrisonerFromCell(Person prisoner) throws PersonNotFoundException, NotImprisonedException { + fail("This method should not be called."); + } + + @Override + public void deletePrisonerFromCellFromUndo(Person prisoner, String cellAddress) { + fail("This method should not be called."); + } + + @Override + public void addPrisonerToCellFromUndo(Person prisoner, String cellAddress) { + fail("This method should not be called."); + } + + @Override + public void updatePrisonerFromUndo(Person orignal, Person changed) { + fail("This method should not be called."); + } + + @Override + public void updateFilteredPersonListForCell(Predicate predicate, String cellAddress) { + fail("This method should not be called."); + } +``` +###### \java\seedu\address\logic\commands\DeleteCellCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; +import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; +import static seedu.address.logic.commands.DeleteCellCommand.MESSAGE_NOT_IMPRISONED; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +public class DeleteCellCommandTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + public DeleteCellCommandTest() { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Test + public void execute_validIndex_success() throws Exception { + DeleteCellCommand deleteCellCommand = prepareCommand(INDEX_THIRD_PERSON); + Person prisoner = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + + String expectedMessage = String.format(DeleteCellCommand.MESSAGE_DELETE_CELL_SUCCESS, + prisoner.getName().toString()); + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + expectedModel.deletePrisonerFromCell(prisoner); + + assertCommandSuccess(deleteCellCommand, model, expectedMessage, expectedModel); + deleteCellCommand.undo(); + expectedModel.addPrisonerToCellFromUndo(prisoner, prisoner.getCellAddress().toString()); + } + + @Test + public void execute_indexOfNotImprisonedPrisoner_failure() throws Exception { + DeleteCellCommand deleteCellCommand = prepareCommand(INDEX_FIRST_PERSON); + assertCommandFailure(deleteCellCommand, model, MESSAGE_NOT_IMPRISONED); + } + + @Test + public void execute_indexOfNotImprisonedGuard_failure() throws Exception { + DeleteCellCommand deleteCellCommand = prepareCommand(INDEX_SECOND_PERSON); + assertCommandFailure(deleteCellCommand, model, MESSAGE_NOT_IMPRISONED); + } + + @Test + public void execute_invalidIndex_failure() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + DeleteCellCommand deleteCellCommand = prepareCommand(outOfBoundIndex); + assertCommandFailure(deleteCellCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Person prisonerReleased = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + DeleteCellCommand deleteCellCommand = prepareCommand(INDEX_THIRD_PERSON); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // delete -> first person deleted + deleteCellCommand.execute(); + undoRedoStack.push(deleteCellCommand); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first person deleted again + expectedModel.deletePrisonerFromCell(prisonerReleased); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + deleteCellCommand.undo(); + } + + @Test + public void equals() throws Exception { + DeleteCellCommand deleteCellFirstCommand = prepareCommand(INDEX_THIRD_PERSON); + DeleteCellCommand deleteCellSecondCommand = prepareCommand(Index.fromOneBased(5)); + + // same object -> returns true + assertTrue(deleteCellFirstCommand.equals(deleteCellFirstCommand)); + + // same values -> returns true + DeleteCellCommand deleteCellFirstCommandCopy = prepareCommand(INDEX_THIRD_PERSON); + assertTrue(deleteCellFirstCommand.equals(deleteCellFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + deleteCellFirstCommandCopy.preprocessUndoableCommand(); + assertFalse(deleteCellFirstCommand.equals(deleteCellFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteCellFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteCellFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteCellFirstCommand.equals(deleteCellSecondCommand)); + } + + /** + * Returns a {@code DeleteCellCommand} with the parameter {@code index}. + */ + private DeleteCellCommand prepareCommand(Index index) { + DeleteCellCommand deleteCellCommand = new DeleteCellCommand(index); + deleteCellCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return deleteCellCommand; + } +} +``` +###### \java\seedu\address\logic\commands\ListCellCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.ListCellCommand.MESSAGE_LISTCELL_SUCCESS; +import static seedu.address.logic.commands.ListCellCommand.MESSAGE_NON_EXISTENT_CELL; +import static seedu.address.testutil.TypicalCells.FIRST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalCells.FULL_CELL; +import static seedu.address.testutil.TypicalCells.INVALID_FIRST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalCells.INVALID_SECOND_CELL_ADDRESS; +import static seedu.address.testutil.TypicalCells.LAST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalPersons.ELLE; +import static seedu.address.testutil.TypicalPersons.GEORGE; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.AddressContainsCellPredicate; +import seedu.address.model.person.Person; + +public class ListCellCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + AddressContainsCellPredicate firstPredicate = new AddressContainsCellPredicate(FIRST_CELL_ADDRESS); + AddressContainsCellPredicate secondPredicate = new AddressContainsCellPredicate(LAST_CELL_ADDRESS); + + ListCellCommand lcFirstCommand = new ListCellCommand(firstPredicate, FIRST_CELL_ADDRESS); + ListCellCommand lcSecondCommand = new ListCellCommand(secondPredicate, LAST_CELL_ADDRESS); + + // same object -> returns true + assertTrue(lcFirstCommand.equals(lcFirstCommand)); + + // same values -> returns true + ListCellCommand lcFirstCommandCopy = new ListCellCommand(firstPredicate, FIRST_CELL_ADDRESS); + assertTrue(lcFirstCommand.equals(lcFirstCommandCopy)); + + // different types -> returns false + assertFalse(lcFirstCommand.equals(1)); + + // null -> returns false + assertFalse(lcFirstCommand.equals(null)); + + // different cell address -> returns false + assertFalse(lcFirstCommand.equals(lcSecondCommand)); + } + + @Test + public void execute_validEmptyCellAddress_success() throws Exception { + ListCellCommand listCellCommand = prepareCommand(FIRST_CELL_ADDRESS); + String expectedMessage = String.format(MESSAGE_LISTCELL_SUCCESS, FIRST_CELL_ADDRESS); + assertCommandSuccess(listCellCommand, expectedMessage, Collections.emptyList()); + } + + @Test + public void execute_validNonEmptyCellAddress_success() throws Exception { + ListCellCommand listCellCommand = prepareCommand(FULL_CELL.getCellAddress()); + String expectedMessage = String.format(MESSAGE_LISTCELL_SUCCESS, FULL_CELL.getCellAddress()); + assertCommandSuccess(listCellCommand, expectedMessage, Arrays.asList(ELLE, GEORGE)); + } + + @Test + public void execute_invalidFirstDigitCellAddress_failure() { + ListCellCommand listCellCommand = prepareCommand(INVALID_FIRST_CELL_ADDRESS); + assertCommandFailure(listCellCommand, model, String.format(MESSAGE_NON_EXISTENT_CELL, + INVALID_FIRST_CELL_ADDRESS)); + } + + @Test + public void execute_invalidSecondDigitCellAddress_failure() { + ListCellCommand listCellCommand = prepareCommand(INVALID_SECOND_CELL_ADDRESS); + assertCommandFailure(listCellCommand, model, String.format(MESSAGE_NON_EXISTENT_CELL, + INVALID_SECOND_CELL_ADDRESS)); + } + + private ListCellCommand prepareCommand(String cellAddress) { + ListCellCommand command = new ListCellCommand(new AddressContainsCellPredicate(cellAddress), cellAddress); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(ListCellCommand command, String expectedMessage, + List expectedList) throws Exception { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} +``` +###### \java\seedu\address\logic\commands\ShowCellsCommandTest.java +``` java +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class ShowCellsCommandTest { + private Model model; + private Model expectedModel; + private ShowCellsCommand mapCommand; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + mapCommand = new ShowCellsCommand(); + mapCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void execute_typicalMap() { + assertCommandSuccess(mapCommand, model, String.format(mapCommand.MESSAGE_SUCCESS, + new ShowCellsCommand().getMapString(model.getAddressBook().getCellList().toString())), expectedModel); + + } +} +``` +###### \java\seedu\address\logic\commands\UndoableCommandTest.java +``` java + @Before + public void setUp() { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } +``` +###### \java\seedu\address\logic\parser\AddCellCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalCells.FIRST_CELL_ADDRESS; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.Test; + +import seedu.address.logic.commands.AddCellCommand; + +public class AddCellCommandParserTest { + + private AddCellCommandParser parser = new AddCellCommandParser(); + + @Test + public void parse_validArgs_returnsAddCellCommand() { + assertParseSuccess(parser, "ac 1 1-1", new AddCellCommand(INDEX_FIRST_PERSON, FIRST_CELL_ADDRESS)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "ac 1 sdf32", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCellCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidCellAddress_throwsParseException() { + assertParseFailure(parser, "ac 1 2=1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCellCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\AddressBookParserTest.java +``` java + @Test + public void parseCommand_addCellCommandWord_returnsAddCellCommand() throws Exception { + assertTrue(parser.parseCommand(AddCellCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + + " " + FIRST_CELL_ADDRESS) instanceof AddCellCommand); + assertTrue(parser.parseCommand("addcell 3 1-4") instanceof AddCellCommand); + assertTrue(parser.parseCommand("ac 1 1-1") instanceof AddCellCommand); + } + + @Test + public void parseCommand_deleteCellCommandWord_returnsDeleteCellCommand() throws Exception { + assertTrue(parser.parseCommand(DeleteCellCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()) + instanceof DeleteCellCommand); + assertTrue(parser.parseCommand("deletecell 3") instanceof DeleteCellCommand); + assertTrue(parser.parseCommand("dc 2") instanceof DeleteCellCommand); + } + + @Test + public void parseCommand_listCellCommandWord_returnsListCellCommand() throws Exception { + assertTrue(parser.parseCommand(ListCellCommand.COMMAND_WORD + " " + LAST_CELL_ADDRESS) + instanceof ListCellCommand); + assertTrue(parser.parseCommand("listcell 2-2") instanceof ListCellCommand); + assertTrue(parser.parseCommand("lc 1-1") instanceof ListCellCommand); + } +``` +###### \java\seedu\address\logic\parser\DeleteCellCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.Test; + +import seedu.address.logic.commands.DeleteCellCommand; + +public class DeleteCellCommandParserTest { + + private DeleteCellCommandParser parser = new DeleteCellCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "1", new DeleteCellCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "dc", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCellCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\testutil\CellBuilder.java +``` java +package seedu.address.testutil; + +import java.util.ArrayList; + +import seedu.address.model.cell.Cell; +import seedu.address.model.person.Person; + +/** + * A utility class to help with building Cell objects. + */ + +public class CellBuilder { + public static final ArrayList DEFAULT_PRISONERS = new ArrayList(); + public static final String DEFAULT_CELLADDRESS = "1-1"; + public static final boolean DEFAULT_IS_LAST = false; + + private ArrayList prisoners; + private String cellAddress; + private boolean isLast; + + public CellBuilder() { + prisoners = new ArrayList(DEFAULT_PRISONERS); + cellAddress = new String(DEFAULT_CELLADDRESS); + isLast = DEFAULT_IS_LAST; + } + + /** + * Initialises the CellBuilder with data of {@code cellToCopy} + */ + public CellBuilder(Cell celltoCopy) { + prisoners = celltoCopy.getPrisoners(); + cellAddress = celltoCopy.getCellAddress(); + isLast = celltoCopy.getIsLast(); + } + + /** + * Sets the {@code prisoners} of the {@code Cell} that we are building. + */ + public CellBuilder withPrisoners(ArrayList prisoners) { + this.prisoners = new ArrayList(prisoners); + return this; + } + + /** + * Sets the {@code prisoner} of the {@code Cell} that we are building. + */ + public CellBuilder withPrisoner(Person prisoner) { + prisoners.add(prisoner); + return this; + } + + /** + * Sets the {@code cellAddress} of the {@code cell} that we are building. + */ + public CellBuilder withCellAddress(String cellAddress) { + this.cellAddress = cellAddress; + return this; + } + + /** + * Sets the {@code isLast} of the {@code cell} that we are building. + */ + public CellBuilder withIsLast(boolean isLast) { + this.isLast = isLast; + return this; + } + + public Cell build() { + return new Cell(prisoners, cellAddress, isLast); + } +} +``` +###### \java\seedu\address\testutil\TypicalCells.java +``` java +package seedu.address.testutil; + +import static seedu.address.testutil.TypicalPersons.CARL; +import static seedu.address.testutil.TypicalPersons.ELLE; +import static seedu.address.testutil.TypicalPersons.GEORGE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.cell.Cell; + +/** + * A utility class containing a list of {@code Cell} objects to be used in tests. + */ +public class TypicalCells { + public static final Cell EMPTY_CELL = new CellBuilder().build(); + public static final Cell CELL_WITH_PRISONER = new CellBuilder().withCellAddress("1-2").withPrisoner(CARL).build(); + public static final Cell FULL_CELL = new CellBuilder().withCellAddress("1-3").withPrisoner(ELLE) + .withPrisoner(GEORGE).build(); + public static final Cell LAST_CELL_OF_ROW = new CellBuilder().withCellAddress("1-5").withIsLast(true).build(); + + public static final String FIRST_CELL_ADDRESS = "1-1"; + public static final String LAST_CELL_ADDRESS = "3-5"; + public static final String INVALID_FIRST_CELL_ADDRESS = "0-1"; + public static final String INVALID_SECOND_CELL_ADDRESS = "3-6"; + + + public static List getTypicalCells() { + return new ArrayList<>(Arrays.asList(EMPTY_CELL, LAST_CELL_OF_ROW, CELL_WITH_PRISONER, FULL_CELL)); + } +} +``` +###### \java\systemtests\AddressBookSystemTest.java +``` java + /** + * Asserts that the total number of people and sync status in the status bar was changed to the timing of + * {@code ClockRule#getInjectedClock()}, while the save location remains the same. + */ + protected void assertStatusBarChangedExceptSaveLocation(int expectedNumberOfPeople) { + StatusBarFooterHandle handle = getStatusBarFooter(); + String timestamp = new Date(clockRule.getInjectedClock().millis()).toString(); + String expectedSyncStatus = String.format(SYNC_STATUS_UPDATED, timestamp); + String expectedNumberOfPeopleStatus = expectedNumberOfPeople + " person(s) total."; + assertEquals(expectedSyncStatus, handle.getSyncStatus()); + assertEquals(expectedNumberOfPeopleStatus, handle.getNumberOfPeopleStatus()); + assertFalse(handle.isSaveLocationChanged()); + } +``` +###### \java\systemtests\AddressBookSystemTest.java +``` java + /** + * Returns a new model that starts with unfiltered list + */ + protected Model getNewModel() { + return testApp.getNewModel(); + } +} +``` diff --git a/collated/test/zacci.md b/collated/test/zacci.md new file mode 100644 index 000000000000..cb0b2ae89fa7 --- /dev/null +++ b/collated/test/zacci.md @@ -0,0 +1,118 @@ +# zacci +###### \java\seedu\address\logic\commands\AddCommandTest.java +``` java + @Override + public void login(String username, int securityLevel){}; + + @Override + public void logout(){}; + + @Override + public Session getSession() { + return new Session(); + }; + + @Override + public String getSessionDetails() { + return ""; + } + + @Override + public boolean attemptLogin(String username, String password) { + return true; + } + + @Override + public int getSecurityLevel() { + return 5; + } + + @Override + public void addUser(User user) {}; + + @Override + public void deleteUser(String user) {}; + + @Override + public boolean checkIsLoggedIn() { + return true; + }; +``` +###### \java\seedu\address\logic\LogicManagerTest.java +``` java + public LogicManagerTest() { + model.login("maxSecurityLevelUser", 999); + } +``` +###### \java\seedu\address\TestApp.java +``` java + public Model getModel() { + Model copy = new ModelManager((model.getAddressBook()), new UserPrefs()); + model.login("maxSecurityLevelUser", 999); + ModelHelper.setFilteredList(copy, model.getFilteredPersonList()); + return copy; + } + + //@author sarahgoh97 + public Model getNewModel() { + Model copy = new ModelManager((model.getAddressBook()), new UserPrefs()); + model.login("maxSecurityLevelUser", 999); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + ModelHelper.setFilteredList(copy, model.getFilteredPersonList()); + return copy; + } +``` +###### \java\seedu\address\testutil\TypicalUsers.java +``` java +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; + +/** + * A utility class containing a list of Users, usernames, passwords and security levels to be used in tests. + */ +public class TypicalUsers { + public static final String VALID_USERNAME = "prisonwarden99"; + public static final String VALID_PASSWORD = "password99"; + public static final int VALID_MINIMUM_SECURITY_LEVEL = 1; + public static final int VALID_MAXIMUM_SECURITY_LEVEL = 3; + public static final String INVALID_USERNAME = "prison warden"; //cannot have spaces + public static final String INVALID_PASSWORD = "password1("; //cannot have "(" + public static final int INVALID_SECURITY_LEVEL = 4; + + public static final User PRISONWARDEN = new User("prisonwarden", "password3", 3); + public static final User PRISONLEADER = new User("prisonleader", "password2", 2); + public static final User PRISONGUARD = new User("prisonguard", "password1", 1); + + //Manually Added + public static final User PRISONWARDEN2 = new User("prisonwarden2", "password3", 3); + public static final User PRISONLEADER2 = new User("prisonleader2", "password2", 2); + public static final User PRISONGUARD2 = new User("prisonguard2", "password1", 1); + + + /** + * Returns an {@code AddressBook} with all the typical persons. + */ + public static AddressBook getTypicalUserDatabase() { + AddressBook ab = new AddressBook(); + for (User user : getTypicalUsers()) { + try { + ab.addUser(user); + } catch (UserAlreadyExistsException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getTypicalUsers() { + return new ArrayList(Arrays.asList(PRISONWARDEN, PRISONLEADER, PRISONGUARD)); + } +} +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..d0fc09210b23 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,53 +3,37 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +PrisonBook was developed by the https://cs2103jan2018-t11-b2.github.io/docs/Team.html team. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Isaac +image::zacci.jpg[width="150", align="left"] +{empty}[http://github.com/zacci[github]] [<>] -Role: Project Advisor +Role: Team Lead + Testing IC +Responsibilities: Ensuring Code is tested thoroughly before merging. Maintaining Versioning. ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Sarah Goh Shi Ning +image::sarahgoh97.jpg[width="150", align="left"] +{empty}[http://github.com/sarahgoh97[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Documentation IC + +Responsibilities: Assigning tasks regarding documentation and checking pull requests regarding documenation -''' - -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] - -Role: Developer + -Responsibilities: Data ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] - -Role: Developer + -Responsibilities: Dev Ops + Threading - -''' +=== Chung-Yen (Philos) Tsai +image::philos22.jpg[width="250", align="left"] +{empty}[http://github.com/philos22[github]] [<>] -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +Role: Code QC/QA IC + +Responsibilities: Checking for proper coding style and syntax in pull requests -Role: Developer + -Responsibilities: UI ''' diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..ee2aa1a9d99f 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += PrisonBook - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,9 +10,9 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103JAN2018-T11-B2/main/ -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `CS2103-T11-B2`      Since: `Jan 2018`      Licence: `MIT` == Setting up @@ -140,7 +140,7 @@ The _Sequence Diagram_ below shows how the components interact for the scenario image::SDforDeletePerson.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `AddressBookChangedEvent` when PrisonBook data is changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. @@ -204,8 +204,10 @@ image::ModelClassDiagram.png[width="800"] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores PrisonBook data. +* stores the cells in the prison. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list changes. +* exposes an unmodifiable `ObservableList` that can be 'observed' in a map e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list changes. * does not depend on any of the other three components. [[Design-Storage]] @@ -219,7 +221,8 @@ image::StorageClassDiagram.png[width="800"] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the PrisonBook data in xml format and read it back. +* can save the user database in xml format and read it back. [[Design-Commons]] === Common classes @@ -304,7 +307,7 @@ The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, an [NOTE] If the `redoStack` is empty, then there are no other commands left to be redone, and an `Exception` will be thrown when popping the `redoStack`. -The user now decides to execute a new command, `clear`. As before, `clear` will be pushed into the `undoStack`. This time the `redoStack` is no longer empty. It will be purged as it no longer make sense to redo the `add n/David` command (this is the behavior that most modern desktop applications follow). +The user now decides to execute a new command, `edit`. As before, `edit` will be pushed into the `undoStack`. This time the `redoStack` is no longer empty. It will be purged as it no longer make sense to redo the `add n/David` command (this is the behavior that most modern desktop applications follow). image::UndoRedoNewCommand2StackDiagram.png[width="800"] @@ -329,7 +332,7 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire prison book. ** Pros: Easy to implement. ** Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. @@ -339,25 +342,320 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: Type of commands that can be undone/redone -* **Alternative 1 (current choice):** Only include commands that modifies the address book (`add`, `clear`, `edit`). -** Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost). -** Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing `undo`. +* **Alternative 1 (current choice):** Only include commands that modifies the address book (`add`, `edit`, `delete`). +** Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are lost). +** Cons: User might think that undo also applies when the list is modified (undoing filtering for example), only to realize that it does not do that, after executing `undo`. * **Alternative 2:** Include all commands. ** Pros: Might be more intuitive for the user. -** Cons: User have no way of skipping such commands if he or she just want to reset the state of the address * book and not the view. +** Cons: User have no way of skipping such commands if he or she just want to reset the state of the PrisonBook and not the view. **Additional Info:** See our discussion https://github.com/se-edu/addressbook-level4/issues/390#issuecomment-298936672[here]. ===== Aspect: Data structure to support the undo/redo commands * **Alternative 1 (current choice):** Use separate stack for undo and redo -** Pros: Easy to understand for new Computer Science student undergraduates to understand, who are likely to be * the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update * both `HistoryManager` and `UndoRedoStack`. +** Pros: Easy to understand for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `UndoRedoStack`. * **Alternative 2:** Use `HistoryManager` for undo/redo ** Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. +** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. // end::undoredo[] +//tag::loginsecurity[] +=== Login and Security Feature +==== Current Implementation + +The Login and Security feature in the PrisonBook is implemented using Users and Sessions. + +===== Sessions +Sessions are implemented with a `Session` class which resides inside `Model Manager`. It supports the logging in and logging out of a user as well as support restricted access for specific commands. For example read access (list command) vs write access (add command and edit command). + +image::ModelClassDiagramwSessionwCells.png[width="800"] + +As can be seen from the diagram, a Session is initialised upon the creation of the ModelManager. The session stores information such as the logged-in user's username, as well as the security level of the user. + +The accessibility of each command by the user is implemented on the logic level. Each command has a static `minimumSecurityLevel`. The usage of a command goes through the sequence diagram as follows (the example command being used here is the Delete command). + +image::SecurityLevelSequenceDiagram.png[width="800"] + +The `LogicManager` checks the `minSecurityLevel` of the command against the `securityLevel` of the current `Session` in the Model. If `the securityLevel` greater than or equal to the `minSecurityLevel`, the LogicManager will then call the `execute()` method on the Command. + +===== Design Considerations + +====== Aspect: Implementation for checking of security level for command access +* **Alternative 1 (current choice):** Implemented by the `LogicManager` +** Pros: Decreases coupling between `Command` and `Session`. This allows for flexibility in future implementation changes. +** Cons: Sequence during execution needs to go back and forth with the `LogicManager` +* **Alternative 2:** Implement at the `Command` level +** Pros: Easy to understand, the sequence during execution keeps moving on to the next step instead of going back and forth with the LogicManager. +** Cons: Commands are also responsible for checking if they should be run. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. + + +===== Users +Users are implemented with a `User` class, as well as a `UniqueUserMap` class which also reside in the `Model Manager`. + +The `UniqueUserMap` is implemented using a Hashmap which maps `usernames` as Keys and `User` as Values. Additionally, there is an internal list implemented via an `ObservableList` of all the Users in the Hashmap. + +To save the database of the users, a new type is added to the storage so that it is able to store the Users in the storage file. + +image::StorageClassDiagramwUserwCells.png[width="800"] + +Upon initialization of the AddressBook, the AddressBook will read the user data from the storage file and create the internal list of Users. The list is then iterated through and mapped to the HashMap. + +When adding and deleting users, both the Hashmap and the internal list are updated so as to reflect accurately in the model and stored correctly in the storage. + +Currently, the user data, including passwords are stored in plaintext, which may pose a security problem. For future implementations, we will include hashing of the passwords before storage to as to increase the security of the application. + + +===== Design Considerations + +====== Aspect: Data Structures for UniqueUserMap +* **Alternative 1 (current choice):** Hashmap + ObservableList +** Pros: Easy to access users, can immediately call up the correct `User` object by passing in the username key. Whereas the ObservableList is used to complement the storage of the user data, where it is necessary to iterate through all the Users in order to store in the storage .xml file. +** Cons: Duplicate information stored in two different data structures. +* **Alternative 2:** ObservableList only +** Pros: No duplication of Users. Storing is also easy as it uses the existing AddressBook storage framework which works with ObservableLists. +** Cons: Everytime we want to access a specific User, we have to iterate through the entire list of Users in order to do so. +* **Alternative 3:** HashMap only +** Pros: Fast access of users in O(1) time +** Cons: Unable to be stored efficiently with the current method of storing which works with XML file. + +====== Aspect: Storage location of User Data +* **Alternative 1 (current choice):** Stored in the same XML File with the AddressBook data +** Pros: Easy to implement with current method of saving, where the entire AddressBook is upon an Address Book Changed Event. There is no need to maintain an additional stack for a User Database for UndoRedo. +** Cons: User data should theoretically be separated from AddressBook data (persons, cells, tags). +* **Alternative 2:** Store in a separate XML File +** Pros: Separation of storage for two different aspects. If User data file is corrupted, it does not affect the AddressBook data and vice versa. +** Cons: Very difficult to implement with the current method of saving. Furthermore, there needs to be addtional stacks implemented to keep track of the past states of User data for the UndoRedo functions. + + +//end::loginsecurity[] + +// tag::cellmap[] +=== Prison Cell System +==== Current Implementation + +In a prison, it is necessary to have cells where prisoners stay in. +It is important to keep track of the prisoners in the cell and whether the `Cell` is full. + +This cell system is most clearly represented using a 2-D array. +Hence, this was used as the basis of the map system of the prison. +However, to prevent accidental changes, a read-only version of the cells are returned in an `ObservableList`. +Since this is part of the `Model` component of the PrisonBook, the `Model` component has been updated accordingly as shown below. +At the bottom of the `Model` class diagram, it has been updated to show how the cell system is linked to the other components. + +image::ModelClassDiagramwSessionwCells.png[Width="550"] + +To save and load the data of the cell system, storage also played an important component to ensure no data is lost when closing the app. +Below is a class diagram of `Storage` to show how the cells are saved. A new type is added to the storage so that it knows how to save and load data for cells. + +image::StorageClassDiagramwUserwCells.png[Width="550"] + +This map is shown on the user interface on the right side area, shown in the picture below. +Since the app is still being tested, the `map` command will also show the actual cells in the result display box. +This basic map shows the cell system and helps keep track of what is happening in the cells. + +image::MapPanel.png[width="550"] + +This cell system helps users visualise the locations of the prisoners more easily. Not only that, prisoners can also be added and removed from them. +These changes can be viewed in the bigger map which displays the number of people in individual cells below each cell address. + +Assuming the user has access to the functions to add prisoners into prison cells, they can use the `addcell` command to do so. +Below is the sequence diagram of the logic and model components. + +image::AddCellSequenceDiagram.PNG[width="550"] +image::AddCellModelSequenceDiagram.PNG[width="550"] + +Besides the cell map system being updated, we will also update the addresses of these prisoners added to the prison, since they now live in the prison, rather than in their own homes. + +Prisoners can also be removed from their cells using the `deletecell` command when they are being transferred to another cell, +or if they can leave prison and go back home to their loved ones. + +To aid in adding prisoners to cells and deleting them from the cells, a `listcell` command has been implemented. +With this command, users can see who are in a specified cell. This helps users know whether the cell is full and +if some of these prisoners should not be added to the same cell. + +For future implementations, we can also allow only the prison wardens/owners to redefine the maximum size of the prison as their prisons +may grow or shrink in the future. In this application, we will only show an example with 15 cells with a maximum of 2 prisoners per cell. +These values can be easily changed and we will customise it based on your needs. The naming of the cells can also be adjusted accordingly. + +==== Design Considerations +===== Aspect: Data Structure of Cells in CellMap + +* **Alternative 1 (current choice):** Use a 2-D array +** Pros: Easy to visualise the system. +** Cons: Harder to do more functions that the Java API has provided. Does not fit in with the other models of Person and Tag and their internalList. +It also makes it harder to implement undo and redo functions for actions below. +* **Alternative 2:** Use a 2-level ArrayList +** Pros: Can use Java functions meant for Lists, can have different number of units for each block +** Cons: Not uniform number of units for each block will be harder to take into consideration when using the individual cells. +* **Alternative 3:** Use an ObservableList +** Pros: Easier to save state of cells when undoing and redoing. +** Cons: Harder to visualise where each row of cells/prisons end. + +===== Aspect: Implementation of ShowCellsCommand (`map`) + +* **Alternative 1 (current choice):** Keep the map always on display. +** Pros: Easier to see when adding prisoners to cells and removing prisoners from cells. +** Cons: Anyone walking by from behind can see the information easily and compromises security. +* **Alternative 2:** Create a new Command to show the map. +** Pros: Easy to implement, similar to listing. Extending the current list of commands. Only when function is called can the map be seen. +** Cons: Easy to forget which cell in the map can add prisoners but the map is not permanently there. + +===== Aspect: Implementation of AddCellCommand (`addcell`) + +* **Alternative 1 (current choice):** Prisoners are added into cells in a cellMap and their addresses will be updated. +** Pros: Cells are always there and can hold prisoners. Since persons in the PrisonBook can be either prisoners or guards, not all persons may have a cell. +Easier to delete prisoners from cells and update their addresses accordingly when they are released from prison. +** Cons: Need to have extra implementation to ensure undo and redo commands work properly when deleting a prisoner who is imprisoned as well +as to remove them or add them to cells. +* **Alternative 2:** Not have a cell map but to add cells to prisoners. +** Pros: Might be more intuitive for the user, to check a prisoner's cell location. +** Cons: Harder to manage a system of cells. Easy to accidentally overcrowd a cell. Not all persons in PrisonBook are prisoners. +Some of them are guards. +* **Alternative 3:** Automatically assigning cells to prisoners +** Pros: Removes the burden of choosing a cell for user. Fewer commands required as prisoners are automatically added. +** Cons: More complex in automatically choosing cells for prisoners. User might want to have control on which prisoners should be in which cell. + +===== Aspect: Implementation of ListCellCommand (`listcell`) + +* **Alternative 1 (current choice):** Users to give cell address to show prisoners in them. +** Pros: Easy to implement, similar in structure to `Find` Command and its listing of persons. +** Cons: Need to find address of cell first that is required. +* **Alternative 2:** Clicking on a cell to show prisoners in them +** Pros: Might be more intuitive for the user to click on a cell. +** Cons: Not ideal for users who prefer CLI. + + +===== Aspect: Implementation of DeleteCellCommand (`deletecell`) + +* **Alternative 1 (current choice):** Removes a prisoner from a cell by choosing the prisoner and updates his/her address back accordingly. +** Pros: Faster way to remove since only the index of the prisoner is required. User does not have to figure out which cell the prisoner he or she is in. +** Cons: Since cells have a size limit of 2, it is easier to delete a person after selecting a cell. +* **Alternative 2:** Choosing a cell and the person to delete from the cell after seeing the list. +** Pros: Easier to choose a cell when there are many people in the PrisonBook and is difficult to see who the prisoners are. +** Cons: Requires more steps to delete a person from the cell, by having to first list then delete. +Current implementation can work in a similar way that makes it easier to delete a prisoner from a cell. +// end::cellmap[] + +//tag::findbytag[] +=== Find by Tag Command +==== Current Implementation +When the Find command is called, FindCommandParser will look for the tag and/or name prefixes (t/ and n/ respectively). +The arguments following each prefix will be made into a list. A mapping is then generated between prefixes/values and new predicate instances are created. + +image::FindCommand_sequence.png[Width="550"] + +==== Design Considerations +===== Aspect: Find Command +* **Alternative 1 (current choice):** Add on to the current Find command +** Pros: Allows filtering by names and/or tags +** Cons: Requires user to include prefixes in the command to differentiate between name and tag arguments +* **Alternative 2:** Create new command for finding tags +** Pros: More convenient if user only needs to filter by tags or filter by names separately, no need for prefixes +** Cons: Cannot filter by names and tags in a single command + +===== Aspect: Argument Deliminator +* **Alternative 1 (current choice):** Only one prefix with all arguments coming after, delimited by space +** Pros: Relatively convenient for user when they are inputting multiple arguments +** Cons: Easier for users to mistakenly merge two arguments into one when forgetting a space +* **Alternative 2:** One prefix required for each argument +** Pros: Allows user to order name and tag arguments in any order - the tag arguments and name arguments do not + have to be grouped together after a single prefix +** Cons: May become tedious and redundant when inputting multiple arguments, more prone to typos and human error + +//end::findbytag[] + +//tag::scheduling[] +=== Scheduling with Google Calendar +==== Current Implementation +The PrisonBook calendar commands will instantiate a calendar class to interact with the Google Calendar API. +The initial step is to obtain valid credentials authorized from the API side to access its resources, then +the calendar class will send HTTPS requests to the server, whether it is calling the listEvents, addEvents or delEvents API command. + +Calendar Commands that are supported are: + +* listing upcoming events + +* adding an event + +* removing an event + +image::CalendarCommands_classDiagrams.PNG[Width="350"] + +===== Aspect: Choosing Number of Accounts to Work With + + * **Alternative 1 (current choice):** Using one Google account for all prison staff + ** Pros: Very simple to administrate and fits the needs of the project scope. + ** Cons: No personalization for calendars of Warden/Guards. + * **Alternative 2:** Use one Google account for Warden and one for Guards + ** Pros: Two accounts is still relatively simple to maintain, allows for some separation of events-scheduling. + ** Cons: All guards still share the same schedule, so they cannot have personal schedules. + * **Alternative 3:** Use one Google account for Warden and one for Guards + ** Pros: Two accounts is still relatively simple to maintain, allows for some separation of events-scheduling. + ** Cons: All guards still share the same schedule, so they cannot have personal schedules. + * **Alternative 4:** Use one Google account for Warden and one for Guards + ** Pros: All prison staff have their own calendar for private individual events. + ** Cons: Hard to maintain because we have to make sure all staff have the master prison schedule of the inmates' activities non-overlapping with personal events. There will also be a lot of calendars to keep track of, depending on how many guards are hired. + +===== Aspect: Choosing Number of Calendars per Person + +* **Alternative 1 (current choice):** Only one calendar per person +** Pros: Simple to implement, administrate and satisfies users' daily tasks +** Cons: Events cannot overlap, limited to fit events in separate rigid time slots. +* **Alternative 2:** Multiple calendars per person +** Pros: Users can track multiple events happening at the same time. Different calendars can also be used to organize and group logically-related events. +** Cons: Complicated on the administration side and oversteps the scope of this enhancement. Not really looking to replace personal Google Calendars, this is just to allow prison staff to work with prison schedules. + +=== View Calendar Feature +==== Current Implementation +The View Schedule feature can be accessed via entering the command `cal`. +It allows guards/wardens to see upcoming events and their start times. + +===== Aspect: Event Listing UI + +* **Alternative 1 (current choice):** List events as a list format +** Pros: Simple to implement, shows more details than calendar UI +** Cons: Not as visually pleasing, users cannot use mouse to edit calendar intuitively +* **Alternative 2:** List events in the calendar UI display +** Pros: More intuitive with an actual interactive calendar, allows users to click and drag using mouse +** Cons: No longer command line based, takes up more space than console + +=== Add Event to Calendar Feature +==== Current Implementation +The Add Event feature can be accessed via entering the command `calAdd event/EVENTNAME loc/LOCATION start/STARTDATETIME end/ENDDATETIME`. +It allows guards/wardens to add an event, specifying the event name, location, starting and ending times. + +image::CalendarAddCommand_sequence.png[Width="550"] + +===== Aspect: Relevancy of Event Attendee List + +* **Alternative 1 (current choice):** Excluding Attendee List +** Pros: Less content for user to input when creating events, does not restrict any functionality in a prison setting +** Cons: Slightly less customization when adding events +* **Alternative 2:** Including Attendee List +** Pros: Can invite specific individuals for smaller scale events +** Cons: Typing each user can become tedious and extra work for the users, also not suitable for prison setting because prisoners are not "invited" for meals or to get back into their cells + +=== Delete Event from Calendar Feature +==== Current Implementation +The Delete Event feature can be accessed via entering the command `calDel EVENTNUMBER`. +It allows guards/wardens to remove events from the upcoming events list. +Since each event has its own eventID which is a random alphanumeric String, they are mapped to Integers through an ArrayList for easier selection by the users. + +image::CalendarDeleteCommand_sequence.png[Width="550"] + +===== Aspect: Specification of Which Event to Delete + +* **Alternative 1 (current choice):** Enumerating the event list and storing it in an array +** Pros: Easy interface for users, they just pick a simple integer from the list +** Cons: Have to map these enumerations to the actual IDs +* **Alternative 2:** Displaying the actual event IDs from the API +** Pros: No additional parsing or eventID storing is needed +** Cons: Users have to type the event IDs manually, which are seemingly random alphanumeric strings (prone to human error) +* **Alternative 3:** No change to list +** Pros: No additional clutter to existing list +** Cons: Users have to specify event name and start/end times, which is quite tedious + + +//end::scheduling[] + // tag::dataencryption[] === [Proposed] Data Encryption @@ -493,367 +791,227 @@ Here are the steps to create a new release. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +A project often depends on third-party libraries. For example, PrisonBook depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) [[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started - -Suggested path for new programmers: - -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. - -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. - -[[GetStartedProgramming-EachComponent]] -=== Improving each component - -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). - -[discrete] -==== `Logic` component - -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. - -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. - -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** - -[discrete] -==== `Model` component - -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. - -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -*** The current codebase has a flaw in tags management. Tags no longer in use by anyone may still exist on the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. This may cause some tests to fail. See issue https://github.com/se-edu/addressbook-level4/issues/753[`#753`] for more information about this flaw. -*** The solution PR has a temporary fix for the flaw mentioned above in its first commit. -**** - -[discrete] -==== `Ui` component - -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. - -[TIP] -Do take a look at <> before attempting to modify the `UI` component. - -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** - -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** - -[discrete] -==== `Storage` component - -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. - -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. - -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** - -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` - -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. - -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. - -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` - -Examples: - -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. - -==== Step-by-step Instructions - -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. - -**Main:** - -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/UndoableCommand.java[`UndoableCommand`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. - -**Tests:** - -. Add `RemarkCommandTest` that tests that `executeUndoableCommand()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. - -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` - -**Main:** - -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. - -**Tests:** - -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. - -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. - -**Main:** - -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. - -**Tests:** +== Product Scope -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +*Target user profile*: -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +* works at a prison with around 1000 inmates +* has a need to keep track of prisoner locations and details +* has a need to organize and direct guards efficiently +* has a need to manage a significant number of contacts +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -**Main:** +*Value proposition*: manage contacts faster than a typical mouse/GUI driven app -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +*Feature Contributions*: + +// tag::FeatureContributionPhilos[] + +* Chung-Yen (Philos) Tsai (philos22) + 1. Major enhancement: + + Scheduling: Keeps track of schedule of prisoners and guards. + 2. Minor enhancement: + + Find by tag: This function will allow admins to filter user list by tags. Tags may include classification of Prisoner vs. Guards, Prisoner cell blocks and cell numbers, Guards' current teams and assignments, etc... + +// end::FeatureContributionPhilos[] +// tag::FeatureContributionIsaac[] + +* Isaac Gideon Tan (zacci) + 1. Major enhancement: + + Login and Authorization Levels: Enable creation of account for guards, logging in, and authorization levels for commands that may compromise the security of the Prison data. + 2. Minor enhancement: + + Additional Attribute to store 'Role' information. To identify the person as a Prisoner or Guard + +// end::FeatureContributionIsaac[] +// tag::FeatureContributionSarah[] + +* Sarah Goh Shi Ning (sarahgoh97) + 1. Major enhancement: + + Creating a cell system of all the prison cells in the prison. + This helps users add and remove prisoners to their prison and prevent users from overcrowding their prison. + It can also help users keep track of locations of the cells of each prisoner. + Users can also add and remove prisoners more easily by first listing people in each cell to decide where to add or remove them. + 2. Minor enhancement: + + Show map of the cell system on the UI. + Also implemented the UI to show the list of persons and the map when first logged in + and to hide it when logging out and when the application is first opened. +// end::FeatureContributionSarah[] + +// tag::UserStories[] +[appendix] +== User Stories -**Tests:** +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -. Add test for `Remark`, to test the `Remark#equals()` method. +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |administrator |group guards together |form teams to carry out various tasks: patrol rotations, perimeter control, cell guards -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +|`* * *` |guard |have a track record of each prisoner’s past offences + Type 1 – Injuring guard + Type 2 – Infighting with other inmates + Type 3 – Vandalism + |use the appropriate level of precaution when dealing with unruly individuals -**Main:** +|`* * *` |guard |keep track of each prisoner’s holding cells |ensure that there are two prisoners per cell and that certain bad prisoner combinations are avoided -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +|`* * *` |prison warden |assign ranks to each guard |enforce access rights to view/edit prisoner and officer data -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +|`* * *` |guard |check the length of imprisonment of prisoners I am in charge of |know when they are leaving -**Main:** +|`* * *` |guard |add prisoners to prison cells |the prisoners have a prison cell to stay in -. Add a new Xml field for `Remark`. +|`* * *` |guard |check number of prison cells available |decide whether to take more prisoners in -**Tests:** +|`* * *` |guard |delete a prisoner |add more prisoners to prison -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +|`* * *` |guard |check when I am on duty |see when I have to come to work -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +|`* * *` |guard |check who are in my team |coordinate with my team -**Tests:** +|`* * *` |prison warden |add police officers |give access to new police officers -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +|`* *` |guard |assign prisoners to different recess blocks |separate those that start fights or cause trouble when together -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +|`* *` |prison warden |shuffle patrol guards around teams every day |each team is made up of different members every day, prisoners will acquaint themselves with specific guards -**Main:** +|`* *` |prison warden |divide cells into blocks (1, 2, 3) and name each cell numerically (1-1, 1-2, etc…) |refer to each cell easily when assigning prisoners to them -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +|`* *` |guard |check whether there are visitor appointments |inform prisoners and bring them to see their visitors -**Tests:** +|`* *` |guard |edit details of prisoners |update information of prisoners -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +|`* *` |guard |transfer people to other prisons |have more empty cells for more prisoners -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +|`*` |prisoner |know my release date |look forward to the day I can see my family/friends again -**Main:** +|`*` |guard |see my assigned prisoners’ social networks: friends, enemies, family, etc… |use this as reference when dealing with inmates -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +|`*` |guard |assign prisoners to solitary confinement |punish those that have continuously committed serious offences -**Tests:** +|`*` |cook |check dietary requirements of all people in prison |cook sufficient food -. Update `RemarkCommandTest` to test that the `execute()` logic works. +|`*` |counselor |check what prisoners' crimes |understand my patients better -==== Full Solution +|======================================================================= -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +// end::UserStories[] +// tag::UseCases[] [appendix] -== Product Scope +== Use Cases -*Target user profile*: +[discrete] +=== Use case: Add an event on the calendar. +Actor: Guard (Guard) -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +*MSS* -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +1. Guard requests to add event to calendar. +2. PrisonBook adds event to calendar. ++ +Use case ends. -[appendix] -== User Stories +*Extensions* -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +[none] +* 1a. PrisonBook detects that guard's security level is not high enough. ++ +[none] +** 1a1. PrisonBook shows an error message regarding security level. ++ +Use case ends. -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +* 1b. The format of adding event to calendar is wrong. ++ +[none] +** 1b1. PrisonBook shows an error message. ++ +Use case ends. -|`* * *` |user |add a new person | +[discrete] +=== Use case: Add prisoner to a cell +Actor: Guard -|`* * *` |user |delete a person |remove entries that I no longer need +*MSS* -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +1. PrisonBook shows a list of available cells. +2. Guard selects a prisoner and cell to add. +3. PrisonBook updates prisoner status and cell status. ++ +Use case ends. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +*Extensions* -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +[none] +* 1a. Guard does not have sufficient access to view cells. ++ +Use case ends. -_{More to be added}_ +* 2a. PrisonBook detects that prisoner is already added to a cell. ++ +[none] +** 2a1. PrisonBook sends an error message regarding prisoner already imprisoned. ++ +Use case resumes at step 1. -[appendix] -== Use Cases +* 3a. PrisonBook detects that the cell is already full. ++ +[none] +** 3a1. PrisonBook sends an error message regarding adding prisoner to a full cell. ++ +Use case resumes at step 1. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +* 4a. PrisonBook detects that address of cell is invalid. ++ +[none] +** 4a1. PrisonBook sends an error message regarding invalid cell address. ++ +Use case resumes at step 1. [discrete] -=== Use case: Delete person +=== Use case: Get the list of prisoners in a certain cell +Actor: Guard *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Guard requests a list of prisoners in a specified cell +2. PrisonBook shows the list of prisoners in specified cell + Use case ends. *Extensions* [none] -* 2a. The list is empty. +* 2a. Guard does not have access rights to view the details of the requested cell + Use case ends. -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. - -_{More to be added}_ +// end::UseCases[] +// tag::NFR[] [appendix] == Non Functional Requirements -. Should work on any <> as long as it has Java `1.8.0_60` or higher installed. +. Should work on any mainstream OS as long as it has Java `1.8.0_60` or higher installed. . Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. Should work for 32-bit and 64-bit environments +. System should respond within 2 seconds +. Should have different levels of access for users of different ranks. +. Should have a backup copy at all times in case a user accidentally deletes any information. -_{More to be added}_ +// end::NFR[] [appendix] == Glossary @@ -861,25 +1019,19 @@ _{More to be added}_ [[mainstream-os]] Mainstream OS:: Windows, Linux, Unix, OS-X -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +[[warden]] Prison warden:: +Owner of the prison, who has the highest level of access available. -[appendix] -== Product Survey - -*Product Name* - -Author: ... - -Pros: +[[guard]] Guard:: +A person working in the prison, to watch over prisoners and ensure order. -* ... -* ... +[[prisoner]] Prisoner:: +A person has been sent to prison for a crime. -Cons: - -* ... -* ... +[[cell]] Cell:: +A place where prisoners stay and live in when they are imprisoned. +Each cell has a maximum number of people who can live in them to prevent overcrowding. +Their addresses are fixed and cannot be changed unless the application is customised. [appendix] == Instructions for Manual Testing @@ -895,34 +1047,184 @@ These instructions only provide a starting point for testers to work on; testers .. Download the jar file and copy into an empty folder .. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. - + Expected: Shows the GUI with a set of sample prisoners and guards. The window size may not be optimum. +.. Please do not open the jar file through a terminal. . Saving window preferences .. Resize the window to an optimum size. Move the window to a different location. Close the window. .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ - -=== Deleting a person +// tag::isaactest[] + +=== Major Feature: Users, Sessions and Security Levels +==== Logging in and Logging out +. For security purposes, users of PrisonBook need to login with their user details in order to to view the information on the PrisonBook. They can also logout after finishing their session, so that the next person will need to login again. + +.. Prerequisites: You must not be logged in. (By default, you are not logged in when you first open the application.) + + Test case: Type `login user/prisonguard pw/password1` + + Expected: PrisonBook will display `Login Success` + +.. Prerequisites: You are logged in. + + Test case: Type `status` to show your current session's details + + Expected: `Username: prisonguard Security Level: 1` + +.. Prerequisites: You are logged in. + + Test case: Type `logout` to logout + + Expected: PrisonBook will display `Successfully logged out`. Furthermore the person details in the left panel will be cleared, and the cell details in the right panel will reflect that you have insufficient authority to view the information. + +==== Commands have restricted access +. A minimum Security Level is required to use and access certain commands. + +Refer to Section 5: Command Summary for the detailed list of commands and their respective minimum security level ranging from 0 to 3. + +.. Test case: Type `logout` to ensure that you are logged out. (By default you are not logged in when you first open the application) + + Type `list` + + Expected: PrisonBook will show `Your security level is insufficient to access this command.` + +.. Test case: Login using `login user/prisonleader pw/password2` (This user has Security Level of 2) + + Type `list` + + Expected: PrisonBook will say `Listed all persons` and show the full list of persons on the left column of the UI. + + You can test the other commands with the following user details: + ... `login user/prisonwarden pw/password3` Security Level 3. + ... `login user/prisonleader pw/password2` Security Level 2. + ... `login user/prisonguard pw/password1` Security Level 1. + +==== Adding a new user +. You can add new users to the PrisonBook when you want to give access to new people. + +.. Prerequisites: Have sufficient security level[3] + + Test case: `adduser user/prisonguard99 pw/password2 sl/1` + + Expected: `New user prisonguard99 added to PrisonBook` + + You can then proceed to logout and attempt to login with the new user `prisonguard99`. + +.. Prerequisites: Have sufficient security level[3] + + Test case: `adduser user/prisonwarden pw/password3 sl/3` + + Expected: `prisonwarden is already a user in PrisonBook` + + +==== Deleting a user +. You can delete users from the PrisonBook + +.. Prerequisites: You must be logged in with a higher security level than the user you want to delete. (Users with security level 3 can delete anyone, including other security level 3 users.) + + Test case: Login with `login user/prisonleader pw/password2`, then type `deleteuser user/prisonguard99` (Which was added above in F.2.3) + + Expected: `User has been successfully deleted`. You can logout and attempt to login to `prisonguard99` but you will not be able to successfully login as the user has been deleted. + +.. Prerequisites: You must be logged in with a minimum security level of 2. + + Test case: `delete user/nonexistentuser` + Expected: `The username does not exist` + +=== Minor Feature: Role Attribute added to Persons +. Allow user to indicate to the PrisonBook if added person is a Guard or a Prisoner. + +.. Prerequisites: Have sufficient security level[2] +.. Test case: `add n/Sample Prisoner a/Chua Chu Kang p/91234567 e/sp@example.com r/p` + + Expected: New person to show up on the UI with the name Sample Prisoner and shown to be a Prisoner on the person card. +.. Test case: `add n/Sample Guard a/Chua Chu Kang p/91234567 e/sg@example.com r/g` + + Expected: New person to show up on the UI with the name Sample Guard and shown to be a Guard on the person card. + +// end::isaactest[] + +// tag::sarahtest[] +=== Minor Feature: Showing prisoners in the UI +. Map panel in UI showing the map of cells in the system. + +.. Prerequisites: Have sufficient security level[1] +.. Test case: Starting up the app and before logging in + + Expected: No details regarding cells shown on the map. Will show 0 persons in PrisonBook. + Map shows that user has insufficient access to view information. +.. Test case: Using features below while logged in + + Expected: UI matches what `map` command shows on the display. Will show number of persons in PrisonBook. + +=== Major Feature: Managing prisoners in a prison +==== Adding a prisoner to a cell + +. Adding a prisoner to a cell when all prisoners are listed and has sufficient security level [2]. + +.. Prerequisites: List all persons using the `list` command. Multiple persons in the list, from initial state of program. +.. Test case: `addcell 5 1-5` (person at index is a prisoner and is not in a cell and cell is not full) + + Expected: Fifth person is added to the cell with address 1-5. + Address of the prisoner updated in the person card and message of prisoner added to cell is displayed. + The map on the right side of the UI will also update correspondingly by adding to the number corresponding to cell address 1-5. + Timestamp in the status bar is updated. +.. Test case: `addcell 0 1-4` + + Expected: No prisoner is added to a cell because of the index. + Error message displayed regarding format. The map and status bar will not be updated. +.. Other incorrect add cell commands to try: `ac`, `addcell 2 1=1` + + Expected: No change to UI and displays error message. + +==== Deleting prisoner from a cell + +. Deleting a prisoner from a cell when all prisoners are listed and has sufficient security level [2]. + +.. Prerequisites: List all persons using the `list` command. Multiple persons in the list, from initial state of program. +.. Test case: `deletecell 1` + + Expected: Prisoner is deleted from his/her cell and their original cell will be updated. + Message regarding prisoner being released will be displayed. + The map on the right will be updated, as well as the prisoner's address back to their old address. +.. Test case: `deletecell 0` + + Expected: No change to map and to the list of persons. + Message regarding error will be shown for correct format of command. +.. Other incorrect delete cell commands to try: `dc`, `dc 0` + + Expected: Similar to previous. -. Deleting a person while all persons are listed +==== Listing prisoners in a cell -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + +. Listing a prisoner from a cell when here is sufficient security level [1]. +.. Test case: `listcell 1-1` + + Expected: List of prisoners in cell address 1-1 will be updated and shown. +.. Test case: `listcell 10-10` + + Expected: There is no change in the UI except the error message regarding an invalid cell. +.. Other incorrect list cell commands to try: `lc`, `lc 1` + Expected: Similar to previous. -_{ more test cases ... }_ +// end::sarahtest[] + +// tag::philostest[] +=== Major Feature: Scheduling with Google Calendar + +==== Listing 10 upcoming events +Allows guards and prison warden to view the upcoming 10 events scheduled in Google Calendar. + +.. Prerequisites: Have sufficient security level[1] + + Test case: Type `cal` or `calendar`, if this is your first time connecting to Google Calendar, you will need to log into the PrisonBook Calendar account: + A new browser window will open, do NOT close the window before you have logged in! + Use email: prisonbooksystem@gmail.com + Use password: prisonbook2018 + Expected: 10 upcoming events will be listed, each showing an assigned Event Number, Event Name, Time range and Location. + + +==== Adding an event +. You can add new events to the PrisonBook Calendar. + +.. Prerequisites: Have sufficient security level[3] + + Test case: `calA event/Showing this works loc/NUS start/2018-04-28 12:00:00 end/2018-04-28 13:00:00` + + Expected: `A new event will have been added to the calendar on April 28 at 12 o'clock.` + +.. Prerequisites: Have sufficient security level[3] + + Test case: `calA event/Showing this works loc/NUS start/2018-04-28 12:00:00 end/2017-04-28 13:00:00` + + Expected: `The API will throw an error because the end date is earlier than the start date. It will tell you the time range is empty.` + + +==== Deleting an event +. You can delete events from the listed Calendar events + +.. Prerequisites: Have sufficient security level[3] + + Test case: `calD 2` + + Expected: The second event (Event 2) will have been deleted next time you call the `cal` list event command. + +.. Prerequisites: Have sufficient security level[3] + + Test case: `calD 11` + Expected: Since only 10 events are listed, this command will not affect anything as there is no EventID linked to 11. + +=== Minor Feature: Find by Tags +. Allow user to find persons by tags as well as names + +.. Prerequisites: Have sufficient security level[1] +.. Test case: `find n/Alex t/ChickenAllergy` + + Expected: 1 person listed: Alex Yeoh who is allergic to chickens. +.. Test case: `find t/Vegetarian` + + Expected: 1 person listed: Bernice Yu who is a Vegetarian. +// end::philostest[] === Saving data . Dealing with missing/corrupted data files -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ - -_{ more test cases ... }_ +.. Should the data file be missing, a sample prison book will be provided, with the 3 users mentioned in the user guide: +prisonwarden, prisonleader and prisonguard diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..bf24ccfcd410 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += PrisonBook - User Guide :toc: :toc-title: :toc-placement: preamble @@ -11,13 +11,13 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103JAN2018-T11-B2/main/ -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `CS2103-T11-B2` Since: `Jan 2018` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +PrisonBook is for those who *prefer to use a desktop app for managing their prison and accessing the database of prisoners*. More importantly, PrisonBook is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, PrisonBook can help you manage your prison faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! == Quick Start @@ -27,9 +27,9 @@ AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for mana Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. +. Download the latest `prisonbook.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your PrisonBook. +. Double-click the file to start the app. The GUI should appear in a few seconds. Do NOT use command prompt to open the app. + image::Ui.png[width="790"] + @@ -37,9 +37,9 @@ image::Ui.png[width="790"] e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* *`list`* : lists all prisoners and guards +* **`add`** `n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to PrisonBook. +* **`delete`** `3` : deletes the 3rd person shown in the current list * *`exit`* : exits the app . Refer to <> for details of each command. @@ -54,33 +54,145 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. * Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. * Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. * Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* For security reasons, users need to log in before having access to certain commands and will only see an empty PrisonBook. Users have a `Security Level` assigned to them. All commands have a minimum `Security Level` required before the user is able to access it. +** Security Level 0: Most basic commands such as Login and Status. +** Security Level 1: Most commands that only require read access. +** Security Level 2: Most commands that require write access. +** Security Level 3: Complete access to all commands. +* The minimum `Security Level` to access each command is listed beside each command below in square brackets []. ==== -=== Viewing help : `help` +=== Viewing help : `help` [0] Format: `help` -=== Adding a person: `add` +// tag::login[] +=== Logging in: `login` [0] (v1.2) -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Logs in to your account on the PrisonBook + +Format: `login user/USER_NAME pw/PASSWORD` + +Examples: + +* `login user/prisonwarden pw/password3` + +FOR USER ACCEPTANCE TESTING PURPOSES: +The following user accounts have been pre-added + +* Username: prisonguard | Password: password1 | Security Level: 1 +* Username: prisonleader | Password: password2 | Security Level: 2 +* Username: prisonwarden | Password: password3 | Security Level: 3 +// end::login[] + +// tag::logout[] +=== Logging out: `logout` [0] (v1.3) + +Logs out of your account + +After logging out, you will not be able to undo actions that were completed before logging out. + +Format: `logout` + +// end::logout[] + +// tag::status[] +=== Check Log-in Status: `status` [0] (v1.4) + +Checks your current log-in status + +Format: `status` +// end::status[] + +// tag::adduser[] +=== Add new user: `adduser` [3] (v1.4) + +Adds new user to have access to the PrisonBook + +Format: `adduser user/NEW_USERNAME pw/NEW_PASSWORD sl/SECURITY_LEVEL` + +Higher security levels allow users to access a greater range of commands: +Security levels for new users must be from levels 1 to 3, refer to the list below for the differences in security levels. + +* Security Level 1: Most commands that only require read access. +* Security Level 2: Most commands that require write access. +* Security Level 3: Complete access to all commands. + +Examples: + +* `adduser user/newuser pw/newpassword sl/1` +// end::adduser[] + +// tag::deleteuser[] +=== Delete a user: `deleteuser` [2] (v1.5rc) + +Deletes a user from PrisonBook + +Format: `deleteuser user/USERNAME` + +You can only delete users which have a lower security level than yourself. + +Only a user with Security Level 3 is able to delete any user that is of same or lower security level. + +Examples: + +* `deleteuser user/prisonguard` +// end::deleteuser[] + +=== Adding a person: `add or a` [2] + +Adds a person to PrisonBook + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS r/ROLE [t/TAG]...` + +[TIP] +A person can have any number of tags (including 0). + +A person can only be a prisoner (ROLE = p) or a guard (ROLE = g). + +Examples: + +* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 r/g` +* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 r/p t/criminal` + +// tag::addcell[] + +=== Adding a prisoner to a cell: `addcell or ac` [2] (v1.3) + +Adds a person to one of the cells in the prison. + +Format: `addcell INDEX CELL` [TIP] -A person can have any number of tags (including 0) +Person added must be a prisoner and not already in a cell within the prison. + +The cell must be in the map and not full. + +The maximum number of people in a cell is 2 people. Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `addcell 1 1-1` +* `ac 2 2-1` +// end::addcell[] -=== Listing all persons : `list` +=== Listing all persons : `list or l` [1] -Shows a list of all persons in the address book. + +Shows a list of all persons in PrisonBook. + Format: `list` -=== Editing a person : `edit` +// tag::listcell[] +=== Listing prisoners in a cell : `listcell or lc` [1] (v1.5rc) + +Shows a list of all persons in a prison cell in PrisonBook. + +Format: `listcell CELL` + +Examples: + +* `listcell 1-1` +* `lc 3-3` +// end::listcell[] -Edits an existing person in the address book. + +// tag::map[] +=== Showing cells in the map: `map or m` [1] (v1.1) + +Shows a table of prison cells like a map in the command display (used for debugging and checking UI). + +Format: `map` +// end::map[] + +=== Editing a person : `edit or e` [2] + +Edits an existing person in PrisonBook. + Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` **** @@ -89,6 +201,8 @@ Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` * Existing values will be updated to the input values. * When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. * You can remove all the person's tags by typing `t/` without specifying any tags after it. +* Editing an imprisoned prisoner's address will only change his old address, not address of his/her cell. +* You cannot edit a person's role. **** Examples: @@ -98,29 +212,67 @@ Edits the phone number and email address of the 1st person to be `91234567` and * `edit 2 n/Betsy Crower t/` + Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -=== Locating persons by name: `find` +// tag::find[] +=== Locating persons by name or tags: `find or f` [1] -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Finds persons whose names/tags contain all of the given keywords. + +Format: `find n/NAME_KEYWORD [MORE_KEYWORDS] t/TAG_KEYWORD [MORE_KEYWORDS]` **** * The search is case insensitive. e.g `hans` will match `Hans` * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. +* Only the name and tags are searched. * Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Persons matching all keywords will be returned (i.e. `AND` search). e.g. `n/Hans t/family` will return `n/Hans t/family`, not `n/Hans t/enemy` or `n/Gret t/family` **** Examples: -* `find John` + +* `find n/John` + Returns `john` and `John Doe` -* `find Betsy Tim John` + +* `find n/Betsy Tim John` + Returns any person having names `Betsy`, `Tim`, or `John` +* `find n/Betsy t/Friends` + +Returns any person having names `Betsy` and are tagged `Friends` +// end::find[] + +// tag::calendar[] +=== Various commands to interact with Google Calendar + +For the first time that you open the calendar, you will be prompted to login to PrisonBook Google account. The PrisonBook app will attempt to open the browser for you, alternatively you can copy the link and login with the following details: + +* Google Username: prisonbooksystem | Password: prisonbook2018 + +==== View upcoming events in the calendar: `calendar or cal` [1] (v1.3) + +Displays first 10 events in the calendar below Command Line Input. + +Format: `calendar` + +==== Add event to the calendar: `calAdd or calA` [3] (v1.4) + +Adds an event to the calendar. + +Format: `calAdd event/EVENTNAME loc/LOCATION start/STARTDATETIME end/ENDDATETIME` + +Examples: -=== Deleting a person : `delete` +* `calAdd event/IT Skills Training loc/Computer Lab 1 start/2018-06-06 10:00:00 end/2018-06-06 12:00:00` + +Adds an event `IT Skills Training` at `Computer Lab 1` from `June 6th, 2018 10am` to `June 6th, 2018 12pm` -Deletes the specified person from the address book. + +==== Delete event from the calendar: `calDel or calD` [3] (v1.5) + +Deletes an event from the first 10 events in the calendar. + +Format: `calDel EVENTNUMBER` + +Examples: + +* `calDel 5` + +Deletes the 5th event on the calendar events list. + +// end::calendar[] + +=== Deleting a person : `delete or d` [2] + +Deletes the specified person from PrisonBook. + Format: `delete INDEX` **** @@ -133,32 +285,34 @@ Examples: * `list` + `delete 2` + -Deletes the 2nd person in the address book. +Deletes the 2nd person in PrisonBook. * `find Betsy` + `delete 1` + Deletes the 1st person in the results of the `find` command. -=== Selecting a person : `select` +//tag::deletecell[] +=== Deleting a person from a cell : `deletecell or dc` [2] (v1.4) -Selects the person identified by the index number used in the last person listing. + -Format: `select INDEX` +Deletes the specified person from his/her cell in PrisonBook. + +Format: `deletecell INDEX` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. +* Deletes the person at the specified `INDEX`. * The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* `1, 2, 3, ...` +* The index *must be a positive integer* 1, 2, 3, ... +* The person must be imprisoned in the prison already. **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `dc 1` + +Deletes the first person in PrisonBook from his/her cell if he/she is imprisoned here. +* `listcell 1-1` + +`deletecell 1` + +Deletes the first person in the cell 1-1 if there are any prisoners inside it. +//end::deletecell[] -=== Listing entered commands : `history` +=== Listing entered commands : `history or h` [0] Lists all the commands that you have entered in reverse chronological order. + Format: `history` @@ -169,14 +323,14 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and ==== // tag::undoredo[] -=== Undoing previous command : `undo` +=== Undoing previous command : `undo or u` [0] -Restores the address book to the state before the previous _undoable_ command was executed. + +Restores PrisonBook to the state before the previous _undoable_ command was executed. + Format: `undo` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify PrisonBook's content (`add`, `delete`, `edit` and `clear`). ==== Examples: @@ -185,17 +339,14 @@ Examples: `list` + `undo` (reverses the `delete 1` command) + -* `select 1` + -`list` + +* `list` + `undo` + The `undo` command fails as there are no undoable commands executed previously. * `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +`undo` (reverses the `delete 1` command) -=== Redoing the previously undone command : `redo` +=== Redoing the previously undone command : `redo or r` [0] Reverses the most recent `undo` command. + Format: `redo` @@ -211,19 +362,14 @@ Examples: The `redo` command fails as there are no `undo` commands executed previously. * `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + +`edit 1 e/prison@gmail.com` + +`undo` (reverses the `edit` command) + `undo` (reverses the `delete 1` command) + `redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + +`redo` (reapplies the `edit` command) // end::undoredo[] -=== Clearing all entries : `clear` - -Clears all entries from the address book. + -Format: `clear` - -=== Exiting the program : `exit` +=== Exiting the program : `exit` [0] Exits the program. + Format: `exit` @@ -244,21 +390,51 @@ _{explain how the user can enable/disable data encryption}_ *Q*: How do I transfer my data to another Computer? + *A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*Q*: Can I increase the size of the prison in terms of number of cells and the number of prisoners it can contain? + +*A*: You cannot do it directly as we want to prevent users from accidentally changing these things. +Let us know what you want and we will adjust it for you according to your custom specifications. + +//tag::commandsummary[] == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +* Security Level 0: + +** *Check status* : `status` + +** *Exit* : `exit` + +** *Help* : `help` + +** *History* : `history` or `h` + +** *Log in* : `login` + +** *Log out* : `logout` + +** *Undo* : `undo` or `u` + +** *Redo* : `redo` or `r` + +* Security Level 1: + +** *Calendar* : `calendar` or `cal` + +** *Find* : `find n/NAME_KEYWORD [MORE_KEYWORDS] t/TAG_KEYWORD [MORE_KEYWORDS]` + +e.g. `find n/Meier` + +** *List* : `list` or `l` + +** *List prisoners in a cell* : `listcell CELL` + +e.g. `listcell 1-1` + +** *Map* : `map` or `m` + +* Security Level 2: + +** *Add* : `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS r/ROLE [t/TAG]...` + +e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 r/p t/friend t/colleague` + +** *Add prisoner to cell* : `addcell INDEX CELL` + +e.g. `addcell 1 1-1` + +** *Delete* : `delete INDEX` + +e.g. `delete 1` + +** *Delete prisoner from cell* : `deletecell INDEX` + +e.g. `deletecell 1` + +** *Edit* : `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + +eg. `edit 1 p/91234567 e/johndoe@example.com` + +** *Delete User*: `deleteuser user/USERNAME_TO_DELETE` + +eg. deleteuser user/prisonguard1 + +* Security Level 3: + +** *Add user* : `adduser user/NEW_USERNAME pw/NEW_PASSWORD sl/SECURITY_LEVEL` + +e.g. `adduser user/newuser pw/newpassword sl/1` +** *Calendar Delete Event* : `calDel EVENTNUMBER` + +e.g. `calDel 7` + +** *Add event to calendar* : `calAdd event/EVENTNAME loc/LOCATION start/STARTDATETIME end/ENDDATETIME` + +e.g. `calAdd event/IT Skills Training loc/Computer Lab 1 start/2018-06-06 10:00:00 end/2018-06-06 12:00:00` + +// end::commandsummary[] diff --git a/docs/diagrams/LogicComponentClassDiagramCalendarVersion.pptx b/docs/diagrams/LogicComponentClassDiagramCalendarVersion.pptx new file mode 100644 index 000000000000..4f18f33413f0 Binary files /dev/null and b/docs/diagrams/LogicComponentClassDiagramCalendarVersion.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..6bceac2dc6d7 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 418360e10b26..98c2d34c83b7 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..2dcc932734d2 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx b/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx index a505f5b3cafd..274fc4bc3a28 100644 Binary files a/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx b/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx index fa018c1b3d7b..53a6ee8846b8 100644 Binary files a/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx differ diff --git a/docs/images/AddCellModelSequenceDiagram.PNG b/docs/images/AddCellModelSequenceDiagram.PNG new file mode 100644 index 000000000000..050dc828626d Binary files /dev/null and b/docs/images/AddCellModelSequenceDiagram.PNG differ diff --git a/docs/images/AddCellSequenceDiagram.PNG b/docs/images/AddCellSequenceDiagram.PNG new file mode 100644 index 000000000000..79c39832bf3e Binary files /dev/null and b/docs/images/AddCellSequenceDiagram.PNG differ diff --git a/docs/images/CalendarAddCommand_sequence.png b/docs/images/CalendarAddCommand_sequence.png new file mode 100644 index 000000000000..c8a39a23ab33 Binary files /dev/null and b/docs/images/CalendarAddCommand_sequence.png differ diff --git a/docs/images/CalendarCommands_classDiagrams.PNG b/docs/images/CalendarCommands_classDiagrams.PNG new file mode 100644 index 000000000000..d08e6ed03bf3 Binary files /dev/null and b/docs/images/CalendarCommands_classDiagrams.PNG differ diff --git a/docs/images/CalendarDeleteCommand_sequence.png b/docs/images/CalendarDeleteCommand_sequence.png new file mode 100644 index 000000000000..e042003cca42 Binary files /dev/null and b/docs/images/CalendarDeleteCommand_sequence.png differ diff --git a/docs/images/FindCommand_sequence.png b/docs/images/FindCommand_sequence.png new file mode 100644 index 000000000000..898d73ea4dfd Binary files /dev/null and b/docs/images/FindCommand_sequence.png differ diff --git a/docs/images/LogicClassDiagramwSession.png b/docs/images/LogicClassDiagramwSession.png new file mode 100644 index 000000000000..58092ca913b7 Binary files /dev/null and b/docs/images/LogicClassDiagramwSession.png differ diff --git a/docs/images/MapPanel.png b/docs/images/MapPanel.png new file mode 100644 index 000000000000..4fe9f5a62c50 Binary files /dev/null and b/docs/images/MapPanel.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 7ea5b4b42fb2..ac1053c0f6af 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModelClassDiagramwSession.jpg b/docs/images/ModelClassDiagramwSession.jpg new file mode 100644 index 000000000000..398d2449e153 Binary files /dev/null and b/docs/images/ModelClassDiagramwSession.jpg differ diff --git a/docs/images/ModelClassDiagramwSessionwCells.png b/docs/images/ModelClassDiagramwSessionwCells.png new file mode 100644 index 000000000000..9e288f7a1ab5 Binary files /dev/null and b/docs/images/ModelClassDiagramwSessionwCells.png differ diff --git a/docs/images/SecurityLevelSequenceDiagram.png b/docs/images/SecurityLevelSequenceDiagram.png new file mode 100644 index 000000000000..9f59cfdbd4e3 Binary files /dev/null and b/docs/images/SecurityLevelSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..b15d330e99ce 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StorageClassDiagramwUserwCells.png b/docs/images/StorageClassDiagramwUserwCells.png new file mode 100644 index 000000000000..b6d3c25132a0 Binary files /dev/null and b/docs/images/StorageClassDiagramwUserwCells.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..c234de60248c 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UndoRedoNewCommand2StackDiagram.png b/docs/images/UndoRedoNewCommand2StackDiagram.png index bee02191a56b..9222fdb98b39 100644 Binary files a/docs/images/UndoRedoNewCommand2StackDiagram.png and b/docs/images/UndoRedoNewCommand2StackDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand3StackDiagram.png b/docs/images/UndoRedoNewCommand3StackDiagram.png index bb125b2ccf4c..24f325efe788 100644 Binary files a/docs/images/UndoRedoNewCommand3StackDiagram.png and b/docs/images/UndoRedoNewCommand3StackDiagram.png differ diff --git a/docs/images/currentMap.PNG b/docs/images/currentMap.PNG new file mode 100644 index 000000000000..fa1a0570ced3 Binary files /dev/null and b/docs/images/currentMap.PNG differ diff --git a/docs/images/philos22.jpg b/docs/images/philos22.jpg new file mode 100644 index 000000000000..9a6b2d83fa56 Binary files /dev/null and b/docs/images/philos22.jpg differ diff --git a/docs/images/sarahgoh97.jpg b/docs/images/sarahgoh97.jpg new file mode 100644 index 000000000000..91b1888c8b3f Binary files /dev/null and b/docs/images/sarahgoh97.jpg differ diff --git a/docs/images/zacci.jpg b/docs/images/zacci.jpg new file mode 100644 index 000000000000..3ab47d83e330 Binary files /dev/null and b/docs/images/zacci.jpg differ diff --git a/docs/team/[T11-B2][Sarah Goh Shi Ning]Portfolio.pdf b/docs/team/[T11-B2][Sarah Goh Shi Ning]Portfolio.pdf new file mode 100644 index 000000000000..b4448e4a315d Binary files /dev/null and b/docs/team/[T11-B2][Sarah Goh Shi Ning]Portfolio.pdf differ diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 0dfa757e454b..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= John Doe - Project Portfolio -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/philos22.adoc b/docs/team/philos22.adoc new file mode 100644 index 000000000000..632745eb5e7a --- /dev/null +++ b/docs/team/philos22.adoc @@ -0,0 +1,64 @@ += Chung-Yen (Philos) Tsai - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: PrisonBook + +--- + +== Overview + +PrisonBook is a desktop address book application used for Prison Guards/Inmates management. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: Calendar Scheduling Feature +** What it does: Allows user to interact with Google Calendar through PrisonBook CLI +** Justification: Prisons need to follow a strict schedule and guards need to know where inmates are at all times. +** Credits: Google API Guide + +* *Minor enhancement*: Enhanced the find command to be able to search by Tags in addition to Name-search + +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B2/main/blob/master/collated/functional/philos22.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B2/main/blob/master/collated/test/philos22.md[Test code]] + +* *Other contributions*: + +** Project management: +*** PR management and tagging up till v1.3 +*** Verified and closed issues for v1.3 Milestone +** Enhancements to existing features: +*** Find by tags command (Pull request https://github.com/CS2103JAN2018-T11-B2/main/pull/25[#25]) +*** View upcoming events from PrisonBook Google Calendar (Pull request https://github.com/CS2103JAN2018-T11-B2/main/pull/55[#55]) +** Documentation: +*** Converted initial documentation into PrisonBook format. +*** Fix overall typos or missed headers. +*** Constant updates on User Guide and Developer Guide as new features are rolled out. + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=find] + +include::../UserGuide.adoc[tag=calendar] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=FeatureContributionPhilos] + +include::../DeveloperGuide.adoc[tag=findbytag] + +include::../DeveloperGuide.adoc[tag=scheduling] + +include::../DeveloperGuide.adoc[tag=philostest] + + diff --git a/docs/team/sarahgoh97.adoc b/docs/team/sarahgoh97.adoc new file mode 100644 index 000000000000..2e064b8a330b --- /dev/null +++ b/docs/team/sarahgoh97.adoc @@ -0,0 +1,67 @@ += Sarah Goh Shi Ning - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: PrisonBook + +--- + +== Overview + +Prisonbook is a desktop address book application used for Prison Guards/Inmates management. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: Allowing users to manage the prison cells. +** What it does: There is a cell map that has a fixed size and certain number of cells. +It allows guards and prison wardens to add prisoners to cells and to remove them. +It also allows guards and prison wardens to view a list of prisoners in a specified cell to help them do the previously mentioned features. +** Justification: This helps to keep track of prisoners and their locations and to take into consideration prisoner combinations +when adding and removing them from cells. + +* *Minor enhancement*: Map of prison cells on GUI. It hides information on GUI before logging in and after logging out. +It will also show the list of persons in PrisonBook and people in the map when logged in successfully. +** Justification: This helps to boost security for the application, by preventing users who have insufficient security level to view classified information. +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B2/main/blob/master/collated/functional/sarahgoh97.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B2/main/blob/master/collated/test/sarahgoh97.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed release `v1.3` on GitHub +*** Opened and tagged issues +*** Assigned issues to different team members +** Enhancements to existing features: +*** Status bar shows number of people in the PrisonBook +*** Map panel to show cell system of prison +** Documentation: +*** Did revisions to existing contents of the User Guide and Developer Guide + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=addcell] + +include::../UserGuide.adoc[tag=deletecell] + +include::../UserGuide.adoc[tag=listcell] + +include::../UserGuide.adoc[tag=map] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=cellmap] + +include::../DeveloperGuide.adoc[tag=FeatureContributionSarah] + +include::../DeveloperGuide.adoc[tag=sarahtest] + diff --git a/docs/team/zacci.adoc b/docs/team/zacci.adoc new file mode 100644 index 000000000000..1c0f4becac3f --- /dev/null +++ b/docs/team/zacci.adoc @@ -0,0 +1,68 @@ += Isaac Gideon Tan - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: PrisonBook + +--- + +== Overview + +Prisonbook is a desktop address book application used for Prison Guards/Inmates management. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: added *Added login and logout security system with access levels* +** What it does: Allows users of the PrisonBook to log in and logout with their usernames and passwords. The system also controls access levels (such as read/write permissions) based on the user rights. +** Justification: risonBook must have this feature to improve the security of the Prison. Not all prison guards using the PrisonBook should be able to edit and make changes to the database. They should also only be able to view information pertinent to them to avoid security breaches. +** Highlights: This enhancement affects the logic of the entire PrisonBook and requires thoughtful design to implement. + +* *Minor enhancement*: added Prisoner and Guard tags to facilitate all further functionality to our PrisonBook + +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B2/main/blob/master/collated/functional/zacci.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B2/main/blob/master/collated/test/zacci.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Code Quality Manager +*** Opened and Tagged Issues +*** Assigned issues to different team members +** Enhancements to existing features: +*** Command Aliases to shorten the amount of typing that users have to do +** Documentation: +*** Updated documentation accordingly +** Community: +** Tools: + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=login] + +include::../UserGuide.adoc[tag=logout] + +include::../UserGuide.adoc[tag=status] + +include::../UserGuide.adoc[tag=adduser] + +include::../UserGuide.adoc[tag=deleteuser] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=loginsecurity] + +include::../DeveloperGuide.adoc[tag=FeatureContributionIsaac] + +include::../DeveloperGuide.adoc[tag=isaactest] + +--- + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b5166dad4d90..f6b961fd5a86 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d04417b7daf..bf3de218305a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Mar 21 13:28:19 SGT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip diff --git a/gradlew b/gradlew index 91a7e269e19d..cccdd3d517fc 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282aa688..f9553162f122 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/src/main/java/seedu/address/Calendar.java b/src/main/java/seedu/address/Calendar.java new file mode 100644 index 000000000000..91892897b75a --- /dev/null +++ b/src/main/java/seedu/address/Calendar.java @@ -0,0 +1,210 @@ +package seedu.address; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.DateTime; +import com.google.api.client.util.store.FileDataStoreFactory; + +import com.google.api.services.calendar.CalendarScopes; +import com.google.api.services.calendar.model.Event; +import com.google.api.services.calendar.model.EventDateTime; +import com.google.api.services.calendar.model.Events; + +/** The main Calendar Functionality class + * Returns a list of upcoming events and times + * */ +public class Calendar { + /** Application name. */ + private static final String application_name = + "Google Calendar API Java seedu.address.Calendar"; + + /** Directory to store user credentials for this application. */ + private static final java.io.File data_store_dir = new java.io.File( + System.getProperty("user.home"), ".credentials/calendar-java-quickstart"); + + /** Global instance of the {@link FileDataStoreFactory}. */ + private static FileDataStoreFactory datastorefactory; + + /** Global instance of the JSON factory. */ + private static final JsonFactory json_factory = + JacksonFactory.getDefaultInstance(); + + /** Global instance of the HTTP transport. */ + private static HttpTransport httptransport; + + private static ArrayList eventIDs = new ArrayList<>(); + + /** Global instance of the scopes required by this quickstart. + * + * If modifying these scopes, delete your previously saved credentials + * at ~/.credentials/calendar-java-quickstart + */ + private static final List scopes = + Arrays.asList(CalendarScopes.CALENDAR); + + static { + try { + httptransport = GoogleNetHttpTransport.newTrustedTransport(); + datastorefactory = new FileDataStoreFactory(data_store_dir); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + public static void addEventIDs(String event) { + eventIDs.add(event); + } + + /** + * Creates an authorized Credential object. + * @return an authorized Credential object. + * @throws IOException + */ + public static Credential authorize() throws IOException { + // Load client secrets. + InputStream in = + Calendar.class.getResourceAsStream("/client_secret.json"); + GoogleClientSecrets clientSecrets = + GoogleClientSecrets.load(json_factory, new InputStreamReader(in)); + + // Build flow and trigger user authorization request. + GoogleAuthorizationCodeFlow flow = + new GoogleAuthorizationCodeFlow.Builder( + httptransport, json_factory, clientSecrets, scopes) + .setDataStoreFactory(datastorefactory) + .setAccessType("offline") + .build(); + Credential credential = new AuthorizationCodeInstalledApp( + flow, new LocalServerReceiver()).authorize("user"); + System.out.println( + "Credentials saved to " + data_store_dir.getAbsolutePath()); + return credential; + } + + /** + * Build and return an authorized Calendar client service. + * @return an authorized Calendar client service + * @throws IOException + */ + public static com.google.api.services.calendar.Calendar + getCalendarService() throws IOException { + Credential credential = authorize(); + return new com.google.api.services.calendar.Calendar.Builder( + httptransport, json_factory, credential) + .setApplicationName(application_name) + .build(); + } + + //@@author philos22 + + /** + * iterate through events and list them out with their respective start times + * @return a list of upcoming events + * @throws IOException + */ + public static String listEvents() throws IOException { + + com.google.api.services.calendar.Calendar service = + getCalendarService(); + + // List the next 10 events from the primary calendar. + DateTime now = new DateTime(System.currentTimeMillis()); + Events events = service.events().list("primary") + .setMaxResults(10) + .setTimeMin(now) + .setOrderBy("startTime") + .setSingleEvents(true) + .execute(); + List items = events.getItems(); + StringBuilder result = new StringBuilder(); + + if (items.size() == 0) { + result.append("No upcoming events found."); + } else { + Integer eventNumber = 1; + for (Event event : items) { + String eventId = event.getId(); + addEventIDs(eventId); + result.append(String.format("[Event %s] \t %s \t [%s] to [%s] \tLocation: %s\n", + eventNumber, event.getSummary(), event.getStart().getDateTime(), + event.getEnd().getDateTime(), event.getLocation())); + eventNumber++; + } + } + return result.toString(); + } + + /** + * Adds event to the calendar - specifying Name, Location, StartTime, EndTime + * @return success code + * @throws IOException + */ + public static String addEvent(String eventName, String eventLocation, DateTime startDateTime, + DateTime endDateTime) throws IOException { + + String successAddedMessage = "Event added successfully"; + + com.google.api.services.calendar.Calendar service = + getCalendarService(); + + Event event = new Event() + .setSummary(eventName) + .setLocation(eventLocation); + + EventDateTime start = new EventDateTime() + .setDateTime(startDateTime) + .setTimeZone(""); + event.setStart(start); + + EventDateTime end = new EventDateTime() + .setDateTime(endDateTime) + .setTimeZone(""); + event.setEnd(end); + + String calendarId = "primary"; + event = service.events().insert(calendarId, event).execute(); + System.out.printf("Event created: %s\n", event.getHtmlLink()); + + return successAddedMessage; + } + + /** + * Delete event from the calendar - specifying EventID + * @return success code + * @throws IOException + */ + public static String delEvent(String eventArrayId) throws IOException { + + String successDeletedMessage = "Event successfully deleted."; + + int eventArrayIdInt = Integer.parseInt(eventArrayId) - 1; + String eventId = eventIDs.get(eventArrayIdInt); + + // Build a new authorized API client service. + com.google.api.services.calendar.Calendar service = getCalendarService(); + + service.events().delete("primary", eventId).execute(); + + eventIDs.clear(); + + String reList = listEvents(); // to regenerate the EventIDs array + + return successDeletedMessage; + } + +} diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index fa0800d55cb9..ead23543319f 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -40,7 +40,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 5, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +54,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing PrisonBook ]==========================="); super.init(); config = initConfig(getApplicationParameter("config")); @@ -88,17 +88,19 @@ private String getApplicationParameter(String parameterName) { private Model initModelManager(Storage storage, UserPrefs userPrefs) { Optional addressBookOptional; ReadOnlyAddressBook initialData; + //listOfUsers = Storage.getUsers //debug_isaac + try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample PrisonBook"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty PrisonBook"); initialData = new AddressBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty PrisonBook"); initialData = new AddressBook(); } @@ -163,7 +165,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty PrisonBook"); initializedPrefs = new UserPrefs(); } @@ -183,13 +185,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting PrisonBook " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Prison Book ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/client_secret.json b/src/main/java/seedu/address/client_secret.json new file mode 100644 index 000000000000..f857c266e44a --- /dev/null +++ b/src/main/java/seedu/address/client_secret.json @@ -0,0 +1 @@ +{"installed":{"client_id":"824981534536-7k6u7vm63qpv15valckvlg7n4d4hkecq.apps.googleusercontent.com","project_id":"fiery-topic-199411","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"vIpAveyVpUqN90Ckf892Xdi6","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index 8f4d737d0e24..5b83c1c38970 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -11,7 +11,7 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "PrisonBook App"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..9abafb00dd20 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -6,8 +6,13 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format!\n%s"; + public static final String MESSAGE_INVALID_DATETIME_FORMAT = + "Invalid DateTime format - should be yyyy-MM-dd HH:mm:ss."; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_TAGS_RESULT_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INSUFFICIENT_SECURITY_CLEARANCE = "Your security level is insufficient to " + + "access this command."; } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java index 7db9b5c48ed6..a6c778d60f19 100644 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java @@ -1,6 +1,7 @@ package seedu.address.commons.events.model; import seedu.address.commons.events.BaseEvent; +import seedu.address.logic.commands.ShowCellsCommand; import seedu.address.model.ReadOnlyAddressBook; /** Indicates the AddressBook in the model has changed*/ @@ -14,6 +15,7 @@ public AddressBookChangedEvent(ReadOnlyAddressBook data) { @Override public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); + return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size() + + "\n" + new ShowCellsCommand().getMapString(data.getCellList().toString()); } } diff --git a/src/main/java/seedu/address/commons/events/ui/HideMapEvent.java b/src/main/java/seedu/address/commons/events/ui/HideMapEvent.java new file mode 100644 index 000000000000..8502e7479cf1 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/HideMapEvent.java @@ -0,0 +1,25 @@ +//@@author sarahgoh97 +package seedu.address.commons.events.ui; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates an event to hide the map. + */ +public class HideMapEvent extends BaseEvent { + + public final ObservableList list = FXCollections.observableArrayList(); + + public HideMapEvent() { + for (int i = 0; i < 15; i++) { + list.add("Insufficient Access"); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..bc261751e16c 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,9 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.cell.Cell; import seedu.address.model.person.Person; +import seedu.address.model.user.User; /** * API of the Logic component @@ -24,4 +26,10 @@ public interface Logic { /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); + + /** Returns an unmodifiable view of the celllist */ + ObservableList getCellList(); + + /** Returns an unmodifiable view of the userList */ + ObservableList getUserList(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9f6846bdfc74..dbd1b37fe71c 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,5 +1,7 @@ package seedu.address.logic; +import static seedu.address.commons.core.Messages.MESSAGE_INSUFFICIENT_SECURITY_CLEARANCE; + import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -11,7 +13,9 @@ import seedu.address.logic.parser.AddressBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.cell.Cell; import seedu.address.model.person.Person; +import seedu.address.model.user.User; /** * The main LogicManager of the app. @@ -37,14 +41,33 @@ public CommandResult execute(String commandText) throws CommandException, ParseE try { Command command = addressBookParser.parseCommand(commandText); command.setData(model, history, undoRedoStack); - CommandResult result = command.execute(); - undoRedoStack.push(command); + CommandResult result = restrictedExecute(command); return result; } finally { history.add(commandText); } } + //@@author zacci + /** + * Executes the received command if the logged in user's security level meets the MIN_SECURITY_LEVEL for the command + */ + private CommandResult restrictedExecute (Command command) throws CommandException { + logger.info("Command MIN_SECURITY_LEVEL: " + command.getMinSecurityLevel()); + if (command.getMinSecurityLevel() <= model.getSecurityLevel()) { + try { + CommandResult result = command.execute(); + undoRedoStack.push(command); + return result; + } finally { + } + } else { + CommandResult result = new CommandResult(MESSAGE_INSUFFICIENT_SECURITY_CLEARANCE); + return result; + } + } + //@@author + @Override public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); @@ -54,4 +77,15 @@ public ObservableList getFilteredPersonList() { public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + + @Override + public ObservableList getCellList() { + return model.getAddressBook().getCellList(); + } + + //@@author zacci + @Override + public ObservableList getUserList() { + return model.getAddressBook().getUserList(); + } } diff --git a/src/main/java/seedu/address/logic/UndoRedoStack.java b/src/main/java/seedu/address/logic/UndoRedoStack.java index ddb62ef0ea87..4babd7140d84 100644 --- a/src/main/java/seedu/address/logic/UndoRedoStack.java +++ b/src/main/java/seedu/address/logic/UndoRedoStack.java @@ -54,6 +54,14 @@ public UndoableCommand popRedo() { return toRedo; } + /** + * Clears undo and redo stack + */ + public void clearStack() { + undoStack = new Stack<>(); + redoStack = new Stack<>(); + } + /** * Returns true if there are more commands that can be undone. */ diff --git a/src/main/java/seedu/address/logic/commands/AddCellCommand.java b/src/main/java/seedu/address/logic/commands/AddCellCommand.java new file mode 100644 index 000000000000..98ce5d48ae37 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddCellCommand.java @@ -0,0 +1,110 @@ +//@@author sarahgoh97 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.cell.exceptions.AlreadyInCellException; +import seedu.address.model.cell.exceptions.FullCellException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.cell.exceptions.NotPrisonerException; +import seedu.address.model.person.Person; + +/** + * Adds a prisoner to a cell in the address book. + */ +public class AddCellCommand extends UndoableCommand { + public static final String COMMAND_WORD = "addcell"; + public static final String COMMAND_ALIAS = "ac"; + public static final int MIN_SECURITY_LEVEL = 2; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a prisoner to the specified cell." + + "by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer) " + + "CELLADDRESS (block-unit)\n" + + "Example: " + COMMAND_WORD + " 1 1-1"; + + public static final String MESSAGE_ADD_CELL_SUCCESS = "Prisoner %s added to %s."; + public static final String MESSAGE_FULL_CELL = "Cell %s is already full. Below is the map."; + public static final String MESSAGE_NON_EXISTENT_CELL = "This cell %s does not exist. Below is the map."; + public static final String MESSAGE_NOT_PRISONER = "%s is not a prisoner."; + public static final String MESSAGE_ALREADY_IN_CELL = "%s is already in cell %s"; + + public final Index index; + + private Person prisonerToAdd; + private String cellAddress; + + /** + * @param index of the person in the filtered person list to edit + * @param cellAddress cell to be added to + */ + public AddCellCommand(Index index, String cellAddress) { + requireNonNull(index); + requireNonNull(cellAddress); + this.index = index; + this.cellAddress = cellAddress; + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + requireNonNull(prisonerToAdd); + try { + model.addPrisonerToCell(prisonerToAdd, cellAddress); + } catch (FullCellException fce) { + throw new CommandException(String.format(MESSAGE_FULL_CELL, + cellAddress, new ShowCellsCommand().getMapString( + model.getAddressBook().getCellList().toString()))); + } catch (NonExistentCellException nece) { + throw new CommandException(String.format(MESSAGE_NON_EXISTENT_CELL, cellAddress)); + } catch (NotPrisonerException npe) { + throw new CommandException(String.format(MESSAGE_NOT_PRISONER, prisonerToAdd.getName())); + } catch (AlreadyInCellException aice) { + throw new CommandException(String.format(MESSAGE_ALREADY_IN_CELL, + prisonerToAdd.getName(), prisonerToAdd.getCellAddress())); + } + return new CommandResult(String.format(MESSAGE_ADD_CELL_SUCCESS, prisonerToAdd.getName(), cellAddress)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + prisonerToAdd = lastShownList.get(index.getZeroBased()); + } + + public Person getPrisonerToAdd() { + return prisonerToAdd; + } + + public String getCellAddress() { + return cellAddress; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCellCommand // instanceof handles nulls + && this.index.equals(((AddCellCommand) other).index) // state check + && Objects.equals(this.prisonerToAdd, ((AddCellCommand) other).prisonerToAdd) //prisoner check + && Objects.equals(this.cellAddress, ((AddCellCommand) other).cellAddress)); //celladdress check + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index c334710c0ea3..c3b709ee6b0c 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -5,6 +5,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.logic.commands.exceptions.CommandException; @@ -17,6 +18,8 @@ public class AddCommand extends UndoableCommand { public static final String COMMAND_WORD = "add"; + public static final String COMMAND_ALIAS = "a"; + public static final int MIN_SECURITY_LEVEL = 2; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + "Parameters: " @@ -24,12 +27,14 @@ public class AddCommand extends UndoableCommand { + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_ROLE + "ROLE " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_ROLE + "g " + PREFIX_TAG + "friends " + PREFIX_TAG + "owesMoney"; @@ -46,6 +51,14 @@ public AddCommand(Person person) { toAdd = person; } + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + @Override public CommandResult executeUndoableCommand() throws CommandException { requireNonNull(model); diff --git a/src/main/java/seedu/address/logic/commands/AddUserCommand.java b/src/main/java/seedu/address/logic/commands/AddUserCommand.java new file mode 100644 index 000000000000..0b49ccb6a808 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddUserCommand.java @@ -0,0 +1,73 @@ +//@@author zacci +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; + +/** + * Adds a new user to the PrisonBook + */ +public class AddUserCommand extends Command { + public static final String COMMAND_WORD = "adduser"; + public static final String COMMAND_ALIAS = "au"; + public static final int MIN_SECURITY_LEVEL = 3; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a new user to the PrisonBook.\n" + + "Parameters: user/NEW_USERNAME pw/NEW_PASSWORD sl/SECURITY_LEVEL (integer from 0 to 3)...\n" + + "Example: " + COMMAND_WORD + " user/newuser1 pw/password1 sl/2"; + + public static final String MESSAGE_ADD_USER_SUCCESS = "New user %s added to PrisonBook"; + public static final String MESSAGE_ALREADY_EXISTING_USER = "%s is already a user in PrisonBook"; + private final String username; + + private User userToAdd; + + /** + * @param username of the new user to be added to the PrisonBook + * @param password of the new user to be added to the PrisonBook + * @param securityLevel of the new user to be added to the PrisonBook + */ + public AddUserCommand(String username, String password, int securityLevel) { + requireNonNull(username); + requireNonNull(password); + this.username = username; + userToAdd = new User(username, password, securityLevel); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public CommandResult execute() throws CommandException { + requireNonNull(model); + requireNonNull(userToAdd); + try { + model.addUser(userToAdd); + } catch (UserAlreadyExistsException uaee) { + throw new CommandException(String.format(MESSAGE_ALREADY_EXISTING_USER, username)); + } + return new CommandResult(String.format(MESSAGE_ADD_USER_SUCCESS, username)); + } + + public User getUserToAdd() { + return userToAdd; + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddUserCommand // instanceof handles nulls + && Objects.equals(this.userToAdd, ((AddUserCommand) other).userToAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CalendarAddCommand.java b/src/main/java/seedu/address/logic/commands/CalendarAddCommand.java new file mode 100644 index 000000000000..a8f7c25b1723 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CalendarAddCommand.java @@ -0,0 +1,79 @@ +//@@author philos22 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START; + +import java.io.IOException; + +import com.google.api.client.util.DateTime; + +import seedu.address.Calendar; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Adds an event to the Google Calendar. + */ +public class CalendarAddCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "calAdd"; + public static final String COMMAND_ALIAS = "calA"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an event to the calendar. \n" + + "Parameters: " + + PREFIX_EVENT + "EVENT NAME " + + PREFIX_LOCATION + "EVENT LOCATION " + + PREFIX_START + "EVENT_START_TIME " + + PREFIX_END + "EVENT_END_TIME \n" + + "Example: \n" + COMMAND_WORD + " " + + PREFIX_EVENT + "Prison Security Sweep " + + PREFIX_LOCATION + "NUS Prison " + + PREFIX_START + "2020-04-01 15:00:00 " + + PREFIX_END + "2020-04-01 17:00:00"; + + private final String calEventName; + private final String calEventLocation; + private final DateTime calStartDateTime; + private final DateTime calEndDateTime; + private final String toAdd; + + + /** + * Creates an CalendarAddCommand to add the specified {@code Event} + */ + public CalendarAddCommand(String eventName, String eventLocation, DateTime startDateTime, DateTime endDateTime) { + requireNonNull(eventName); + requireNonNull(eventLocation); + requireNonNull(startDateTime); + requireNonNull(endDateTime); + calEventName = eventName; + calEventLocation = eventLocation; + calStartDateTime = startDateTime; + calEndDateTime = endDateTime; + toAdd = calEventName + " " + calEventLocation + " " + calStartDateTime.toString() + " " + + calEndDateTime.toString(); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + String successMessage = new Calendar().addEvent(calEventName, calEventLocation, calStartDateTime, + calEndDateTime); + return new CommandResult(String.format(successMessage, toAdd)); + } catch (IOException e) { + throw new CommandException(e.toString()); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CalendarAddCommand // instanceof handles nulls + && toAdd.equals(((CalendarAddCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CalendarCommand.java b/src/main/java/seedu/address/logic/commands/CalendarCommand.java new file mode 100644 index 000000000000..532afe1f762f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CalendarCommand.java @@ -0,0 +1,37 @@ +//@@author philos22 +package seedu.address.logic.commands; + +import java.io.IOException; + +import seedu.address.Calendar; + +/** + * Lists all the commands entered by user from the start of app launch. + */ +public class CalendarCommand extends Command { + + public static final String COMMAND_WORD = "calendar"; + public static final String COMMAND_ALIAS = "cal"; + public static final String ERROR_MESSAGE = "calendar execution failed"; + public static final int MIN_SECURITY_LEVEL = 1; + + @Override + public CommandResult execute() { + try { + String messagesuccess = new Calendar().listEvents(); + return new CommandResult(messagesuccess); + } catch (IOException e) { + e.printStackTrace(); + } + return new CommandResult(ERROR_MESSAGE); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/CalendarDeleteCommand.java b/src/main/java/seedu/address/logic/commands/CalendarDeleteCommand.java new file mode 100644 index 000000000000..71a0fec502d1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CalendarDeleteCommand.java @@ -0,0 +1,52 @@ +//@@author philos22 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; + +import seedu.address.Calendar; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Deletes an event from the Google Calendar. + */ +public class CalendarDeleteCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "calDel"; + public static final String COMMAND_ALIAS = "calD"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes an event from the calendar. \n" + + "Parameter: EVENT NUMBER (from the event list in Calendar command)" + + "Example: \n" + COMMAND_WORD + " 2\n" + + "Deletes the second event listed in cal command"; + + private final String toDel; + + /** + * Creates an CalendarAddCommand to add the specified {@code Event} + */ + public CalendarDeleteCommand(String eventId) { + requireNonNull(eventId); + toDel = eventId; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + String successMessage = new Calendar().delEvent(toDel); + return new CommandResult(String.format(successMessage, toDel)); + } catch (IOException e) { + throw new CommandException(e.toString()); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CalendarDeleteCommand // instanceof handles nulls + && toDel.equals(((CalendarDeleteCommand) other).toDel)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CheckStatusCommand.java b/src/main/java/seedu/address/logic/commands/CheckStatusCommand.java new file mode 100644 index 000000000000..35bd73ff2dd6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckStatusCommand.java @@ -0,0 +1,25 @@ +//@@author zacci +package seedu.address.logic.commands; + +/** + * Displays the status of the current session + */ +public class CheckStatusCommand extends Command { + + public static final String COMMAND_WORD = "status"; + + public static final String MESSAGE_USER_NOT_LOGGED_IN = "You are not currently logged in"; + + /** + * Checks the status of current session + * @return details of the status + */ + public CommandResult execute() { + if (!model.checkIsLoggedIn()) { + return new CommandResult(MESSAGE_USER_NOT_LOGGED_IN); + } + String details = (model.getSessionDetails()); + return new CommandResult(details); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index ceeb7ba913c6..624880716491 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -10,7 +10,17 @@ public class ClearCommand extends UndoableCommand { public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_ALIAS = "c"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final int MIN_SECURITY_LEVEL = 3; + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } @Override diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 6580e0b51c90..32263c558283 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -10,6 +10,9 @@ * Represents a command with hidden internal logic and the ability to be executed. */ public abstract class Command { + + protected static final int MIN_SECURITY_LEVEL = 0; + protected Model model; protected CommandHistory history; protected UndoRedoStack undoRedoStack; @@ -24,6 +27,16 @@ public static String getMessageForPersonListShownSummary(int displaySize) { return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); } + /** + * Constructs a feedback message to summarise an operation that displayed the result of a search by tag. + * + * @param displaySize used to generate summary + * @return summary message for tag search displayed + */ + public static String getMessageForTagShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_TAGS_RESULT_LISTED_OVERVIEW, displaySize); + } + /** * Executes the command and returns the result message. * @@ -39,5 +52,15 @@ public static String getMessageForPersonListShownSummary(int displaySize) { */ public void setData(Model model, CommandHistory history, UndoRedoStack undoRedoStack) { this.model = model; + this.undoRedoStack = undoRedoStack; + } + + //@@author zacci + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; } + //@@author } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCellCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCellCommand.java new file mode 100644 index 000000000000..c20dc7f89956 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteCellCommand.java @@ -0,0 +1,86 @@ +//@@author sarahgoh97 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.cell.exceptions.NotImprisonedException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +/** + * Deletes a prisoner from a cell in the address book. + */ +public class DeleteCellCommand extends UndoableCommand { + public static final String COMMAND_WORD = "deletecell"; + public static final String COMMAND_ALIAS = "dc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes prisoners in specified cell.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + "1"; + + public static final String MESSAGE_DELETE_CELL_SUCCESS = "Prisoner %s has been released."; + public static final String MESSAGE_NOT_IMPRISONED = "The target person is not imprisoned here"; + + public final Index targetIndex; + + private Person prisonerToDelete; + private String cellAddress; + + /** + * Creates a deleteCellCommand object + * @param targetIndex of the person being deleted + */ + public DeleteCellCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(prisonerToDelete); + try { + model.deletePrisonerFromCell(prisonerToDelete); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } catch (NotImprisonedException nie) { + throw new CommandException(MESSAGE_NOT_IMPRISONED); + } + + return new CommandResult(String.format(MESSAGE_DELETE_CELL_SUCCESS, prisonerToDelete.getName().toString())); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + prisonerToDelete = lastShownList.get(targetIndex.getZeroBased()); + if (prisonerToDelete.getIsInCell()) { + cellAddress = prisonerToDelete.getCellAddress().toString(); + } + } + + public Person getPrisonerToDelete() { + return prisonerToDelete; + } + + public String getCellAddress() { + return cellAddress; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCellCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteCellCommand) other).targetIndex) // state check + && Objects.equals(this.prisonerToDelete, ((DeleteCellCommand) other).prisonerToDelete)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index b539d240001a..4c56f716a533 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -17,6 +17,8 @@ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; + public static final int MIN_SECURITY_LEVEL = 2; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the person identified by the index number used in the last person listing.\n" @@ -33,6 +35,13 @@ public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; } + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } @Override public CommandResult executeUndoableCommand() { @@ -64,4 +73,8 @@ public boolean equals(Object other) { && this.targetIndex.equals(((DeleteCommand) other).targetIndex) // state check && Objects.equals(this.personToDelete, ((DeleteCommand) other).personToDelete)); } + + public Person getPersonToDelete() { + return personToDelete; + } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteUserCommand.java b/src/main/java/seedu/address/logic/commands/DeleteUserCommand.java new file mode 100644 index 000000000000..08da9a33e76f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteUserCommand.java @@ -0,0 +1,72 @@ +//@@author zacci +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; + +/** + * Deletes a user from the PrisonBook. + */ +public class DeleteUserCommand extends UndoableCommand { + public static final String COMMAND_WORD = "deleteuser"; + public static final String COMMAND_ALIAS = "du"; + public static final int MIN_SECURITY_LEVEL = 2; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes specified user.\n" + + "Parameters: user/USERNAME_TO_BE_DELETED\n" + + "Example: " + COMMAND_WORD + " user/prisonguard"; + + public static final String MESSAGE_DELETE_CELL_SUCCESS = "User has been successfully deleted"; + + private String userToDelete; + + /** + * Creates a deleteUserCommand object + * @param userToDelete username to be deleted + */ + public DeleteUserCommand(String userToDelete) { + this.userToDelete = userToDelete; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(userToDelete); + try { + model.deleteUser(userToDelete); + } catch (CannotDeleteSelfException cdse) { + throw new CommandException(cdse.getMessage()); + } catch (MustHaveAtLeastOneSecurityLevelThreeUserException mhalosltue) { + throw new CommandException(mhalosltue.getMessage()); + } catch (UserDoesNotExistException udnee) { + throw new CommandException(udnee.getMessage()); + } catch (NotEnoughAuthorityToDeleteException neatde) { + throw new CommandException(neatde.getMessage()); + } + + return new CommandResult(MESSAGE_DELETE_CELL_SUCCESS); + } + + @Override + protected void preprocessUndoableCommand() { + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteUserCommand // instanceof handles nulls + && this.userToDelete.equals(((DeleteUserCommand) other).userToDelete)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e6c3a3e034bc..faa8f6ab2b74 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -24,6 +24,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; @@ -34,6 +35,8 @@ public class EditCommand extends UndoableCommand { public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "e"; + public static final int MIN_SECURITY_LEVEL = 2; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the last person listing. " @@ -51,6 +54,7 @@ public class EditCommand extends UndoableCommand { public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_CANNOT_CHANGE_ROLE = "Roles cannot be changed."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -105,10 +109,43 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript 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()); + Address updatedAddress = getUpdatedAddress(personToEdit, editPersonDescriptor); + Role updatedRole = editPersonDescriptor.getRole().orElse(personToEdit.getRole()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRole, + updatedTags, personToEdit.getIsInCell()); + } + + //@@author sarahgoh97 + private static Address getUpdatedAddress(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + if (!updatedAddress.equals(personToEdit.getAddress()) && personToEdit.getIsInCell()) { + //if address has changed and imprisoned person + String original = personToEdit.getAddress().toString(); + String newAddress = original.substring(0, original.indexOf("s: ") + 3) + updatedAddress.toString() + "]"; + updatedAddress = new Address(newAddress); + } + return updatedAddress; + } + + //@@author zacci + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + //@@author + + public Person getPersonToEdit() { + return personToEdit; + } + + public Person getEditedPerson() { + return editedPerson; } @Override @@ -139,6 +176,7 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; + private Role role; private Set tags; public EditPersonDescriptor() {} @@ -152,6 +190,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); + setRole(toCopy.role); setTags(toCopy.tags); } @@ -159,7 +198,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags); + return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.role, this.tags); } public void setName(Name name) { @@ -194,6 +233,14 @@ public Optional
getAddress() { return Optional.ofNullable(address); } + public void setRole(Role role) { + this.role = role; + } + + public Optional getRole() { + return Optional.ofNullable(role); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -230,6 +277,7 @@ public boolean equals(Object other) { && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) && getAddress().equals(e.getAddress()) + && getRole().equals(e.getRole()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index b1e671f633d2..eebbfc745293 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,23 +1,28 @@ package seedu.address.logic.commands; +import java.util.function.Predicate; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case sensitive. + * Keyword matching is case insensitive. */ 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 persons whose names contain any of " + public static final int MIN_SECURITY_LEVEL = 1; + + //@@author philos22 + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names or tags contain any of " + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Parameters: n/NAME_KEYWORDS t/TAG_KEYWORDS...\n" + + "Example: " + COMMAND_WORD + " n/alice t/tag1"; - private final NameContainsKeywordsPredicate predicate; + private final Predicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(Predicate predicate) { this.predicate = predicate; } @@ -27,10 +32,19 @@ public CommandResult execute() { return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); } + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof FindCommand // instanceof handles nulls && this.predicate.equals(((FindCommand) other).predicate)); // state check } + } diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f87abee5511d..a3a8f11563d0 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -15,6 +15,7 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; + public static final String COMMAND_ALIAS = "h"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; diff --git a/src/main/java/seedu/address/logic/commands/ListCellCommand.java b/src/main/java/seedu/address/logic/commands/ListCellCommand.java new file mode 100644 index 000000000000..96292fde5a00 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListCellCommand.java @@ -0,0 +1,56 @@ +//@@author sarahgoh97 +package seedu.address.logic.commands; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.person.Person; + +/** + * Lists all person in specified cell + */ +public class ListCellCommand extends Command { + public static final String COMMAND_WORD = "listcell"; + public static final String COMMAND_ALIAS = "lc"; + public static final int MIN_SECURITY_LEVEL = 1; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all people in the specified cell." + + " Parameters: CELLADDRESS\n" + + "Example: " + COMMAND_WORD + " 1-1"; + + public static final String MESSAGE_LISTCELL_SUCCESS = "Listed persons in cell %s"; + public static final String MESSAGE_NON_EXISTENT_CELL = "This cell %s does not exist"; + private final Predicate predicate; + private final String cellAddress; + + public ListCellCommand(Predicate predicate, String cellAddress) { + this.predicate = predicate; + this.cellAddress = cellAddress; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.updateFilteredPersonListForCell(predicate, cellAddress); + } catch (NonExistentCellException nece) { + throw new CommandException(String.format(MESSAGE_NON_EXISTENT_CELL, cellAddress)); + } + return new CommandResult(String.format(MESSAGE_LISTCELL_SUCCESS, cellAddress)); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListCellCommand // instanceof handles nulls + && this.predicate.equals(((ListCellCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 7b6463780824..1094c642ac76 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -8,9 +8,18 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "l"; + public static final int MIN_SECURITY_LEVEL = 1; public static final String MESSAGE_SUCCESS = "Listed all persons"; + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } @Override public CommandResult execute() { diff --git a/src/main/java/seedu/address/logic/commands/LoginCommand.java b/src/main/java/seedu/address/logic/commands/LoginCommand.java new file mode 100644 index 000000000000..fd3a05d1c394 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LoginCommand.java @@ -0,0 +1,46 @@ +//@@author zacci +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Attempts to log in user with given Username and Password + */ +public class LoginCommand extends Command { + + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_LOGIN_FAILURE = "Login failed. Username and/or Password entered incorrectly."; + public static final String MESSAGE_LOGIN_SUCCESS = "Login Success"; + public static final String MESSAGE_ALREADY_LOGGED_IN = "You are already logged in. Please logout before " + + "attempting to login with another account."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logs in with your username and password to gain " + + "access to the Prison Book.\n" + + "Parameters: user/YOUR_USERNAME pw/YOUR_PASSWORD...\n" + + "Example: " + COMMAND_WORD + " user/prisonwarden99 pw/password1"; + + private final String username; + private final String password; + + public LoginCommand(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public CommandResult execute() throws CommandException { + if (model.checkIsLoggedIn()) { + throw new CommandException(MESSAGE_ALREADY_LOGGED_IN); + } + if (!model.attemptLogin(username, password)) { + throw new CommandException(MESSAGE_LOGIN_FAILURE); + } else { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_LOGIN_SUCCESS); + } + } + +} diff --git a/src/main/java/seedu/address/logic/commands/LogoutCommand.java b/src/main/java/seedu/address/logic/commands/LogoutCommand.java new file mode 100644 index 000000000000..4e1dcdf6dcf8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogoutCommand.java @@ -0,0 +1,32 @@ +//@@author zacci +package seedu.address.logic.commands; + +import java.util.ArrayList; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.HideMapEvent; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.NameContainsKeywordsPredicate; + +/** + * Logs the user out of the current session + */ +public class LogoutCommand extends Command { + + public static final String COMMAND_WORD = "logout"; + + public static final String MESSAGE_SUCCESS = "Successfully logged out"; + public static final String MESSAGE_USER_NOT_LOGGED_IN = "You are not currently logged in"; + + @Override + public CommandResult execute() throws CommandException { + undoRedoStack.clearStack(); + if (!model.checkIsLoggedIn()) { + throw new CommandException(MESSAGE_USER_NOT_LOGGED_IN); + } + model.logout(); + model.updateFilteredPersonList(new NameContainsKeywordsPredicate(new ArrayList())); + EventsCenter.getInstance().post(new HideMapEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 7b99d0f372fc..311c95c1a1f2 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -13,6 +13,7 @@ public class RedoCommand extends Command { public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index 9e3840a9dde6..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,52 +0,0 @@ -package seedu.address.logic.commands; - -import java.util.List; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; - -/** - * Selects a person identified using it's last displayed index from the address book. - */ -public class SelectCommand extends Command { - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - private final Index targetIndex; - - public SelectCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute() throws CommandException { - - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && this.targetIndex.equals(((SelectCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ShowCellsCommand.java b/src/main/java/seedu/address/logic/commands/ShowCellsCommand.java new file mode 100644 index 000000000000..099979f4d1cb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowCellsCommand.java @@ -0,0 +1,41 @@ +//@@author sarahgoh97 +package seedu.address.logic.commands; + +import seedu.address.model.cell.CellMap; + +/** + * Shows all cells and number of prisoners in each of them to the user. + */ +public class ShowCellsCommand extends Command { + + public static final String COMMAND_WORD = "map"; + public static final String COMMAND_ALIAS = "m"; + public static final int MIN_SECURITY_LEVEL = 1; + + public static final String MESSAGE_SUCCESS = "%s\nShown cells with number of people in them."; + + + @Override + public CommandResult execute() { + String cells = model.getAddressBook().getCellList().toString(); + String map = getMapString(cells); + return new CommandResult(String.format(MESSAGE_SUCCESS, map)); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + + public String getMapString(String cells) { + for (int i = 1; i <= CellMap.MAX_ROW; i++) { + cells = cells.replace(", " + i + "-1", i + "-1"); + } + cells = cells.substring(1, cells.length() - 1); + + return cells; + } +} diff --git a/src/main/java/seedu/address/logic/commands/ShowUsersCommand.java b/src/main/java/seedu/address/logic/commands/ShowUsersCommand.java new file mode 100644 index 000000000000..f9cd6d3a42be --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowUsersCommand.java @@ -0,0 +1,29 @@ +//@@zacci +package seedu.address.logic.commands; + +/** + * Shows all cells and number of prisoners in each of them to the user. + */ +public class ShowUsersCommand extends Command { + + public static final String COMMAND_WORD = "users"; + public static final int MIN_SECURITY_LEVEL = 3; + + public static final String MESSAGE_SUCCESS = "%s\nShown users."; + + + @Override + public CommandResult execute() { + String users = model.getAddressBook().getUserList().toString(); + return new CommandResult(String.format(MESSAGE_SUCCESS, users)); + } + + @Override + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 1f3dcea8bbaa..53e121ce7ad3 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -6,6 +6,7 @@ import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.person.Person; /** * Undo the previous {@code UndoableCommand}. @@ -13,6 +14,7 @@ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; @@ -24,7 +26,34 @@ public CommandResult execute() throws CommandException { throw new CommandException(MESSAGE_FAILURE); } - undoRedoStack.popUndo().undo(); + //@author sarahgoh97 + UndoableCommand command = undoRedoStack.popUndo(); + if (command instanceof AddCellCommand) { + String cellAddress = ((AddCellCommand) command).getCellAddress(); + Person prisoner = ((AddCellCommand) command).getPrisonerToAdd(); + model.deletePrisonerFromCellFromUndo(prisoner, cellAddress); + } + if (command instanceof DeleteCommand) { + Person prisoner = ((DeleteCommand) command).getPersonToDelete(); + if (prisoner.getIsInCell()) { + String cellAddress = prisoner.getCellAddress().toString(); + model.addPrisonerToCellFromUndo(prisoner, cellAddress); + } + } + if (command instanceof DeleteCellCommand) { + Person prisoner = ((DeleteCellCommand) command).getPrisonerToDelete(); + String cellAddress = ((DeleteCellCommand) command).getCellAddress(); + model.addPrisonerToCellFromUndo(prisoner, cellAddress); + } + if (command instanceof EditCommand) { + Person original = ((EditCommand) command).getPersonToEdit(); + Person changed = ((EditCommand) command).getEditedPerson(); + if (original.getIsInCell()) { + model.updatePrisonerFromUndo(changed, original); + } + } + command.undo(); + //@author return new CommandResult(MESSAGE_SUCCESS); } diff --git a/src/main/java/seedu/address/logic/commands/UndoableCommand.java b/src/main/java/seedu/address/logic/commands/UndoableCommand.java index c107ffcd9cb3..cffec98e6886 100644 --- a/src/main/java/seedu/address/logic/commands/UndoableCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoableCommand.java @@ -12,6 +12,9 @@ * Represents a command which can be undone and redone. */ public abstract class UndoableCommand extends Command { + + protected static final int MIN_SECURITY_LEVEL = 0; + private ReadOnlyAddressBook previousAddressBook; protected abstract CommandResult executeUndoableCommand() throws CommandException; @@ -40,6 +43,14 @@ protected final void undo() { model.resetData(previousAddressBook); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } + //@@author zacci + /** + * Returns the MIN_SECURITY_LEVEL to caller + */ + public int getMinSecurityLevel() { + return MIN_SECURITY_LEVEL; + } + //@@author /** * Executes the command and updates the filtered person diff --git a/src/main/java/seedu/address/logic/parser/AddCellCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCellCommandParser.java new file mode 100644 index 000000000000..7660c0b8ba8a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddCellCommandParser.java @@ -0,0 +1,54 @@ +//@@author sarahgoh97 +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddCellCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCellCommand object + */ +public class AddCellCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddCellCommand + * and returns an AddCellCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCellCommand parse (String args) throws ParseException { + try { + requireNonNull(args); + String removePrefix = null; + String unparsedIndex = null; + if (args.contains(" ") && args.length() > args.indexOf(" ")) { + removePrefix = args.substring(args.indexOf(" ") + 1); + if (removePrefix.contains(" ") && removePrefix.length() > removePrefix.indexOf(" ")) { + unparsedIndex = removePrefix.substring(0, removePrefix.indexOf(" ")); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCellCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCellCommand.MESSAGE_USAGE)); + } + if (removePrefix != null && removePrefix.contains(" ") && unparsedIndex != null) { + String cellAddress = removePrefix.substring(removePrefix.indexOf(" ") + 1); + Index index = ParserUtil.parseIndex(unparsedIndex); + if (cellAddress.matches("\\d+-\\d+")) { + return new AddCellCommand(index, cellAddress); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddCellCommand.MESSAGE_USAGE)); + } + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCellCommand.MESSAGE_USAGE)); + } + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCellCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3c729b388554..24a165e15e08 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -5,6 +5,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; @@ -18,6 +19,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; /** @@ -32,9 +34,10 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_ROLE, + PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ROLE) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } @@ -44,9 +47,10 @@ public AddCommand parse(String args) throws ParseException { Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); + Role role = ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE)).get(); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, address, role, tagList); return new AddCommand(person); } catch (IllegalValueException ive) { diff --git a/src/main/java/seedu/address/logic/parser/AddUserCommandParser.java b/src/main/java/seedu/address/logic/parser/AddUserCommandParser.java new file mode 100644 index 000000000000..d580ff420825 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddUserCommandParser.java @@ -0,0 +1,58 @@ +//@@author zacci +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SECURITY_LEVEL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddUserCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddUserCommand object + */ +public class AddUserCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddUserCommand + * and returns an AddUserCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddUserCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_SECURITY_LEVEL); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_SECURITY_LEVEL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddUserCommand.MESSAGE_USAGE)); + } + + String username = ""; + String password = ""; + int securityLevel = -1; + try { + username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)); + password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD)); + securityLevel = ParserUtil.parseSecurityLevel(argMultimap.getValue(PREFIX_SECURITY_LEVEL)); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + + return new AddUserCommand(username, password, securityLevel); + + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..01b9c86019fc 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -6,19 +6,31 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import seedu.address.logic.commands.AddCellCommand; import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.AddUserCommand; +import seedu.address.logic.commands.CalendarAddCommand; +import seedu.address.logic.commands.CalendarCommand; +import seedu.address.logic.commands.CalendarDeleteCommand; +import seedu.address.logic.commands.CheckStatusCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCellCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteUserCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ListCellCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.ShowCellsCommand; +import seedu.address.logic.commands.ShowUsersCommand; import seedu.address.logic.commands.UndoCommand; + import seedu.address.logic.parser.exceptions.ParseException; /** @@ -49,27 +61,59 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { case AddCommand.COMMAND_WORD: + case AddCommand.COMMAND_ALIAS: return new AddCommandParser().parse(arguments); case EditCommand.COMMAND_WORD: + case EditCommand.COMMAND_ALIAS: return new EditCommandParser().parse(arguments); - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: + case DeleteCommand.COMMAND_ALIAS: return new DeleteCommandParser().parse(arguments); - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - case FindCommand.COMMAND_WORD: + case FindCommand.COMMAND_ALIAS: return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: + case ListCommand.COMMAND_ALIAS: return new ListCommand(); + //@@author philos22 + case CalendarCommand.COMMAND_WORD: + case CalendarCommand.COMMAND_ALIAS: + return new CalendarCommand(); + + case CalendarAddCommand.COMMAND_WORD: + case CalendarAddCommand.COMMAND_ALIAS: + return new CalendarAddCommandParser().parse(arguments); + + case CalendarDeleteCommand.COMMAND_WORD: + case CalendarDeleteCommand.COMMAND_ALIAS: + return new CalendarDeleteCommandParser().parse(arguments); + + //@@author zacci + case LoginCommand.COMMAND_WORD: + return new LoginCommandParser().parse(arguments); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommand(); + + case CheckStatusCommand.COMMAND_WORD: + return new CheckStatusCommand(); + + case ShowUsersCommand.COMMAND_WORD: + return new ShowUsersCommand(); + + case AddUserCommand.COMMAND_WORD: + return new AddUserCommandParser().parse(arguments); + + case DeleteUserCommand.COMMAND_WORD: + return new DeleteUserCommandParser().parse(arguments); + //@@author case HistoryCommand.COMMAND_WORD: + case HistoryCommand.COMMAND_ALIAS: return new HistoryCommand(); case ExitCommand.COMMAND_WORD: @@ -79,11 +123,31 @@ public Command parseCommand(String userInput) throws ParseException { return new HelpCommand(); case UndoCommand.COMMAND_WORD: + case UndoCommand.COMMAND_ALIAS: return new UndoCommand(); case RedoCommand.COMMAND_WORD: + case RedoCommand.COMMAND_ALIAS: return new RedoCommand(); + //@@author sarahgoh97 + case ShowCellsCommand.COMMAND_WORD: + case ShowCellsCommand.COMMAND_ALIAS: + return new ShowCellsCommand(); + + case AddCellCommand.COMMAND_WORD: + case AddCellCommand.COMMAND_ALIAS: + return new AddCellCommandParser().parse(arguments); + + case DeleteCellCommand.COMMAND_WORD: + case DeleteCellCommand.COMMAND_ALIAS: + return new DeleteCellCommandParser().parse(arguments); + + case ListCellCommand.COMMAND_WORD: + case ListCellCommand.COMMAND_ALIAS: + return new ListCellCommandParser().parse(arguments); + //@@author + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/CalendarAddCommandParser.java b/src/main/java/seedu/address/logic/parser/CalendarAddCommandParser.java new file mode 100644 index 000000000000..16780532f35f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CalendarAddCommandParser.java @@ -0,0 +1,59 @@ +//@@author philos22 +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START; + +import java.time.format.DateTimeParseException; +import java.util.stream.Stream; + +import com.google.api.client.util.DateTime; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CalendarAddCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new CalendarAddCommandParser object + */ +public class CalendarAddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CalendarAddCommand + * and returns the message of whether execution was successful or not. + * @throws ParseException if the user input does not conform the expected format + */ + public CalendarAddCommand parse(String args) throws ParseException, DateTimeParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_EVENT, PREFIX_LOCATION, PREFIX_START, PREFIX_END); + + if (!arePrefixesPresent(argMultimap, PREFIX_EVENT, PREFIX_LOCATION, PREFIX_START, PREFIX_END)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CalendarAddCommand.MESSAGE_USAGE)); + } + + try { + String eventName = ParserUtil.parseName(argMultimap.getValue(PREFIX_EVENT)).get().toString(); + String eventLocation = ParserUtil.parseName(argMultimap.getValue(PREFIX_LOCATION)).get().toString(); + DateTime startDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_START).toString()); + DateTime endDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_END).toString()); + return new CalendarAddCommand(eventName, eventLocation, startDateTime, endDateTime); + + } catch (IllegalValueException | DateTimeParseException e) { + throw new ParseException(e.getMessage(), e); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/CalendarDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/CalendarDeleteCommandParser.java new file mode 100644 index 000000000000..0ad85f183031 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CalendarDeleteCommandParser.java @@ -0,0 +1,30 @@ +//@@author philos22 +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.CalendarDeleteCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new CalendarDeleteCommandParser object + */ +public class CalendarDeleteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CalendarDeleteCommand + * and returns the message of whether execution was successful or not. + * @throws ParseException if the user input does not conform the expected format + */ + public CalendarDeleteCommand parse(String args) throws ParseException { + try { + int intCheck = Integer.parseInt(args); + } catch (Exception e) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CalendarDeleteCommand.MESSAGE_USAGE)); + } + + return new CalendarDeleteCommand(args.trim()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..d226906fa70e 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,15 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); - + //@@author zacci + public static final Prefix PREFIX_ROLE = new Prefix("r/"); + public static final Prefix PREFIX_USERNAME = new Prefix("user/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("pw/"); + public static final Prefix PREFIX_SECURITY_LEVEL = new Prefix("sl/"); + //@@author philos22 + public static final Prefix PREFIX_EVENT = new Prefix("event/"); + public static final Prefix PREFIX_LOCATION = new Prefix("loc/"); + public static final Prefix PREFIX_START = new Prefix("start/"); + public static final Prefix PREFIX_END = new Prefix("end/"); + //@@author } diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCellCommandParser.java similarity index 59% rename from src/main/java/seedu/address/logic/parser/SelectCommandParser.java rename to src/main/java/seedu/address/logic/parser/DeleteCellCommandParser.java index bbcae8f4b588..12c3d8ff8e09 100644 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCellCommandParser.java @@ -1,29 +1,30 @@ +//@@author sarahgoh97 package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.DeleteCellCommand; import seedu.address.logic.parser.exceptions.ParseException; /** - * Parses input arguments and creates a new SelectCommand object + * Parses the given input and gives a new DeleteCellCommand object. */ -public class SelectCommandParser implements Parser { - +public class DeleteCellCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the SelectCommand - * and returns an SelectCommand object for execution. + * Parses the given {@code String} of arguments in the context of DeleteCellCommand + * and returns a DeleteCellCommand object for execution * @throws ParseException if the user input does not conform the expected format */ - public SelectCommand parse(String args) throws ParseException { + @Override + public DeleteCellCommand parse(String args) throws ParseException { try { Index index = ParserUtil.parseIndex(args); - return new SelectCommand(index); + return new DeleteCellCommand(index); } catch (IllegalValueException ive) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCellCommand.MESSAGE_USAGE)); } } } diff --git a/src/main/java/seedu/address/logic/parser/DeleteUserCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteUserCommandParser.java new file mode 100644 index 000000000000..e98eb2a025ea --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteUserCommandParser.java @@ -0,0 +1,53 @@ +//@@author zacci +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.DeleteUserCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteUserCommand object + */ +public class DeleteUserCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteUserCommand + * and returns an AddUserCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteUserCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteUserCommand.MESSAGE_USAGE)); + } + + String username = ""; + + try { + username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + + return new DeleteUserCommand(username); + + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index c9cdbed26cf1..05840fd638de 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -6,6 +6,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; @@ -32,8 +33,10 @@ public class EditCommandParser implements Parser { */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); + ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_ROLE, PREFIX_TAG); Index index; @@ -54,6 +57,12 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(ive.getMessage(), ive); } + //@@author sarahgoh97 + if (args.contains(PREFIX_ROLE.toString())) { + throw new ParseException(String.format(EditCommand.MESSAGE_CANNOT_CHANGE_ROLE)); + } + //@@author + if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index b186a967cb94..8354142322d7 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,12 +1,19 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Arrays; +import java.util.stream.Stream; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.ContainsKeywordsPredicate; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.TagContainsKeywordsPredicate; + + /** * Parses input arguments and creates a new FindCommand object @@ -19,15 +26,37 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { + //@@author philos22 + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TAG); + + if (!(arePrefixesPresent(argMultimap, PREFIX_NAME) || arePrefixesPresent(argMultimap, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty())) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + if ((arePrefixesPresent(argMultimap, PREFIX_NAME)) && (arePrefixesPresent(argMultimap, PREFIX_TAG))) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().split("\\s+"); + return new FindCommand(new ContainsKeywordsPredicate(Arrays.asList(nameKeywords), + Arrays.asList(tagKeywords))); + } else if (arePrefixesPresent(argMultimap, PREFIX_NAME)) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } else if (arePrefixesPresent(argMultimap, PREFIX_TAG)) { + String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().split("\\s+"); + return new FindCommand(new TagContainsKeywordsPredicate(Arrays.asList(tagKeywords))); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); } } diff --git a/src/main/java/seedu/address/logic/parser/ListCellCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCellCommandParser.java new file mode 100644 index 000000000000..978c014588c3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCellCommandParser.java @@ -0,0 +1,29 @@ +//@@author sarahgoh97 +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ListCellCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.AddressContainsCellPredicate; + +/** + * Parses the given input and gives a new ListCellCommand object. + */ +public class ListCellCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of ListCellCommand + * and returns a ListCellCommand object for execution + * @throws ParseException if the user input does not conform the expected format + */ + + @Override + public ListCellCommand parse(String args) throws ParseException { + args = args.trim(); + if (args.matches("\\d+-\\d+")) { + return new ListCellCommand(new AddressContainsCellPredicate(args), args); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCellCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/LoginCommandParser.java b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java new file mode 100644 index 000000000000..c94b9878d531 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java @@ -0,0 +1,55 @@ +//@@author zacci +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new LoginCommand object + */ +public class LoginCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the LoginCommand + * and returns an LoginCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public LoginCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + String username = ""; + String password = ""; + try { + username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)); + password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD)); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + + return new LoginCommand(username, password); + + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5d6d4ae3f7b1..54ec9f7cb96b 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,12 +1,19 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATETIME_FORMAT; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; import java.util.Collection; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import com.google.api.client.util.DateTime; + import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.StringUtil; @@ -14,6 +21,7 @@ import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; /** @@ -139,6 +147,30 @@ public static Optional parseEmail(Optional email) throws IllegalV return email.isPresent() ? Optional.of(parseEmail(email.get())) : Optional.empty(); } + /** + * Parses a {@code String role} into an {@code role}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code role} is invalid. + */ + public static Role parseRole(String role) throws IllegalValueException { + //requireNonNull(role); null accepted for now + String trimmedRole = role.trim(); + if (!Role.isValidRole(trimmedRole)) { + throw new IllegalValueException(Role.MESSAGE_ROLE_CONSTRAINTS); + } + return new Role(trimmedRole); + } + + /** + * Parses a {@code Optional email} into an {@code Optional} if {@code email} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseRole(Optional role) throws IllegalValueException { + //requireNonNull(role); null accepted for now + return role.isPresent() ? Optional.of(parseRole(role.get())) : Optional.empty(); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -165,4 +197,102 @@ public static Set parseTags(Collection tags) throws IllegalValueExc } return tagSet; } + + //@@author zacci + /** + * Parses a @code String username + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code password} is invalid. + */ + public static String parseUsername(String username) throws IllegalValueException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!trimmedUsername.matches("[\\p{Alnum}]*")) { + throw new IllegalValueException("Username can only consist of alphanumeric characters"); + } + return trimmedUsername; + } + + /** + * Parses a {@code Optional username} into an {@code Optional} if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static String parseUsername(Optional username) throws IllegalValueException { + //requireNonNull(username); null accepted for now + return username.isPresent() ? parseUsername(username.get()) : ""; + } + + /** + * Parses a @code String password + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code password} is invalid. + */ + public static String parsePassword(String password) throws IllegalValueException { + requireNonNull(password); + String trimmedPassword = password.trim(); + + if (!trimmedPassword.matches("[\\p{Alnum}]*")) { + throw new IllegalValueException("Password can only consist of alphanumeric characters"); + } + return trimmedPassword; + } + + /** + * Parses a {@code Optional password} into an {@code Optional} if {@code password} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static String parsePassword(Optional password) throws IllegalValueException { + requireNonNull(password); + return password.isPresent() ? parsePassword(password.get()) : ""; + } + + /** + * Parses a @code String securityLevel + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code securityLevel} is invalid. + */ + public static int parseSecurityLevel(String securityLevel) throws NumberFormatException, IllegalValueException { + requireNonNull(securityLevel); + String trimmedSecurityLevel = securityLevel.trim(); + + if (!trimmedSecurityLevel.matches("[123]")) { + throw new IllegalValueException("Security Level can only take integer values 1, 2 or 3"); + } + + int intSecurityLevel = Integer.parseInt(trimmedSecurityLevel); + return intSecurityLevel; + } + + /** + * Parses a {@code Optional securityLevel} into an {@code Optional} + * if {@code securityLevel} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static int parseSecurityLevel (Optional password) + throws NumberFormatException, IllegalValueException { + requireNonNull(password); + return password.isPresent() ? parseSecurityLevel(password.get()) : -1; + } + //@@author + + //@@author philos22 + /** + * Parses a {@code Optional DateTime} if present. + */ + public static DateTime parseDateTime(String dateTime) throws DateTimeParseException { + + String theDateTime = dateTime.replaceAll("[\\[\\]]", "").replaceAll("Optional", ""); + + try { + TemporalAccessor ta = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").parse(theDateTime); + String strDateTime = LocalDateTime.from(ta).toString() + ":00+08:00"; + return new DateTime(strDateTime); + } catch (DateTimeParseException e) { + throw new DateTimeParseException(MESSAGE_INVALID_DATETIME_FORMAT, theDateTime, 0, e); + } + } + } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index f8d0260de159..0237176cf91d 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.HashMap; import java.util.HashSet; @@ -11,12 +12,26 @@ import java.util.stream.Collectors; import javafx.collections.ObservableList; +import seedu.address.model.cell.Cell; +import seedu.address.model.cell.CellMap; +import seedu.address.model.cell.exceptions.AlreadyInCellException; +import seedu.address.model.cell.exceptions.FullCellException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.cell.exceptions.NotPrisonerException; import seedu.address.model.person.Person; +import seedu.address.model.person.Role; import seedu.address.model.person.UniquePersonList; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; +import seedu.address.model.user.UniqueUserMap; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; /** * Wraps all data at the address-book level @@ -26,6 +41,8 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; private final UniqueTagList tags; + private final CellMap cells; + private final UniqueUserMap users; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -37,12 +54,14 @@ public class AddressBook implements ReadOnlyAddressBook { { persons = new UniquePersonList(); tags = new UniqueTagList(); + cells = new CellMap(); + users = new UniqueUserMap(); } public AddressBook() {} /** - * Creates an AddressBook using the Persons and Tags in the {@code toBeCopied} + * Creates an AddressBook using the Persons and Tags and Cells in the {@code toBeCopied} */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); @@ -59,6 +78,9 @@ public void setTags(Set tags) { this.tags.setTags(tags); } + public void setCells(ObservableList cells) { + this.cells.setCells(cells); + } /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -74,6 +96,8 @@ public void resetData(ReadOnlyAddressBook newData) { } catch (DuplicatePersonException e) { throw new AssertionError("AddressBooks should not have duplicate persons"); } + setCells(newData.getCellList()); + setUsers(newData.getUserList()); } //// person-level operations @@ -112,7 +136,22 @@ public void updatePerson(Person target, Person editedPerson) // This can cause the tags master list to have additional tags that are not tagged to any person // in the person list. persons.setPerson(target, syncedEditedPerson); + //@@author sarahgoh97 + if (target.getIsInCell()) { + cells.setPrisonerToCell(target, syncedEditedPerson); + } + } + + /** + * Replaces the given person {@code changed} in the list with {@code original} in the cellMap. + * This is only done from undo. + */ + public void updatePrisonerFromUndo(Person changed, Person original) { + requireAllNonNull(original, changed); + + cells.setPrisonerToCell(changed, original); } + //@@author /** * Updates the master tag list to include tags in {@code person} that are not in the list. @@ -132,7 +171,8 @@ private Person syncWithMasterTagList(Person person) { final Set correctTagReferences = new HashSet<>(); personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), person.getRole(), + correctTagReferences, person.getIsInCell()); } /** @@ -141,6 +181,12 @@ private Person syncWithMasterTagList(Person person) { */ public boolean removePerson(Person key) throws PersonNotFoundException { if (persons.remove(key)) { + //@@author sarahgoh97 + if (key.getIsInCell() == true) { + String cellAddress = key.getCellAddress().toString(); + cells.deletePrisonerFromCell(key, cellAddress); + } + //@@author return true; } else { throw new PersonNotFoundException(); @@ -153,14 +199,111 @@ public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { tags.add(t); } + //@@author sarahgoh97 + //// cell-level operations + + /** + * + * @param c is the cell to add to the map + */ + public void addCell(Cell c) { + String cellAddress = c.getCellAddress(); + cells.setCell(c, cellAddress); + } + + /** + * Adds a prisoner to a cell + * @param cellAddress to get the correct cell + * @param prisoner to be added into the cell + * @throws FullCellException if the cell already has the maximum number of prisoners + * @throws NonExistentCellException if the cell address is invalid + * @throws NotPrisonerException if the Person passed is not a prisoner + */ + public void addPrisonerToCell(String cellAddress, Person prisoner) throws FullCellException, + NonExistentCellException, NotPrisonerException, AlreadyInCellException { + requireAllNonNull(prisoner, cellAddress); + if (!Cell.isValidCellAddress(cellAddress)) { + throw new NonExistentCellException(); + } else if (!prisoner.getRole().equals(Role.PRISONER)) { + throw new NotPrisonerException(); + } else if (prisoner.getIsInCell()) { + throw new AlreadyInCellException(); + } else if (cells.getCell(cellAddress).getNumberOfPrisoners() >= Cell.MAX_SIZE) { + throw new FullCellException(); + } else { + Person updatedPrisoner = new Person(prisoner, true, cellAddress); + updatePrisoner(prisoner, updatedPrisoner); + addPrisonerToCellPermitted(updatedPrisoner, cellAddress); + } + } + + /** + * Adding prisoner to CellMap once exceptions cleared + * @param prisoner is the correct person without requiring editing + * @param cellAddress is the String corresponding to the cell shown on map + */ + public void addPrisonerToCellPermitted(Person prisoner, String cellAddress) { + cells.addPrisonerToCell(prisoner, cellAddress); + } + + /** + * Deletes prisoner from a specified cell + */ + public void deletePrisonerFromCell(Person prisoner, String cellAddress) { + cells.deletePrisonerFromCell(prisoner, cellAddress); + } + + /** + * Replaces the given person {@code target} in the list with {@code updatedPrisoner}. + */ + public void updatePrisoner(Person target, Person updatedPrisoner) { + persons.setPrisoner(target, updatedPrisoner); + } + //// util methods @Override public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; + return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags\n" + + cells.getCellList() + users.getUserList(); // TODO: refine later } + //@@author zacci + //// user-level operations + + /** + * + * @param u is the user to add to the HashMap + */ + public void addUser(User u) throws UserAlreadyExistsException { + users.addUser(u); + } + + /** + * Attempt to log in with the entered username and password + */ + public int attemptLogin(String username, String password) { + return users.verify(username, password); + } + + @Override + public ObservableList getUserList() { + return users.getUserList(); + } + + public void setUsers(ObservableList users) { + this.users.setUsers(users); + } + + public void deleteUser(String userToDelete, String deleterUsername) throws CannotDeleteSelfException, + MustHaveAtLeastOneSecurityLevelThreeUserException, UserDoesNotExistException, + NotEnoughAuthorityToDeleteException { + users.deleteUser(userToDelete, deleterUsername); + } + + //@@author + @Override public ObservableList getPersonList() { return persons.asObservableList(); @@ -171,12 +314,21 @@ public ObservableList getTagList() { return tags.asObservableList(); } + //@@author sarahgoh97 + @Override + public ObservableList getCellList() { + return cells.getCellList(); + } + //@@author + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls && this.persons.equals(((AddressBook) other).persons) - && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); + && this.tags.equalsOrderInsensitive(((AddressBook) other).tags) + && this.cells.equals(((AddressBook) other).cells) + && this.users.equals(((AddressBook) other).users)); } @Override diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4a6079ce0199..ec257c36e09a 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,9 +3,20 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; +import seedu.address.model.cell.exceptions.AlreadyInCellException; +import seedu.address.model.cell.exceptions.FullCellException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.cell.exceptions.NotImprisonedException; +import seedu.address.model.cell.exceptions.NotPrisonerException; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; /** * The API of the Model component. @@ -20,12 +31,60 @@ public interface Model { /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); + //@@author zacci + /** Returns the session */ + Session getSession(); + + /** Checks if there is a user logged in to the current session*/ + boolean checkIsLoggedIn(); + + /** Clears existing session*/ + void logout(); + + /** Logs in verified user and assigns security level to the session */ + void login(String username, int securityLevel); + + /** Attempts to login user with entered username and password */ + boolean attemptLogin(String username, String password); + + /** Returns Session details to caller */ + String getSessionDetails(); + + /** Returns Session security level to caller */ + int getSecurityLevel(); + + /** Adds given user to the PrisonBook */ + void addUser(User user) throws UserAlreadyExistsException; + + /** Adds given user to the PrisonBook */ + void deleteUser(String user) throws CannotDeleteSelfException, MustHaveAtLeastOneSecurityLevelThreeUserException, + UserDoesNotExistException, NotEnoughAuthorityToDeleteException; + //@@author + /** Deletes the given person. */ void deletePerson(Person target) throws PersonNotFoundException; /** Adds the given person */ void addPerson(Person person) throws DuplicatePersonException; + //@@author sarahgoh97 + /** Adds given prisoner into a cell */ + void addPrisonerToCell(Person prisoner, String cellAddress) + throws FullCellException, NonExistentCellException, + NotPrisonerException, AlreadyInCellException; + + /**Deletes given prisoner from a cell from DeleteCellCommand */ + void deletePrisonerFromCell(Person prisoner) throws PersonNotFoundException, NotImprisonedException; + + /**Adds given prisoner back into a cell from undo command */ + void addPrisonerToCellFromUndo(Person prisoner, String cellAddress); + + /** Deletes given prisoner from a cell from undo command*/ + void deletePrisonerFromCellFromUndo(Person prisoner, String cellAddress); + + /** Updates given prisoner who changed from undo command*/ + void updatePrisonerFromUndo(Person changed, Person original); + //@@author /** * Replaces the given person {@code target} with {@code editedPerson}. * @@ -45,4 +104,11 @@ void updatePerson(Person target, Person editedPerson) */ void updateFilteredPersonList(Predicate predicate); + //@@author sarahgoh97 + /** + * Updates the filter of the filtered person list to show only people in the cell. + */ + void updateFilteredPersonListForCell(Predicate predicate, String cellAddress) + throws NonExistentCellException; + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 22a7d0eb3f4d..600919b2c88a 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.ArrayList; import java.util.function.Predicate; import java.util.logging.Logger; @@ -12,9 +13,22 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.cell.Cell; +import seedu.address.model.cell.exceptions.AlreadyInCellException; +import seedu.address.model.cell.exceptions.FullCellException; +import seedu.address.model.cell.exceptions.NonExistentCellException; +import seedu.address.model.cell.exceptions.NotImprisonedException; +import seedu.address.model.cell.exceptions.NotPrisonerException; +import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; /** * Represents the in-memory model of the address book data. @@ -25,9 +39,11 @@ public class ModelManager extends ComponentManager implements Model { private final AddressBook addressBook; private final FilteredList filteredPersons; + private final Session session; /** * Initializes a ModelManager with the given addressBook and userPrefs. + * Also initialises an empty cellMap and empty session */ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { super(); @@ -37,6 +53,13 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { this.addressBook = new AddressBook(addressBook); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + updateFilteredPersonList(new NameContainsKeywordsPredicate(new ArrayList())); + + //@@author zacci + logger.info("Initialising session"); + session = new Session(); + logger.info("Initialised session"); + //@@ author } public ModelManager() { @@ -54,6 +77,69 @@ public ReadOnlyAddressBook getAddressBook() { return addressBook; } + //@@author zacci + @Override + public Session getSession() { + return session; + } + + @Override + public void logout() { + session.logout(); + logger.info("User logged out"); + } + + @Override + public boolean checkIsLoggedIn() { + return session.checkIsLoggedIn(); + } + + @Override + public void login(String username, int securityLevel) { + session.login(username, securityLevel); + logger.info("User logged in with: u/" + username + " slevel/" + securityLevel); + } + + @Override + public boolean attemptLogin(String username, String password) { + logger.info("Current session: " + getSessionDetails()); + int securityLevel = addressBook.attemptLogin(username, password); + if (securityLevel < 0) { + return false; + } else { + login(username, securityLevel); + indicateAddressBookChanged(); + return true; + } + } + + @Override + public String getSessionDetails() { + return ("Username: " + session.getUsername() + " Security Level: " + session.getSecurityLevel()); + } + + @Override + public int getSecurityLevel() { + return session.getSecurityLevel(); + } + + @Override + public void addUser(User userToAdd) throws UserAlreadyExistsException { + addressBook.addUser(userToAdd); + indicateAddressBookChanged(); + logger.info("New user added: " + userToAdd.getUsername()); + } + + @Override + public void deleteUser (String userToDelete) throws CannotDeleteSelfException, + MustHaveAtLeastOneSecurityLevelThreeUserException, UserDoesNotExistException, + NotEnoughAuthorityToDeleteException { + addressBook.deleteUser(userToDelete, session.getUsername()); + indicateAddressBookChanged(); + logger.info("User deleted: " + userToDelete); + } + //@@author + /** Raises an event to indicate the model has changed */ private void indicateAddressBookChanged() { raise(new AddressBookChangedEvent(addressBook)); @@ -81,6 +167,65 @@ public void updatePerson(Person target, Person editedPerson) indicateAddressBookChanged(); } + //@@author sarahgoh97 + @Override + public void updatePrisonerFromUndo(Person changed, Person original) { + addressBook.updatePrisonerFromUndo(changed, original); + indicateAddressBookChanged(); + } + + @Override + public void addPrisonerToCell(Person prisoner, String cellAddress) + throws FullCellException, NonExistentCellException, + NotPrisonerException, AlreadyInCellException { + requireAllNonNull(prisoner, cellAddress); + addressBook.addPrisonerToCell(cellAddress, prisoner); + indicateAddressBookChanged(); + } + + /** + * Deletes a prisoner from a cell from DeleteCellCommand + * @param prisoner to be removed from cell + * @throws PersonNotFoundException if prisoner does not exist + * @throws NotImprisonedException if person chosen is not imprisoned here + */ + @Override + public void deletePrisonerFromCell(Person prisoner) throws PersonNotFoundException, NotImprisonedException { + requireNonNull(prisoner); + if (!filteredPersons.contains(prisoner)) { + throw new PersonNotFoundException(); + } else { + String cellAddress = prisoner.getAddress().toString(); + if (prisoner.getIsInCell()) { + cellAddress = cellAddress.substring(0, cellAddress.indexOf(" ")); + addressBook.deletePrisonerFromCell(prisoner, cellAddress); + Person freedPrisoner = new Person(prisoner, false); + addressBook.updatePrisoner(prisoner, freedPrisoner); + } else { + throw new NotImprisonedException(); + } + } + indicateAddressBookChanged(); + } + + /* this is to undo AddCellCommand*/ + @Override + public void deletePrisonerFromCellFromUndo(Person prisoner, String cellAddress) { + requireAllNonNull(prisoner, cellAddress); + Person updatedPrisoner = new Person(prisoner, true, cellAddress); + addressBook.deletePrisonerFromCell(updatedPrisoner, cellAddress); + indicateAddressBookChanged(); + } + + /* this is to undo DeleteCellCommand*/ + @Override + public void addPrisonerToCellFromUndo(Person prisoner, String cellAddress) { + requireAllNonNull(prisoner, cellAddress); + addressBook.addPrisonerToCellPermitted(prisoner, cellAddress); + indicateAddressBookChanged(); + } + //@@author + //=========== Filtered Person List Accessors ============================================================= /** @@ -112,8 +257,27 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && filteredPersons.equals(other.filteredPersons); + return addressBook.equals(other.addressBook) && filteredPersons.equals(other.filteredPersons); + } + + //@@author sarahgoh97 + /* This is for ListCellCommand */ + @Override + public void updateFilteredPersonListForCell(Predicate predicate, String cellAddress) + throws NonExistentCellException { + requireNonNull(predicate); + requireNonNull(cellAddress); + if (Cell.isValidCellAddress(cellAddress)) { + filteredPersons.setPredicate(predicate); + } else { + throw new NonExistentCellException(); + } + } + //@@author + + @Override + public String toString() { + return filteredPersons.toString(); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 1f4e49a37d67..bc84e79ed07a 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,8 +1,10 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.cell.Cell; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.model.user.User; /** * Unmodifiable view of an address book @@ -21,4 +23,19 @@ public interface ReadOnlyAddressBook { */ ObservableList getTagList(); + //@@author sarahgoh97 + /** + * Returns an unmodifiable view of the cells list. + * This list will not contain any duplicate cells. + */ + ObservableList getCellList(); + + //@@author zacci + /** + * Returns an unmodifiable view of the users list. + * This list will not contain any duplicate users. + */ + ObservableList getUserList(); + //@@author + } diff --git a/src/main/java/seedu/address/model/Session.java b/src/main/java/seedu/address/model/Session.java new file mode 100644 index 000000000000..94fcf1e47be4 --- /dev/null +++ b/src/main/java/seedu/address/model/Session.java @@ -0,0 +1,65 @@ +//@@author zacci +package seedu.address.model; + +/** + * Represents User's Session. + */ +public class Session { + + private String username; + private int securityLevel; + private boolean isLoggedIn = false; + + public Session() { + resetSession(); + } + + /** + * Sets session details for user upon successful login + */ + public void login(String username, int securityLevel) { + this.username = username; + this.securityLevel = securityLevel; + this.isLoggedIn = true; + } + + public void logout() { + resetSession(); + } + + private void resetSession() { + username = ""; + securityLevel = 0; + isLoggedIn = false; + } + + public String getUsername() { + return username; + } + + public int getSecurityLevel() { + return securityLevel; + } + + public boolean checkIsLoggedIn () { + return isLoggedIn; + } + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof Session)) { + return false; + } + + // state check + Session other = (Session) obj; + return username.equals(other.username) + && securityLevel == other.securityLevel; + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 8c8a071876eb..a417ee719dbe 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -10,8 +10,8 @@ public class UserPrefs { private GuiSettings guiSettings; - private String addressBookFilePath = "data/addressbook.xml"; - private String addressBookName = "MyAddressBook"; + private String addressBookFilePath = "data/prisonbook.xml"; + private String addressBookName = "MyPrisonBook"; public UserPrefs() { this.setGuiSettings(500, 500, 0, 0); diff --git a/src/main/java/seedu/address/model/cell/Cell.java b/src/main/java/seedu/address/model/cell/Cell.java new file mode 100644 index 000000000000..2b9b70990a1a --- /dev/null +++ b/src/main/java/seedu/address/model/cell/Cell.java @@ -0,0 +1,96 @@ +//@@author sarahgoh97 +package seedu.address.model.cell; + +import java.util.ArrayList; + +import seedu.address.model.person.Person; + +/** + * Represents a cell in the prison. + * Guarantees: Cells created are all part of the CellMap + */ +public class Cell { + + public static final int MAX_SIZE = 2; + private final ArrayList prisoners; + private String cellAddress; + private boolean isLast; + + /** + * Represents a cell in the Prison. + */ + public Cell(int row, int column) { + prisoners = new ArrayList(MAX_SIZE); + cellAddress = row + "-" + column; + if (column == CellMap.MAX_COL) { + isLast = true; + } else { + isLast = false; + } + } + + /** + * A copied cell + */ + public Cell(ArrayList prisoners, String cellAddress, boolean isLast) { + this.prisoners = new ArrayList(prisoners); + this.cellAddress = cellAddress; + this.isLast = isLast; + } + + public static int getCol(String cellAddress) { + return Integer.parseInt(cellAddress.substring(cellAddress.indexOf("-") + 1)); + } + + public static int getRow(String cellAddress) { + return Integer.parseInt(cellAddress.substring(0, cellAddress.indexOf("-"))); + } + + public void addPrisoner(Person prisoner) { + prisoners.add(prisoner); + } + + public void deletePrisoner(Person prisoner) { + prisoners.remove(prisoner); + } + + public ArrayList getPrisoners() { + return prisoners; + } + + public String getCellAddress() { + return cellAddress; + } + + public int getNumberOfPrisoners() { + return prisoners.size(); + } + + public boolean getIsLast() { + return isLast; + } + + /** + * Returns true if a given string is a valid cell. + */ + public static boolean isValidCellAddress(String test) { + if (test.matches("\\d+-\\d+")) { + int row = getRow(test); + int col = getCol(test); + return row <= CellMap.MAX_ROW && row > 0 + && col <= CellMap.MAX_COL && col > 0; + } else { + return false; + } + } + + @Override + public String toString() { + String string = getCellAddress() + " [" + getNumberOfPrisoners() + "]"; + if (isLast) { + return string + "\n"; + } + return string; + } + +} diff --git a/src/main/java/seedu/address/model/cell/CellMap.java b/src/main/java/seedu/address/model/cell/CellMap.java new file mode 100644 index 000000000000..2f526a0f35ae --- /dev/null +++ b/src/main/java/seedu/address/model/cell/CellMap.java @@ -0,0 +1,163 @@ +//@@author sarahgoh97 +package seedu.address.model.cell; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.person.Person; + +/** + * Contains the cells of the prison for the PrisonBook + * Cells are not allowed to exceed the maximum fixed size of the prison + */ +public class CellMap { + public static final int MAX_ROW = 3; + public static final int MAX_COL = 5; + private Cell[][] cellMap; + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Represents a fixed-sized map of the cells in the prison. + * Initialised at start of program. + */ + public CellMap() { + resetData(); + } + + /** + * @param cellAddress has to be within boundaries + * @return Cell from cellAddress + */ + public Cell getCell(String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + int index = getIndex(row, col); + return internalList.get(index); + } + + public void setCells(ObservableList cells) { + for (Cell c: cells) { + setCell(c, c.getCellAddress()); + } + internalList.clear(); + internalList.setAll(cells); + } + + public void setCell(Cell cell, String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + cellMap[row][col] = cell; + int num = getIndex(row, col); + if (num >= internalList.size()) { + internalList.add(cell); + } else { + internalList.set(num, cell); + } + } + + private int getCellMapRow(String cellAddress) { + return Cell.getRow(cellAddress) - 1; + } + + private int getCellMapCol(String cellAddress) { + return Cell.getCol(cellAddress) - 1; + } + + /** + * Adds a prisoner to a specified cell. + */ + public void addPrisonerToCell(Person prisoner, String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + addPrisonerToCell(prisoner, row, col); + } + + /** + * private method called from public method above + */ + private void addPrisonerToCell(Person prisoner, int row, int col) { + Cell cell = cellMap[row][col]; + cellMap[row][col].addPrisoner(prisoner); + int index = getIndex(row, col); + internalList.set(index, cell); + } + + private int getIndex(int row, int col) { + return row * MAX_COL + col; + } + + /** + * Removes a prisoner from a specified cell + */ + public void deletePrisonerFromCell(Person prisoner, String cellAddress) { + int row = getCellMapRow(cellAddress); + int col = getCellMapCol(cellAddress); + deletePrisonerFromCell(prisoner, row, col); + } + + /** + * private method called from public method above + */ + private void deletePrisonerFromCell(Person prisoner, int row, int col) { + Cell cell = cellMap[row][col]; + cell.deletePrisoner(prisoner); + int index = getIndex(row, col); + internalList.set(index, cell); + } + + /** + * Sets new edited person to original cell + * Precondition: target must be imprisoned + */ + public void setPrisonerToCell(Person target, Person updatedPrisoner) { + assert(target.getIsInCell()); + assert(updatedPrisoner.getIsInCell()); + String cellAddress = target.getCellAddress().toString(); + deletePrisonerFromCell(target, cellAddress); + addPrisonerToCell(updatedPrisoner, cellAddress); + } + + /** + * For storage purposes + */ + public ObservableList getCellList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Resets the existing data of this {@code CellMap}. + */ + public void resetData() { + cellMap = new Cell[MAX_ROW][MAX_COL]; + internalList.clear(); + for (int currRow = 0; currRow < MAX_ROW; currRow++) { + for (int currCol = 0; currCol < MAX_COL; currCol++) { + Cell cell = new Cell(currRow + 1, currCol + 1); + cellMap[currRow][currCol] = cell; + internalList.add(cell); + } + } + } + + /** + * Returns a map of the cells and their addresses + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Cell[] cArray: cellMap) { + for (Cell c: cArray) { + sb.append(c.getCellAddress() + " [" + c.getNumberOfPrisoners() + "] "); + } + sb.delete(sb.length() - 1, sb.length()); + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CellMap // instanceof handles nulls + && this.toString().equals((other.toString()))); + } +} diff --git a/src/main/java/seedu/address/model/cell/exceptions/AlreadyInCellException.java b/src/main/java/seedu/address/model/cell/exceptions/AlreadyInCellException.java new file mode 100644 index 000000000000..3d1c5b763f4f --- /dev/null +++ b/src/main/java/seedu/address/model/cell/exceptions/AlreadyInCellException.java @@ -0,0 +1,13 @@ +//@@author sarahgoh97 +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the prisoner being added is already in a cell in the prison. + */ +public class AlreadyInCellException extends IllegalValueException { + public AlreadyInCellException() { + super("Prisoner already in prison."); + } +} diff --git a/src/main/java/seedu/address/model/cell/exceptions/FullCellException.java b/src/main/java/seedu/address/model/cell/exceptions/FullCellException.java new file mode 100644 index 000000000000..add9619b5dc4 --- /dev/null +++ b/src/main/java/seedu/address/model/cell/exceptions/FullCellException.java @@ -0,0 +1,13 @@ +//@@author sarahgoh97 +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the operation will be adding a prisoner to a full cell. + */ +public class FullCellException extends IllegalValueException { + public FullCellException() { + super("Invalid cell. Cell is already full."); + } +} diff --git a/src/main/java/seedu/address/model/cell/exceptions/NonExistentCellException.java b/src/main/java/seedu/address/model/cell/exceptions/NonExistentCellException.java new file mode 100644 index 000000000000..49f14b87ed63 --- /dev/null +++ b/src/main/java/seedu/address/model/cell/exceptions/NonExistentCellException.java @@ -0,0 +1,13 @@ +//@@author sarahgoh97 +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the cell indicated is not within boundaries + */ +public class NonExistentCellException extends IllegalValueException { + public NonExistentCellException() { + super("No such cell exists."); + } +} diff --git a/src/main/java/seedu/address/model/cell/exceptions/NotImprisonedException.java b/src/main/java/seedu/address/model/cell/exceptions/NotImprisonedException.java new file mode 100644 index 000000000000..9101e0d45ea4 --- /dev/null +++ b/src/main/java/seedu/address/model/cell/exceptions/NotImprisonedException.java @@ -0,0 +1,13 @@ +//@@author sarahgoh97 +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the operation will be removing a person not in jail from the jail. + */ +public class NotImprisonedException extends IllegalValueException { + public NotImprisonedException() { + super("This person is not imprisoned in this prison."); + } +} diff --git a/src/main/java/seedu/address/model/cell/exceptions/NotPrisonerException.java b/src/main/java/seedu/address/model/cell/exceptions/NotPrisonerException.java new file mode 100644 index 000000000000..f678a3762360 --- /dev/null +++ b/src/main/java/seedu/address/model/cell/exceptions/NotPrisonerException.java @@ -0,0 +1,13 @@ +//@@author sarahgoh97 +package seedu.address.model.cell.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the person added to a cell is not a prisoner + */ +public class NotPrisonerException extends IllegalValueException { + public NotPrisonerException() { + super("Invalid person to add, not a prisoner."); + } +} diff --git a/src/main/java/seedu/address/model/person/AddressContainsCellPredicate.java b/src/main/java/seedu/address/model/person/AddressContainsCellPredicate.java new file mode 100644 index 000000000000..7af6f78d6bdf --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressContainsCellPredicate.java @@ -0,0 +1,35 @@ +//@@author sarahgoh97 +package seedu.address.model.person; + +import java.util.function.Predicate; + +/** + * Tests that the person is in the cell + */ +public class AddressContainsCellPredicate implements Predicate { + private final String cellAddress; + + public AddressContainsCellPredicate(String cellAddress) { + this.cellAddress = cellAddress; + } + + @Override + public boolean test(Person person) { + if (person.getIsInCell()) { + if (person.getCellAddress().toString().equals(cellAddress)) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsCellPredicate // instanceof handles nulls + && this.cellAddress.equals(((AddressContainsCellPredicate) other).cellAddress)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java new file mode 100644 index 000000000000..b00f92694cff --- /dev/null +++ b/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +//@@author philos22 +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests that a {@code Person}'s fields matches any of the keywords given. + */ +public class ContainsKeywordsPredicate implements Predicate { + private final Predicate namePredicate; + private final Predicate tagPredicate; + + public ContainsKeywordsPredicate(List nameKeywords, List tagKeywords) { + this.namePredicate = new NameContainsKeywordsPredicate(nameKeywords); + this.tagPredicate = new TagContainsKeywordsPredicate(tagKeywords); + } + + + @Override + public boolean test(Person person) { + return namePredicate.test(person) && tagPredicate.test(person); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof ContainsKeywordsPredicate // Used to handle Nulls + && this.namePredicate.equals(((ContainsKeywordsPredicate) other).namePredicate) + && this.tagPredicate.equals(((ContainsKeywordsPredicate) other).tagPredicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index ec9f2aa5e919..25ed962ebf5a 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -1,5 +1,6 @@ package seedu.address.model.person; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Collections; @@ -19,20 +20,74 @@ public class Person { private final Phone phone; private final Email email; private final Address address; + private final Role role; private final UniqueTagList tags; + private boolean isInCell = false; + /** + * New Constructor for working * Every field must be present and not null. + * This is for adding people into the prisonbook database and by default, they are not imprisoned */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { + public Person(Name name, Phone phone, Email email, Address address, Role role, Set tags) { + requireAllNonNull(name, phone, email, address, tags); + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.role = role; + // protect internal tags from changes in the arg list + this.tags = new UniqueTagList(tags); + } + + //for storage purposes and tests + public Person(Name name, Phone phone, Email email, Address address, Role role, Set tags, boolean isInCell) { requireAllNonNull(name, phone, email, address, tags); this.name = name; this.phone = phone; this.email = email; this.address = address; + this.role = role; // protect internal tags from changes in the arg list this.tags = new UniqueTagList(tags); + this.isInCell = isInCell; + } + + /** + * Constructor for updatedPrisoner after being added to cell + * @param person is prisoner being added to cell + * @param isInCell is true if adding to cell + * @param cellAddress is cell prisoner is entering + */ + public Person (Person person, boolean isInCell, String cellAddress) { + requireAllNonNull(person, isInCell); + this.name = person.getName(); + this.phone = person.getPhone(); + this.email = person.getEmail(); + this.address = new Address(cellAddress + " [Old address: " + person.getAddress().toString() + "]"); + this.role = person.getRole(); + this.tags = new UniqueTagList(person.getTags()); + this.isInCell = isInCell; + } + + /** + * Constructor for freedPrisoner after being deleted from cell + * @param person deleted from cell + * @param isInCell is false because deleting from cell + */ + public Person (Person person, boolean isInCell) { + requireNonNull(person); + this.name = person.getName(); + this.phone = person.getPhone(); + this.email = person.getEmail(); + String addressString = person.getAddress().toString(); + this.address = new Address(addressString.substring( + addressString.indexOf(": ") + 2, addressString.indexOf("]"))); + this.role = person.getRole(); + this.tags = new UniqueTagList(person.getTags()); + this.isInCell = isInCell; } public Name getName() { @@ -51,6 +106,23 @@ public Address getAddress() { return address; } + //@@author sarahgoh97 + //only called if prisoner isInCell + public Address getCellAddress() { + assert(isInCell); + String cellAddress = address.value.substring(0, address.value.indexOf("[") - 1); + return new Address(cellAddress); + } + //@@author + + public Role getRole() { + return role; + } + + public boolean getIsInCell() { + return isInCell; + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -73,7 +145,9 @@ public boolean equals(Object other) { return otherPerson.getName().equals(this.getName()) && otherPerson.getPhone().equals(this.getPhone()) && otherPerson.getEmail().equals(this.getEmail()) - && otherPerson.getAddress().equals(this.getAddress()); + && otherPerson.getAddress().equals(this.getAddress()) + && otherPerson.getRole().equals(this.getRole()) + && otherPerson.getIsInCell() == this.getIsInCell(); } @Override @@ -92,6 +166,8 @@ public String toString() { .append(getEmail()) .append(" Address: ") .append(getAddress()) + .append(" Role: ") + .append(getRole()) .append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 11b5435ac247..f88d22e5f4f9 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,8 +11,8 @@ public class Phone { public static final String MESSAGE_PHONE_CONSTRAINTS = - "Phone numbers can only contain numbers, and should be at least 3 digits long"; - public static final String PHONE_VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers can only contain numbers, and should be at least 3 digits long and at most 15 digits long."; + public static final String PHONE_VALIDATION_REGEX = "\\d{3,15}"; public final String value; /** diff --git a/src/main/java/seedu/address/model/person/Prisoner.java b/src/main/java/seedu/address/model/person/Prisoner.java new file mode 100644 index 000000000000..f3bea3b11992 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Prisoner.java @@ -0,0 +1,70 @@ +//@@author philos22-unused +package seedu.address.model.person; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; + + +/** + * Prisoner class is child class of Person: but each instance has a release date + */ +public class Prisoner extends Person { + + private final ReleaseDate releaseDate; + + public Prisoner(Name name, Phone phone, Email email, Address address, Role role, Set tags, + ReleaseDate releaseDate) { + super(name, phone, email, address, role, tags); + requireAllNonNull(name, phone, email, address, tags, releaseDate); + this.releaseDate = releaseDate; + } + + public ReleaseDate getRelease_date() { + return releaseDate; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Prisoner)) { + return false; + } + + Prisoner otherPerson = (Prisoner) other; + return otherPerson.getName().equals(this.getName()) + && otherPerson.getPhone().equals(this.getPhone()) + && otherPerson.getEmail().equals(this.getEmail()) + && otherPerson.getAddress().equals(this.getAddress()) + && otherPerson.getRelease_date().equals(this.getRelease_date()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(this.getName(), this.getPhone(), this.getEmail(), this.getAddress(), this.getTags(), + this.getRelease_date()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Address: ") + .append(getAddress()) + .append(" Tags: "); + getTags().forEach(builder::append); + builder.append(" Release Date: ").append(getRelease_date()); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/ReleaseDate.java b/src/main/java/seedu/address/model/person/ReleaseDate.java new file mode 100644 index 000000000000..c72b5de6be9a --- /dev/null +++ b/src/main/java/seedu/address/model/person/ReleaseDate.java @@ -0,0 +1,52 @@ +//@@author philos22-unused +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the release date of the prisoner in date time format + */ +public class ReleaseDate { + + public static final String MESSAGE_RELEASE_DATE_CONSTRAINTS = "The date format: yyyy-MM-dd"; + // credit of the regex goes to - Ofir Luzon - on Stack Overflow + public static final String RELEASE_DATE_VALIDATION_REGEX = "^(?:(?:31(-)(?:0?[13578]|1[02]))\\1|" + + + "(?:(?:29|30)(-)(?:0?[1,3-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:29(-)0?2\\3" + + + "(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))" + + + "$|^(?:0?[1-9]|1\\d|2[0-8])(-)(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$"; + public final String value; + + + // Constructs valid release date with valid parameter + public ReleaseDate(String releaseDate) { + requireNonNull(releaseDate); + checkArgument(isValidReleaseDate(releaseDate), MESSAGE_RELEASE_DATE_CONSTRAINTS); + this.value = releaseDate; + } + + // Returns true if release date is valid + public static boolean isValidReleaseDate(String test) { + return test.matches(RELEASE_DATE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReleaseDate // instanceof handles nulls + && this.value.equals(((ReleaseDate) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Role.java b/src/main/java/seedu/address/model/person/Role.java new file mode 100644 index 000000000000..f7df49d03fe0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Role.java @@ -0,0 +1,55 @@ +//@@author zacci +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's role in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidRole(String)} + */ +public class Role { + + + public static final String MESSAGE_ROLE_CONSTRAINTS = + "Role can only take on the values 'g' or 'p', which represents Guard or Prisoner respectively"; + public static final Role PRISONER = new Role("p"); + public static final Role GUARD = new Role("g"); + public final String value; + + /** + * Constructs a {@code Role}. + * + * @param role 'g' or 'p' to represent Guard or Prisoner respectively. + */ + public Role(String role) { + requireNonNull(role); + checkArgument(isValidRole(role), MESSAGE_ROLE_CONSTRAINTS); + this.value = role; + } + + /** + * Returns true if a given string is a valid person phone number. + */ + public static boolean isValidRole(String test) { + return test.equals("g") || test.equals("p"); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Role // instanceof handles nulls + && this.value.equals(((Role) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java new file mode 100644 index 000000000000..41d1d73f0b47 --- /dev/null +++ b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +//@@author philos22 +package seedu.address.model.person; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class TagContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + // Making a string of all tags + if (person.getTags().size() == 0) { + return false; + } + Iterator tagIteration = person.getTags().iterator(); + StringBuilder strBuild = new StringBuilder(); + strBuild.append(tagIteration.next()); + while (tagIteration.hasNext()) { + strBuild.append(" " + tagIteration.next()); + } + String tagList = strBuild.toString().replace("[", "").replace("]", ""); + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tagList, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index f2c4c4c585e4..ba8dc93a74fe 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -67,6 +67,16 @@ public void setPerson(Person target, Person editedPerson) internalList.set(index, editedPerson); } + //@@author sarahgoh97 + /** + * Replaces the person {@code target} in the list with {@code updatedPrisoner}. + */ + public void setPrisoner(Person target, Person updatedPrisoner) { + int index = internalList.indexOf(target); + internalList.set(index, updatedPrisoner); + } + //@@author + /** * Removes the equivalent person from the list. * diff --git a/src/main/java/seedu/address/model/user/UniqueUserMap.java b/src/main/java/seedu/address/model/user/UniqueUserMap.java new file mode 100644 index 000000000000..31e8651838fe --- /dev/null +++ b/src/main/java/seedu/address/model/user/UniqueUserMap.java @@ -0,0 +1,155 @@ +//@@author zacci +package seedu.address.model.user; + +import java.util.HashMap; + +import java.util.Iterator; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.user.exceptions.CannotDeleteSelfException; +import seedu.address.model.user.exceptions.MustHaveAtLeastOneSecurityLevelThreeUserException; +import seedu.address.model.user.exceptions.NotEnoughAuthorityToDeleteException; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; +import seedu.address.model.user.exceptions.UserDoesNotExistException; + +/** + * Contains the users of the PrisonBook + */ +public class UniqueUserMap { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + private HashMap userMap; + + private int numberOfSecurityLevelThree = 0; + + private final User defaultUser1 = new User("prisonguard", "password1", 1); + private final User defaultUser2 = new User("prisonleader", "password2", 2); + private final User defaultUser3 = new User("prisonwarden", "password3", 3); + + public UniqueUserMap() { + resetData(); + } + + /** + * Resets the existing data of this {@code userMap}. + */ + public void resetData() { + userMap = new HashMap(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Returns user's securityLevel if username and matching password is found in database, else return -1 + * @param username + * @param password + * @return + */ + public int verify(String username, String password) { + if (contains(username)) { + return userMap.get(username).checkPassword(password); + } else { + return -1; + } + } + + public boolean contains(String username) { + return userMap.containsKey(username); + } + + /** + * For storage purposes + */ + public ObservableList getUserList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Adds user to the userMap and the internalList + * @param user must be valid User + * @return true if added successfully and false if failed to add + */ + public boolean addUser(User user) throws UserAlreadyExistsException { + if (contains(user.getUsername())) { + throw new UserAlreadyExistsException(); + } else { + userMap.put(user.getUsername(), user); + internalList.add(user); + if (user.getSecurityLevel() == 3) { + numberOfSecurityLevelThree++; + } + return true; + } + } + + /** + * Delete user from the userMap and the internalList + * @param userToDelete must be an existing user + * @return true if added successfully and false if failed to add + */ + public boolean deleteUser(String userToDelete, String deleterUsername) throws UserDoesNotExistException, + NotEnoughAuthorityToDeleteException, CannotDeleteSelfException, + MustHaveAtLeastOneSecurityLevelThreeUserException { + int deleterSecurityLevel = userMap.get(deleterUsername).getSecurityLevel(); + if (!contains(userToDelete)) { + throw new UserDoesNotExistException(); + } else if (userToDelete.equals(deleterUsername)) { + throw new CannotDeleteSelfException(); + } else if (deleterSecurityLevel != 3 && userMap.get(userToDelete).getSecurityLevel() >= deleterSecurityLevel) { + throw new NotEnoughAuthorityToDeleteException(); + } else if (userMap.get(userToDelete).getSecurityLevel() == 3 && numberOfSecurityLevelThree <= 1) { + throw new MustHaveAtLeastOneSecurityLevelThreeUserException(); + } else { + userMap.remove(userToDelete); + Iterator iter = internalList.listIterator(); + for (int i = 0; i < internalList.size(); i++) { + User curr = iter.next(); + if (userToDelete.equals(curr.getUsername())) { + iter.remove(); + break; + } + } + return true; + } + } + + public void setUsers(ObservableList users) { + for (User u: users) { + try { + addUser(u); + } catch (UserAlreadyExistsException e) { + int dummy = 0; + } + } + internalList.clear(); + internalList.setAll(users); + } + + /** + * Checks if two UniqueUserMaps are equal + * @param obj any object + * @return return true if the userMap and internalList are equal + */ + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof UniqueUserMap)) { + return false; + } + + // state check + UniqueUserMap other = (UniqueUserMap) obj; + return userMap.equals(other.userMap) && internalList.equals(other.internalList); + } +} diff --git a/src/main/java/seedu/address/model/user/User.java b/src/main/java/seedu/address/model/user/User.java new file mode 100644 index 000000000000..adbcbf00b98a --- /dev/null +++ b/src/main/java/seedu/address/model/user/User.java @@ -0,0 +1,64 @@ +//@@author zacci +package seedu.address.model.user; + +/** + * Represents a user of the PrisonBook + */ +public class User { + + public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + private final String username; + private final String password; + private final int securityLevel; + + public User (String username, String password, int securityLevel) { + this.username = username; + this.password = password; + this.securityLevel = securityLevel; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public int getSecurityLevel() { + return securityLevel; + } + + /** + * Returns true if a given string is a valid username. + */ + public static boolean isValidName(String test) { + return test.matches(NAME_VALIDATION_REGEX); + } + + /** + * Checks if given string matches password, returns user securityLevel if password matches, else returns -1 + */ + public int checkPassword(String enteredPassword) { + if (enteredPassword.equals(password)) { + return securityLevel; + } else { + return -1; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof User // instanceof handles nulls + && username.equals(((User) other).username)); + } + + @Override + public String toString() { + return ("Username: " + username + " securityLevel: " + securityLevel); + } + +} + diff --git a/src/main/java/seedu/address/model/user/exceptions/CannotDeleteSelfException.java b/src/main/java/seedu/address/model/user/exceptions/CannotDeleteSelfException.java new file mode 100644 index 000000000000..16ca8d9ddf0c --- /dev/null +++ b/src/main/java/seedu/address/model/user/exceptions/CannotDeleteSelfException.java @@ -0,0 +1,16 @@ +package seedu.address.model.user.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the username that is being added already exists. + */ +public class CannotDeleteSelfException extends IllegalValueException { + + public static final String MESSAGE = "You cannot delete yourself"; + + public CannotDeleteSelfException() { + super("You cannot delete yourself"); + } + +} diff --git a/src/main/java/seedu/address/model/user/exceptions/MustHaveAtLeastOneSecurityLevelThreeUserException.java b/src/main/java/seedu/address/model/user/exceptions/MustHaveAtLeastOneSecurityLevelThreeUserException.java new file mode 100644 index 000000000000..f761c56a55e5 --- /dev/null +++ b/src/main/java/seedu/address/model/user/exceptions/MustHaveAtLeastOneSecurityLevelThreeUserException.java @@ -0,0 +1,12 @@ +package seedu.address.model.user.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the username that is being added already exists. + */ +public class MustHaveAtLeastOneSecurityLevelThreeUserException extends IllegalValueException { + public MustHaveAtLeastOneSecurityLevelThreeUserException() { + super("You cannot delete this user as there must be at least one user with Security Level 3"); + } +} diff --git a/src/main/java/seedu/address/model/user/exceptions/NotEnoughAuthorityToDeleteException.java b/src/main/java/seedu/address/model/user/exceptions/NotEnoughAuthorityToDeleteException.java new file mode 100644 index 000000000000..61c2e465e45c --- /dev/null +++ b/src/main/java/seedu/address/model/user/exceptions/NotEnoughAuthorityToDeleteException.java @@ -0,0 +1,17 @@ +package seedu.address.model.user.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the username that is being added already exists. + */ +public class NotEnoughAuthorityToDeleteException extends IllegalValueException { + + public static final String MESSAGE = "You do not have the authority to delete this user"; + + public NotEnoughAuthorityToDeleteException() { + super("You do not have the authority to delete this user"); + } + +} + diff --git a/src/main/java/seedu/address/model/user/exceptions/UserAlreadyExistsException.java b/src/main/java/seedu/address/model/user/exceptions/UserAlreadyExistsException.java new file mode 100644 index 000000000000..a07c8b6f2b1a --- /dev/null +++ b/src/main/java/seedu/address/model/user/exceptions/UserAlreadyExistsException.java @@ -0,0 +1,13 @@ +//@@author zacci +package seedu.address.model.user.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the username that is being added already exists. + */ +public class UserAlreadyExistsException extends IllegalValueException { + public UserAlreadyExistsException() { + super("This username is already used"); + } +} diff --git a/src/main/java/seedu/address/model/user/exceptions/UserDoesNotExistException.java b/src/main/java/seedu/address/model/user/exceptions/UserDoesNotExistException.java new file mode 100644 index 000000000000..604ac0fe7897 --- /dev/null +++ b/src/main/java/seedu/address/model/user/exceptions/UserDoesNotExistException.java @@ -0,0 +1,16 @@ +package seedu.address.model.user.exceptions; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Signals that the username that the user does not exist. + */ +public class UserDoesNotExistException extends IllegalValueException { + + public static final String MESSAGE = "The username does not exist"; + + public UserDoesNotExistException() { + super("The username does not exist"); + } + +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index aea96bfb31f3..477361b4be40 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,17 +1,22 @@ package seedu.address.model.util; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.cell.Cell; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; +import seedu.address.model.user.User; +import seedu.address.model.user.exceptions.UserAlreadyExistsException; /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -20,35 +25,136 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), + new Address("1-1 [Old address: Blk 30 Geylang Street 29, #06-40]"), new Role("p"), + getTagSet("ChickenAllergy"), true), new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Role("g"), + getTagSet("Vegetarian")), new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), + new Address("1-1 [Old address: Blk 11 Ang Mo Kio Street 74, #11-04]"), new Role("p"), + getTagSet("ViolentTendencies"), true), new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Role("g"), + getTagSet()), new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), + new Address("Blk 47 Tampines Street 20, #17-35"), new Role("p"), + getTagSet("HalalDiet")), new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Address("Blk 45 Aljunied Street 85, #11-31"), new Role("g"), + getTagSet("CounsellorTrained")), + new Person(new Name("John Tan"), new Phone("85112935"), new Email("johnt@example.com"), + new Address("Blk 504 Elias Road, #22-11"), new Role("g"), + getTagSet("Sharpshooter")), + new Person(new Name("Abdulluh Muhammad"), new Phone("91882734"), new Email("dulluhm@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), new Role("g"), + getTagSet("HalalDiet")), + new Person(new Name("Max Balakrishnan"), new Phone("98672261"), new Email("maxb@example.com"), + new Address("Blk 402 Kent Ridge Street 9, #11-31"), new Role("g"), + getTagSet("MMATrained")), + new Person(new Name("Xiang Tao"), new Phone("93255626"), new Email("xiangtao@example.com"), + new Address("38B Craig Road, 089676, Singapore"), new Role("g"), + getTagSet("PeanutAllergy")), + new Person(new Name("An Luo"), new Phone("89923718"), new Email("anluo@example.com"), + new Address("Blk 568 Ganges Avenue, 01-100 160568, Singapore"), new Role("g"), + getTagSet("NightShift")), + new Person(new Name("Shui Peng"), new Phone("94772831"), new Email("shuipeng@example.com"), + new Address("63 Arab Street 199760, Singapore"), new Role("p"), + getTagSet()), + new Person(new Name("Xinyi Pan"), new Phone("89917238"), new Email("xinyip@example.com"), + new Address("1 Pickering Street #07-03 Great Eastern Centre, 048659, Singapore"), new Role("p"), + getTagSet()), + new Person(new Name("Shi Mao"), new Phone("90197583"), new Email("shimao@example.com"), + new Address("10 Hoe Chiang Road #01-01 Keppel Towers, 089315, Singapore"), new Role("p"), + getTagSet()), + 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 Role("p"), + getTagSet("SubmissionJujitsuTrained")), + new Person(new Name("Yimu Huang"), new Phone("89371844"), new Email("yimuhuang@example.com"), + new Address("227 Ubi Avenue 4, 408815, Singapore"), new Role("p"), + getTagSet()), + new Person(new Name("Dan Zhan"), new Phone("97773917"), new Email("danzhan@example.com"), + new Address("166 Upper East Coast Road 455270, Singapore"), new Role("p"), + getTagSet()), + new Person(new Name("Rachiket Arya"), new Phone("99283411"), new Email("rachicketarya@example.com"), + new Address("143 Cecil Street #16-04 Gb Building, 069542, Singapore"), new Role("p"), + getTagSet()), + new Person(new Name("Hafiz Muhammad"), new Phone("89923645"), new Email("hafizm@example.com"), + new Address("Golden Sultan Plaza 100 Jalan Sultan #01-32 199001, Singapore"), new Role("p"), + getTagSet("HalalDiet")), + new Person(new Name("Vinodhanan SO Balakrishnan"), new Phone("88199374"), new Email("vinodb@example.com"), + new Address("Blk 402 Kent Ridge Street 9, #11-31"), new Role("p"), + getTagSet()), + new Person(new Name("Philos Tsai"), new Phone("81227783"), new Email("philostsai@example.com"), + new Address("11 Chancery Lane #01-04, 309502, Singapore"), new Role("p"), + getTagSet("French")), + new Person(new Name("Sarah Goh"), new Phone("90288173"), new Email("gohst@example.com"), + new Address("3-5 [Old address: Tung Ann Association Building 141 Cecil Street #03-02]"), + new Role("p"), getTagSet(), true) }; } + //@@author sarahgoh97 TODO + public static Cell[] getSampleCells() { + Person alex = new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("1-1 [Old address: Blk 30 Geylang Street 29, #06-40]"), new Role("p"), + getTagSet("ChickenAllergy"), true); + Person charlotte = new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), + new Address("1-1 [Old address: Blk 11 Ang Mo Kio Street 74, #11-04]"), new Role("p"), + getTagSet("ViolentTendencies"), true); + Person sarah = new Person(new Name("Sarah Goh"), new Phone("90288173"), new Email("gohst@example.com"), + new Address("3-5 [Old address: Tung Ann Association Building 141 Cecil Street #03-02]"), + new Role("p"), getTagSet(), true); + + return new Cell[] { + new Cell(getPrisoners(alex, charlotte), "1-1", false), + new Cell(1, 2), new Cell(1, 3), new Cell(1, 4), new Cell(1, 5), + new Cell(2, 1), new Cell(2, 2), new Cell(2, 3), new Cell(2, 4), new Cell(2, 5), + new Cell(3, 1), new Cell(3, 2), new Cell(3, 3), new Cell(3, 4), + new Cell(getPrisoners(sarah), "3-5", true) + }; + } + + public static ArrayList getPrisoners(Person... prisoners) { + ArrayList list = new ArrayList(); + for (Person p: prisoners) { + list.add(p); + } + return list; + } + + //@@author zacci + public static User[] getSampleUsers() { + return new User[] { + new User("prisonguard", "password1", 1), + new User("prisonleader", "password2", 2), + new User("prisonwarden", "password3", 3), + new User("prisonguard2", "password1", 1), + new User("prisonguard3", "password1", 1), + new User("prisonleader2", "password2", 2), + new User("prisonleader3", "password2", 2), + new User("prisonwarden2", "password3", 3) + }; + } + //@@author + public static ReadOnlyAddressBook getSampleAddressBook() { try { AddressBook sampleAb = new AddressBook(); for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + for (Cell sampleCell : getSampleCells()) { + sampleAb.addCell(sampleCell); + } + for (User sampleUser : getSampleUsers()) { + sampleAb.addUser(sampleUser); + } return sampleAb; } catch (DuplicatePersonException e) { throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (UserAlreadyExistsException uaee) { + throw new AssertionError("sample data cannot contain duplicate users", uaee); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedCell.java b/src/main/java/seedu/address/storage/XmlAdaptedCell.java new file mode 100644 index 000000000000..1fdbe3f1ebe1 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedCell.java @@ -0,0 +1,89 @@ +//@@author sarahgoh97 +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.cell.Cell; +import seedu.address.model.person.Person; + +/** + * JAXB-friendly version of the Cell. + */ +public class XmlAdaptedCell { + + public static final String INVALID_CELL = "This cell does not exist!"; + + @XmlElement (required = true) + private String cellAddress; + @XmlElement (required = true) + private List prisoners = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedCell. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedCell() {} + + /** + * Constructs a {@code XmlAdaptedCell} with the given Cell. + */ + public XmlAdaptedCell(String cellAddress, List prisoners) { + this.cellAddress = cellAddress; + if (prisoners != null) { + this.prisoners = new ArrayList<>(prisoners); + } + } + + /** + * Converts a given Cell into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedCell(Cell source) { + cellAddress = source.getCellAddress(); + for (Person person: source.getPrisoners()) { + prisoners.add(new XmlAdaptedPerson(person)); + } + } + + /** + * Converts this jaxb-friendly adapted Cell object into the model's Cell object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted cell + */ + public Cell toModelType() throws IllegalValueException { + if (this.cellAddress == null) { + throw new IllegalValueException(INVALID_CELL); + } + if (!Cell.isValidCellAddress(this.cellAddress)) { + throw new IllegalValueException(INVALID_CELL); + } + int row = Cell.getRow(cellAddress); + int col = Cell.getCol(cellAddress); + Cell cell = new Cell(row, col); + + for (XmlAdaptedPerson person : prisoners) { + cell.addPrisoner(person.toModelType()); + } + return cell; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedCell)) { + return false; + } + + XmlAdaptedCell otherCell = (XmlAdaptedCell) other; + return Objects.equals(cellAddress, otherCell.cellAddress) + && prisoners.equals(otherCell.prisoners); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java index 2cd92dc4fd20..b8fe02163dde 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java @@ -14,6 +14,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Role; import seedu.address.model.tag.Tag; /** @@ -31,6 +32,10 @@ public class XmlAdaptedPerson { private String email; @XmlElement(required = true) private String address; + @XmlElement(required = true) + private String role; + @XmlElement(required = true) + private boolean isInCell; @XmlElement private List tagged = new ArrayList<>(); @@ -44,14 +49,17 @@ public XmlAdaptedPerson() {} /** * Constructs an {@code XmlAdaptedPerson} with the given person details. */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { + public XmlAdaptedPerson(String name, String phone, String email, String address, String role, + List tagged) { this.name = name; this.phone = phone; this.email = email; this.address = address; + this.role = role; if (tagged != null) { this.tagged = new ArrayList<>(tagged); } + this.isInCell = false; } /** @@ -64,10 +72,12 @@ public XmlAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; + role = source.getRole().value; tagged = new ArrayList<>(); for (Tag tag : source.getTags()) { tagged.add(new XmlAdaptedTag(tag)); } + isInCell = source.getIsInCell(); } /** @@ -113,8 +123,19 @@ public Person toModelType() throws IllegalValueException { } final Address address = new Address(this.address); + if (this.role == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Role.isValidRole(this.role)) { + throw new IllegalValueException(Role.MESSAGE_ROLE_CONSTRAINTS); + } + final Role role = new Role(this.role); + final Set tags = new HashSet<>(personTags); - return new Person(name, phone, email, address, tags); + + final boolean isInCell = this.isInCell; + + return new Person(name, phone, email, address, role, tags, isInCell); } @Override @@ -132,6 +153,8 @@ public boolean equals(Object other) { && Objects.equals(phone, otherPerson.phone) && Objects.equals(email, otherPerson.email) && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); + && Objects.equals(role, otherPerson.role) + && tagged.equals(otherPerson.tagged) + && Objects.equals(isInCell, otherPerson.isInCell); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedUser.java b/src/main/java/seedu/address/storage/XmlAdaptedUser.java new file mode 100644 index 000000000000..6917a1280226 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedUser.java @@ -0,0 +1,69 @@ +//@@author zacci +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.model.user.User; + +/** + * JAXB-friendly version of the Person. + */ +public class XmlAdaptedUser { + + @XmlElement(required = true) + private String username; + @XmlElement(required = true) + private String password; + @XmlElement(required = true) + private int securityLevel; + + /** + * Constructs an XmlAdaptedUser. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedUser() {} + + /** + * Constructs an {@code XmlAdaptedPerson} with the given person details. + */ + public XmlAdaptedUser(String username, String password, int securityLevel) { + this.username = username; + this.password = password; + this.securityLevel = securityLevel; + } + + /** + * Converts a given User into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedUser + */ + public XmlAdaptedUser(User source) { + username = source.getUsername(); + password = source.getPassword(); + securityLevel = source.getSecurityLevel(); + } + + /** + * Converts this jaxb-friendly adapted user object into the model's User object. + * + */ + public User toModelType() { + return new User(username, password, securityLevel); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedUser)) { + return false; + } + + XmlAdaptedUser otherUser = (XmlAdaptedUser) other; + return this.username.equals(((XmlAdaptedUser) other).username) + && this.password.equals(((XmlAdaptedUser) other).password) + && this.securityLevel == (((XmlAdaptedUser) other).securityLevel); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java index c77ebe67435c..b4a76abc6720 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java @@ -52,7 +52,7 @@ public Optional readAddressBook(String filePath) throws Dat return Optional.empty(); } - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadAddressBookDataFromSaveFile(new File(filePath)); try { return Optional.of(xmlAddressBook.toModelType()); } catch (IllegalValueException ive) { @@ -76,7 +76,7 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th File file = new File(filePath); FileUtil.createIfMissing(file); - XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); + XmlFileStorage.saveAddressBookDataToFile(file, new XmlSerializableAddressBook(addressBook)); } } diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index 289fcb63038e..f93a58fe6933 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -15,7 +15,7 @@ public class XmlFileStorage { /** * Saves the given addressbook data to the specified file. */ - public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) + public static void saveAddressBookDataToFile(File file, XmlSerializableAddressBook addressBook) throws FileNotFoundException { try { XmlUtil.saveDataToFile(file, addressBook); @@ -27,7 +27,7 @@ public static void saveDataToFile(File file, XmlSerializableAddressBook addressB /** * Returns address book in the file or an empty address book */ - public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, + public static XmlSerializableAddressBook loadAddressBookDataFromSaveFile(File file) throws DataConversionException, FileNotFoundException { try { return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); @@ -35,5 +35,4 @@ public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws throw new DataConversionException(e); } } - } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index dc820896c312..7627524df516 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -21,6 +21,10 @@ public class XmlSerializableAddressBook { private List persons; @XmlElement private List tags; + @XmlElement + private List cells; + @XmlElement + private List users; /** * Creates an empty XmlSerializableAddressBook. @@ -29,6 +33,8 @@ public class XmlSerializableAddressBook { public XmlSerializableAddressBook() { persons = new ArrayList<>(); tags = new ArrayList<>(); + cells = new ArrayList<>(); + users = new ArrayList<>(); } /** @@ -38,6 +44,8 @@ public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + cells.addAll(src.getCellList().stream().map(XmlAdaptedCell::new).collect(Collectors.toList())); + users.addAll(src.getUserList().stream().map(XmlAdaptedUser::new).collect(Collectors.toList())); } /** @@ -54,6 +62,12 @@ public AddressBook toModelType() throws IllegalValueException { for (XmlAdaptedPerson p : persons) { addressBook.addPerson(p.toModelType()); } + for (XmlAdaptedCell c: cells) { + addressBook.addCell(c.toModelType()); + } + for (XmlAdaptedUser u: users) { + addressBook.addUser(u.toModelType()); + } return addressBook; } @@ -68,6 +82,7 @@ public boolean equals(Object other) { } XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); + return persons.equals(otherAb.persons) && tags.equals(otherAb.tags) && cells.equals(otherAb.cells) + && users.equals(otherAb.users); } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index bb0d61380d5a..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(this.getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); - } -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 20ad5fee906a..f83849d6058d 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,13 +34,17 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; + //private BrowserPanel browserPanel; + private MapPanel mapPanel; private PersonListPanel personListPanel; private Config config; private UserPrefs prefs; + //@FXML + //private StackPane browserPlaceholder; + @FXML - private StackPane browserPlaceholder; + private StackPane mapPanelPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -116,16 +120,20 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + //@@author sarahgoh97 + mapPanel = new MapPanel(); + mapPanelPlaceholder.getChildren().add(mapPanel.getRoot()); + //@@author + ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath(), + logic.getFilteredPersonList()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -185,10 +193,6 @@ public PersonListPanel getPersonListPanel() { return this.personListPanel; } - void releaseResources() { - browserPanel.freeResources(); - } - @Subscribe private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); diff --git a/src/main/java/seedu/address/ui/MapPanel.java b/src/main/java/seedu/address/ui/MapPanel.java new file mode 100644 index 000000000000..5671bc985cf4 --- /dev/null +++ b/src/main/java/seedu/address/ui/MapPanel.java @@ -0,0 +1,103 @@ +//@@author sarahgoh97 +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.ui.HideMapEvent; +import seedu.address.model.cell.Cell; + + +/** + * The cellMap of the App. + */ +public class MapPanel extends UiPart { + + private static final String FXML = "MapPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(MapPanel.class); + + @FXML + private Label cellAddress11; + @FXML + private Label cellAddress12; + @FXML + private Label cellAddress13; + @FXML + private Label cellAddress14; + @FXML + private Label cellAddress15; + @FXML + private Label cellAddress21; + @FXML + private Label cellAddress22; + @FXML + private Label cellAddress23; + @FXML + private Label cellAddress24; + @FXML + private Label cellAddress25; + @FXML + private Label cellAddress31; + @FXML + private Label cellAddress32; + @FXML + private Label cellAddress33; + @FXML + private Label cellAddress34; + @FXML + private Label cellAddress35; + + public MapPanel() { + super(FXML); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList list) { + cellAddress11.setText(list.get(0)); + cellAddress12.setText(list.get(1)); + cellAddress13.setText(list.get(2)); + cellAddress14.setText(list.get(3)); + cellAddress15.setText(list.get(4)); + cellAddress21.setText(list.get(5)); + cellAddress22.setText(list.get(6)); + cellAddress23.setText(list.get(7)); + cellAddress24.setText(list.get(8)); + cellAddress25.setText(list.get(9)); + cellAddress31.setText(list.get(10)); + cellAddress32.setText(list.get(11)); + cellAddress33.setText(list.get(12)); + cellAddress34.setText(list.get(13)); + cellAddress35.setText(list.get(14)); + } + + private void setStrings(ObservableList cellList) { + ObservableList list = FXCollections.observableArrayList(); + for (int i = 0; i < 15; i++) { + list.add(Integer.toString(cellList.get(i).getNumberOfPrisoners())); + } + setConnections(list); + } + + @Subscribe + public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Changing map\n")); + setStrings(abce.data.getCellList()); + } + + @Subscribe + private void handleHideMapRequestEvent(HideMapEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setConnections(event.list); + } + +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index f6727ea83abd..dbb06f4bc4e8 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -37,6 +37,8 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML + private Label role; + @FXML private FlowPane tags; public PersonCard(Person person, int displayedIndex) { @@ -47,6 +49,11 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); + if (person.getRole().value.equals("p")) { + role.setText("Prisoner"); + } else { + role.setText("Guard"); + } person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index 06fb7e50c935..28b6b461deb7 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -9,10 +9,13 @@ import com.google.common.eventbus.Subscribe; import javafx.application.Platform; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.ui.HideMapEvent; +import seedu.address.model.person.Person; /** * A ui for the status bar that is displayed at the footer of the application. @@ -39,16 +42,20 @@ public class StatusBarFooter extends UiPart { @FXML private StatusBar syncStatus; @FXML + private StatusBar numberOfPeopleStatus; + @FXML private StatusBar saveLocationStatus; - public StatusBarFooter(String saveLocation) { + public StatusBarFooter(String saveLocation, ObservableList personList) { super(FXML); setSyncStatus(SYNC_STATUS_INITIAL); setSaveLocation("./" + saveLocation); + setNumberOfPeopleStatus(personList.size()); registerAsAnEventHandler(this); } + /** * Sets the clock used to determine the current time. */ @@ -63,7 +70,7 @@ public static Clock getClock() { return clock; } - private void setSaveLocation(String location) { + void setSaveLocation(String location) { Platform.runLater(() -> this.saveLocationStatus.setText(location)); } @@ -71,11 +78,22 @@ private void setSyncStatus(String status) { Platform.runLater(() -> this.syncStatus.setText(status)); } + private void setNumberOfPeopleStatus(int numberOfPeople) { + Platform.runLater(() -> this.numberOfPeopleStatus.setText(numberOfPeople + " person(s) total.")); + } + + @Subscribe + private void handleHideMapRequestEvent(HideMapEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setNumberOfPeopleStatus(0); + } + @Subscribe public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); + setNumberOfPeopleStatus(abce.data.getPersonList().size()); } } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..d3796dbcef73 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/prison.png"; private Logic logic; private Config config; @@ -66,7 +66,7 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); + //mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/resources/build.gradle b/src/main/resources/build.gradle new file mode 100644 index 000000000000..9b98069f37d0 --- /dev/null +++ b/src/main/resources/build.gradle @@ -0,0 +1,19 @@ +apply plugin: 'java' +apply plugin: 'application' + +mainClassName = 'Calendar' +sourceCompatibility = 1.7 +targetCompatibility = 1.7 +version = '1.0' + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.google.api-client:google-api-client:1.23.0' + compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0' + compile 'com.google.apis:google-api-services-calendar:v3-rev301-1.23.0' + + +} diff --git a/src/main/resources/client_secret.json b/src/main/resources/client_secret.json new file mode 100644 index 000000000000..f857c266e44a --- /dev/null +++ b/src/main/resources/client_secret.json @@ -0,0 +1 @@ +{"installed":{"client_id":"824981534536-7k6u7vm63qpv15valckvlg7n4d4hkecq.apps.googleusercontent.com","project_id":"fiery-topic-199411","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"vIpAveyVpUqN90Ckf892Xdi6","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} diff --git a/src/main/resources/gradle/wrapper/gradle-wrapper.jar b/src/main/resources/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000000..f6b961fd5a86 Binary files /dev/null and b/src/main/resources/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/main/resources/gradle/wrapper/gradle-wrapper.properties b/src/main/resources/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..bf3de218305a --- /dev/null +++ b/src/main/resources/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/src/main/resources/gradlew b/src/main/resources/gradlew new file mode 100644 index 000000000000..cccdd3d517fc --- /dev/null +++ b/src/main/resources/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/src/main/resources/gradlew.bat b/src/main/resources/gradlew.bat new file mode 100644 index 000000000000..f9553162f122 --- /dev/null +++ b/src/main/resources/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/resources/images/prison.png b/src/main/resources/images/prison.png new file mode 100644 index 000000000000..0ee0731fe8a3 Binary files /dev/null and b/src/main/resources/images/prison.png differ diff --git a/src/main/resources/settings.gradle b/src/main/resources/settings.gradle new file mode 100644 index 000000000000..55d96b99065e --- /dev/null +++ b/src/main/resources/settings.gradle @@ -0,0 +1,9 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user guide at https://docs.gradle.org/4.6/userguide/multi_project_builds.html + */ + +rootProject.name = 'resources' diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..30d6fbd21edf 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -11,10 +11,11 @@ + - + @@ -54,11 +55,13 @@ - - - - - + + + + + + diff --git a/src/main/resources/view/MapPanel.fxml b/src/main/resources/view/MapPanel.fxml new file mode 100644 index 000000000000..7b69b1e81e42 --- /dev/null +++ b/src/main/resources/view/MapPanel.fxml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..56855069f876 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -31,6 +31,7 @@ diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml index c0da5c86d080..ea04d7dd0751 100644 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ b/src/test/data/XmlUtilTest/missingPersonField.xml @@ -4,5 +4,6 @@ 9482424 hans@example
4th street
+ p friends
diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml index 6265778674d3..4203be61e221 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validAddressBook.xml @@ -5,53 +5,62 @@ 9482424 hans@example.com
4th street
+ p Ruth Mueller 87249245 ruth@example.com
81th street
+ p
Heinz Kurz 95352563 heinz@example.com
wall street
+ p
Cornelia Meier 87652533 cornelia@example.com
10th street
+ p
Werner Meyer 9482224 werner@example.com
michegan ave
+ p
Lydia Kunz 9482427 lydia@example.com
little tokyo
+ p
Anna Best 9482442 anna@example.com
4th street
+ p
Stefan Meier 8482424 stefan@example.com
little india
+ p
Martin Mueller 8482131 hans@example.com
chicago ave
+ p
diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml index c029008d54f4..b367a60fe82f 100644 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ b/src/test/data/XmlUtilTest/validPerson.xml @@ -4,5 +4,6 @@ 9482424 hans@example
4th street
+ p friends
diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..f86067848674 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -12,7 +12,6 @@ public class MainWindowHandle extends StageHandle { private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; public MainWindowHandle(Stage stage) { super(stage); @@ -22,7 +21,6 @@ public MainWindowHandle(Stage stage) { commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); } public PersonListPanelHandle getPersonListPanel() { @@ -45,7 +43,4 @@ public MainMenuHandle getMainMenu() { return mainMenu; } - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; - } } diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java index d337d3a4cee9..e2e9f99b91f3 100644 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -16,6 +16,7 @@ public class PersonCardHandle extends NodeHandle { private static final String ADDRESS_FIELD_ID = "#address"; private static final String PHONE_FIELD_ID = "#phone"; private static final String EMAIL_FIELD_ID = "#email"; + private static final String ROLE_FIELD_ID = "#role"; private static final String TAGS_FIELD_ID = "#tags"; private final Label idLabel; @@ -23,6 +24,7 @@ public class PersonCardHandle extends NodeHandle { private final Label addressLabel; private final Label phoneLabel; private final Label emailLabel; + private final Label roleLabel; private final List