diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java index 1585d4a7e8d..2f4e9a6789a 100644 --- a/src/main/java/seedu/address/logic/commands/SortCommand.java +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -5,9 +5,11 @@ import java.util.Comparator; import java.util.Optional; +import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.model.Model; import seedu.address.model.student.Student; +import seedu.address.model.student.TestNameEqualsKeywordPredicate; /** @@ -24,7 +26,7 @@ public class SortCommand extends Command { + "/r means reverse so it will sort in decreasing order, highest grade first.\n" + "Example: " + COMMAND_WORD + " ca1 /r"; - private final String testName; + private final TestNameEqualsKeywordPredicate predicate; private final boolean isReverse; /** @@ -41,8 +43,8 @@ public class SortCommand extends Command { * Example: * - sort Math /r: Sorts students based on their Math test grades in decreasing order. */ - public SortCommand(String testName, boolean isReverse) { - this.testName = testName; + public SortCommand(TestNameEqualsKeywordPredicate predicate, boolean isReverse) { + this.predicate = predicate; this.isReverse = isReverse; } @@ -50,8 +52,8 @@ public SortCommand(String testName, boolean isReverse) { public CommandResult execute(Model model) { requireNonNull(model); Comparator gradeComparator = (student1, student2) -> { - String grade1 = Optional.ofNullable(student1.getGradeForTest(testName)).orElse("0"); - String grade2 = Optional.ofNullable(student2.getGradeForTest(testName)).orElse("0"); + String grade1 = Optional.ofNullable(student1.getGradeForTest(predicate.keyword)).orElse("0"); + String grade2 = Optional.ofNullable(student2.getGradeForTest(predicate.keyword)).orElse("0"); return grade1.compareTo(grade2); }; @@ -59,9 +61,31 @@ public CommandResult execute(Model model) { gradeComparator = gradeComparator.reversed(); } - model.sortFilteredStudentList(gradeComparator); + model.sortFilteredStudentList(gradeComparator, predicate); return new CommandResult(String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, - model.getFilteredStudentList().size())); + model.getFilteredStudentList().size())); } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SortCommand)) { + return false; + } + + SortCommand otherSortCommand = (SortCommand) other; + return predicate.equals(otherSortCommand.predicate) && isReverse == otherSortCommand.isReverse; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .add("isReverse", isReverse) + .toString(); + } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 5a7d85a1e54..99667181e07 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -148,6 +148,9 @@ private Optional> parseTimeslotsForEdit(Collection timesl Set timeslotSet = new HashSet<>(); for (String timeslotString : timeslots) { String trimmedTimeslotString = timeslotString.trim(); + if (!Timeslots.isValidTimeslot(trimmedTimeslotString)) { + throw new ParseException(Timeslots.MESSAGE_CONSTRAINTS); + } // Remove leading and trailing curly braces if present if (trimmedTimeslotString.startsWith("[[") && trimmedTimeslotString.endsWith("]]")) { trimmedTimeslotString = trimmedTimeslotString.substring(2, trimmedTimeslotString.length() - 2); @@ -177,6 +180,9 @@ private Optional> parseGradesForEdit(Collection grades) throw Set gradeSet = new HashSet<>(); for (String gradeString : grades) { String trimmedGradeString = gradeString.trim(); + if (!Grade.isValidGrade(trimmedGradeString)) { + throw new ParseException(Timeslots.MESSAGE_CONSTRAINTS); + } // Remove leading and trailing curly braces if present if (trimmedGradeString.startsWith("[[") && trimmedGradeString.endsWith("]]")) { trimmedGradeString = trimmedGradeString.substring(2, trimmedGradeString.length() - 2); diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java index 756c02ab688..3e9dafece85 100644 --- a/src/main/java/seedu/address/logic/parser/SortCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -4,6 +4,7 @@ import seedu.address.logic.commands.SortCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.TestNameEqualsKeywordPredicate; /** * Parses input arguments and creates a new SortCommand object. @@ -31,7 +32,6 @@ public SortCommand parse(String args) throws ParseException { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); } - - return new SortCommand(testName, isReverse); + return new SortCommand(new TestNameEqualsKeywordPredicate(testName), isReverse); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 195fd03cea3..2d0acad2b34 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -92,5 +92,5 @@ public interface Model { * * @param comparator The comparator to determine the order of the list. */ - void sortFilteredStudentList(Comparator comparator); + void sortFilteredStudentList(Comparator comparator, Predicate predicate); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 92ddbb2ca97..9e2dccb0b24 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -135,8 +135,11 @@ public void updateFilteredStudentList(Predicate predicate) { } @Override - public void sortFilteredStudentList(Comparator comparator) { - this.sortedStudents.setComparator(comparator); + public void sortFilteredStudentList(Comparator comparator, Predicate predicate) { + requireNonNull(comparator); + requireNonNull(predicate); + filteredStudents.setPredicate(predicate); + sortedStudents.setComparator(comparator); } @Override diff --git a/src/main/java/seedu/address/model/student/TestNameEqualsKeywordPredicate.java b/src/main/java/seedu/address/model/student/TestNameEqualsKeywordPredicate.java new file mode 100644 index 00000000000..7185cf69601 --- /dev/null +++ b/src/main/java/seedu/address/model/student/TestNameEqualsKeywordPredicate.java @@ -0,0 +1,43 @@ +package seedu.address.model.student; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that any of {@code student}'s {@code Grades} matches any of the keywords given. + */ +public class TestNameEqualsKeywordPredicate implements Predicate { + public final String keyword; + + public TestNameEqualsKeywordPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Student student) { + return student.getGrades().stream() + .anyMatch(grades -> grades.testName.equalsIgnoreCase(keyword)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TestNameEqualsKeywordPredicate)) { + return false; + } + + TestNameEqualsKeywordPredicate gradesContainsKeywordsPredicate = + (TestNameEqualsKeywordPredicate) other; + return keyword.equals(gradesContainsKeywordsPredicate.keyword); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keyword).toString(); + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index a29292f29ef..3152af296e9 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -160,7 +160,7 @@ public void updateFilteredStudentList(Predicate predicate) { } @Override - public void sortFilteredStudentList(Comparator comparator) { + public void sortFilteredStudentList(Comparator comparator, Predicate predicate) { throw new AssertionError("This method should not be called."); } } diff --git a/src/test/java/seedu/address/logic/commands/FilterCommandTest.java b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java index 60e49b38ec6..b7a130099f6 100644 --- a/src/test/java/seedu/address/logic/commands/FilterCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java @@ -82,7 +82,7 @@ public void toStringMethod() { } /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + * Parses {@code userInput} into a {@code TimeslotContainsKeywordsPredicate}. */ private TimeslotsContainsKeywordsPredicate preparePredicate(String userInput) { return new TimeslotsContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); diff --git a/src/test/java/seedu/address/logic/commands/SortCommandTest.java b/src/test/java/seedu/address/logic/commands/SortCommandTest.java new file mode 100644 index 00000000000..f93ac6ba59a --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/SortCommandTest.java @@ -0,0 +1,109 @@ +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_STUDENTS_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalStudents.ALICE; +import static seedu.address.testutil.TypicalStudents.BENSON; +import static seedu.address.testutil.TypicalStudents.getTypicalAddressBook; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; + +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.student.Student; +import seedu.address.model.student.TestNameEqualsKeywordPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FilterCommand}. + */ +public class SortCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + TestNameEqualsKeywordPredicate firstPredicate = + new TestNameEqualsKeywordPredicate("first"); + TestNameEqualsKeywordPredicate secondPredicate = + new TestNameEqualsKeywordPredicate("second"); + + SortCommand sortFirstCommand = new SortCommand(firstPredicate, false); + SortCommand sortSecondCommand = new SortCommand(secondPredicate, false); + + // same object -> returns true + assertTrue(sortFirstCommand.equals(sortFirstCommand)); + + // same values -> returns true + SortCommand sortFirstCommandCopy = new SortCommand(firstPredicate, false); + assertTrue(sortFirstCommand.equals(sortFirstCommandCopy)); + + // different types -> returns false + assertFalse(sortFirstCommand.equals(1)); + + // null -> returns false + assertFalse(sortFirstCommand.equals(null)); + + // different student -> returns false + assertFalse(sortFirstCommand.equals(sortSecondCommand)); + } + + @Test + public void execute_zeroKeywords_nostudentFound() { + String expectedMessage = String.format(MESSAGE_STUDENTS_LISTED_OVERVIEW, 0); + TestNameEqualsKeywordPredicate predicate = preparePredicate(" "); + Comparator gradeComparator = (student1, student2) -> { + String grade1 = Optional.ofNullable(student1.getGradeForTest(predicate.keyword)).orElse("0"); + String grade2 = Optional.ofNullable(student2.getGradeForTest(predicate.keyword)).orElse("0"); + return grade1.compareTo(grade2); + }; + SortCommand command = new SortCommand(predicate, false); + expectedModel.sortFilteredStudentList(gradeComparator, predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredStudentList()); + } + + @Test + public void execute_multipleKeywords_multiplestudentsFound() { + String expectedMessage = String.format(MESSAGE_STUDENTS_LISTED_OVERVIEW, 2); + TestNameEqualsKeywordPredicate predicate = preparePredicate("Ca1"); + Comparator gradeComparator = (student1, student2) -> { + String grade1 = Optional.ofNullable(student1.getGradeForTest(predicate.keyword)).orElse("0"); + String grade2 = Optional.ofNullable(student2.getGradeForTest(predicate.keyword)).orElse("0"); + return grade1.compareTo(grade2); + }; + SortCommand command = new SortCommand(predicate, false); + expectedModel.sortFilteredStudentList(gradeComparator, predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON), model.getFilteredStudentList()); + } + + @Test + public void toStringMethod() { + TestNameEqualsKeywordPredicate predicate = new TestNameEqualsKeywordPredicate("keyword"); + Comparator gradeComparator = (student1, student2) -> { + String grade1 = Optional.ofNullable(student1.getGradeForTest(predicate.keyword)).orElse("0"); + String grade2 = Optional.ofNullable(student2.getGradeForTest(predicate.keyword)).orElse("0"); + return grade1.compareTo(grade2); + }; + SortCommand sortCommand = new SortCommand(predicate, false); + String expected = SortCommand.class.getCanonicalName() + "{predicate=" + predicate + ", " + + "isReverse=" + false + "}"; + assertEquals(expected, sortCommand.toString()); + } + + /** + * Parses {@code userInput} into a {@code TestNameEqualsKeywordPredicate}. + */ + private TestNameEqualsKeywordPredicate preparePredicate(String userInput) { + return new TestNameEqualsKeywordPredicate(userInput); + } +} diff --git a/src/test/java/seedu/address/logic/parser/SortCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SortCommandParserTest.java new file mode 100644 index 00000000000..d3d5af40f22 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/SortCommandParserTest.java @@ -0,0 +1,33 @@ +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 org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.SortCommand; +import seedu.address.model.student.TestNameEqualsKeywordPredicate; + +public class SortCommandParserTest { + + private SortCommandParser parser = new SortCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsSortCommand() { + // no leading and trailing whitespaces + SortCommand expectedSortCommand = + new SortCommand(new TestNameEqualsKeywordPredicate("ca1"), false); + assertParseSuccess(parser, "ca1", expectedSortCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n ca1 \t", expectedSortCommand); + } + +}