diff --git a/Logic.java b/Logic.java new file mode 100644 index 000000000..060ace020 --- /dev/null +++ b/Logic.java @@ -0,0 +1,100 @@ +package seedu.addressbook.logic; + +import seedu.addressbook.commands.Command; +import seedu.addressbook.commands.CommandResult; +import seedu.addressbook.data.AddressBook; +import seedu.addressbook.data.person.ReadOnlyPerson; +import seedu.addressbook.parser.Parser; +import seedu.addressbook.storage.StorageFile; +import seedu.addressbook.storage.Storage; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Represents the main Logic of the AddressBook. + */ +public class Logic { + + + private Storage storage; + private AddressBook addressBook; + + /** The list of person shown to the user most recently. */ + private List lastShownList = Collections.emptyList(); + + public Logic() throws Exception{ + setStorage(initializeStorage()); + setAddressBook(storage.load()); + } + + Logic(Storage saveFile, AddressBook addressBook){ + setStorage(saveFile); + setAddressBook(addressBook); + } + + void setStorage(Storage saveFile){ + this.storage = saveFile; + } + + void setAddressBook(AddressBook addressBook){ + this.addressBook = addressBook; + } + + /** + * Creates the StorageFile object based on the user specified path (if any) or the default storage path. + * @throws StorageFile.InvalidStorageFilePathException if the target file path is incorrect. + */ + private StorageFile initializeStorage() throws StorageFile.InvalidStorageFilePathException { + return new StorageFile(); + } + + public String getStorageFilePath() { + return storage.getPath(); + } + + /** + * Unmodifiable view of the current last shown list. + */ + public List getLastShownList() { + return Collections.unmodifiableList(lastShownList); + } + + protected void setLastShownList(List newList) { + lastShownList = newList; + } + + /** + * Parses the user command, executes it, and returns the result. + * @throws Exception if there was any problem during command execution. + */ + public CommandResult execute(String userCommandText) throws Exception { + Command command = new Parser().parseCommand(userCommandText); + CommandResult result = execute(command); + recordResult(result); + return result; + } + + /** + * Executes the command, updates storage, and returns the result. + * + * @param command user command + * @return result of the command + * @throws Exception if there was any problem during command execution. + */ + private CommandResult execute(Command command) throws Exception { + command.setData(addressBook, lastShownList); + CommandResult result = command.execute(); + storage.save(addressBook); + return result; + } + + /** Updates the {@link #lastShownList} if the result contains a list of Persons. */ + private void recordResult(CommandResult result) { + final Optional> personList = result.getRelevantPersons(); + if (personList.isPresent()) { + lastShownList = personList.get(); + } + } +} diff --git a/Storage.java b/Storage.java new file mode 100644 index 000000000..4d2300c69 --- /dev/null +++ b/Storage.java @@ -0,0 +1,17 @@ +package seedu.addressbook.storage; + +import seedu.addressbook.data.AddressBook; +import seedu.addressbook.storage.StorageFile.StorageOperationException; + +/* abstraction for Logic and StorageFile + * to reduce dependency + * */ + +public abstract class Storage { + /** Default file path used if the user doesn't provide the file name. */ + public static final String DEFAULT_STORAGE_FILEPATH = "addressbook.txt"; + + public abstract void save(AddressBook addressBook) throws StorageOperationException; + public abstract AddressBook load() throws StorageOperationException; + public abstract String getPath(); +} \ No newline at end of file diff --git a/StorageFile.java b/StorageFile.java new file mode 100644 index 000000000..607d7c011 --- /dev/null +++ b/StorageFile.java @@ -0,0 +1,148 @@ +package seedu.addressbook.storage; + +import seedu.addressbook.data.AddressBook; +import seedu.addressbook.data.exception.IllegalValueException; +import seedu.addressbook.storage.jaxb.AdaptedAddressBook; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Represents the file used to store address book data. + */ +public class StorageFile extends Storage { + + /** Default file path used if the user doesn't provide the file name. */ + public static final String DEFAULT_STORAGE_FILEPATH = "addressbook.txt"; + + /* Note: Note the use of nested classes below. + * More info https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html + */ + + /** + * Signals that the given file path does not fulfill the storage filepath constraints. + */ + public static class InvalidStorageFilePathException extends IllegalValueException { + public InvalidStorageFilePathException(String message) { + super(message); + } + } + + /** + * Signals that some error has occured while trying to convert and read/write data between the application + * and the storage file. + */ + public static class StorageOperationException extends Exception { + public StorageOperationException(String message) { + super(message); + } + } + + private final JAXBContext jaxbContext; + + public final Path path; + + /** + * @throws InvalidStorageFilePathException if the default path is invalid + */ + public StorageFile() throws InvalidStorageFilePathException { + this(DEFAULT_STORAGE_FILEPATH); + } + + /** + * @throws InvalidStorageFilePathException if the given file path is invalid + */ + public StorageFile(String filePath) throws InvalidStorageFilePathException { + try { + jaxbContext = JAXBContext.newInstance(AdaptedAddressBook.class); + } catch (JAXBException jaxbe) { + throw new RuntimeException("jaxb initialisation error"); + } + + path = Paths.get(filePath); + if (!isValidPath(path)) { + throw new InvalidStorageFilePathException("Storage file should end with '.txt'"); + } + } + + /** + * Returns true if the given path is acceptable as a storage file. + * The file path is considered acceptable if it ends with '.txt' + */ + private static boolean isValidPath(Path filePath) { + return filePath.toString().endsWith(".txt"); + } + + /** + * Saves all data to this storage file. + * + * @throws StorageOperationException if there were errors converting and/or storing data to file. + */ + public void save(AddressBook addressBook) throws StorageOperationException { + + /* Note: Note the 'try with resource' statement below. + * More info: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html + */ + try (final Writer fileWriter = + new BufferedWriter(new FileWriter(path.toFile()))) { + + final AdaptedAddressBook toSave = new AdaptedAddressBook(addressBook); + final Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.marshal(toSave, fileWriter); + + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path + " error: " + ioe.getMessage()); + } catch (JAXBException jaxbe) { + throw new StorageOperationException("Error converting address book into storage format"); + } + } + + /** + * Loads data from this storage file. + * + * @throws StorageOperationException if there were errors reading and/or converting data from file. + */ + public AddressBook load() throws StorageOperationException { + try (final Reader fileReader = + new BufferedReader(new FileReader(path.toFile()))) { + + final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + final AdaptedAddressBook loaded = (AdaptedAddressBook) unmarshaller.unmarshal(fileReader); + // manual check for missing elements + if (loaded.isAnyRequiredFieldMissing()) { + throw new StorageOperationException("File data missing some elements"); + } + return loaded.toModelType(); + + /* Note: Here, we are using an exception to create the file if it is missing. However, we should minimize + * using exceptions to facilitate normal paths of execution. If we consider the missing file as a 'normal' + * situation (i.e. not truly exceptional) we should not use an exception to handle it. + */ + + // create empty file if not found + } catch (FileNotFoundException fnfe) { + final AddressBook empty = new AddressBook(); + save(empty); + return empty; + + // other errors + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path); + } catch (JAXBException jaxbe) { + throw new StorageOperationException("Error parsing file data format"); + } catch (IllegalValueException ive) { + throw new StorageOperationException("File contains illegal data values; data type constraints not met"); + } + } + + public String getPath() { + return path.toString(); + } + +} diff --git a/doc/DeveloperGuide.md b/doc/DeveloperGuide.md index c82c3afb9..4af72c4e4 100644 --- a/doc/DeveloperGuide.md +++ b/doc/DeveloperGuide.md @@ -44,11 +44,17 @@ Priority | As a ... | I want to ... | So that I can... `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App `* * *` | user | add a new person | `* * *` | user | delete a person | remove entries that I no longer need +`* * *` | user | edit a person | update the person contact details `* * *` | user | find a person by name | locate details of persons without having to go through the entire list +`* * *` | user | find a person by tag | locate details of persons without having to go through the entire list `* *` | user | hide [private contact details](#private-contact-detail) by default | minimize chance of someone else seeing them by accident +`* *` | user | add tag to a person | adding a relevant tag to a person +`* *` | user | remove tag from a person | remove current tag of the person +`*` | user with multiple address book data | import & export from each address book | clone contacts from another address book data `*` | user with many persons in the address book | sort persons by name | locate a person easily + ## Appendix B : Use Cases (For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)