Skip to content

Commit

Permalink
Merge pull request AY2425S1-CS2103T-T12-4#131
Browse files Browse the repository at this point in the history
Implement FindTimeCommand and link to AddressBookParser
  • Loading branch information
JJtan2002 authored Oct 24, 2024
2 parents ba0a911 + 8b4a5d7 commit 0bb2608
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 4 deletions.
60 changes: 60 additions & 0 deletions src/main/java/seedu/address/logic/commands/FindTimeCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package seedu.address.logic.commands;

import static java.util.Objects.requireNonNull;

import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.preferredtime.PreferredTimeOverlapsRangesPredicate;


/**
* Finds and lists all persons in address book whose preferred time ranges overlap with the specified time range.
* Time matching will not acknowledge the only overlap in boundary.
*/
public class FindTimeCommand extends Command {
public static final String COMMAND_WORD = "findtime";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose preferred time ranges"
+ "overlap with the specified time ranges"
+ "and displays them as a list with index numbers.\n"
+ "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " 1100-1230 2130-2245";

private final PreferredTimeOverlapsRangesPredicate predicate;

public FindTimeCommand(PreferredTimeOverlapsRangesPredicate predicate) {
this.predicate = predicate;
}

@Override
public CommandResult execute(Model model) {
requireNonNull(model);
model.updateFilteredPersonList(predicate);
return new CommandResult(
String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}

// instanceof handles nulls
if (!(other instanceof FindTimeCommand)) {
return false;
}

FindTimeCommand otherFindTimeCommand = (FindTimeCommand) other;
return predicate.equals(otherFindTimeCommand.predicate);
}


@Override
public String toString() {
return new ToStringBuilder(this)
.add("predicate", predicate)
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FavouriteGameCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.FindTimeCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.LoadCommand;
Expand Down Expand Up @@ -72,6 +73,9 @@ public Command parseCommand(String userInput) throws ParseException {
case FindCommand.COMMAND_WORD:
return new FindCommandParser().parse(arguments);

case FindTimeCommand.COMMAND_WORD:
return new FindTimeCommandParser().parse(arguments);

case ListCommand.COMMAND_WORD:
return new ListCommand();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package seedu.address.logic.parser;

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;

import java.util.Arrays;
import java.util.List;

import seedu.address.logic.commands.FindTimeCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.preferredtime.PreferredTime;
import seedu.address.model.preferredtime.PreferredTimeOverlapsRangesPredicate;



/**
* Parses input arguments and creates a new FindTimeCommand object
*/
public class FindTimeCommandParser implements Parser<FindTimeCommand> {
/**
* Parses the given {@code String} of arguments in the context of the FindTimeCommand
* and returns a FindTimeCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
public FindTimeCommand parse(String args) throws ParseException {
String trimmedArgs = args.trim();
if (trimmedArgs.isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTimeCommand.MESSAGE_USAGE));
}

String[] timeKeywords = trimmedArgs.split("\\s+");
List<String> validRanges = Arrays.stream(timeKeywords)
.filter(PreferredTime::isValidPreferredTime).toList();

if (validRanges.isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTimeCommand.MESSAGE_USAGE));
}

// return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(timeKeywords)));
return new FindTimeCommand(new PreferredTimeOverlapsRangesPredicate(validRanges));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public static boolean isValidPreferredTime(String test) {
return LocalTime.parse(start, TIME_FORMATTER).isBefore(LocalTime.parse(end, TIME_FORMATTER));
}

public boolean overlaps(PreferredTime other) {
return !(end.isBefore(other.start) || start.isAfter(other.end));
}

@Override
public boolean equals(Object other) {
if (other == this) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package seedu.address.model.preferredtime;

import java.util.List;
import java.util.Set;
import java.util.function.Predicate;

import seedu.address.commons.util.ToStringBuilder;
import seedu.address.model.person.Person;

/**
* Tests that a {@code Person}'s {@code PreferredTime} overlaps with any of the ranges given.
*/
public class PreferredTimeOverlapsRangesPredicate implements Predicate<Person> {
private final List<String> ranges;

public PreferredTimeOverlapsRangesPredicate(List<String> ranges) {
this.ranges = ranges;
}

@Override
public boolean test(Person person) {
return ranges.stream()
.anyMatch(range -> overlapsRanges(person.getPreferredTimes(), range));
}

private static boolean overlapsRanges(Set<PreferredTime> preferredTimes, String range) {
return preferredTimes.stream()
.anyMatch(preferredTime -> preferredTime.overlaps(new PreferredTime(range)));
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}

// instanceof handles nulls
if (!(other instanceof PreferredTimeOverlapsRangesPredicate)) {
return false;
}

PreferredTimeOverlapsRangesPredicate otherPreferredTimeOverlapsRangesPredicate =
(PreferredTimeOverlapsRangesPredicate) other;
return ranges.equals(otherPreferredTimeOverlapsRangesPredicate.ranges);
}

@Override
public String toString() {
return new ToStringBuilder(this).add("ranges", ranges).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"email" : "[email protected]",
"address" : "123, Jurong West Ave 6, #08-111",
"tags" : [ "friends" ],
"preferred times": ["1200-1400"]
"preferred times": ["1200-1400", "2100-2230"]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "[email protected]",
"address" : "311, Clementi Ave 2, #02-25",
"tags" : [ "owesMoney", "friends" ],
"preferred times": [ ]
"preferred times": ["1800-2000"]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package seedu.address.logic.commands;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;

import java.util.Arrays;
import java.util.Collections;

import org.junit.jupiter.api.Test;

import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.preferredtime.PreferredTimeOverlapsRangesPredicate;


/**
* Contains integration tests (interaction with the Model) for {@code FindTimeCommand}.
*/
public class FindTimeCommandTest {
private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());

@Test
public void equals() {
PreferredTimeOverlapsRangesPredicate firstPredicate =
new PreferredTimeOverlapsRangesPredicate(Collections.singletonList("1200-1400"));
PreferredTimeOverlapsRangesPredicate secondPredicate =
new PreferredTimeOverlapsRangesPredicate(Collections.singletonList("1800-2100"));

FindTimeCommand findFirstCommand = new FindTimeCommand(firstPredicate);
FindTimeCommand findSecondCommand = new FindTimeCommand(secondPredicate);

// same object -> returns true
assertTrue(findFirstCommand.equals(findFirstCommand));

// same values -> returns true
FindTimeCommand findFirstCommandCopy = new FindTimeCommand(firstPredicate);
assertTrue(findFirstCommand.equals(findFirstCommandCopy));

// different types -> returns false
assertFalse(findFirstCommand.equals(1));

// null -> returns false
assertFalse(findFirstCommand.equals(null));

// different person -> returns false
assertFalse(findFirstCommand.equals(findSecondCommand));
}

@Test
public void execute_zeroKeywords_noPersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
PreferredTimeOverlapsRangesPredicate predicate = preparePredicate(" ");
FindTimeCommand command = new FindTimeCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Collections.emptyList(), model.getFilteredPersonList());
}

// TODO: add execute_singleKeyword_multiplePersonsFound(or no need?)

@Test
public void execute_multipleKeywords_multiplePersonsFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2);
PreferredTimeOverlapsRangesPredicate predicate = preparePredicate("1200-1300 1900-2000");
FindTimeCommand command = new FindTimeCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(ALICE, BENSON), model.getFilteredPersonList());
}

@Test
public void toStringMethod() {
PreferredTimeOverlapsRangesPredicate predicate =
new PreferredTimeOverlapsRangesPredicate(Arrays.asList("keyword"));
FindTimeCommand findTimeCommand = new FindTimeCommand(predicate);
String expected = FindTimeCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";
assertEquals(expected, findTimeCommand.toString());
}

/**
* Parses {@code userInput} into a {@code PreferredTimeOverlapsRangesPredicate}.
*/
private PreferredTimeOverlapsRangesPredicate preparePredicate(String userInput) {
return new PreferredTimeOverlapsRangesPredicate(Arrays.asList(userInput.split("\\s+")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.FindTimeCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.model.preferredtime.PreferredTimeOverlapsRangesPredicate;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
import seedu.address.testutil.PersonUtil;
Expand Down Expand Up @@ -68,6 +70,14 @@ public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand);
}

@Test
public void parseCommand_findTime() throws Exception {
List<String> keywords = Arrays.asList("1800-1900", "1600-1700", "2100-2315");
FindTimeCommand command = (FindTimeCommand) parser.parseCommand(
FindTimeCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
assertEquals(new FindTimeCommand(new PreferredTimeOverlapsRangesPredicate(keywords)), command);
}

@Test
public void parseCommand_find() throws Exception {
List<String> keywords = Arrays.asList("foo", "bar", "baz");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package seedu.address.logic.parser;

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;

import java.util.Arrays;

import org.junit.jupiter.api.Test;

import seedu.address.logic.commands.FindTimeCommand;
import seedu.address.model.preferredtime.PreferredTimeOverlapsRangesPredicate;


public class FindTimeCommandParserTest {
private FindTimeCommandParser parser = new FindTimeCommandParser();

@Test
public void parse_emptyArg_throwsParseException() {
assertParseFailure(parser, " ",
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTimeCommand.MESSAGE_USAGE));
}

@Test
public void parse_validArgs_returnsFindTimeCommand() {
// no leading and trailing whitespaces
FindTimeCommand expectedFindTimeCommand =
new FindTimeCommand(new PreferredTimeOverlapsRangesPredicate(Arrays.asList("1200-1330", "1830-1930")));
assertParseSuccess(parser, "1200-1330 1830-1930", expectedFindTimeCommand);

// multiple whitespaces between keywords
assertParseSuccess(parser, " \n 1200-1330 \n \t 1830-1930 \t", expectedFindTimeCommand);
}

}
5 changes: 3 additions & 2 deletions src/test/java/seedu/address/testutil/TypicalPersons.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ public class TypicalPersons {
.withAddress("123, Jurong West Ave 6, #08-111").withEmail("[email protected]")
.withPhone("94351253")
.withTags("friends").withGames()
.withPreferredTimes("1200-1400").build();
.withPreferredTimes("1200-1400", "2100-2230").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
.withAddress("311, Clementi Ave 2, #02-25")
.withEmail("[email protected]").withPhone("98765432")
.withTags("owesMoney", "friends").withGames().withPreferredTimes().build();
.withTags("owesMoney", "friends").withGames()
.withPreferredTimes("1800-2000").build();
public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
.withEmail("[email protected]").withAddress("wall street").build();
public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
Expand Down

0 comments on commit 0bb2608

Please sign in to comment.