Skip to content

Commit

Permalink
Merge pull request #93 from bryanyee33/86-undo-command
Browse files Browse the repository at this point in the history
Add Undo command
  • Loading branch information
yisiox authored Mar 20, 2024
2 parents 3e89032 + 9159657 commit babe006
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 23 deletions.
3 changes: 1 addition & 2 deletions src/main/java/seedu/address/logic/Messages.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public class Messages {
"Multiple values specified for the following single-valued field(s): ";

public static final String MESSAGE_SHOWING_HELP = "Opened help window.";
public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";

public static final String MESSAGE_EXITING = "Exiting Address Book as requested ...";

/**
* Returns an error message indicating the duplicate prefixes.
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/seedu/address/logic/commands/CommandType.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ public Command createCommand(String arguments) {
public Command createCommand(String arguments) {
return new HelpCommand();
}
},
UNDO {
@Override
public Command createCommand(String arguments) {
return new UndoCommand();
}
};

public abstract Command createCommand(String arguments) throws IllegalArgumentException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ExitCommand extends Command {

@Override
public String execute(Model model) {
return Messages.MESSAGE_EXIT_ACKNOWLEDGEMENT;
return Messages.MESSAGE_EXITING;
}

}
28 changes: 28 additions & 0 deletions src/main/java/seedu/address/logic/commands/UndoCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package seedu.address.logic.commands;

import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;

/**
* Undoes the latest modifying command
*/
public class UndoCommand extends Command {

public static final String COMMAND_WORD = "undo";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Undo the latest modifying command.\n"
+ "Example: " + COMMAND_WORD;

public static final String MESSAGE_SUCCESS = "Undid the latest modifying command.";
public static final String MESSAGE_UNDO_EXCEPTION = "No modifying command to reverse.";

@Override
public String execute(Model model) throws CommandException {
if (!model.canUndo()) {
throw new CommandException(MESSAGE_UNDO_EXCEPTION);
}

model.undo();
return MESSAGE_SUCCESS;
}
}
55 changes: 39 additions & 16 deletions src/main/java/seedu/address/model/AddressBook.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import static java.util.Objects.requireNonNull;

import java.util.List;
import java.util.Stack;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.model.exceptions.AddressBookUndoException;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;

Expand All @@ -14,37 +18,54 @@
* Duplicates are not allowed (by .isSamePerson comparison)
*/
public class AddressBook implements ReadOnlyAddressBook {
private final UniquePersonList persons = new UniquePersonList();
@JsonIgnore
private final Stack<UniquePersonList> undoList = new Stack<>();

private final UniquePersonList persons;

/*
* The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
* between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
*
* Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
* among constructors.
*/
{
persons = new UniquePersonList();
public AddressBook() {
}

public AddressBook() {}

/**
* Creates an AddressBook using the Persons in the {@code toBeCopied}
*/
public AddressBook(ReadOnlyAddressBook toBeCopied) {
this();
resetData(toBeCopied);
persons.setPersons(toBeCopied.getPersonList());
}

//// list overwrite operations

/**
* Makes a copy of persons, and stores it in personsList.
*/
private void save() {
UniquePersonList savedList = new UniquePersonList();
savedList.setPersons(persons);
undoList.add(savedList);
}

/**
* Returns true if there are states to reverse to.
*/
public boolean canUndo() {
return !undoList.empty();
}

/**
* Undoes the latest change to address book.
*/
public void undo() {
if (undoList.empty()) {
throw new AddressBookUndoException();
}
persons.setPersons(undoList.pop());
}

/**
* Replaces the contents of the person list with {@code persons}.
* {@code persons} must not contain duplicate persons.
*/
public void setPersons(List<Person> persons) {
save();
this.persons.setPersons(persons);
}

Expand Down Expand Up @@ -72,6 +93,7 @@ public boolean hasPerson(Person person) {
* The person must not already exist in the address book.
*/
public void addPerson(Person p) {
save();
persons.add(p);
}

Expand All @@ -82,7 +104,7 @@ public void addPerson(Person p) {
*/
public void setPerson(Person target, Person editedPerson) {
requireNonNull(editedPerson);

save();
persons.setPerson(target, editedPerson);
}

Expand All @@ -91,6 +113,7 @@ public void setPerson(Person target, Person editedPerson) {
* {@code key} must exist in the address book.
*/
public void removePerson(Person key) {
save();
persons.remove(key);
}

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/seedu/address/model/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.model.exceptions.AddressBookUndoException;
import seedu.address.model.person.Person;

/**
Expand Down Expand Up @@ -52,6 +53,16 @@ public interface Model {
/** Returns the AddressBook */
ReadOnlyAddressBook getAddressBook();

/**
* Returns true if there are states to reverse to.
*/
boolean canUndo();

/**
* Undoes the latest change to address book.
*/
void undo() throws AddressBookUndoException;

/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/seedu/address/model/ModelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}

@Override
public boolean canUndo() {
return addressBook.canUndo();
}

@Override
public void undo() {
addressBook.undo();
}

@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package seedu.address.model.exceptions;

/**
* Signals that there are no more states to undo to.
*/
public class AddressBookUndoException extends RuntimeException {
public AddressBookUndoException() {
super("There are no previous AddressBook states to return to.");
}
}
2 changes: 1 addition & 1 deletion src/main/java/seedu/address/storage/StorageManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public Path getAddressBookFilePath() {

@Override
public Optional<ReadOnlyAddressBook> readAddressBook() throws DataLoadingException {
return readAddressBook(addressBookStorage.getAddressBookFilePath());
return readAddressBook(getAddressBookFilePath());
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/seedu/address/ui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ private String executeCommand(String commandText) throws CommandException, Parse
throw e;
}

if (commandResult.equals(Messages.MESSAGE_SHOWING_HELP)) {
if (commandResult == Messages.MESSAGE_SHOWING_HELP) {
handleHelp();
}
if (commandResult.equals(Messages.MESSAGE_EXIT_ACKNOWLEDGEMENT)) {
if (commandResult == Messages.MESSAGE_EXITING) {
handleExit();
}
return commandResult;
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/seedu/address/logic/commands/AddCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,16 @@ public ReadOnlyAddressBook getAddressBook() {
throw new AssertionError("This method should not be called.");
}

@Override
public boolean canUndo() {
throw new AssertionError("This method should not be called.");
}

@Override
public void undo() {
throw new AssertionError("This method should not be called.");
}

@Override
public boolean hasPerson(Person person) {
throw new AssertionError("This method should not be called.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ public class ExitCommandTest {

@Test
public void execute_exit_success() {
assertCommandSuccess(new ExitCommand(), model, Messages.MESSAGE_EXIT_ACKNOWLEDGEMENT, expectedModel);
assertCommandSuccess(new ExitCommand(), model, Messages.MESSAGE_EXITING, expectedModel);
}
}
77 changes: 77 additions & 0 deletions src/test/java/seedu/address/logic/commands/UndoCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package seedu.address.logic.commands;

import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;

import org.junit.jupiter.api.Test;

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.Person;
import seedu.address.testutil.PersonBuilder;

public class UndoCommandTest {

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

@Test
public void execute_multipleInvalidUndo_throwsCommandException() {
UndoCommand undoCommand = new UndoCommand();

String expectedMessage = UndoCommand.MESSAGE_UNDO_EXCEPTION;

assertCommandFailure(undoCommand, model, expectedMessage);
assertCommandFailure(undoCommand, model, expectedMessage);
assertCommandFailure(undoCommand, model, expectedMessage);
}

@Test
public void execute_oneUndo_success() {
UndoCommand undoCommand = new UndoCommand();

String expectedMessage = UndoCommand.MESSAGE_SUCCESS;

Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
model.deletePerson(personToDelete);
ModelManager expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());

assertCommandSuccess(undoCommand, model, expectedMessage, expectedModel);
}

@Test
public void execute_multipleUndo_success() {
UndoCommand undoCommand = new UndoCommand();

String expectedMessage = UndoCommand.MESSAGE_SUCCESS;
ModelManager expectedModelBase = new ModelManager(getTypicalAddressBook(), new UserPrefs());

// delete
Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
ModelManager expectedModelDelete = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
expectedModelDelete.deletePerson(personToDelete);
model.deletePerson(personToDelete);

// edit
Person editedPerson = new PersonBuilder().build();
ModelManager expectedModelEdit = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
expectedModelEdit.setPerson(model.getFilteredPersonList().get(0), editedPerson);
editedPerson = new PersonBuilder().build();
model.setPerson(model.getFilteredPersonList().get(0), editedPerson);

// clear
model.setAddressBook(new AddressBook());

// undo clear
assertCommandSuccess(undoCommand, model, expectedMessage, expectedModelEdit);
// undo edit
assertCommandSuccess(undoCommand, model, expectedMessage, expectedModelDelete);
// undo delete
assertCommandSuccess(undoCommand, model, expectedMessage, expectedModelBase);
// no more to undo
assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_UNDO_EXCEPTION);
}
}
13 changes: 13 additions & 0 deletions src/test/java/seedu/address/model/AddressBookTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package seedu.address.model;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -18,6 +19,7 @@

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seedu.address.model.exceptions.AddressBookUndoException;
import seedu.address.model.person.Person;
import seedu.address.model.person.exceptions.DuplicatePersonException;
import seedu.address.testutil.PersonBuilder;
Expand All @@ -31,6 +33,17 @@ public void constructor() {
assertEquals(Collections.emptyList(), addressBook.getPersonList());
}

@Test
public void undo_undoRemaining_success() {
addressBook.addPerson(ALICE);
assertDoesNotThrow(addressBook::undo);
}

@Test
public void undo_noUndoRemaining_throwsAddressBookUndoException() {
assertThrows(AddressBookUndoException.class, addressBook::undo);
}

@Test
public void resetData_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> addressBook.resetData(null));
Expand Down
Loading

0 comments on commit babe006

Please sign in to comment.