Skip to content

Commit

Permalink
Add UnassignCommand and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
IzN432 committed Oct 18, 2024
1 parent b53bb36 commit 33d8208
Show file tree
Hide file tree
Showing 12 changed files with 409 additions and 12 deletions.
6 changes: 2 additions & 4 deletions src/main/java/keycontacts/logic/Messages.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ public static String format(Student student) {
/**
* Formats a {@code Collection<T>} for display to the user.
*/
public static <T> String format(Collection<T> t) {
final StringBuilder builder = new StringBuilder();
t.forEach(builder::append);
return builder.toString();
public static <T> String format(Collection<T> collection) {
return collection.stream().map(T::toString).collect(Collectors.joining(", "));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import keycontacts.commons.core.index.Index;
import keycontacts.commons.util.ToStringBuilder;
Expand Down Expand Up @@ -33,13 +34,13 @@ public class AssignPiecesCommand extends Command {

public static final String MESSAGE_SUCCESS = "Piano piece(s): %1$s\nAdded to student: %2$s";
public static final String MESSAGE_DUPLICATE_PIANO_PIECE = "The student already"
+ " has one or more of the piano pieces";
+ " has the following piano piece(s): %1$s";
private final Index index;
private final Set<PianoPiece> pianoPieces;

/**
* @param index of the student in the filtered student list to edit
* @param pianoPieces the name of the piece to be added
* @param pianoPieces the names of the pieces to be added
*/
public AssignPiecesCommand(Index index, Set<PianoPiece> pianoPieces) {
requireAllNonNull(index, pianoPieces);
Expand All @@ -60,10 +61,10 @@ public CommandResult execute(Model model) throws CommandException {

Student studentToUpdate = lastShownList.get(index.getZeroBased());

boolean hasDuplicate = pianoPieces.stream()
.anyMatch(studentToUpdate.getPianoPieces()::contains);
if (hasDuplicate) {
throw new CommandException(MESSAGE_DUPLICATE_PIANO_PIECE);
Set<PianoPiece> duplicatePieces = pianoPieces.stream()
.filter(studentToUpdate.getPianoPieces()::contains).collect(Collectors.toSet());
if (!duplicatePieces.isEmpty()) {
throw new CommandException(String.format(MESSAGE_DUPLICATE_PIANO_PIECE, Messages.format(duplicatePieces)));
}

Student updatedStudent = studentToUpdate.withAddedPianoPieces(pianoPieces);
Expand Down
113 changes: 113 additions & 0 deletions src/main/java/keycontacts/logic/commands/UnassignPiecesCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package keycontacts.logic.commands;

import static java.util.Objects.requireNonNull;
import static keycontacts.commons.util.CollectionUtil.requireAllNonNull;
import static keycontacts.logic.parser.CliSyntax.PREFIX_PIECE_NAME;

import java.util.List;
import java.util.Set;

import keycontacts.commons.core.index.Index;
import keycontacts.commons.util.ToStringBuilder;
import keycontacts.logic.Messages;
import keycontacts.logic.commands.exceptions.CommandException;
import keycontacts.model.Model;
import keycontacts.model.pianopiece.PianoPiece;
import keycontacts.model.student.Student;

/**
* Adds a student to the student directory.
*/
public class UnassignPiecesCommand extends Command {

public static final String COMMAND_WORD = "unassign";

public static final String MESSAGE_USAGE = COMMAND_WORD
+ ": Unassigns one or more piano pieces from the student identified "
+ "by the index number used in the displayed student list\n"
+ "All piano pieces will be unassigned if none are given as argument\n"
+ "Parameters: INDEX (must be a positive integer) "
+ PREFIX_PIECE_NAME + "[PIECE_NAME]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PIECE_NAME + "Für Elise "
+ PREFIX_PIECE_NAME + "Moonlight Sonata";

public static final String MESSAGE_SUCCESS = "Piano piece(s): %1$s\nRemoved from student: %2$s";
public static final String MESSAGE_PIANO_PIECE_NOT_FOUND = "The student does not have"
+ " the piano piece(s) %1$s assigned.";
public static final String MESSAGE_NO_PIANO_PIECE_FOUND = "The student does not have any"
+ " piano pieces assigned";
private final Index index;
private final Set<PianoPiece> pianoPieces;

/**
* @param index of the student in the filtered student list to edit
* @param pianoPieces the names of the pieces to be removed
*/
public UnassignPiecesCommand(Index index, Set<PianoPiece> pianoPieces) {
requireAllNonNull(index, pianoPieces);

this.index = index;
this.pianoPieces = pianoPieces;
}

@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);

List<Student> lastShownList = model.getFilteredStudentList();

if (index.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX);
}

Student studentToUpdate = lastShownList.get(index.getZeroBased());

if (studentToUpdate.getPianoPieces().isEmpty()) {
throw new CommandException(MESSAGE_NO_PIANO_PIECE_FOUND);
}

Set<PianoPiece> piecesToRemove = pianoPieces;
if (piecesToRemove.isEmpty()) {
piecesToRemove = studentToUpdate.getPianoPieces();
}

List<PianoPiece> piecesNotInStudent = piecesToRemove.stream()
.filter(pianoPiece -> !studentToUpdate.getPianoPieces().contains(pianoPiece)).toList();
if (!piecesNotInStudent.isEmpty()) {
throw new CommandException(String.format(MESSAGE_PIANO_PIECE_NOT_FOUND,
Messages.format(piecesNotInStudent)));
}

Student updatedStudent = studentToUpdate.withRemovedPianoPieces(piecesToRemove);

model.setStudent(studentToUpdate, updatedStudent);

return new CommandResult(String.format(MESSAGE_SUCCESS,
Messages.format(piecesToRemove), Messages.format(updatedStudent)));
}

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

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

UnassignPiecesCommand otherUnassignPiecesCommand = (UnassignPiecesCommand) other;
return index.equals(otherUnassignPiecesCommand.index)
&& pianoPieces.equals(otherUnassignPiecesCommand.pianoPieces);
}

@Override
public String toString() {
return new ToStringBuilder(this)
.add("index", index)
.add("pianoPieces", pianoPieces)
.toString();
}
}
2 changes: 2 additions & 0 deletions src/main/java/keycontacts/logic/parser/KeyContactsParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import keycontacts.logic.commands.ListCommand;
import keycontacts.logic.commands.MakeupLessonCommand;
import keycontacts.logic.commands.ScheduleCommand;
import keycontacts.logic.commands.UnassignPiecesCommand;
import keycontacts.logic.parser.exceptions.ParseException;

/**
Expand Down Expand Up @@ -67,6 +68,7 @@ public Command parseCommand(String userInput) throws ParseException {
case MakeupLessonCommand.COMMAND_WORD -> new MakeupCommandParser().parse(arguments);
case ScheduleCommand.COMMAND_WORD -> new ScheduleCommandParser().parse(arguments);
case AssignPiecesCommand.COMMAND_WORD -> new AssignPiecesCommandParser().parse(arguments);
case UnassignPiecesCommand.COMMAND_WORD -> new UnassignPiecesCommandParser().parse(arguments);
case CancelLessonCommand.COMMAND_WORD -> new CancelLessonCommandParser().parse(arguments);
default -> {
logger.finer("This user input caused a ParseException: " + userInput);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package keycontacts.logic.parser;

import static keycontacts.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static keycontacts.logic.parser.CliSyntax.PREFIX_PIECE_NAME;

import java.util.Set;

import keycontacts.commons.core.index.Index;
import keycontacts.logic.commands.UnassignPiecesCommand;
import keycontacts.logic.parser.exceptions.ParseException;
import keycontacts.model.pianopiece.PianoPiece;


/**
* Parses input arguments and creates a new UnassignPieceCommand object
*/
public class UnassignPiecesCommandParser implements Parser<UnassignPiecesCommand> {
/**
* Parses the given {@code String} of arguments in the context of the UnassignPieceCommand
* and returns a UnassignPieceCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
public UnassignPiecesCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, PREFIX_PIECE_NAME);

Index index;
try {
index = ParserUtil.parseIndex(argMultimap.getPreamble());
} catch (ParseException pe) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
UnassignPiecesCommand.MESSAGE_USAGE), pe);
}

Set<PianoPiece> pianoPieces = ParserUtil.parsePianoPieces(argMultimap.getAllValues(PREFIX_PIECE_NAME));

return new UnassignPiecesCommand(index, pianoPieces);
}

}
10 changes: 10 additions & 0 deletions src/main/java/keycontacts/model/student/Student.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ public Student withAddedPianoPieces(Set<PianoPiece> addedPianoPieces) {
return new Updater().withPianoPieces(updatedPianoPieces).update();
}

/**
* Creates and returns a new {@code Student} with the {@code removedPianoPieces} removed.
*/
public Student withRemovedPianoPieces(Set<PianoPiece> removedPianoPieces) {
Set<PianoPiece> updatedPianoPieces = new HashSet<>(pianoPieces);
updatedPianoPieces.removeAll(removedPianoPieces);

return new Updater().withPianoPieces(updatedPianoPieces).update();
}


/**
* Creates and returns a new {@code Student} with the updated {@code regularLesson}.
Expand Down
1 change: 0 additions & 1 deletion src/main/java/keycontacts/model/util/SampleDataUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
public class SampleDataUtil {
public static Student[] getSampleStudents() {


return new Student[] {
new Student(new Name("Alex Yeoh"), new Phone("87438807"), new Address("Blk 30 Geylang Street 29, #06-40"),
new GradeLevel("ABRSM 1"), PianoPiece.getPianoPieceSet("Canon in D"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public void execute_duplicatePianoPiece_throwsCommandException() throws Exceptio
Set<PianoPiece> firstPianoPiece = validPianoPieces.stream().limit(1).collect(Collectors.toSet());
AssignPiecesCommand command = new AssignPiecesCommand(INDEX_FIRST_STUDENT, firstPianoPiece);

assertCommandFailure(command, model, AssignPiecesCommand.MESSAGE_DUPLICATE_PIANO_PIECE);
assertCommandFailure(command, model, String.format(AssignPiecesCommand.MESSAGE_DUPLICATE_PIANO_PIECE,
Messages.format(firstPianoPiece)));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package keycontacts.logic.commands;

import static keycontacts.logic.commands.CommandTestUtil.VALID_PIANO_PIECE_BEETHOVEN;
import static keycontacts.logic.commands.CommandTestUtil.VALID_PIANO_PIECE_PACHELBEL;
import static keycontacts.logic.commands.CommandTestUtil.assertCommandFailure;
import static keycontacts.logic.commands.CommandTestUtil.assertCommandSuccess;
import static keycontacts.testutil.Assert.assertThrows;
import static keycontacts.testutil.TypicalIndexes.INDEX_FIRST_STUDENT;
import static keycontacts.testutil.TypicalIndexes.INDEX_SECOND_STUDENT;
import static keycontacts.testutil.TypicalStudents.ALICE;
import static keycontacts.testutil.TypicalStudents.BENSON;
import static keycontacts.testutil.TypicalStudents.getTypicalStudentDirectory;
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 java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;

import keycontacts.commons.core.index.Index;
import keycontacts.logic.Messages;
import keycontacts.model.Model;
import keycontacts.model.ModelManager;
import keycontacts.model.StudentDirectory;
import keycontacts.model.UserPrefs;
import keycontacts.model.pianopiece.PianoPiece;
import keycontacts.model.student.Student;

public class UnassignPiecesCommandTest {

private final Set<PianoPiece> validPianoPieces = PianoPiece.getPianoPieceSet(VALID_PIANO_PIECE_BEETHOVEN,
VALID_PIANO_PIECE_PACHELBEL);

private final Model model = new ModelManager(getTypicalStudentDirectory(), new UserPrefs());

@Test
public void constructor_nullIndex_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> new UnassignPiecesCommand(null, validPianoPieces));
}
@Test
public void constructor_nullPianoPieces_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> new UnassignPiecesCommand(INDEX_FIRST_STUDENT, null));
}

@Test
public void execute_validIndexAndPianoPiece_success() {
Student student = model.getFilteredStudentList().get(INDEX_FIRST_STUDENT.getZeroBased());
Set<PianoPiece> studentFirstPianoPiece = student.getPianoPieces().stream()
.limit(1).collect(Collectors.toSet());

UnassignPiecesCommand command = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, studentFirstPianoPiece);

Student updatedStudent = student.withRemovedPianoPieces(studentFirstPianoPiece);
Model expectedModel = new ModelManager(new StudentDirectory(model.getStudentDirectory()), new UserPrefs());
expectedModel.setStudent(model.getFilteredStudentList().get(0), updatedStudent);

CommandResult commandResult = new CommandResult(String.format(UnassignPiecesCommand.MESSAGE_SUCCESS,
Messages.format(studentFirstPianoPiece),
Messages.format(student)));

assertCommandSuccess(command, model, commandResult, expectedModel);
}
@Test
public void execute_indexOutOfBounds_failure() {
Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredStudentList().size() + 1);
UnassignPiecesCommand command = new UnassignPiecesCommand(outOfBoundsIndex, validPianoPieces);

assertCommandFailure(command, model, Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX);
}
@Test
public void execute_studentNoPianoPiece_throwsCommandException() {
UnassignPiecesCommand command = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, validPianoPieces);
Student student = model.getFilteredStudentList().get(0);
model.setStudent(model.getFilteredStudentList().get(0),
student.withRemovedPianoPieces(student.getPianoPieces()));
assertCommandFailure(command, model, UnassignPiecesCommand.MESSAGE_NO_PIANO_PIECE_FOUND);
}
@Test
public void execute_allPianoPiecesNotOnStudent_throwsCommandException() {
model.setStudent(model.getFilteredStudentList().get(0), ALICE);
Set<PianoPiece> piecesToRemove = BENSON.getPianoPieces();
UnassignPiecesCommand command = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, piecesToRemove);

assertCommandFailure(command, model, String.format(UnassignPiecesCommand.MESSAGE_PIANO_PIECE_NOT_FOUND,
Messages.format(piecesToRemove)));
}
@Test
public void execute_subsetOfPianoPiecesNotOnStudent_throwsCommandException() {
model.setStudent(model.getFilteredStudentList().get(0), ALICE);
Set<PianoPiece> subsetNotPresent = BENSON.getPianoPieces();
Set<PianoPiece> piecesToRemove = new HashSet<>(subsetNotPresent);
piecesToRemove.addAll(ALICE.getPianoPieces());

UnassignPiecesCommand command = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, piecesToRemove);

assertCommandFailure(command, model, String.format(UnassignPiecesCommand.MESSAGE_PIANO_PIECE_NOT_FOUND,
Messages.format(subsetNotPresent)));
}
@Test
public void equals() {
Set<PianoPiece> bensonPianoPieces = BENSON.getPianoPieces();
Set<PianoPiece> alicePianoPieces = ALICE.getPianoPieces();

UnassignPiecesCommand baseCommand = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, bensonPianoPieces);

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

// same values -> returns true
UnassignPiecesCommand identicalCommand = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, bensonPianoPieces);
assertTrue(baseCommand.equals(identicalCommand));
assertTrue(identicalCommand.equals(baseCommand));

// different values -> returns false
UnassignPiecesCommand differentIndexCommand = new UnassignPiecesCommand(INDEX_SECOND_STUDENT, bensonPianoPieces);
assertFalse(baseCommand.equals(differentIndexCommand));

UnassignPiecesCommand differentPianoPiecesCommand = new UnassignPiecesCommand(INDEX_FIRST_STUDENT,
alicePianoPieces);
assertFalse(baseCommand.equals(differentPianoPiecesCommand));

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

}

@Test
public void toStringMethod() {
UnassignPiecesCommand unassignPiecesCommand = new UnassignPiecesCommand(INDEX_FIRST_STUDENT, validPianoPieces);
String expected = UnassignPiecesCommand.class.getCanonicalName() + "{index=" + INDEX_FIRST_STUDENT + ", "
+ "pianoPieces=" + validPianoPieces + "}";
assertEquals(expected, unassignPiecesCommand.toString());
}
}
Loading

0 comments on commit 33d8208

Please sign in to comment.