Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Untag feature #64

Merged
merged 12 commits into from
Oct 10, 2024
1 change: 1 addition & 0 deletions src/main/java/seedu/address/logic/Messages.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class Messages {
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
public static final String MESSAGE_TAG_NOT_FOUND = "One or more specified tags do not exist in the model.";
public static final String MESSAGE_TAG_NOT_FOUND_IN_CONTACT = "Some tags were not found in the person's tag list.";
public static final String MESSAGE_ADD_TAG_SUCCESS = "Added tag(s) %1$s to %2$s.";

/**
Expand Down
116 changes: 116 additions & 0 deletions src/main/java/seedu/address/logic/commands/UntagCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package seedu.address.logic.commands;

import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import static seedu.address.logic.Messages.MESSAGE_TAG_NOT_FOUND_IN_CONTACT;

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

import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;

/**
* Removes a tag associated with an existing person in the address book.
*/
public class UntagCommand extends Command {

public static final String COMMAND_WORD = "untag";
public static final String MESSAGE_REMOVE_TAG_SUCCESS = "Removed tag(s) %1$s from %2$s.";

public static final String MESSAGE_USAGE = COMMAND_WORD
+ ": Removes one or multiple tags from the person identified "
+ "by the index number used in the last person listing.\n"
+ "Parameters: INDEX (must be a positive integer) "
+ "t/[TAG]... (can specify multiple tags)\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ "t/florist t/photographer.";

private final Index index;
private final HashSet<Tag> tagsToRemove;



/**
* Constructs an UntagCommand to remove tags from a person.
*
* @param index The index of the person in the person list.
* @param tagsToRemove The list of tags to be removed.
*/
public UntagCommand(Index index, HashSet<Tag> tagsToRemove) {
this.index = index;
this.tagsToRemove = tagsToRemove;
}

/**
* Generates a command execution success message showing the removed tags and the person.
*
* @param personToEdit The person from whom the tags were removed.
* @return A success message indicating the tags that were removed and the name of the person.
*/
private String generateSuccessMessage(Person personToEdit) {
String removedTags = tagsToRemove.stream()
.map(tag -> tag.toString().replaceAll("[\\[\\]]", ""))
.collect(Collectors.joining(", "));
return String.format(MESSAGE_REMOVE_TAG_SUCCESS, removedTags, personToEdit.getName().toString());
}


@Override
public CommandResult execute(Model model) throws CommandException {
List<Person> lastShownList = model.getFilteredPersonList();

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

Person personToEdit = lastShownList.get(index.getZeroBased());
Set<Tag> updatedTags = new HashSet<>(personToEdit.getTags());

if (personToEdit.getTags().isEmpty()) {
throw new CommandException(MESSAGE_TAG_NOT_FOUND_IN_CONTACT);
}

if (tagsToRemove.isEmpty()) {
throw new CommandException(MESSAGE_TAG_NOT_FOUND_IN_CONTACT);
}

if (!updatedTags.containsAll(tagsToRemove)) {
throw new CommandException(MESSAGE_TAG_NOT_FOUND_IN_CONTACT);
}
updatedTags.removeAll(tagsToRemove);

Person editedPerson = new Person(
personToEdit.getName(),
personToEdit.getPhone(),
personToEdit.getEmail(),
personToEdit.getAddress(),
updatedTags);

model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);

return new CommandResult(generateSuccessMessage(personToEdit));
}

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

Check warning on line 103 in src/main/java/seedu/address/logic/commands/UntagCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/UntagCommand.java#L103

Added line #L103 was not covered by tests
}

if (!(other instanceof UntagCommand)) {
return false;

Check warning on line 107 in src/main/java/seedu/address/logic/commands/UntagCommand.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/commands/UntagCommand.java#L107

Added line #L107 was not covered by tests
}

UntagCommand otherCommand = (UntagCommand) other;
return index.equals(otherCommand.index)
&& tagsToRemove.equals(otherCommand.tagsToRemove);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.TagCommand;
import seedu.address.logic.commands.UntagCommand;
import seedu.address.logic.parser.exceptions.ParseException;

/**
Expand Down Expand Up @@ -68,6 +69,7 @@
case CreateTagCommand.COMMAND_WORD -> new CreateTagCommandParser().parse(arguments);
case DeleteTagCommand.COMMAND_WORD -> new DeleteTagCommandParser().parse(arguments);
case TagCommand.COMMAND_WORD -> new TagCommandParser().parse(arguments);
case UntagCommand.COMMAND_WORD -> new UntagCommandParser().parse(arguments);

Check warning on line 72 in src/main/java/seedu/address/logic/parser/AddressBookParser.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/seedu/address/logic/parser/AddressBookParser.java#L72

Added line #L72 was not covered by tests
default -> {
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/seedu/address/logic/parser/UntagCommandParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package seedu.address.logic.parser;

import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;

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

import seedu.address.commons.core.index.Index;
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.logic.commands.UntagCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.tag.Tag;
import seedu.address.model.tag.TagName;

/**
* Parses input arguments and creates a new UntagCommand object.
*/
public class UntagCommandParser implements Parser<UntagCommand> {

/**
* Parses the given String of arguments in the context of the UntagCommand
* and returns a UntagCommand object for execution.
*
* @param args the user input string containing the index and tags to be removed
* @return a new UntagCommand object that contains the parsed index and list of tags
* @throws ParseException if the input does not conform to the expected format (i.e., invalid index or missing tags)
*/
public UntagCommand parse(String args) throws ParseException {
requireNonNull(args);

ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TAG);

Index index;

try {
// Parse the index from the preamble
index = ParserUtil.parseIndex(argMultimap.getPreamble());
} catch (IllegalValueException ive) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE), ive);
}

List<String> tagValues = argMultimap.getAllValues(PREFIX_TAG);
if (tagValues.isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE));
}

// Convert tag values to Tag objects
List<Tag> tags = tagValues.stream()
.map(TagName::new)
.map(Tag::new)
.collect(Collectors.toList());

return new UntagCommand(index, new HashSet<>(tags));
}
}
4 changes: 3 additions & 1 deletion src/main/java/seedu/address/model/AddressBook.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ public ObservableList<Person> getPersonList() {
}

@Override
public ObservableList<Tag> getTagList() { return tags.asUnmodifiableObservableList(); }
public ObservableList<Tag> getTagList() {
return tags.asUnmodifiableObservableList();
}

@Override
public boolean equals(Object other) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/seedu/address/model/ModelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ public void setTag(Tag target, Tag editedTag) {
}

@Override
public void deleteTag(Tag target) { addressBook.removeTag(target); }
public void deleteTag(Tag target) {
addressBook.removeTag(target);
}

//=========== Filtered Person List Accessors =============================================================

Expand Down
133 changes: 133 additions & 0 deletions src/test/java/seedu/address/logic/commands/UntagCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package seedu.address.logic.commands;

import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.junit.jupiter.api.Test;

import seedu.address.commons.core.index.Index;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;
import seedu.address.model.tag.TagName;

public class UntagCommandTest {

private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());


@Test
public void execute_validTagsUnfilteredList_success() {
Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
HashSet<Tag> tagsToRemove = new HashSet<>(Arrays.asList(new Tag(new TagName("friends"))));

UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tagsToRemove);

String expectedMessage = String.format(UntagCommand.MESSAGE_REMOVE_TAG_SUCCESS,
"friends", personToEdit.getName().toString());

Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
Set<Tag> updatedTags = new HashSet<>(personToEdit.getTags());
updatedTags.removeAll(tagsToRemove);
Person editedPerson = new Person(
personToEdit.getName(),
personToEdit.getPhone(),
personToEdit.getEmail(),
personToEdit.getAddress(),
updatedTags);
expectedModel.setPerson(personToEdit, editedPerson);

CommandTestUtil.assertCommandSuccess(untagCommand, model, expectedMessage, expectedModel);
}


@Test
public void execute_validMultipleTagsUnfilteredList_success() {
Person personWithTags = new Person(
new seedu.address.model.person.Name("Test Person"),
new seedu.address.model.person.Phone("99999999"),
new seedu.address.model.person.Email("[email protected]"),
new seedu.address.model.person.Address("123, Test Street"),
new HashSet<>(Arrays.asList(new Tag(new TagName("friends")), new Tag(new TagName("owesMoney"))))
);
model.setPerson(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()), personWithTags);
HashSet<Tag> tagsToRemove = new HashSet<>(Arrays.asList(new Tag(new TagName("friends")),
new Tag(new TagName("owesMoney"))));
UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tagsToRemove);
String expectedMessage = String.format(UntagCommand.MESSAGE_REMOVE_TAG_SUCCESS,
"owesMoney, friends", personWithTags.getName().toString());

// Create the expected model with the updated tags (i.e., an empty set of tags)
Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
HashSet<Tag> updatedTags = new HashSet<>(personWithTags.getTags());
updatedTags.removeAll(tagsToRemove);
Person editedPerson = new Person(
personWithTags.getName(),
personWithTags.getPhone(),
personWithTags.getEmail(),
personWithTags.getAddress(),
updatedTags);
expectedModel.setPerson(personWithTags, editedPerson);

CommandTestUtil.assertCommandSuccess(untagCommand, model, expectedMessage, expectedModel);
}


@Test
public void execute_nonExistentTag_failure() {
HashSet<Tag> tagsToRemove = new HashSet<>(Arrays.asList(new Tag(new TagName("nonExistentTag"))));
UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tagsToRemove);
String expectedMessage = Messages.MESSAGE_TAG_NOT_FOUND_IN_CONTACT;
CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}


@Test
public void execute_noTagsSpecified_failure() {
// No tags specified to remove
HashSet<Tag> emptyTagsToRemove = new HashSet<>(Arrays.asList());
UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, emptyTagsToRemove);
String expectedMessage = Messages.MESSAGE_TAG_NOT_FOUND_IN_CONTACT;
CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}


@Test
public void execute_invalidIndexUnfilteredList_failure() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
HashSet<Tag> tagsToRemove = new HashSet<>(Arrays.asList(new Tag(new TagName("friends"))));

UntagCommand untagCommand = new UntagCommand(outOfBoundIndex, tagsToRemove);

String expectedMessage = Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;

CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}


@Test
public void execute_personWithoutTags_failure() {
Person personWithoutTags = new Person(
new seedu.address.model.person.Name("Test Person"),
new seedu.address.model.person.Phone("99999999"),
new seedu.address.model.person.Email("[email protected]"),
new seedu.address.model.person.Address("123, Test Street"),
new HashSet<>() // No tags
);

model.setPerson(model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()), personWithoutTags);
HashSet<Tag> tagsToRemove = new HashSet<>(Arrays.asList(new Tag(new TagName("friends"))));
UntagCommand untagCommand = new UntagCommand(INDEX_SECOND_PERSON, tagsToRemove);
String expectedMessage = Messages.MESSAGE_TAG_NOT_FOUND_IN_CONTACT;

CommandTestUtil.assertCommandFailure(untagCommand, model, expectedMessage);
}
}
Loading