[
  {
    "path": ".gitignore",
    "content": "# Default storage file for addressbook : don't need to cleanup when running from IDE\naddressbook.txt\n\n# Compiled classfiles\n*.class\n\n# Package Files #\n*.jar\n*.war\n*.ear\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n# IDEA files\n.idea/\n*.iml\n\n# Temp files used for testing\ntest/actual.txt\ntest/localrun.bat\ntest/data/\n/bin/\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 nus-oss\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# AddressBook (Level 1)\n* This is a CLI (Command Line Interface) Address Book application **written in procedural fashion**. \n* It is a Java sample application intended for students learning Software Engineering while using Java as \n  the main programming language. \n* It provides a **reasonably well-written** code example that is **significantly bigger** than what students \n  usually write in data structure modules. \n* It can be used to achieve a number of beginner-level [learning outcomes](#learning-outcomes) without \n  running into the complications of OOP or GUI programmings.\n\n**Table of Contents**\n* [**User Guide**](#user-guide)\n    * [Starting the program](#starting-the-program)\n    * [List of commands](#list-of-commands)\n* [**Developer Guide**](#developer-guide)\n    * [Setting Up](#setting-up)\n    * [Design](#design)\n    * [Testing](#testing)\n* [**Learning Outcomes**](#learning-outcomes)\n    1. [Set up a project in an IDE [`LO-IdeSetup`]](#set-up-a-project-in-an-ide-lo-idesetup)\n    2. [Navigate code efficiently [`LO-CodeNavigation`]](#navigate-code-efficiently-lo-codenavigation)\n    3. [Use a debugger [`LO-Debugging`]](#use-a-debugger-lo-debugging)\n    4. [Automate CLI testing [`LO-AutomatedCliTesting`]](#automate-cli-testing-lo-automatedclitesting)\n    5. [Use Collections [`LO-Collections`]](#use-collections-lo-collections)\n    6. [Use Enums [`LO-Enums`]](#use-enums-lo-enums)\n    7. [Use Varargs [`LO-Varargs`]](#use-varargs-lo-varargs)\n    8. [Follow a coding standard [`LO-CodingStandard`]](#follow-a-coding-standard-lo-codingstandard)\n    9. [Apply coding best practices [`LO-CodingBestPractices`]](#apply-coding-best-practices-lo-codingbestpractices)\n    10. [Refactor code [`LO-Refactor`]](#refactor-code-lo-refactor)\n    11. [Abstract methods well [`LO-MethodAbstraction`]](#abstract-methods-well-lo-methodabstraction)\n    12. [Follow SLAP [`LO-SLAP`]](#follow-slap-lo-slap)\n    13. [Work in a 1kLoC code base [`LO-1KLoC`]](#work-in-a-1kloc-code-baselo-1kloc)\n* [**Contributors**](#contributors)\n* [**Contact Us**](#contact-us)\n\n-----------------------------------------------------------------------------------------------------\n# User Guide\n\nThis product is not meant for end-users and therefore there is no user-friendly installer. \nPlease refer to the [Setting up](#setting-up) section to learn how to set up the project.\n\n## Starting the program\n\n**Using IntelliJ**\n\n1. Find the project in the `Project Explorer` (usually located at the left side)\n   1. If the `Project Explorer` is not visible, press <kbd>ALT</kbd>+<kbd>1</kbd> for Windows/Linux, <kbd>CMD</kbd>+<kbd>1</kbd> for macOS to open the `Project Explorer` tab\n2. Go to the `src` folder and locate the `AddressBook` file\n3. Right click the file and select `Run AddressBook.main()`\n4. The program now should run on the `Console` (usually located at the bottom side)\n5. Now you can interact with the program through the `Console`\n\n**Using Command Line**\n\n1. 'Build' the project using IntelliJ (`Build` -> `Build Project`)\n2. Open the `Terminal`/`Command Prompt`. Note: You can open a terminal inside Intellij too (`View` -> `Tool Windows` -> `Terminal`)\n3. `cd` into the project's `out\\production\\addressbook-level1` directory. Note: That is the where Intellij puts its compiled class files.\n4. Type `java seedu.addressbook.AddressBook`, then <kbd>Enter</kbd> to execute\n5. Now you can interact with the program through the CLI\n\n## List of commands\n#### Viewing help: `help`\nFormat: `help` \n > Help is also shown if you enter an incorrect command e.g. `abcd`\n \n#### Adding a person: `add`\n> Adds a person to the address book\n\nFormat: `add NAME p/PHONE_NUMBER e/EMAIL`  \n> Words in `UPPER_CASE` are the parameters<br>\n  Phone number and email can be in any order but the name must come first.\n\nExamples: \n* `add John Doe p/98765432 e/johnd@gmail.com`\n* `add Betsy Crowe e/bencrowe@gmail.com p/1234567 `\n\n#### Listing all persons: `list`\n\n> Shows a list of persons, as an indexed list, in the order they were added to the address book, \noldest first.\n\nFormat: `list`  \n\n#### Finding a person by keyword `find`\n> Finds persons that match given keywords\n\nFormat: `find KEYWORD [MORE_KEYWORDS]`  \n> The search is case insensitive, the order of the keywords does not matter, only the name is searched, \nand persons matching at least one keyword will be returned (i.e. `OR` search).\n\nExamples: \n* `find John`\n  > Returns `John Doe` but not `john`\n   \n* `find Betsy Tim John`\n  > Returns Any person having names `Betsy`, `Tim`, or `John`\n\n#### Deleting a person: `delete`\n\nFormat: `delete INDEX`  \n> Deletes the person at the specified `INDEX`. \n  The index refers to the index numbers shown in the most recent listing.\n\nExamples: \n* `list`<br>\n  `delete 2`\n  > Deletes the 2nd person in the address book.\n  \n* `find Betsy` <br> \n  `delete 1`\n  > Deletes the 1st person in the results of the `find` command.\n\n#### Clearing all entries: `clear`\n> Clears all entries from the address book.  \nFormat: `clear`  \n\n#### Exiting the program: `exit`\nFormat: `exit`  \n\n#### Saving the data \nAddress book data are saved in the hard disk automatically after any command that changes the data. \nThere is no need to save manually.\n\n#### Changing the save location\nAddress book data are saved in a file called `addressbook.txt` in the project root folder.\nYou can change the location by specifying the file path as a program argument.\n\nExample:\n\n* `java seedu.addressbook.AddressBook mydata.txt`\n* `java seedu.addressbook.AddressBook myFolder/mydata.txt`\n\n> The file path must contain a valid file name and a valid parent directory.<br>\n  File name is valid if it has an extension and no reserved characters (OS-dependent).<br>\n  Parent directory is valid if it exists.<br>\n  Note for non-Windows users: if the file already exists, it must be a 'regular' file.<br>\n\n> When running the program inside IntelliJ, there is a way to set command line parameters\n  before running the program.\n\n-----------------------------------------------------------------------------------------------------\n# Developer Guide\n\n## Setting up\n\n**Prerequisites**\n\n* JDK 8 or later \n* IntelliJ IDE\n\n**Importing the project into IntelliJ**\n\n1. Open IntelliJ (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first)\n2. Set up the correct JDK version\n   1. Click `Configure` > `Project Defaults` > `Project Structure`\n   2. If JDK 8 is listed in the drop down, select it. If it is not, click `New...` and select the directory where you installed JDK 8.\n   3. Click `OK`.\n3. Click `Import Project`\n4. Locate the project directory and click `OK`\n5. Select `Create project from existing sources` and click `Next`\n6. Rename the project if you want. Click `Next`\n7. Ensure that your src folder is checked. Keep clicking `Next`\n8. Click `Finish`\n\n## Design\n\nAddressBook saves data in a plain text file, one line for each person, in the format `NAME p/PHONE e/EMAIL`.\nHere is an example:\n\n```\nJohn Doe p/98765432 e/johnd@gmail.com\nJane Doe p/12346758 e/jane@gmail.com\n```\n\nAll person data are loaded to memory at start up and written to the file after any command that mutates data.\nIn-memory data are held in a `ArrayList<String[]>` where each `String[]` object represents a person.\n\n\n## Testing\n\n**Windows**\n\n1. Open a DOS window in the `test` folder\n2. Run the `runtests.bat` script\n3. If the script reports that there is no difference between `actual.txt` and `expected.txt`, \n   the test has passed.\n\n**Mac/Unix/Linux**\n\n1. Open a terminal window in the `test` folder\n2. Run the `runtests.sh` script\n3. If the script reports that there is no difference between `actual.txt` and `expected.txt`, \n   the test has passed.\n\n**Troubleshooting test failures**\n\n* Problem: How do I examine the exact differences between `actual.txt` and `expected.txt`?<br>\n  Solution: You can use a diff/merge tool with a GUI e.g. WinMerge (on Windows)\n* Problem: The two files look exactly the same, but the test script reports they are different.<br>\n  Solution: This can happen because the line endings used by Windows is different from Unix-based\n  OSes. Convert the `actual.txt` to the format used by your OS using some [utility](https://kb.iu.edu/d/acux).\n* Problem: Test fails during the very first time.<br>\n  Solution: The output of the very first test run could be slightly different because the program\n  creates a new storage file. Tests should pass from the 2nd run onwards.\n\n-----------------------------------------------------------------------------------------------------\n# Learning Outcomes\n_Learning Outcomes_ are the things you should be able to do after studying this code and completing the\ncorresponding exercises.\n\n## Set up a project in an IDE `[LO-IdeSetup]`\n\n* Learn [how to set up a project in IntelliJ](https://se-edu.github.io/se-book/intellij/projectSetup/).\n\n#### Exercise: Setup project in IntelliJ \n\nPart A:\n* Create a new project in IntelliJ and write a small HelloWorld program.\n\nPart B:\n* Download the source code for this project: Click on the `clone or download` link above and either,\n   1. download as a zip file and unzip content.\n   2. clone the repo (if you know how to use Git) to your Computer.\n* [Set up](#setting-up) the project in IntelliJ.\n* [Run the program](#starting-the-program) from within IntelliJ, and try the features described in\n  the [User guide](#user-guide) section.\n\n## Navigate code efficiently `[LO-CodeNavigation]`\n\nThe `AddressBook.java` code is rather long, which makes it cumbersome to navigate by scrolling alone. \nNavigating code using IDE shortcuts is a more efficient option. \nFor example, <kbd>CTRL</kbd>+<kbd>B</kbd> will navigate to the definition of the method/field at the cursor.\n \nLearn [basic IntelliJ code navigation features](https://se-edu.github.io/se-book/intellij/codeNavigation/).\n\n#### Exercise: Learn to navigate code using shortcuts\n\n* Use Intellij basic code navigation features to navigate code of this project.\n\n## Use a debugger `[LO-Debugging]`\n\nLearn [basic IntelliJ debugging features](https://se-edu.github.io/se-book/intellij/debuggingBasic/).\n\n#### Exercise: Learn to step through code using the debugger\n\nPrerequisite: `[LO-IdeSetup]`\n\nDemonstrate your debugging skills using the AddressBook code. \n\nHere are some things you can do in your demonstration:\n\n1. Set a 'break point'\n2. Run the program in debug mode\n3. 'Step through' a few lines of code while examining variable values\n4. 'Step into', and 'step out of', methods as you step through the code\n5. ...\n\n## Automate CLI testing `[LO-AutomatedCliTesting]`\n\nLearn [how to automate testing of CLIs](https://se-edu.github.io/se-book/testing/testAutomation/testingTextUis/).\n\n#### Exercise: Practice automated CLI testing\n\n* Run the tests as explained in the [Testing](#testing) section.\n* Examine the test script to understand how the script works.\n* Add a few more tests to the `input.txt`. Run the tests. It should fail.<br> \n  Modify `expected.txt` to make the tests pass again.\n* Edit the `AddressBook.java` to modify the behavior slightly and modify tests to match.\n\n## Use Collections `[LO-Collections]`\n\nNote how the `AddressBook` class uses `ArrayList<>` class (from the Java `Collections` library) to store a list of `String` or `String[]` objects.\n\nLearn [how to use some Java `Collections` classes, such as `ArrayList` and `HashMap`](https://se-edu.github.io/se-book/javaTools/collections/)\n\n\n#### Exercise: Use `HashMap`\n\nCurrently, a person's details are stored as a `String[]`. Modify the code to use a `HashMap<String, String>` instead.\nA sample code snippet is given below.\n\n```java\nprivate static final String PERSON_PROPERTY_NAME = \"name\";\nprivate static final String PERSON_PROPERTY_EMAIL = \"email\";\n...\nHashMap<String,String> john = new HashMap<>();\njohn.put(PERSON_PROPERTY_NAME, \"John Doe\");\njohn.put(PERSON_PROPERTY_EMAIL, \"john.doe@email.com\");\n//etc.\n```\n\n## Use Enums `[LO-Enums]`\n\n#### Exercise: Use `HashMap` + `Enum`\n\nSimilar to the exercise in the `LO-Collections` section, but also bring in Java `enum` feature.\n\n```java\nprivate enum PersonProperty  {NAME, EMAIL, PHONE};\n...\nHashMap<PersonProperty,String> john = new HashMap<>();\njohn.put(PersonProperty.NAME, \"John Doe\");\njohn.put(PersonProperty.EMAIL, \"john.doe@email.com\");\n//etc.\n```\n\n## Use Varargs `[LO-Varargs]`\n\nNote how the `showToUser` method uses [Java Varargs feature](https://se-edu.github.io/se-book/javaTools/varargs/).\n\n#### Exercise: Use Varargs\n\nModify the code to remove the use of the Varargs feature.\nCompare the code with and without the varargs feature.\n\n## Follow a coding standard `[LO-CodingStandard]`\n\nThe given code follows the [coding standard][coding-standard] \nfor the most part.\n\nThis learning outcome is covered by the exercise in `[LO-Refactor]`.\n\n## Apply coding best practices `[LO-CodingBestPractices]`\n\nMost of the given code follows the best practices mentioned\n[here][code-quality].\n\nThis learning outcome is covered by the exercise in `[LO-Refactor]`\n\n## Refactor code `[LO-Refactor]`\n\n**Resources**:\n\n* [se-edu/se-gook: Refactoring](https://se-edu.github.io/se-book/refactoring/)\n* [se-edu/se-book: Refactoring in Intellij](https://se-edu.github.io/se-book/intellij/refactoring/)\n\n#### Exercise: Refactor the code to make it better\n\nNote: this exercise covers two other Learning Outcomes: `[LO-CodingStandard]`, `[LO-CodingBestPractices]`\n\n* Improve the code in the following ways,\n  * Fix [coding standard][coding-standard]\n    violations.\n  * Fix violations of the best practices given in [in this document][code-quality].\n  * Any other change that you think will improve the quality of the code.\n* Try to do the modifications as a combination of standard refactorings given in this\n  [catalog](http://refactoring.com/catalog/)\n* As far as possible, use automated refactoring features in IntelliJ.\n* If you know how to use Git, commit code after each refactoring.<br>\n  In the commit message, mention which refactoring you applied.<br>\n  Example commit messages: `Extract variable isValidPerson`, `Inline method isValidPerson()`\n* Remember to run the test script after each refactoring to prevent [regressions](https://se-edu.github.io/se-book/testing/testingTypes/regressionTesting).\n\n## Abstract methods well `[LO-MethodAbstraction]`\n\nNotice how most of the methods in `AddressBook` are short and focused (does only one thing and does it well).\n\n**Case 1**. Consider the following three lines in the `main` method.\n\n```java\n    String userCommand = getUserInput();\n    echoUserCommand(userCommand);\n    String feedback = executeCommand(userCommand);\n```\n\nIf we include the code of `echoUserCommand(String)` method inside the `getUserInput()`\n(resulting in the code given below), the behavior of AddressBook remains as before.\nHowever, that is a not a good approach because now the `getUserInput()` is doing two distinct things.\nA well-abstracted method should do only one thing.\n\n```java\n    String userCommand = getUserInput(); //also echos the command back to the user\n    String feedback = executeCommand(userCommand);\n```\n\n**Case 2**. Consider the method `removePrefixSign(String s, String sign)`.\nWhile it is short, there are some problems with how it has been abstracted.\n\n1. It contains the term `sign` which is not a term used by the AddressBook vocabulary.\n   \n   > **A method adds a new term to the vocabulary used to express the solution**.\n   > Therefore, it is not good when a method name contains terms that are not strictly necessary to express the\n   > solution (e.g. there is another term already used to express the same thing) or not in tune with the solution\n   > (e.g. it does not go well with the other terms already used).\n   \n2. Its implementation is not doing exactly what is advertised by the method name and the header comment.\n   For example, the code does not remove only prefixes; it removes `sign` from anywhere in the `s`.\n3. The method can be _more general_ and _more independent_ from the rest of the code. For example,\n   the method below can do the same job, but is more general (works for any string, not just parameters)\n   and is more independent from the rest of the code (not specific to AddressBook)\n\n   ```java\n   /**\n    * Removes prefix from the given fullString if prefix occurs at the start of the string.\n    */\n    private static String removePrefix(String fullString, String prefix) { ... }\n   ```\n   \n   If needed, a more AddressBook-specific method that works on parameter strings only can be defined.\n   In that case, that method can make use of the more general method suggested above.\n\n#### Exercise: Improve abstraction of method\n\nRefactor the method `removePrefixSign` as suggested above.\n\n\n## Follow SLAP `[LO-SLAP]`\n\nNotice how most of the methods in `AddressBook` are written at a single\nlevel of abstraction (_cf_ [se-edu/se-book:SLAP](https://se-edu.github.io/se-book/codeQuality/practices/slapHard/))\n\nHere is an example:\n\n```java\n    public static void main(String[] args) {\n        showWelcomeMessage();\n        processProgramArgs(args);\n        loadDataFromStorage();\n        while (true) {\n            userCommand = getUserInput();\n            echoUserCommand(userCommand);\n            String feedback = executeCommand(userCommand);\n            showResultToUser(feedback);\n        }\n    }\n```\n\n#### Exercise 1: Reduce SLAP of method\n\nIn the `main` method, replace the `processProgramArgs(args)` call with the actual code of that method.\nThe `main` method no longer has SLAP. Notice how mixing low level code with high level code reduces\nreadability.\n\n#### Exercise 2: Refactor the code to make it worse!\n\nSometimes, going in the wrong direction can be a good learning experience too. \nIn this exercise, we explore how low code qualities can go.\n\n* Refactor the code to make the code as bad as possible.<br>\n  i.e. How bad can you make it without breaking the functionality while still making it look like it was written by a \n  programmer (but a very bad programmer :-)).\n* In particular, inlining methods can worsen the code quality fast.\n\n## Work in a 1kLoC code base`[LO-1KLoC]`\n\n#### Exercise: Enhance the code\n\nEnhance the AddressBook to prove that you can work in a codebase of 1KLoC. <br>\nRemember to change code in small steps, update/run tests after each change, and commit after each significant change.\n\nSome suggested enhancements:\n\n* Make the `find` command case insensitive e.g. `find john` should match `John`\n* Add a `sort` command that can list the persons in alphabetical order\n* Add an `edit` command that can edit properties of a specific person\n* Add an additional field (like date of birth) to the person record\n\n-----------------------------------------------------------------------------------------------------\n# Contributors\n\nThe full list of contributors for se-edu can be found [here](https://se-edu.github.io/docs/Team.html).\n\n-----------------------------------------------------------------------------------------------------\n# Contact Us\n\n* **Bug reports, Suggestions**: Post in our [issue tracker](https://github.com/se-edu/addressbook-level1/issues)\n  if you noticed bugs or have suggestions on how to improve.\n* **Contributing**: We welcome pull requests. Refer to our website [here](https://se-edu.github.io/#contributing).\n* If you would like to contact us, refer to [our website](https://se-edu.github.io/#contact).\n\n[coding-standard]: https://github.com/oss-generic/process/blob/master/codingStandards/CodingStandard-Java.md \"Java Coding Standard\"\n[code-quality]: https://se-edu.github.io/se-book/codeQuality/ \"Code Quality Best Practices\""
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-cayman"
  },
  {
    "path": "src/seedu/addressbook/AddressBook.java",
    "content": "package seedu.addressbook;\n\n/*\n * NOTE : =============================================================\n * This class is written in a procedural fashion (i.e. not Object-Oriented)\n * Yes, it is possible to write non-OO code using an OO language.\n * ====================================================================\n */\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\n\n/*\n * NOTE : =============================================================\n * This class header comment below is brief because details of how to\n * use this class are documented elsewhere.\n * ====================================================================\n */\n\n/**\n * This class is used to maintain a list of person data which are saved\n * in a text file.\n **/\npublic class AddressBook {\n\n    /**\n     * Default file path used if the user doesn't provide the file name.\n     */\n    private static final String DEFAULT_STORAGE_FILEPATH = \"addressbook.txt\";\n\n    /**\n     * Version info of the program.\n     */\n    private static final String VERSION = \"AddessBook Level 1 - Version 1.0\";\n\n    /**\n     * A decorative prefix added to the beginning of lines printed by AddressBook\n     */\n    private static final String LINE_PREFIX = \"][ \";\n\n    /**\n     * A platform independent line separator.\n     */\n    private static final String LS = System.lineSeparator() + LINE_PREFIX;\n\n    /*\n     * NOTE : ==================================================================\n     * These messages shown to the user are defined in one place for convenient\n     * editing and proof reading. Such messages are considered part of the UI\n     * and may be subjected to review by UI experts or technical writers. Note\n     * that Some of the strings below include '%1$s' etc to mark the locations\n     * at which java String.format(...) method can insert values.\n     * =========================================================================\n     */\n    private static final String MESSAGE_ADDED = \"New person added: %1$s, Phone: %2$s, Email: %3$s\";\n    private static final String MESSAGE_ADDRESSBOOK_CLEARED = \"Address book has been cleared!\";\n    private static final String MESSAGE_COMMAND_HELP = \"%1$s: %2$s\";\n    private static final String MESSAGE_COMMAND_HELP_PARAMETERS = \"\\tParameters: %1$s\";\n    private static final String MESSAGE_COMMAND_HELP_EXAMPLE = \"\\tExample: %1$s\";\n    private static final String MESSAGE_DELETE_PERSON_SUCCESS = \"Deleted Person: %1$s\";\n    private static final String MESSAGE_DISPLAY_PERSON_DATA = \"%1$s  Phone Number: %2$s  Email: %3$s\";\n    private static final String MESSAGE_DISPLAY_LIST_ELEMENT_INDEX = \"%1$d. \";\n    private static final String MESSAGE_GOODBYE = \"Exiting Address Book... Good bye!\";\n    private static final String MESSAGE_INVALID_COMMAND_FORMAT = \"Invalid command format: %1$s \" + LS + \"%2$s\";\n    private static final String MESSAGE_INVALID_FILE = \"The given file name [%1$s] is not a valid file name!\";\n    private static final String MESSAGE_INVALID_PROGRAM_ARGS = \"Too many parameters! Correct program argument format:\"\n            + LS + \"\\tjava AddressBook\"\n            + LS + \"\\tjava AddressBook [custom storage file path]\";\n    private static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = \"The person index provided is invalid\";\n    private static final String MESSAGE_INVALID_STORAGE_FILE_CONTENT = \"Storage file has invalid content\";\n    private static final String MESSAGE_PERSON_NOT_IN_ADDRESSBOOK = \"Person could not be found in address book\";\n    private static final String MESSAGE_ERROR_CREATING_STORAGE_FILE = \"Error: unable to create file: %1$s\";\n    private static final String MESSAGE_ERROR_MISSING_STORAGE_FILE = \"Storage file missing: %1$s\";\n    private static final String MESSAGE_ERROR_READING_FROM_FILE = \"Unexpected error: unable to read from file: %1$s\";\n    private static final String MESSAGE_ERROR_WRITING_TO_FILE = \"Unexpected error: unable to write to file: %1$s\";\n    private static final String MESSAGE_PERSONS_FOUND_OVERVIEW = \"%1$d persons found!\";\n    private static final String MESSAGE_STORAGE_FILE_CREATED = \"Created new empty storage file: %1$s\";\n    private static final String MESSAGE_WELCOME = \"Welcome to your Address Book!\";\n    private static final String MESSAGE_USING_DEFAULT_FILE = \"Using default storage file : \" + DEFAULT_STORAGE_FILEPATH;\n\n    // These are the prefix strings to define the data type of a command parameter\n    private static final String PERSON_DATA_PREFIX_PHONE = \"p/\";\n    private static final String PERSON_DATA_PREFIX_EMAIL = \"e/\";\n\n    private static final String PERSON_STRING_REPRESENTATION = \"%1$s \" // name\n            + PERSON_DATA_PREFIX_PHONE + \"%2$s \" // phone\n            + PERSON_DATA_PREFIX_EMAIL + \"%3$s\"; // email\n    private static final String COMMAND_ADD_WORD = \"add\";\n    private static final String COMMAND_ADD_DESC = \"Adds a person to the address book.\";\n    private static final String COMMAND_ADD_PARAMETERS = \"NAME \"\n            + PERSON_DATA_PREFIX_PHONE + \"PHONE_NUMBER \"\n            + PERSON_DATA_PREFIX_EMAIL + \"EMAIL\";\n    private static final String COMMAND_ADD_EXAMPLE = COMMAND_ADD_WORD + \" John Doe p/98765432 e/johnd@gmail.com\";\n\n    private static final String COMMAND_FIND_WORD = \"find\";\n    private static final String COMMAND_FIND_DESC = \"Finds all persons whose names contain any of the specified \"\n            + \"keywords (case-insensitive) and displays them as a list with index numbers.\";\n    private static final String COMMAND_FIND_PARAMETERS = \"KEYWORD [MORE_KEYWORDS]\";\n    private static final String COMMAND_FIND_EXAMPLE = COMMAND_FIND_WORD + \" alice bob charlie\";\n\n    private static final String COMMAND_LIST_WORD = \"list\";\n    private static final String COMMAND_LIST_DESC = \"Displays all persons as a list with index numbers.\";\n    private static final String COMMAND_LIST_EXAMPLE = COMMAND_LIST_WORD;\n\n    private static final String COMMAND_DELETE_WORD = \"delete\";\n    private static final String COMMAND_DELETE_DESC = \"Deletes a person identified by the index number used in \"\n            + \"the last find/list call.\";\n    private static final String COMMAND_DELETE_PARAMETER = \"INDEX\";\n    private static final String COMMAND_DELETE_EXAMPLE = COMMAND_DELETE_WORD + \" 1\";\n\n    private static final String COMMAND_CLEAR_WORD = \"clear\";\n    private static final String COMMAND_CLEAR_DESC = \"Clears address book permanently.\";\n    private static final String COMMAND_CLEAR_EXAMPLE = COMMAND_CLEAR_WORD;\n\n    private static final String COMMAND_HELP_WORD = \"help\";\n    private static final String COMMAND_HELP_DESC = \"Shows program usage instructions.\";\n    private static final String COMMAND_HELP_EXAMPLE = COMMAND_HELP_WORD;\n\n    private static final String COMMAND_EXIT_WORD = \"exit\";\n    private static final String COMMAND_EXIT_DESC = \"Exits the program.\";\n    private static final String COMMAND_EXIT_EXAMPLE = COMMAND_EXIT_WORD;\n\n    private static final String DIVIDER = \"===================================================\";\n\n\n    /* We use a HashMap to store details of a single person.\n     * The enum type given below is the keys for different data elements of a person\n     * used by the internal HashMap<PersonProperty, String> storage format.\n     * For example, a person's name is stored as a key-value pair in a HashMap with key PersonProperty.NAME\n     */\n    private enum PersonProperty {\n        NAME, EMAIL, PHONE\n    }\n\n    /**\n     * The number of data elements for a single person.\n     */\n    private static final int PERSON_DATA_COUNT = 3;\n\n    /**\n     * Offset required to convert between 1-indexing and 0-indexing.COMMAND_\n     */\n    private static final int DISPLAYED_INDEX_OFFSET = 1;\n\n    /**\n     * If the first non-whitespace character in a user's input line is this, that line will be ignored.\n     */\n    private static final char INPUT_COMMENT_MARKER = '#';\n\n    /*\n     * This variable is declared for the whole class (instead of declaring it\n     * inside the readUserCommand() method to facilitate automated testing using\n     * the I/O redirection technique. If not, only the first line of the input\n     * text file will be processed.\n     */\n    private static final Scanner SCANNER = new Scanner(System.in);\n\n    /*\n     * NOTE : =============================================================================================\n     * Note that the type of the variable below can also be declared as List<HashMap<PersonProperty, String>>, as follows:\n     *    private static final List<HashMap<PersonProperty, String>> ALL_PERSONS = new ArrayList<>()\n     * That is because List is an interface implemented by the ArrayList class.\n     * In this code we use ArrayList instead because we wanted to to stay away from advanced concepts\n     * such as interface inheritance.\n     * ====================================================================================================\n     */\n\n    /**\n     * List of all persons in the address book.\n     */\n    private static final ArrayList<HashMap<PersonProperty, String>> ALL_PERSONS = new ArrayList<>();\n\n    /**\n     * Stores the most recent list of persons shown to the user as a result of a user command.\n     * This is a subset of the full list. Deleting persons in the pull list does not delete\n     * those persons from this list.\n     */\n    private static ArrayList<HashMap<PersonProperty, String>> latestPersonListingView = getAllPersonsInAddressBook(); // initial view is of all\n\n    /**\n     * The path to the file used for storing person data.\n     */\n    private static String storageFilePath;\n\n    /*\n     * NOTE : =============================================================\n     * Notice how this method solves the whole problem at a very high level.\n     * We can understand the high-level logic of the program by reading this\n     * method alone.\n     * If the reader wants a deeper understanding of the solution, she can go\n     * to the next level of abstraction by reading the methods that are\n     * referenced by the high-level method below.\n     * ====================================================================\n     */\n\n    public static void main(String[] args) {\n        showWelcomeMessage();\n        processProgramArgs(args);\n        loadDataFromStorage();\n        while (true) {\n            String userCommand = getUserInput();\n            echoUserCommand(userCommand);\n            String feedback = executeCommand(userCommand);\n            showResultToUser(feedback);\n        }\n    }\n\n    /*\n     * NOTE : =============================================================\n     * The method header comment can be omitted if the method is trivial\n     * and the header comment is going to be almost identical to the method\n     * signature anyway.\n     * ====================================================================\n     */\n\n    private static void showWelcomeMessage() {\n        showToUser(DIVIDER, DIVIDER, VERSION, MESSAGE_WELCOME, DIVIDER);\n    }\n\n    private static void showResultToUser(String result) {\n        showToUser(result, DIVIDER);\n    }\n\n    /*\n     * NOTE : =============================================================\n     * Parameter description can be omitted from the method header comment\n     * if the parameter name is self-explanatory.\n     * In the method below, '@param userInput' comment has been omitted.\n     * ====================================================================\n     */\n\n    /**\n     * Echoes the user input back to the user.\n     */\n    private static void echoUserCommand(String userCommand) {\n        showToUser(\"[Command entered:\" + userCommand + \"]\");\n    }\n\n    /**\n     * Processes the program main method run arguments.\n     * If a valid storage file is specified, sets up that file for storage.\n     * Otherwise sets up the default file for storage.\n     *\n     * @param args full program arguments passed to application main method\n     */\n    private static void processProgramArgs(String[] args) {\n        if (args.length >= 2) {\n            showToUser(MESSAGE_INVALID_PROGRAM_ARGS);\n            exitProgram();\n        }\n\n        if (args.length == 1) {\n            setupGivenFileForStorage(args[0]);\n        }\n\n        if (args.length == 0) {\n            setupDefaultFileForStorage();\n        }\n    }\n\n    /**\n     * Sets up the storage file based on the supplied file path.\n     * Creates the file if it is missing.\n     * Exits if the file name is not acceptable.\n     */\n    private static void setupGivenFileForStorage(String filePath) {\n\n        if (!isValidFilePath(filePath)) {\n            showToUser(String.format(MESSAGE_INVALID_FILE, filePath));\n            exitProgram();\n        }\n\n        storageFilePath = filePath;\n        createFileIfMissing(filePath);\n    }\n\n    /**\n     * Displays the goodbye message and exits the runtime.\n     */\n    private static void exitProgram() {\n        showToUser(MESSAGE_GOODBYE, DIVIDER, DIVIDER);\n        System.exit(0);\n    }\n\n    /**\n     * Sets up the storage based on the default file.\n     * Creates file if missing.\n     * Exits program if the file cannot be created.\n     */\n    private static void setupDefaultFileForStorage() {\n        showToUser(MESSAGE_USING_DEFAULT_FILE);\n        storageFilePath = DEFAULT_STORAGE_FILEPATH;\n        createFileIfMissing(storageFilePath);\n    }\n\n    /**\n     * Returns true if the given file path is valid.\n     * A file path is valid if it has a valid parent directory as determined by {@link #hasValidParentDirectory}\n     * and a valid file name as determined by {@link #hasValidFileName}.\n     */\n    private static boolean isValidFilePath(String filePath) {\n        if (filePath == null) {\n            return false;\n        }\n        Path filePathToValidate;\n        try {\n            filePathToValidate = Paths.get(filePath);\n        } catch (InvalidPathException ipe) {\n            return false;\n        }\n        return hasValidParentDirectory(filePathToValidate) && hasValidFileName(filePathToValidate);\n    }\n\n    /**\n     * Returns true if the file path has a parent directory that exists.\n     */\n    private static boolean hasValidParentDirectory(Path filePath) {\n        Path parentDirectory = filePath.getParent();\n        return parentDirectory == null || Files.isDirectory(parentDirectory);\n    }\n\n    /**\n     * Returns true if file path has a valid file name.\n     * File name is valid if it has an extension and no reserved characters.\n     * Reserved characters are OS-dependent.\n     * If a file already exists, it must be a regular file.\n     */\n    private static boolean hasValidFileName(Path filePath) {\n        return filePath.getFileName().toString().lastIndexOf('.') > 0\n                && (!Files.exists(filePath) || Files.isRegularFile(filePath));\n    }\n\n    /**\n     * Initialises the in-memory data using the storage file.\n     * Assumption: The file exists.\n     */\n    private static void loadDataFromStorage() {\n        initialiseAddressBookModel(loadPersonsFromFile(storageFilePath));\n    }\n\n\n    /*\n     * ===========================================\n     *           COMMAND LOGIC\n     * ===========================================\n     */\n\n    /**\n     * Executes the command as specified by the {@code userInputString}\n     *\n     * @param userInputString raw input from user\n     * @return feedback about how the command was executed\n     */\n    private static String executeCommand(String userInputString) {\n        final String[] commandTypeAndParams = splitCommandWordAndArgs(userInputString);\n        final String commandType = commandTypeAndParams[0];\n        final String commandArgs = commandTypeAndParams[1];\n        switch (commandType) {\n            case COMMAND_ADD_WORD:\n                return executeAddPerson(commandArgs);\n            case COMMAND_FIND_WORD:\n                return executeFindPersons(commandArgs);\n            case COMMAND_LIST_WORD:\n                return executeListAllPersonsInAddressBook();\n            case COMMAND_DELETE_WORD:\n                return executeDeletePerson(commandArgs);\n            case COMMAND_CLEAR_WORD:\n                return executeClearAddressBook();\n            case COMMAND_HELP_WORD:\n                return getUsageInfoForAllCommands();\n            case COMMAND_EXIT_WORD:\n                executeExitProgramRequest();\n            default:\n                return getMessageForInvalidCommandInput(commandType, getUsageInfoForAllCommands());\n        }\n    }\n\n    /**\n     * Splits raw user input into command word and command arguments string\n     *\n     * @return size 2 array; first element is the command type and second element is the arguments string\n     */\n    private static String[] splitCommandWordAndArgs(String rawUserInput) {\n        final String[] split = rawUserInput.trim().split(\"\\\\s+\", 2);\n        return split.length == 2 ? split : new String[]{split[0], \"\"}; // else case: no parameters\n    }\n\n    /**\n     * Constructs a generic feedback message for an invalid command from user, with instructions for correct usage.\n     *\n     * @param correctUsageInfo message showing the correct usage\n     * @return invalid command args feedback message\n     */\n    private static String getMessageForInvalidCommandInput(String userCommand, String correctUsageInfo) {\n        return String.format(MESSAGE_INVALID_COMMAND_FORMAT, userCommand, correctUsageInfo);\n    }\n\n    /**\n     * Adds a person (specified by the command args) to the address book.\n     * The entire command arguments string is treated as a string representation of the person to add.\n     *\n     * @param commandArgs full command args string from the user\n     * @return feedback display message for the operation result\n     */\n    private static String executeAddPerson(String commandArgs) {\n        // try decoding a person from the raw args\n        final Optional<HashMap<PersonProperty, String>> decodeResult = decodePersonFromString(commandArgs);\n\n        // checks if args are valid (decode result will not be present if the person is invalid)\n        if (!decodeResult.isPresent()) {\n            return getMessageForInvalidCommandInput(COMMAND_ADD_WORD, getUsageInfoForAddCommand());\n        }\n\n        // add the person as specified\n        final HashMap<PersonProperty, String> personToAdd = decodeResult.get();\n        addPersonToAddressBook(personToAdd);\n        return getMessageForSuccessfulAddPerson(personToAdd);\n    }\n\n    /**\n     * Constructs a feedback message for a successful add person command execution.\n     *\n     * @param addedPerson person who was successfully added\n     * @return successful add person feedback message\n     * @see #executeAddPerson(String)\n     */\n    private static String getMessageForSuccessfulAddPerson(HashMap<PersonProperty, String> addedPerson) {\n        return String.format(MESSAGE_ADDED,\n                getNameFromPerson(addedPerson), getPhoneFromPerson(addedPerson), getEmailFromPerson(addedPerson));\n    }\n\n    /**\n     * Finds and lists all persons in address book whose name contains any of the argument keywords.\n     * Keyword matching is case sensitive.\n     *\n     * @param commandArgs full command args string from the user\n     * @return feedback display message for the operation result\n     */\n    private static String executeFindPersons(String commandArgs) {\n        final Set<String> keywords = extractKeywordsFromFindPersonArgs(commandArgs);\n        final ArrayList<HashMap<PersonProperty, String>> personsFound = getPersonsWithNameContainingAnyKeyword(keywords);\n        showToUser(personsFound);\n        return getMessageForPersonsDisplayedSummary(personsFound);\n    }\n\n    /**\n     * Constructs a feedback message to summarise an operation that displayed a listing of persons.\n     *\n     * @param personsDisplayed used to generate summary\n     * @return summary message for persons displayed\n     */\n    private static String getMessageForPersonsDisplayedSummary(ArrayList<HashMap<PersonProperty, String>> personsDisplayed) {\n        return String.format(MESSAGE_PERSONS_FOUND_OVERVIEW, personsDisplayed.size());\n    }\n\n    /**\n     * Extracts keywords from the command arguments given for the find persons command.\n     *\n     * @param findPersonCommandArgs full command args string for the find persons command\n     * @return set of keywords as specified by args\n     */\n    private static Set<String> extractKeywordsFromFindPersonArgs(String findPersonCommandArgs) {\n        return new HashSet<>(splitByWhitespace(findPersonCommandArgs.trim()));\n    }\n\n    /**\n     * Retrieves all persons in the full model whose names contain some of the specified keywords.\n     *\n     * @param keywords for searching\n     * @return list of persons in full model with name containing some of the keywords\n     */\n    private static ArrayList<HashMap<PersonProperty, String>> getPersonsWithNameContainingAnyKeyword(Collection<String> keywords) {\n        Set<String> keywordsInLowerCase = convertToLowerCase(keywords);\n        final ArrayList<HashMap<PersonProperty, String>> matchedPersons = new ArrayList<>();\n        for (HashMap<PersonProperty, String> person : getAllPersonsInAddressBook()) {\n            final Set<String> wordsInName = new HashSet<>(splitByWhitespace(getNameFromPerson(person)));\n            Set<String> wordsInNameInLowerCase = convertToLowerCase(wordsInName);\n            if (!Collections.disjoint(wordsInNameInLowerCase, keywordsInLowerCase)) {\n                matchedPersons.add(person);\n            }\n        }\n        return matchedPersons;\n    }\n\n    /**\n     * Deletes person identified using last displayed index.\n     *\n     * @param commandArgs full command args string from the user\n     * @return feedback display message for the operation result\n     */\n    private static String executeDeletePerson(String commandArgs) {\n        if (!isDeletePersonArgsValid(commandArgs)) {\n            return getMessageForInvalidCommandInput(COMMAND_DELETE_WORD, getUsageInfoForDeleteCommand());\n        }\n        final int targetVisibleIndex = extractTargetIndexFromDeletePersonArgs(commandArgs);\n        if (!isDisplayIndexValidForLastPersonListingView(targetVisibleIndex)) {\n            return MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;\n        }\n        final HashMap<PersonProperty, String> targetInModel = getPersonByLastVisibleIndex(targetVisibleIndex);\n        return deletePersonFromAddressBook(targetInModel) ? getMessageForSuccessfulDelete(targetInModel) // success\n                : MESSAGE_PERSON_NOT_IN_ADDRESSBOOK; // not found\n    }\n\n    /**\n     * Checks validity of delete person argument string's format.\n     *\n     * @param rawArgs raw command args string for the delete person command\n     * @return whether the input args string is valid\n     */\n    private static boolean isDeletePersonArgsValid(String rawArgs) {\n        try {\n            final int extractedIndex = Integer.parseInt(rawArgs.trim()); // use standard libraries to parse\n            return extractedIndex >= DISPLAYED_INDEX_OFFSET;\n        } catch (NumberFormatException nfe) {\n            return false;\n        }\n    }\n\n    /**\n     * Extracts the target's index from the raw delete person args string\n     *\n     * @param rawArgs raw command args string for the delete person command\n     * @return extracted index\n     */\n    private static int extractTargetIndexFromDeletePersonArgs(String rawArgs) {\n        return Integer.parseInt(rawArgs.trim());\n    }\n\n    /**\n     * Checks that the given index is within bounds and valid for the last shown person list view.\n     *\n     * @param index to check\n     * @return whether it is valid\n     */\n    private static boolean isDisplayIndexValidForLastPersonListingView(int index) {\n        return index >= DISPLAYED_INDEX_OFFSET && index < latestPersonListingView.size() + DISPLAYED_INDEX_OFFSET;\n    }\n\n    /**\n     * Constructs a feedback message for a successful delete person command execution.\n     *\n     * @param deletedPerson successfully deleted\n     * @return successful delete person feedback message\n     * @see #executeDeletePerson(String)\n     */\n    private static String getMessageForSuccessfulDelete(HashMap<PersonProperty, String> deletedPerson) {\n        return String.format(MESSAGE_DELETE_PERSON_SUCCESS, getMessageForFormattedPersonData(deletedPerson));\n    }\n\n    /**\n     * Clears all persons in the address book.\n     *\n     * @return feedback display message for the operation result\n     */\n    private static String executeClearAddressBook() {\n        clearAddressBook();\n        return MESSAGE_ADDRESSBOOK_CLEARED;\n    }\n\n    /**\n     * Displays all persons in the address book to the user; in added order.\n     *\n     * @return feedback display message for the operation result\n     */\n    private static String executeListAllPersonsInAddressBook() {\n        ArrayList<HashMap<PersonProperty, String>> toBeDisplayed = getAllPersonsInAddressBook();\n        showToUser(toBeDisplayed);\n        return getMessageForPersonsDisplayedSummary(toBeDisplayed);\n    }\n\n    /**\n     * Requests to terminate the program.\n     */\n    private static void executeExitProgramRequest() {\n        exitProgram();\n    }\n\n    /*\n     * ===========================================\n     *               UI LOGIC\n     * ===========================================\n     */\n\n    /**\n     * Prompts for the command and reads the text entered by the user.\n     * Ignores lines with first non-whitespace char equal to {@link #INPUT_COMMENT_MARKER} (considered comments)\n     *\n     * @return full line entered by the user\n     */\n    private static String getUserInput() {\n        System.out.print(LINE_PREFIX + \"Enter command: \");\n        String inputLine = SCANNER.nextLine();\n        // silently consume all blank and comment lines\n        while (inputLine.trim().isEmpty() || inputLine.trim().charAt(0) == INPUT_COMMENT_MARKER) {\n            inputLine = SCANNER.nextLine();\n        }\n        return inputLine;\n    }\n\n   /*\n    * NOTE : =============================================================\n    * Note how the method below uses Java 'Varargs' feature so that the\n    * method can accept a varying number of message parameters.\n    * ====================================================================\n    */\n\n    /**\n     * Shows a message to the user\n     */\n    private static void showToUser(String... message) {\n        for (String m : message) {\n            System.out.println(LINE_PREFIX + m);\n        }\n    }\n\n    /**\n     * Shows the list of persons to the user.\n     * The list will be indexed, starting from 1.\n     */\n    private static void showToUser(ArrayList<HashMap<PersonProperty, String>> persons) {\n        String listAsString = getDisplayString(persons);\n        showToUser(listAsString);\n        updateLatestViewedPersonListing(persons);\n    }\n\n    /**\n     * Returns the display string representation of the list of persons.\n     */\n    private static String getDisplayString(ArrayList<HashMap<PersonProperty, String>> persons) {\n        final StringBuilder messageAccumulator = new StringBuilder();\n        for (int i = 0; i < persons.size(); i++) {\n            final HashMap<PersonProperty, String> person = persons.get(i);\n            final int displayIndex = i + DISPLAYED_INDEX_OFFSET;\n            messageAccumulator.append('\\t')\n                    .append(getIndexedPersonListElementMessage(displayIndex, person))\n                    .append(LS);\n        }\n        return messageAccumulator.toString();\n    }\n\n    /**\n     * Constructs a prettified listing element message to represent a person and their data.\n     *\n     * @param visibleIndex visible index for this listing\n     * @param person       to show\n     * @return formatted listing message with index\n     */\n    private static String getIndexedPersonListElementMessage(int visibleIndex, HashMap<PersonProperty, String> person) {\n        return String.format(MESSAGE_DISPLAY_LIST_ELEMENT_INDEX, visibleIndex) + getMessageForFormattedPersonData(person);\n    }\n\n    /**\n     * Constructs a prettified string to show the user a person's data.\n     *\n     * @param person to show\n     * @return formatted message showing internal state\n     */\n    private static String getMessageForFormattedPersonData(HashMap<PersonProperty, String> person) {\n        return String.format(MESSAGE_DISPLAY_PERSON_DATA,\n                getNameFromPerson(person), getPhoneFromPerson(person), getEmailFromPerson(person));\n    }\n\n    /**\n     * Updates the latest person listing view the user has seen.\n     *\n     * @param newListing the new listing of persons\n     */\n    private static void updateLatestViewedPersonListing(ArrayList<HashMap<PersonProperty, String>> newListing) {\n        // clone to insulate from future changes to arg list\n        latestPersonListingView = new ArrayList<>(newListing);\n    }\n\n    /**\n     * Retrieves the person identified by the displayed index from the last shown listing of persons.\n     *\n     * @param lastVisibleIndex displayed index from last shown person listing\n     * @return the actual person object in the last shown person listing\n     */\n    private static HashMap<PersonProperty, String> getPersonByLastVisibleIndex(int lastVisibleIndex) {\n        return latestPersonListingView.get(lastVisibleIndex - DISPLAYED_INDEX_OFFSET);\n    }\n\n\n    /*\n     * ===========================================\n     *             STORAGE LOGIC\n     * ===========================================\n     */\n\n    /**\n     * Creates storage file if it does not exist. Shows feedback to user.\n     *\n     * @param filePath file to create if not present\n     */\n    private static void createFileIfMissing(String filePath) {\n        final File storageFile = new File(filePath);\n        if (storageFile.exists()) {\n            return;\n        }\n\n        showToUser(String.format(MESSAGE_ERROR_MISSING_STORAGE_FILE, filePath));\n\n        try {\n            storageFile.createNewFile();\n            showToUser(String.format(MESSAGE_STORAGE_FILE_CREATED, filePath));\n        } catch (IOException ioe) {\n            showToUser(String.format(MESSAGE_ERROR_CREATING_STORAGE_FILE, filePath));\n            exitProgram();\n        }\n    }\n\n    /**\n     * Converts contents of a file into a list of persons.\n     * Shows error messages and exits program if any errors in reading or decoding was encountered.\n     *\n     * @param filePath file to load from\n     * @return the list of decoded persons\n     */\n    private static ArrayList<HashMap<PersonProperty, String>> loadPersonsFromFile(String filePath) {\n        final Optional<ArrayList<HashMap<PersonProperty, String>>> successfullyDecoded = decodePersonsFromStrings(getLinesInFile(filePath));\n        if (!successfullyDecoded.isPresent()) {\n            showToUser(MESSAGE_INVALID_STORAGE_FILE_CONTENT);\n            exitProgram();\n        }\n        return successfullyDecoded.get();\n    }\n\n    /**\n     * Gets all lines in the specified file as a list of strings. Line separators are removed.\n     * Shows error messages and exits program if unable to read from file.\n     */\n    private static ArrayList<String> getLinesInFile(String filePath) {\n        ArrayList<String> lines = null;\n        try {\n            lines = new ArrayList<>(Files.readAllLines(Paths.get(filePath)));\n        } catch (FileNotFoundException fnfe) {\n            showToUser(String.format(MESSAGE_ERROR_MISSING_STORAGE_FILE, filePath));\n            exitProgram();\n        } catch (IOException ioe) {\n            showToUser(String.format(MESSAGE_ERROR_READING_FROM_FILE, filePath));\n            exitProgram();\n        }\n        return lines;\n    }\n\n    /**\n     * Saves all data to the file. Exits program if there is an error saving to file.\n     *\n     * @param filePath file for saving\n     */\n    private static void savePersonsToFile(ArrayList<HashMap<PersonProperty, String>> persons, String filePath) {\n        final ArrayList<String> linesToWrite = encodePersonsToStrings(persons);\n        try {\n            Files.write(Paths.get(storageFilePath), linesToWrite);\n        } catch (IOException ioe) {\n            showToUser(String.format(MESSAGE_ERROR_WRITING_TO_FILE, filePath));\n            exitProgram();\n        }\n    }\n\n\n    /*\n     * ================================================================================\n     *        INTERNAL ADDRESS BOOK DATA METHODS\n     * ================================================================================\n     */\n\n    /**\n     * Adds a person to the address book. Saves changes to storage file.\n     *\n     * @param person to add\n     */\n    private static void addPersonToAddressBook(HashMap<PersonProperty, String> person) {\n        ALL_PERSONS.add(person);\n        savePersonsToFile(getAllPersonsInAddressBook(), storageFilePath);\n    }\n\n    /**\n     * Deletes the specified person from the addressbook if it is inside. Saves any changes to storage file.\n     *\n     * @param exactPerson the actual person inside the address book (exactPerson == the person to delete in the full list)\n     * @return true if the given person was found and deleted in the model\n     */\n    private static boolean deletePersonFromAddressBook(HashMap<PersonProperty, String> exactPerson) {\n        final boolean isChanged = ALL_PERSONS.remove(exactPerson);\n        if (isChanged) {\n            savePersonsToFile(getAllPersonsInAddressBook(), storageFilePath);\n        }\n        return isChanged;\n    }\n\n    /**\n     * Returns all persons in the address book\n     */\n    private static ArrayList<HashMap<PersonProperty, String>> getAllPersonsInAddressBook() {\n        return ALL_PERSONS;\n    }\n\n    /**\n     * Clears all persons in the address book and saves changes to file.\n     */\n    private static void clearAddressBook() {\n        ALL_PERSONS.clear();\n        savePersonsToFile(getAllPersonsInAddressBook(), storageFilePath);\n    }\n\n    /**\n     * Resets the internal model with the given data. Does not save to file.\n     *\n     * @param persons list of persons to initialise the model with\n     */\n    private static void initialiseAddressBookModel(ArrayList<HashMap<PersonProperty, String>> persons) {\n        ALL_PERSONS.clear();\n        ALL_PERSONS.addAll(persons);\n    }\n\n\n    /*\n     * ===========================================\n     *             PERSON METHODS\n     * ===========================================\n     */\n\n    /**\n     * Returns the given person's name\n     *\n     * @param person whose name you want\n     */\n    private static String getNameFromPerson(HashMap<PersonProperty, String> person) {\n        return person.get(PersonProperty.NAME);\n    }\n\n    /**\n     * Returns given person's phone number\n     *\n     * @param person whose phone number you want\n     */\n    private static String getPhoneFromPerson(HashMap<PersonProperty, String> person) {\n        return person.get(PersonProperty.PHONE);\n    }\n\n    /**\n     * Returns given person's email\n     *\n     * @param person whose email you want\n     */\n    private static String getEmailFromPerson(HashMap<PersonProperty, String> person) {\n        return person.get(PersonProperty.EMAIL);\n    }\n\n    /**\n     * Creates a person from the given data.\n     *\n     * @param name  of person\n     * @param phone without data prefix\n     * @param email without data prefix\n     * @return constructed person\n     */\n    private static HashMap<PersonProperty, String> makePersonFromData(String name, String phone, String email) {\n        final HashMap<PersonProperty, String> person = new HashMap<PersonProperty, String>();\n        person.put(PersonProperty.NAME, name);\n        person.put(PersonProperty.PHONE, phone);\n        person.put(PersonProperty.EMAIL, email);\n        return person;\n    }\n\n    /**\n     * Encodes a person into a decodable and readable string representation.\n     *\n     * @param person to be encoded\n     * @return encoded string\n     */\n    private static String encodePersonToString(HashMap<PersonProperty, String> person) {\n        return String.format(PERSON_STRING_REPRESENTATION,\n                getNameFromPerson(person), getPhoneFromPerson(person), getEmailFromPerson(person));\n    }\n\n    /**\n     * Encodes list of persons into list of decodable and readable string representations.\n     *\n     * @param persons to be encoded\n     * @return encoded strings\n     */\n    private static ArrayList<String> encodePersonsToStrings(ArrayList<HashMap<PersonProperty, String>> persons) {\n        final ArrayList<String> encoded = new ArrayList<>();\n        for (HashMap<PersonProperty, String> person : persons) {\n            encoded.add(encodePersonToString(person));\n        }\n        return encoded;\n    }\n\n    /*\n     * NOTE : =============================================================\n     * Note the use of Java's new 'Optional' feature to indicate that\n     * the return value may not always be present.\n     * ====================================================================\n     */\n\n    /**\n     * Decodes a person from it's supposed string representation.\n     *\n     * @param encoded string to be decoded\n     * @return if cannot decode: empty Optional\n     * else: Optional containing decoded person\n     */\n    private static Optional<HashMap<PersonProperty, String>> decodePersonFromString(String encoded) {\n        // check that we can extract the parts of a person from the encoded string\n        if (!isPersonDataExtractableFrom(encoded)) {\n            return Optional.empty();\n        }\n        final HashMap<PersonProperty, String> decodedPerson = makePersonFromData(\n                extractNameFromPersonString(encoded),\n                extractPhoneFromPersonString(encoded),\n                extractEmailFromPersonString(encoded)\n        );\n        // check that the constructed person is valid\n        return isPersonDataValid(decodedPerson) ? Optional.of(decodedPerson) : Optional.empty();\n    }\n\n    /**\n     * Decodes persons from a list of string representations.\n     *\n     * @param encodedPersons strings to be decoded\n     * @return if cannot decode any: empty Optional\n     * else: Optional containing decoded persons\n     */\n    private static Optional<ArrayList<HashMap<PersonProperty, String>>> decodePersonsFromStrings(ArrayList<String> encodedPersons) {\n        final ArrayList<HashMap<PersonProperty, String>> decodedPersons = new ArrayList<>();\n        for (String encodedPerson : encodedPersons) {\n            final Optional<HashMap<PersonProperty, String>> decodedPerson = decodePersonFromString(encodedPerson);\n            if (!decodedPerson.isPresent()) {\n                return Optional.empty();\n            }\n            decodedPersons.add(decodedPerson.get());\n        }\n        return Optional.of(decodedPersons);\n    }\n\n    /**\n     * Returns true if person data (email, name, phone etc) can be extracted from the argument string.\n     * Format is [name] p/[phone] e/[email], phone and email positions can be swapped.\n     *\n     * @param personData person string representation\n     */\n    private static boolean isPersonDataExtractableFrom(String personData) {\n        final String matchAnyPersonDataPrefix = PERSON_DATA_PREFIX_PHONE + '|' + PERSON_DATA_PREFIX_EMAIL;\n        final String[] splitArgs = personData.trim().split(matchAnyPersonDataPrefix);\n        return splitArgs.length == 3 // 3 arguments\n                && !splitArgs[0].isEmpty() // non-empty arguments\n                && !splitArgs[1].isEmpty()\n                && !splitArgs[2].isEmpty();\n    }\n\n    /**\n     * Extracts substring representing person name from person string representation\n     *\n     * @param encoded person string representation\n     * @return name argument\n     */\n    private static String extractNameFromPersonString(String encoded) {\n        final int indexOfPhonePrefix = encoded.indexOf(PERSON_DATA_PREFIX_PHONE);\n        final int indexOfEmailPrefix = encoded.indexOf(PERSON_DATA_PREFIX_EMAIL);\n        // name is leading substring up to first data prefix symbol\n        int indexOfFirstPrefix = Math.min(indexOfEmailPrefix, indexOfPhonePrefix);\n        return encoded.substring(0, indexOfFirstPrefix).trim();\n    }\n\n    /**\n     * Extracts substring representing phone number from person string representation\n     *\n     * @param encoded person string representation\n     * @return phone number argument WITHOUT prefix\n     */\n    private static String extractPhoneFromPersonString(String encoded) {\n        final int indexOfPhonePrefix = encoded.indexOf(PERSON_DATA_PREFIX_PHONE);\n        final int indexOfEmailPrefix = encoded.indexOf(PERSON_DATA_PREFIX_EMAIL);\n\n        // phone is last arg, target is from prefix to end of string\n        if (indexOfPhonePrefix > indexOfEmailPrefix) {\n            return removePrefix(encoded.substring(indexOfPhonePrefix, encoded.length()).trim(),\n                    PERSON_DATA_PREFIX_PHONE);\n\n            // phone is middle arg, target is from own prefix to next prefix\n        } else {\n            return removePrefix(\n                    encoded.substring(indexOfPhonePrefix, indexOfEmailPrefix).trim(),\n                    PERSON_DATA_PREFIX_PHONE);\n        }\n    }\n\n    /**\n     * Extracts substring representing email from person string representation\n     *\n     * @param encoded person string representation\n     * @return email argument WITHOUT prefix\n     */\n    private static String extractEmailFromPersonString(String encoded) {\n        final int indexOfPhonePrefix = encoded.indexOf(PERSON_DATA_PREFIX_PHONE);\n        final int indexOfEmailPrefix = encoded.indexOf(PERSON_DATA_PREFIX_EMAIL);\n\n        // email is last arg, target is from prefix to end of string\n        if (indexOfEmailPrefix > indexOfPhonePrefix) {\n            return removePrefix(encoded.substring(indexOfEmailPrefix, encoded.length()).trim(),\n                    PERSON_DATA_PREFIX_EMAIL);\n\n            // email is middle arg, target is from own prefix to next prefix\n        } else {\n            return removePrefix(\n                    encoded.substring(indexOfEmailPrefix, indexOfPhonePrefix).trim(),\n                    PERSON_DATA_PREFIX_EMAIL);\n        }\n    }\n\n    /**\n     * Returns true if the given person's data fields are valid\n     *\n     * @param person HashMap representing the person (used in internal data)\n     */\n    private static boolean isPersonDataValid(HashMap<PersonProperty, String> person) {\n        return isPersonNameValid(person.get(PersonProperty.NAME))\n                && isPersonPhoneValid(person.get(PersonProperty.PHONE))\n                && isPersonEmailValid(person.get(PersonProperty.EMAIL));\n    }\n\n    /*\n     * NOTE : =============================================================\n     * Note the use of 'regular expressions' in the method below.\n     * Regular expressions can be very useful in checking if a a string\n     * follows a specific format.\n     * ====================================================================\n     */\n\n    /**\n     * Returns true if the given string as a legal person name\n     *\n     * @param name to be validated\n     */\n    private static boolean isPersonNameValid(String name) {\n        return name.matches(\"(\\\\w|\\\\s)+\");  // name is nonempty mixture of alphabets and whitespace\n        //TODO: implement a more permissive validation\n    }\n\n    /**\n     * Returns true if the given string as a legal person phone number\n     *\n     * @param phone to be validated\n     */\n    private static boolean isPersonPhoneValid(String phone) {\n        return phone.matches(\"\\\\d+\");    // phone nonempty sequence of digits\n        //TODO: implement a more permissive validation\n    }\n\n    /**\n     * Returns true if the given string is a legal person email\n     *\n     * @param email to be validated\n     * @return whether arg is a valid person email\n     */\n    private static boolean isPersonEmailValid(String email) {\n        return email.matches(\"\\\\S+@\\\\S+\\\\.\\\\S+\"); // email is [non-whitespace]@[non-whitespace].[non-whitespace]\n        //TODO: implement a more permissive validation\n    }\n\n\n    /*\n     * ===============================================\n     *         COMMAND HELP INFO FOR USERS\n     * ===============================================\n     */\n\n    /**\n     * Returns usage info for all commands\n     */\n    private static String getUsageInfoForAllCommands() {\n        return getUsageInfoForAddCommand() + LS\n                + getUsageInfoForFindCommand() + LS\n                + getUsageInfoForViewCommand() + LS\n                + getUsageInfoForDeleteCommand() + LS\n                + getUsageInfoForClearCommand() + LS\n                + getUsageInfoForExitCommand() + LS\n                + getUsageInfoForHelpCommand();\n    }\n\n    /**\n     * Returns the string for showing 'add' command usage instruction\n     */\n    private static String getUsageInfoForAddCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_ADD_WORD, COMMAND_ADD_DESC) + LS\n                + String.format(MESSAGE_COMMAND_HELP_PARAMETERS, COMMAND_ADD_PARAMETERS) + LS\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_ADD_EXAMPLE) + LS;\n    }\n\n    /**\n     * Returns the string for showing 'find' command usage instruction\n     */\n    private static String getUsageInfoForFindCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_FIND_WORD, COMMAND_FIND_DESC) + LS\n                + String.format(MESSAGE_COMMAND_HELP_PARAMETERS, COMMAND_FIND_PARAMETERS) + LS\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_FIND_EXAMPLE) + LS;\n    }\n\n    /**\n     * Returns the string for showing 'delete' command usage instruction\n     */\n    private static String getUsageInfoForDeleteCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_DELETE_WORD, COMMAND_DELETE_DESC) + LS\n                + String.format(MESSAGE_COMMAND_HELP_PARAMETERS, COMMAND_DELETE_PARAMETER) + LS\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_DELETE_EXAMPLE) + LS;\n    }\n\n    /**\n     * Returns string for showing 'clear' command usage instruction\n     */\n    private static String getUsageInfoForClearCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_CLEAR_WORD, COMMAND_CLEAR_DESC) + LS\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_CLEAR_EXAMPLE) + LS;\n    }\n\n    /**\n     * Returns the string for showing 'view' command usage instruction\n     */\n    private static String getUsageInfoForViewCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_LIST_WORD, COMMAND_LIST_DESC) + LS\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_LIST_EXAMPLE) + LS;\n    }\n\n    /**\n     * Returns string for showing 'help' command usage instruction\n     */\n    private static String getUsageInfoForHelpCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_HELP_WORD, COMMAND_HELP_DESC)\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_HELP_EXAMPLE);\n    }\n\n    /**\n     * Returns the string for showing 'exit' command usage instruction\n     */\n    private static String getUsageInfoForExitCommand() {\n        return String.format(MESSAGE_COMMAND_HELP, COMMAND_EXIT_WORD, COMMAND_EXIT_DESC)\n                + String.format(MESSAGE_COMMAND_HELP_EXAMPLE, COMMAND_EXIT_EXAMPLE);\n    }\n\n\n    /*\n     * ============================\n     *         UTILITY METHODS\n     * ============================\n     */\n\n    /**\n     * Removes prefix from the given fullString if prefix occurs at the start of the string.\n     *\n     * @param fullString Parameter as a string\n     * @param prefix     Parameter sign to be removed\n     * @return string without the sign\n     */\n    private static String removePrefix(String fullString, String prefix) {\n        return fullString.replaceFirst(prefix, \"\");\n    }\n\n    /**\n     * Splits a source string into the list of substrings that were separated by whitespace.\n     *\n     * @param toSplit source string\n     * @return split by whitespace\n     */\n    private static ArrayList<String> splitByWhitespace(String toSplit) {\n        return new ArrayList<>(Arrays.asList(toSplit.trim().split(\"\\\\s+\")));\n    }\n\n    /**\n     * Convert all strings in a Set<String> to lower case\n     *\n     * @param toConvert source collection\n     * @return collection with strings converted to lower case\n     */\n    private static Set<String> convertToLowerCase(Collection<String> toConvert) {\n        Set<String> convertedStrings = new HashSet<>();\n        Iterator<String> ite = toConvert.iterator();\n        while (ite.hasNext()) {\n            convertedStrings.add(ite.next().toLowerCase());\n        }\n        return convertedStrings;\n    }\n\n}"
  },
  {
    "path": "test/exitinput.txt",
    "content": "# exit directly\nexit"
  },
  {
    "path": "test/expected.txt",
    "content": "][ ===================================================\n][ ===================================================\n][ AddessBook Level 1 - Version 1.0\n][ Welcome to your Address Book!\n][ ===================================================\n][ The given file name [ ] is not a valid file name!\n][ Exiting Address Book... Good bye!\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ AddessBook Level 1 - Version 1.0\n][ Welcome to your Address Book!\n][ ===================================================\n][ The given file name [directoryThatDoesNotExist/valid.filename] is not a valid file name!\n][ Exiting Address Book... Good bye!\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ AddessBook Level 1 - Version 1.0\n][ Welcome to your Address Book!\n][ ===================================================\n][ The given file name [.noFilename] is not a valid file name!\n][ Exiting Address Book... Good bye!\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ AddessBook Level 1 - Version 1.0\n][ Welcome to your Address Book!\n][ ===================================================\n][ The given file name [data/notRegularFile.txt] is not a valid file name!\n][ Exiting Address Book... Good bye!\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ AddessBook Level 1 - Version 1.0\n][ Welcome to your Address Book!\n][ ===================================================\n][ Enter command: ][ [Command entered:exit]\n][ Exiting Address Book... Good bye!\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ ===================================================\n][ AddessBook Level 1 - Version 1.0\n][ Welcome to your Address Book!\n][ ===================================================\n][ Using default storage file : addressbook.txt\n][ Enter command: ][ [Command entered:  sfdfd]\n][ Invalid command format: sfdfd \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ find: Finds all persons whose names contain any of the specified keywords (case-sensitive) and displays them as a list with index numbers.\n][ \tParameters: KEYWORD [MORE_KEYWORDS]\n][ \tExample: find alice bob charlie\n][ \n][ list: Displays all persons as a list with index numbers.\n][ \tExample: list\n][ \n][ delete: Deletes a person identified by the index number used in the last find/list call.\n][ \tParameters: INDEX\n][ \tExample: delete 1\n][ \n][ clear: Clears address book permanently.\n][ \tExample: clear\n][ \n][ exit: Exits the program.\tExample: exit\n][ help: Shows program usage instructions.\tExample: help\n][ ===================================================\n][ Enter command: ][ [Command entered:  clear]\n][ Address book has been cleared!\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \n][ 0 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  add wrong args wrong args]\n][ Invalid command format: add \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  add Valid Name p/12345 valid@email.butNoPrefix]\n][ Invalid command format: add \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  add Valid Name 12345 e/valid@email.butPhonePrefixMissing]\n][ Invalid command format: add \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  add []\\[;] p/12345 e/valid@e.mail]\n][ Invalid command format: add \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  add Valid Name p/not_numbers e/valid@e.mail]\n][ Invalid command format: add \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  add Valid Name p/12345 e/notAnEmail]\n][ Invalid command format: add \n][ add: Adds a person to the address book.\n][ \tParameters: NAME p/PHONE_NUMBER e/EMAIL\n][ \tExample: add John Doe p/98765432 e/johnd@gmail.com\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  add Adam Brown p/111111 e/adam@gmail.com]\n][ New person added: Adam Brown, Phone: 111111, Email: adam@gmail.com\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \n][ 1 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  add Betsy Choo p/222222 e/benchoo@nus.edu.sg]\n][ New person added: Betsy Choo, Phone: 222222, Email: benchoo@nus.edu.sg\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \t2. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \n][ 2 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  add Charlie Dickson e/charlie.d@nus.edu.sg p/333333]\n][ New person added: Charlie Dickson, Phone: 333333, Email: charlie.d@nus.edu.sg\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \t2. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t3. Charlie Dickson  Phone Number: 333333  Email: charlie.d@nus.edu.sg\n][ \n][ 3 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  add Dickson Ee e/dickson@nus.edu.sg p/444444]\n][ New person added: Dickson Ee, Phone: 444444, Email: dickson@nus.edu.sg\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \t2. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t3. Charlie Dickson  Phone Number: 333333  Email: charlie.d@nus.edu.sg\n][ \t4. Dickson Ee  Phone Number: 444444  Email: dickson@nus.edu.sg\n][ \n][ 4 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  add Esther Potato p/555555 e/esther@notreal.potato]\n][ New person added: Esther Potato, Phone: 555555, Email: esther@notreal.potato\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \t2. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t3. Charlie Dickson  Phone Number: 333333  Email: charlie.d@nus.edu.sg\n][ \t4. Dickson Ee  Phone Number: 444444  Email: dickson@nus.edu.sg\n][ \t5. Esther Potato  Phone Number: 555555  Email: esther@notreal.potato\n][ \n][ 5 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find]\n][ \n][ 0 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find bet]\n][ \n][ 0 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find 23912039120]\n][ \n][ 0 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find betsy]\n][ \t1. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \n][ 1 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find Betsy]\n][ \t1. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \n][ 1 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find Dickson]\n][ \t1. Charlie Dickson  Phone Number: 333333  Email: charlie.d@nus.edu.sg\n][ \t2. Dickson Ee  Phone Number: 444444  Email: dickson@nus.edu.sg\n][ \n][ 2 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  find Charlie Betsy]\n][ \t1. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t2. Charlie Dickson  Phone Number: 333333  Email: charlie.d@nus.edu.sg\n][ \n][ 2 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  delete]\n][ Invalid command format: delete \n][ delete: Deletes a person identified by the index number used in the last find/list call.\n][ \tParameters: INDEX\n][ \tExample: delete 1\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  delete should be only one number]\n][ Invalid command format: delete \n][ delete: Deletes a person identified by the index number used in the last find/list call.\n][ \tParameters: INDEX\n][ \tExample: delete 1\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  delete -1]\n][ Invalid command format: delete \n][ delete: Deletes a person identified by the index number used in the last find/list call.\n][ \tParameters: INDEX\n][ \tExample: delete 1\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  delete 0]\n][ Invalid command format: delete \n][ delete: Deletes a person identified by the index number used in the last find/list call.\n][ \tParameters: INDEX\n][ \tExample: delete 1\n][ \n][ ===================================================\n][ Enter command: ][ [Command entered:  delete 3]\n][ The person index provided is invalid\n][ ===================================================\n][ Enter command: ][ [Command entered:  delete 2]\n][ Deleted Person: Charlie Dickson  Phone Number: 333333  Email: charlie.d@nus.edu.sg\n][ ===================================================\n][ Enter command: ][ [Command entered:  delete 2]\n][ Person could not be found in address book\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \t2. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t3. Dickson Ee  Phone Number: 444444  Email: dickson@nus.edu.sg\n][ \t4. Esther Potato  Phone Number: 555555  Email: esther@notreal.potato\n][ \n][ 4 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  delete 4]\n][ Deleted Person: Esther Potato  Phone Number: 555555  Email: esther@notreal.potato\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ \t2. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t3. Dickson Ee  Phone Number: 444444  Email: dickson@nus.edu.sg\n][ \n][ 3 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  delete 1]\n][ Deleted Person: Adam Brown  Phone Number: 111111  Email: adam@gmail.com\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \t1. Betsy Choo  Phone Number: 222222  Email: benchoo@nus.edu.sg\n][ \t2. Dickson Ee  Phone Number: 444444  Email: dickson@nus.edu.sg\n][ \n][ 2 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  clear]\n][ Address book has been cleared!\n][ ===================================================\n][ Enter command: ][ [Command entered:  list]\n][ \n][ 0 persons found!\n][ ===================================================\n][ Enter command: ][ [Command entered:  exit]\n][ Exiting Address Book... Good bye!\n][ ===================================================\n][ ===================================================\n"
  },
  {
    "path": "test/input.txt",
    "content": "##########################################################\n# invalid command\n##########################################################\n\n  # should recognise invalid command\n  sfdfd\n\n##########################################################\n# clean and check state\n##########################################################\n\n  # address book should be emptied\n  clear\n  list\n\n##########################################################\n# test add person command, setup state for future tests\n##########################################################\n\n  # should catch invalid args format\n  add wrong args wrong args\n  add Valid Name p/12345 valid@email.butNoPrefix\n  add Valid Name 12345 e/valid@email.butPhonePrefixMissing\n  \n  # should catch invalid person data\n  add []\\[;] p/12345 e/valid@e.mail\n  add Valid Name p/not_numbers e/valid@e.mail\n  add Valid Name p/12345 e/notAnEmail\n\n  # should add correctly\n  add Adam Brown p/111111 e/adam@gmail.com\n  list\n  add Betsy Choo p/222222 e/benchoo@nus.edu.sg\n  list\n\n  # order of phone and email should not matter\n  add Charlie Dickson e/charlie.d@nus.edu.sg p/333333\n  list\n  add Dickson Ee e/dickson@nus.edu.sg p/444444\n  list\n  add Esther Potato p/555555 e/esther@notreal.potato\n  list\n\n##########################################################\n# test find persons command\n##########################################################\n\n  # should match none with no keywords\n  find\n  # should only match full words in person names\n  find bet\n  # does not match if none have keyword\n  find 23912039120\n  \n  # matching should be case-insensitive\n  find betsy\n  # find unique keyword\n  find Betsy\n  # find multiple with same keyword\n  find Dickson\n  # find multiple with some keywords\n  find Charlie Betsy\n\n##########################################################\n# test delete person command\n##########################################################\n\n# last active view: [1] betsy [2] charlie\n\n  # should catch invalid args format\n  delete\n  delete should be only one number\n\n  # should catch invalid index\n  delete -1\n  delete 0\n  delete 3\n\n  # should catch attempt to delete something already deleted\n  delete 2\n  delete 2\n\n  # should have deleted based on last active view's index\n  list\n\n  # deletes correct person\n  delete 4\n  list\n\n  # listing indexes get updated on next request\n  delete 1\n  list\n\n##########################################################\n# test clear command\n##########################################################\n\n  # clears all\n  clear\n  list\n\n##########################################################\n# test exit command\n##########################################################\n\n  # exits properly\n  exit\n  list"
  },
  {
    "path": "test/runtests.bat",
    "content": "@ECHO OFF\n\nREM create bin directory if it doesn't exist\nif not exist ..\\bin mkdir ..\\bin\n\nREM compile the code into the bin folder\njavac  ..\\src\\seedu\\addressbook\\Addressbook.java -d ..\\bin\n\nREM (invalid) no parent directory, invalid filename with no extension\njava -classpath ..\\bin seedu.addressbook.AddressBook \" \" < NUL > actual.txt\nREM (invalid) invalid parent directory that does not exist, valid filename\njava -classpath ..\\bin seedu.addressbook.AddressBook \"directoryThatDoesNotExist/valid.filename\" < NUL >> actual.txt\nREM (invalid) no parent directory, invalid filename with dot on first character\njava -classpath ..\\bin seedu.addressbook.AddressBook \".noFilename\" < NUL >> actual.txt\nREM (invalid) valid parent directory, non regular file\nif not exist data\\notRegularFile.txt mkdir data\\notRegularFile.txt\njava -classpath ..\\bin seedu.addressbook.AddressBook \"data/notRegularFile.txt\" < NUL >> actual.txt\nREM (valid) valid parent directory, valid filename with extension.\ncopy /y NUL data\\valid.filename\njava -classpath ..\\bin seedu.addressbook.AddressBook \"data/valid.filename\" < exitinput.txt >> actual.txt\nREM run the program, feed commands from input.txt file and redirect the output to the actual.txt\njava -classpath ..\\bin seedu.addressbook.AddressBook < input.txt >> actual.txt\n\nREM compare the output to the expected output\nFC actual.txt expected.txt"
  },
  {
    "path": "test/runtests.sh",
    "content": "#!/usr/bin/env bash\n\n# change to script directory\ncd \"${0%/*}\"\n\n# create ../bin directory if not exists\nif [ ! -d \"../bin\" ]\nthen\n    mkdir ../bin\nfi\n\n# compile the code into the bin folder\njavac  ../src/seedu/addressbook/AddressBook.java -d ../bin\n\n# (invalid) no parent directory, invalid filename with no extension\njava -classpath ../bin seedu.addressbook.AddressBook ' ' < /dev/null > actual.txt\n# (invalid) invalid parent directory that does not exist, valid filename\njava -classpath ../bin seedu.addressbook.AddressBook 'directoryThatDoesNotExist/valid.filename' < /dev/null >> actual.txt\n# (invalid) no parent directory, invalid filename with dot on first character\njava -classpath ../bin seedu.addressbook.AddressBook '.noFilename' < /dev/null >> actual.txt\n# (invalid) valid parent directory, non regular file\nmkdir -p data/notRegularFile.txt\njava -classpath ../bin seedu.addressbook.AddressBook 'data/notRegularFile.txt' < /dev/null >> actual.txt\n# (valid) valid parent directory, valid filename with extension.\ntouch data/valid.filename\njava -classpath ../bin seedu.addressbook.AddressBook 'data/valid.filename' < exitinput.txt >> actual.txt\n# run the program, feed commands from input.txt file and redirect the output to the actual.txt\njava -classpath ../bin seedu.addressbook.AddressBook < input.txt >> actual.txt\n\n# compare the output to the expected output\ndiff actual.txt expected.txt\nif [ $? -eq 0 ]\nthen\n    echo \"Test result: PASSED\"\nelse\n    echo \"Test result: FAILED\"\nfi\n"
  }
]