diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fd8c44d086..6e9958b427 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -32,19 +32,4 @@ jobs: - name: Build and check with Gradle run: ./gradlew check - - - name: Perform IO redirection test (*NIX) - if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (MacOS) - if: always() && runner.os == 'macOS' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (Windows) - if: always() && runner.os == 'Windows' - working-directory: ${{ github.workspace }}/text-ui-test - shell: cmd - run: runtest.bat \ No newline at end of file + \ No newline at end of file diff --git a/.gitignore b/.gitignore index f69985ef1f..a3884e21ba 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +META-INF/MANIFEST.MF +src/main/java/META-INF/MANIFEST.MF +/equipments.json +docs/diagrams/EquipmentManager.vpp.bak_001d +docs/diagrams/EquipmentManager.vpp.bak_002d +docs/diagrams/EquipmentManager.vpp.bak_003d diff --git a/build.gradle b/build.gradle index b0c5528fb5..fc3a80105f 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + implementation 'com.google.code.gson:gson:2.9.0' } test { @@ -43,4 +44,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..398f6401a6 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) + +| Display | Name | Github Profile | Portfolio | +|-----------------------------------------------------|:-----------------:|:---------------------------------------:|:-------------------------------:| +| ![](https://via.placeholder.com/100.png?text=Photo) | Bryan Christopher | [Github](https://github.com/Bryan-BC) | [Portfolio](team/bryan-bc.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Bai Shun Yao | [Github](https://github.com/shunyao643) | [Portfolio](team/shunyao643.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Yu Hao | [Github](https://github.com/yuhaochua) | [Portfolio](team/yuhaochua.md) | + diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..79eacdab0d 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,316 @@ -# Developer Guide +# Table of Content + +- [Acknowledgements](#acknowledgements) +- [Design and Implementation](#design-and-implementation) + - [Architecture](#architecture) + - [Parser](#parser) + - [Command classes](#command-classes) + - [EquipmentManager](#equipmentmanager) +- [Product scope](#product-scope) + - [Target user profile](#target-user-profile) + - [Value proposition](#value-proposition) +- [User stories](#user-stories) +- [NonFunctional requirements](#nonfunctional-requirements) +- [Glossary](#glossary) +- [Instructions for manual testing](#instructions-for-manual-testing) ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +Our project could not have been possible without the prior work of the following: + +- [SE-EDU project team](https://se-education.org/docs/team.html) for their work on [AddressBook Level 3](https://github.com/se-edu/addressbook-level3), for which we referenced ideas as well as code snippets. +- Google's [Gson Java library](https://github.com/google/gson) for which we used to load and save our data files. +- Members of the [AY2122S2-CS2113-F10-2 team](https://github.com/nus-cs2113-AY2122S2) for pointing out the possibility of and requesting permission to make use of the above Gson library. + +-------------------------------------------------------------------------------------------------------------------- +## **Design and Implementation** + +### Architecture + +Our design draws significant inspiration from the implementation of AddressBook Level 3 (henceforth AddressBook). +As the program was initially conceptualised to be a text-based command-line interface, heavy consideration was given +to the design and user interactions when it came to the text input to be parsed by the user. +As such, we referenced AddressBook to segment the program into `Parser`, `Command` and `Equipment` classes. + +**Main components of the architecture** + +- `Duke` is the Main class and entry-point for the program. +- `Parser` serves as the first filter to split raw text input and pass in arguments to the `Command` for further processing. +- `Command`, together with its various subclasses serve as specific implementations to pass the arguments taken in to the various methods of the `EquipmentManager` class. +- `EquipmentManager` keeps track of actual `Equipment` instances created by our program. +- `Storage` performs File I/O functions. + +### Parser + +`Parser` is the first filter for text inputs read to the user. It serves to: +1. Split text input from the user into strings of argument pairs. +2. Check and modify upper/lower case for argument tags. +3. Remove backticks, beginning and trailing whitespace. +4. Create the correct instance of `Command` based on the argument tag. + +As alluded to prior, one major consideration was to build it in a manner that can parse text input as effectively as possible. In considering text input, we divided the parsing into the following segments: + + +

commandWord [argumentType/`argumentValue`] [...]

+ +To dispatch argumentType/\`argumentValue\` strings to the correct `Command` class, the following logic is employed by `Parser#parseCommand`. + +#### 1. Parse the command word + +

commandWord

+ +`Parser#splitCommandTerm` splits the input string upon the first space. +The first substring is used to decide which `Command` to dispatch while the second is used for its arguments. +In the case where a second substring is not required, as in the case of `help` and `list`, a null String is used and the following step is skipped entirely. + +####2. Split arguments + +

[argumentType/`argumentValue`] [...]

+ +Complex commands such as `add` and `update` necessitate multiple arguments. +To implement this while ensuring that multi-word strings are acceptable input, `extractArguments` is implemented. + +The main regular expression ([details here](https://regex101.com/r/dMwMWw/3)) sought to match `argumentType` and argumentValue pairs with a positive lookahead. +The final argument pair will then be extracted using a separate regex. +Together, this ensured that all argument pairs can be effectively parsed and dispatched to each `Command` class. + +
Special delete implementation +

+ +For added safeguards in equipment deletion, the delete command implements a more stringent regex to match one argument pair of s/\`SERIAL_NUMBER\`. + +

+
+ +Throughout the `Parser` implementation, exceptions were caught to return `IncorrectCommand` classes that can be used to pass error messages to the user. These will be discussed in the following segments. + +### Command classes + +`Command` classes are largely similar to each other. Taking in input Strings from `Parser`, they invoke methods from `EquipmentManager` to perform the necessary actions. Where applicable, the `Command` classes also convert values to the necessary data types (Double, EquipmentType and LocalDate) prior to passing these values in. + +The most complex command, `UpdateCommand` will be explained here as the method calls are similar to the remaining commands. The update feature is facilitated by `UpdateCommand`. It extends `ModificationCommand` and implements the following operations: + +* `UpdateCommand#generateUpdatePairs()` — Generates pairs of attributes and their update values. +* `UpdateCommand#generateUpdateString()` — Generates String with details of the update executed. + +Given below is an example usage scenario and how the update feature behaves at each step. + +#### Step 1 +The user adds an equipment to the system with the help of the `add` command. The added equipment has the following attributes shown below. + +![equipment0](images/equipment0.png) + +![sequenceDiagram1](images/sequenceDiagram1.png) + +#### Step 2 +The user executes `update s/S1404115ASF n/SpeakerC c/2000 pd/2022-01-29` to update equipment with serial number S1404115ASF. `Parser#parseCommand` is called from `Duke` to parse the user's input. + +#### Step 3 +The parser recognises that an `UpdateCommand` is required, and the UpdateCommand is prepared to return to `Duke`. In the constructor of `UpdateCommand`, `UpdateCommand#prepareModification` is called to set the values of the attributes to be updated. The other attributes are set to null by default. + +![sequenceDiagramExecute](images/sequenceDiagramExecute.png) + +#### Step 4 +`UpdateCommand#execute` is run to process the update. If the serialNumber attribute is null, a `CommandResult` with a `MISSING_SERIAL_NUMBER` output string will be returned. Otherwise, `EquipmentManager#updateEquipment` is called. + +#### Step 5 +If the update was successful, a `CommandResult` with success message will be returned, else a `CommandResult` with `UPDATE_FAILURE_MESSAGE` will be returned. Upon successful update, the object should be updated with the new attributes as shown in the diagram below. + +![equipment1](images/equipment1.png) -## Design & implementation +#### Step 6 +It is not shown in the sequence diagram but ultimately when the CommandResult is returned to `Duke`, the output of the `CommandResult` gets printed out and displayed to the user. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### EquipmentManager +The `EquipmentManager`, as the name suggests, manages all the equipment that the user has. +When initialised, it creates a HashMap called `equipmentList` which is used to save the equipment with their serial numbers as their keys. +The `EquipmentManager` has several methods which allow manipulation to said HashMap such as `addEquipment`, `checkEquipment`, `listEquipment`, `updateEquipment`, and `deleteEquipment`. +These methods are used during the execution of each of the `Command` class. +* `addEquipment` — Adds an `Equipment` to the `equipmentList`. +* `checkEquipment` — Returns the `Equipment` in `equipmentList` that has the given `itemName`. +* `listEquipment` — Returns an ArrayList of all the `Equipment` in `equipmentList`. +* `updateEquipment` — Updates the specified `Equipment` with the corresponding updates given in the ArrayList of pairs. +* `deleteEquipment` — Deletes the specified `Equipment`. +The `EquipmentManager` is implemented as the main storage unit of all `Equipment` during the execution of the program. + +### Storage + +The `Storage` class is used to store the data of the program. It utilises the Gson library to serialise and deserialise the data. +There is also a LocalDateAdapter class which is used to convert the LocalDate objects to YYYY-MM-DD format. + +#### 1. `saveData` method + +The `saveData` method is used to save the Equipments added to the `equipment.json` file. +The method will automatically create the file if it does not exist. +While saving the data, the `Equipment` objects in `equipmentList` is converted to a JSON string and saved to the file. +During the execution of the program, the `equipment.json` file will be updated with the latest data after every 5 commands. +Alternatively, the user can manually save the data by calling the `save` command. + +#### 2. `loadData` method + +The `loadData` method is used to load the data from the `equipment.json` file. +The method is able to handle the cases where: + +* The file does not exist. +* The file is empty. +* The data in the file is not in the correct format. +* There are more than one `Equipment` object with the same serial number. + +While loading the data, the `equipment.json` file is read and the data is converted from a JSON string to an ArrayList of `Equipment` objects. +The ArrayList of `Equipment` objects is then added to the `equipmentList` HashMap. + +The `Storage` class is used in the `Duke` class. +It is used to save and load the data of the program. +Below are the procedures of the `Storage` class usage during program execution. + +1. `Duke` is initialised. +2. `Storage` is initialised. +3. `Storage#loadData` is called to load the data from the `equipment.json` file. +4. After every 5 commands, `Storage#saveData` is called to save the data to the `equipment.json` file. +5. Lastly, before the program terminates, `Storage#saveData` is called to save the data to the `equipment.json` file. + +-------------------------------------------------------------------------------------------------------------------- ## Product scope ### Target user profile -{Describe the target user profile} +The target user is the logistics chief of an event support club. He is responsible for the logistics of the club and the equipment that is used during events. ### Value proposition -{Describe the value proposition: what problem does it solve?} +Equipment for the club differ in terms of shape and size. Owing to the climate-sensitive and fragile nature of some of these equipment, they are often stored in opaque containers, which may not be easily accessible or quickly identifiable given space constraints. By keeping records of this information on our application, the logistics chief can easily access and retrieve this information without having to make a physical trip down to the storeroom. + +Further, maintaining a record of the equipment is a time-consuming task given that they often need to be referred to by both the serial number and a common name (short alias) used by club members. As the post of logistics chief is changed every year when passed down to a new member, this member will need to be acquainted with both the common name and the serial number of the relevant equipment. Our application thus provides a convenient way to search by both specifications. In fact, additional information such as the vendor, purchased date, equipment type, and the cost of the equipment is also stored and searchable. +-------------------------------------------------------------------------------------------------------------------- ## User Stories -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +| Version | As a ... | I want to ... | So that I can ... | +|---------|---------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------| +| v1.0 | new user | list all equipment | see all current equipment in the application | +| v1.0 | conscientious logs chief | add equipment | newly purchased equipment can be kept up to date | +| v2.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| v2.0 | meticulous logs chief | update equipment | correct any errors in previously recorded equipment | +| v2.0 | conscientious logs chief | save the state of the equipment list | come back to a saved version of my work later on | +| v2.0 | conscientious logs chief | delete equipment | remove decommissioned equipment from the application | +| v2.1 | forgetful logs chief | search for equipment | be able to find equipment by its attributes | +| v3.0 | forward-planning logs chief | filter equipment by date and cost range | conduct more regular checks on expensive or old equipment | +| v3.0 | busy logs chief | mark equipment to be faulty/working | filter and send the faulty ones for repair | +| v3.0 | forgetful logs chief | add comments to equipment | take note of any other information relating to my equipment | +| v3.0 | logs chief supporting multiple events | ensure that equipment can be earmarked for an associated event | my equipment will not be double-booked and have scheduling conflicts | + + -## Non-Functional Requirements +-------------------------------------------------------------------------------------------------------------------- +## NonFunctional Requirements -{Give non-functional requirements} +1. Spaces should be permitted as a delimiter for equipment attributes of type String, e.g. equipment name, vendor name. +2. Equipment name need not be unique as there may be multiple equipment with the same name. +3. Serial number should be unique and case-insensitive. +-------------------------------------------------------------------------------------------------------------------- ## Glossary -* *glossary item* - Definition +* *Equipment* - Each entity that is being stored/manipulated in the application +* *Inventory* - Refers to the whole of the Equipment stored in the application +* *Command Word* - First word keyed preceding any subsequent arguments to indicate a command to the application +* *Argument Pair/Type/Value* - Refers to a pair of words keyed following a command word, indicating a type of attribute to be updated and its value +* *`Command`* - Keywords that users use to tell the application to perform certain actions +* *`CommandResult`* - Results that are produced from the execution of Commands. +-------------------------------------------------------------------------------------------------------------------- ## Instructions for manual testing -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +### Launch and exit +1. Initial launch + 1. Ensure that you have Java 11 or above installed. + 2. Download the latest version of `EquipmentManager` jar file from [here](https://github.com/AY2122S2-CS2113-F12-2/tp/releases) and copy into an empty folder. + 3. Launch command line and change into the directory jar file is saved. + 4. Run java -jar tp.jar.
+ Expected: application starts with greeting message. +2. Exit application + 1. Enter `bye` into command line.
+ Expected: application exits. + +### Help +1. Help + 1. Test case: `help`
+ Expected: List of commands usage examples is shown. + +### Adding equipment +1. Adding equipment to inventory + 1. Test case: ``add n/`SpeakerB` s/`S1404115ASF` t/`Speaker` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23` `` + + Expected: Equipment successfully added. + 2. Test case: ``add n/`SpeakerB` s/`S1404115ASF` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23` `` + + Expected: Unable to add equipment with missing attributes. + 3. Test case: ``add n/`SpeakerB` s/`S1404115ASF` t/`something` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23` `` + + Expected: Unable to add equipment, Equipment Type has to be `MICROPHONE`, `SPEAKER`, `STAND` or `CABLE`. + 4. Test case: ``add n/`SpeakerB` s/`S1404115ASF` t/`something` c/`1000` pf/`Loud_Technologies` pd/`2123928` `` + + Expected: Unable to add equipment, date must be in YYYY-MM-DD format. +2. Adding equipment with duplicate serial number to inventory + 1. Prerequisites: There is already equipment in the inventory, for example the one added above. + 2. Test case: ``add n/`SpeakerB` s/`S1404115ASF` t/`Speaker` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23` `` + + Expected: Unable to add equipment with duplicate serial number. + +### Updating equipment +1. Updating equipment in inventory + 1. Prerequisite: There is already equipment present in the inventory. + 2. Test case: ``update s/`S1404115ASF` n/`SpeakerC` c/`2510` pd/`2022-08-21` `` + + Expected: Equipment details updated with the specified values + 3. Test case: ``update s/`S1404115ASF` c/`2510` rand/`SpeakerC` pd/`2022-08-21` `` + + Expected: Update unsuccessful due to unrecognised tag. + +### Checking equipment +1. Checking for equipment matching to a specified attribute value + 1. Prerequisite: There is already equipment present in the inventory. + 2. Test case: ``check n/`Mic` `` + + Expected: List of equipment with name containing `Mic`. + 3. Test case: ``check c/`700` `` + + Expected: List of equipment with cost of `700`. + 4. Test case: ``check pf/`Tech` `` + + Expected: List of equipment purchased from supplier with name containing `Tech`. + 5. Test case: ``check pd/`2022-01-27` `` + + Expected: List of equipment purchased on `2022-01-27`. + 6. Test case: ``check t/`SPEAKER` `` + + Expected: List of equipment of type `SPEAKER`. +2. Checking for equipment, but using wrong input format + 1. Prerequisite: There is already equipment present in the inventory. + 2. Test case: ``check c/`hello` `` + + Expected: Error in displaying equipment, specified cost needs to be able to be parsed to double. + 3. Test case: ``check t/`BLA` `` + + Expected: Error in displaying equipment, specified type has to be `MICROPHONE`, `SPEAKER`, `STAND` or `CABLE`. + 4. Test case: ``check pd/`2022` `` + + Expected: Error in displaying equipment, specified date needs to follow `YYYY-MM-DD` format. + +### Listing equipment +1. Listing all equipment available in inventory + 1. Test case: `list` + + Expected: List of all equipment. + +### Saving +1. Manual saving of application state + 1. Test case: `save` + + Expected: Successfully saved. +2. Auto saving of application state + 1. Prerequisite: Application state automatically saved after every 5 commands issued. + 2. Test case: Execution of any 5 commands + + Expected: Application auto saved. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..2de8393f3d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,7 @@ -# Duke +# EquipmentManager + +EquipmentManager is a Command Line Interface application to help with keeping track of equipment details (serial number, name, quantity, cost, vendor, purchase date) for an AV club. It provides a clean and fast way to manage the inventory as compared to “traditional” methods such as an Excel spreadsheet. -{Give product intro here} Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..ba2b0e1992 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,250 @@ # User Guide -## Introduction +## Overview -{Give a product intro} +EquipmentManager is a Command Line Interface application to help with keeping track of equipment details (serial number, name, quantity, cost, vendor, purchase date) for an AV club. It provides a clean and fast way to manage the inventory as compared to “traditional” methods such as an Excel spreadsheet. ## Quick Start -{Give steps to get started quickly} - 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +2. Download the latest version of `EquipmentManager` from [here](https://github.com/AY2122S2-CS2113-F12-2/tp/releases). +3. Copy the file to the folder you want to use as the _home folder_ for your `EquipmentManager` application. +4. Open the command line and go to the folder mentioned above. +5. Run java -jar tp.jar +6. Type `help` to see the list of available commands. ## Features -{Give detailed description of each feature} +- [Introduction](#introduction) +- [Adding an equipment: `add`](#adding-an-equipment-add) +- [Checking an equipment: `check`](#checking-an-equipment-check) +- [Listing equipment: `list`](#listing-equipment-list) +- [Updating an equipment: `update`](#updating-an-equipment-update) +- [Deleting an equipment: `delete`](#deleting-an-equipment-delete) +- [Saving application state: `save`](#saving-application-state-save) +- [Getting help: `help`](#getting-help-help) +- [Exiting the application: `bye`](#exiting-the-application-bye) + + +### Introduction + +* Words in `UPPER_CASE` are the parameters to be supplied by the user. Spaces are acceptable. However, the parameter must be enclosed in backtick.
+ e.g. in ``check n/`ITEM_NAME` ``, `ITEM_NAME` is a parameter which can be used as ``check n/`SM-57` ``. + +* Items in square brackets are optional.
+ e.g. ``s/`SERIAL_NUMBER` [n/`ITEM_NAME`] [t/`TYPE`] ...`` can be used as ``s/`SM57-1` n/`SM57` t/`MICROPHONE`...`` or as ``s/`SM57-1` t/`MICROPHONE`...``. + +* After the command word (e.g. `add`, `update`), parameters can be in any order.
+ e.g. if the command specifies ``... s/`SERIAL_NUMBER` n/`ITEM_NAME`...``, ``n/`ITEM_NAME` s/`SERIAL_NUMBER` `` is also acceptable. + +* Only one attribute/value can be saved per parameter. Where multiple inputs are supplied, the last parameter will be taken instead of the first one.
+ e.g. ``... n/`ITEM_NAME_1` n/`ITEM_NAME_2` `` will be interpreted as ``... n/`ITEM_NAME_2` ``, omitting ``n/`ITEM_NAME_1` `` entirely. + +* Extraneous parameters for commands that do not take in parameters (such as `help`, `bye` and `save`) will be ignored.
+ e.g. if the command specifies `help 123`, it will be interpreted as `help`. + +* The maximum cost supported is up to $10 million, any specified cost equal or greater than that will be displayed incorrectly. + +### Adding an equipment: `add` +Adds new Equipment to the list of Equipment. + +Format: ``add n/`ITEM_NAME` s/`SERIAL_NUMBER` t/`TYPE c/COST` pf/`PURCHASED_FROM` pd/`PURCHASED_DATE` `` + +* The `TYPE` must be one of the following types: MICROPHONE, SPEAKER, STAND, CABLE. +* The `COST` cannot contain any symbols. + +Example of usage and output: + +``add n/`SpeakerB` s/`S1404115ASF` t/`Speaker` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23` `` + +Output: + +``` +Equipment successfully added: SpeakerB, serial number S1404115ASF +``` + +### Checking an equipment: `check` +Check the details of the equipments that matches the specified parameter. + +Format: ``check parameter/`PARAMETER_VALUE` `` + +e.g. ``check n/`ITEM_NAME` `` or ``check s/`SERIAL_NUMBER` `` or ``check t/`TYPE` `` + +* Only one parameter can be used to check for a piece of equipment. + +Example of usage and output: + +``check n/`SpeakerB` `` + +Output: + +``` +Here are the equipment matching to 'SpeakerB': +1. serialNumber=S1404115ASF,itemName=SpeakerB,type=SPEAKER,cost=1000.0,purchasedFrom=Loud_Technologies,purchasedDate=2022-02-23 +``` + +### Listing equipment: `list` +Print a list of all equipment in the inventory. If a parameter is supplied, only the equipment matching to the parameter will be printed. + +Format: `list` + +Example of usage and output: + +`list` + +Output: + +``` +TOTAL QUANTITY OF EQUIPMENT: 2 +1. serialNumber=S89347971ASF,itemName=MicrophoneC,type=MICROPHONE,cost=2000.0,purchasedFrom=Loud_Technologies,purchasedDate=2022-01-21 +2. serialNumber=S1404115ASF,itemName=SpeakerB,type=SPEAKER,cost=1000.0,purchasedFrom=Loud_Technologies,purchasedDate=2022-02-23 +``` + +### Updating an equipment: `update` + +Equipment can be updated with new information. Every parameter except serial number can be updated. + +Format: ``update s/`SERIAL_NUMBER` [n/`ITEM_NAME`] [t/`TYPE`] [c/`COST`] [pf/`PURCHASED_FROM`] [pd/`PURCHASED_DATE`]`` + +* The `TYPE` must be one of the following types: MICROPHONE, SPEAKER, STAND, CABLE. +* The `COST` cannot contain any symbols. + +Example of usage and output: + +``update s/`S14115ASF` c/`1200` pf/`AVLFX` `` + +Output: + +``` +Equipment successfully updated for serial number S14115ASF, +Updated details are: +New cost: 1200.0 +New purchased from: AVLFX +``` + +### Deleting an equipment: `delete` + +Removes an Equipment entry from the list of Equipment entirely. This is irreversible after the file is saved. + +Format: ``delete s/`SERIAL_NUMBER` `` + +Example of usage and output: + +``delete s/`S14115ASF` `` + +Output: + +``` +Equipment successfully deleted: SpeakerB, serial number S14115ASF +``` + +### Saving application state: `save` + +Saves the current state of the Equipment list to the equipments.json file. + +Format: `save` + +Example of usage and output: + +`save` + +Output: + +``` +Successfully saved. +``` + +### Getting help: `help` + +When `help` is called, the application will print a list of all available commands along with their usages and examples. + +Format: `help` + +Example of usage and output: + +`help` + +Output: + +``` +add: Adds a Equipment to the equipmentInventory. +Parameters: n/`ITEM NAME` s/`SERIAL NUMBER` t/`TYPE` c/`COST` pf/`PURCHASED FROM` pd/`PURCHASED DATE` +Example: add n/`SpeakerB` s/`S1404115ASF` t/`Speaker` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23` + +delete: Deletes the equipment with the specified serial number. +Parameters: s/`SERIAL_NUMBER` +Example: delete s/`SM57-1` + +update: Updates the equipment with the specified serial number. +Parameters in [square brackets] are optional. +Parameters: s/`SERIAL NUMBER` [n/`ITEM NAME`] [t/`TYPE`] [c/`COST`] [pf/`PURCHASED FROM`] [pd/`PURCHASED DATE`] +Example: update s/`SM57-1` n/`SpeakerC` c/`2510` pd/`2022-08-21` + +list: Prints a list of all equipment in the inventory. +Parameters: NIL +Example: list + +check: Check the details of the equipments that matches the specified parameter. +Parameters: parameter/`PARAMETER_VALUE` +Example: check n/`MixerC` or check s/`SM57-1` or check t/`MICROPHONE` + +save: Saves current state of application. +Parameters: NIL +Example: save + +bye: Exits the application. +Parameters: NIL +Example: bye + +help: Shows details of available commands to users. +Parameters: NIL +Example: help +``` + +### Exiting the application: `bye` + +Exits the application and saves the current state of the Equipment list to the equipments.json file. + +Format: `bye` + +Example of usage and output: -### Adding a todo: `todo` -Adds a new item to the list of todo items. +`bye` -Format: `todo n/TODO_NAME d/DEADLINE` +Output: -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +``` +Bye, See you again! +``` -Example of usage: +## Planned Features (Coming Soon!) -`todo n/Write the rest of the User Guide d/next week` +In v3.0, we will add support to: +1. Filter equipment by range for: + * Date + * Cost +2. Introduce additional fields to + * Mark equipment as faulty/working + * Add comment for equipment +3. Implement a booking system + * Purpose: equipment can be earmarked for an associated event to avoid scheduling conflicts from double-booking -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` ## FAQ **Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: Copy the data folder from the old computer into the other computer, in the same directory as the JAR file. ## Command Summary -{Give a 'cheat sheet' of commands here} +'Cheat Sheet' of commands here -* Add todo `todo n/TODO_NAME d/DEADLINE` +* Add equipment ``add n/`ITEM_NAME` s/`SERIAL_NUMBER` t/`TYPE` c/`COST` pf/`PURCHASED_FROM` pd/`PURCHASED_DATE` `` +* Check equipment ``check n/`ITEM_NAME` `` +* Listing equipment: `list` +* Updating an equipment: ``update s/`SERIAL_NUMBER` [n/`ITEM_NAME`] [t/`TYPE`] [c/`COST`] [pf/`PURCHASED_FROM`] [pd/`PURCHASED_DATE`]`` +* Deleting an equipment: ``delete s/`SERIAL_NUMBER` `` +* Saving application state: `save` +* Getting help: `help` +* Exiting the application: `bye` diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c741881743 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/diagrams/.EquipmentManager.vpp.lck b/docs/diagrams/.EquipmentManager.vpp.lck new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/diagrams/EquipmentManager.vpp b/docs/diagrams/EquipmentManager.vpp new file mode 100644 index 0000000000..2b82860677 Binary files /dev/null and b/docs/diagrams/EquipmentManager.vpp differ diff --git a/docs/diagrams/EquipmentManager.vpp.bak_000f b/docs/diagrams/EquipmentManager.vpp.bak_000f new file mode 100644 index 0000000000..9c3c239b25 Binary files /dev/null and b/docs/diagrams/EquipmentManager.vpp.bak_000f differ diff --git a/docs/diagrams/EquipmentManager.vpp.bak_004d b/docs/diagrams/EquipmentManager.vpp.bak_004d new file mode 100644 index 0000000000..063cb6374c Binary files /dev/null and b/docs/diagrams/EquipmentManager.vpp.bak_004d differ diff --git a/docs/diagrams/EquipmentManager.vpp.bak_005d b/docs/diagrams/EquipmentManager.vpp.bak_005d new file mode 100644 index 0000000000..6d68d886b0 Binary files /dev/null and b/docs/diagrams/EquipmentManager.vpp.bak_005d differ diff --git a/docs/images/equipment0.png b/docs/images/equipment0.png new file mode 100644 index 0000000000..b0b783ebb4 Binary files /dev/null and b/docs/images/equipment0.png differ diff --git a/docs/images/equipment1.png b/docs/images/equipment1.png new file mode 100644 index 0000000000..594549fc24 Binary files /dev/null and b/docs/images/equipment1.png differ diff --git a/docs/images/sequenceDiagram.png b/docs/images/sequenceDiagram.png new file mode 100644 index 0000000000..2141215774 Binary files /dev/null and b/docs/images/sequenceDiagram.png differ diff --git a/docs/images/sequenceDiagram1.png b/docs/images/sequenceDiagram1.png new file mode 100644 index 0000000000..1554233c32 Binary files /dev/null and b/docs/images/sequenceDiagram1.png differ diff --git a/docs/images/sequenceDiagramExecute.png b/docs/images/sequenceDiagramExecute.png new file mode 100644 index 0000000000..0979038334 Binary files /dev/null and b/docs/images/sequenceDiagramExecute.png differ diff --git a/docs/team/bryan-bc.md b/docs/team/bryan-bc.md new file mode 100644 index 0000000000..73405ac73d --- /dev/null +++ b/docs/team/bryan-bc.md @@ -0,0 +1,20 @@ +# Bryan Christopher - Project Portfolio Page + +## Overview +EquipmentManager is a Command Line Interface application to help with keeping track of equipment details (serial number, name, quantity, cost, vendor, purchase date) for an AV club. It provides a clean and fast way to manage the inventory as compared to “traditional” methods such as an Excel spreadsheet. + +## Summary of Contributions +Besides reviewing pull requests and participating in discussions during meetings, below are the main contributions made to the project: + +### Contributions to the Codebase +* Implementation of the classes in the equipment package. +* Implementation of the classes in the storage package. +* Implementation of Gson for serialization and deserialization of the data. + +### Contributions to the UG +* Updated the documentation to reflect the changes made to the codebase. +* Wrote the `list`, `help`, `save`, and `delete` commands in UG + +### Contributions to the DG +* Wrote the `Equipment Manager` part of DG. +* Wrote the `Storage` part of DG. diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/shunyao643.md b/docs/team/shunyao643.md new file mode 100644 index 0000000000..41dd102218 --- /dev/null +++ b/docs/team/shunyao643.md @@ -0,0 +1,45 @@ +# Bai Shun Yao - Project Portfolio Page + +## Overview +EquipmentManager is a Command Line Interface application to help with keeping track of equipment details (serial number, name, quantity, cost, vendor, purchase date) for an AV club. It provides a clean and fast way to manage the inventory as compared to “traditional” methods such as an Excel spreadsheet. + + +## Summary of Contributions +### Code contributed +Code contribution can be found [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=shunyao643&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other). + +### Enhancements implemented +* In charge of the parsing of commands prior to command execution. + * Parser implements a regex-based approach to parsing commands. This is challenging and necessary due to non-functional requirements of the project. +* Implement DateTime functionality throughout the application's classes. + +### Contributions to UG +* Add top-level introduction summary. +* Draft example usage for `Update`, `Delete` and `List` (by type) commands. +* Supplement cheat sheet for commands. + +### Contributions to DG +* Wrote Acknowledgements, Architecture summary, Parser and Product Scope. +* Update user-stories and non-functional requirements in line with progress of the project. + +### Contributions to team-based tasks +* Product Owner for the project. +* Set up GitHub team organisation and repository. +* Maintain issue tracker throughout project with the use of assignees, labels, closing statements and milestones. +* Manage release of v1.0 of the project. + +### Review/mentoring contributions +* Pull requests reviewed (only listed those with significant comments/discussion): + 1. [Reviewed implementation of Commands](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/24) + 2. [Reviewed unordered tag implementation for add command](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/57) + 3. [Reviewed revisions to check command](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/118) + 4. [Reviewed revisions to delete command](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/119) +* Suggest usage of GSON library for file I/O. + +### Contributions beyond the project team +* Reviewed DeveloperGuide.md for another team [here](https://github.com/nus-cs2113-AY2122S2/tp/pull/5) +* Filed bug reports for another team. Examples: + * [Inconsistent usage of code formatting](https://github.com/AY2122S2-CS2113-T11-4/tp/issues/119) + * [No option for multi-step commands](https://github.com/AY2122S2-CS2113-T11-4/tp/issues/129) + * [Salary information required but cannot be accessed or used at all](https://github.com/AY2122S2-CS2113-T11-4/tp/issues/133) + * [Staff are required to memorise order menu by index](https://github.com/AY2122S2-CS2113-T11-4/tp/issues/125) \ No newline at end of file diff --git a/docs/team/yuhaochua.md b/docs/team/yuhaochua.md new file mode 100644 index 0000000000..f4bdc4715d --- /dev/null +++ b/docs/team/yuhaochua.md @@ -0,0 +1,39 @@ +# Yu Hao - Project Portfolio Page + +## Overview +EquipmentManager is a Command Line Interface application to help with keeping track of equipment details (serial number, name, quantity, cost, vendor, purchase date) for an AV club. It provides a clean and fast way to manage the inventory as compared to “traditional” methods such as an Excel spreadsheet. + + +## Summary of Contributions +### Code contributed +Code contribution can be found [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=yuhaochua&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other). + +### Enhancements implemented +* Mainly in charge of Command classes. Implemented the logic for different commands to call different methods from EquipmentManager to query or make changes to data. +* Implemented UI which displays messages to users and read input from them. + +### Contributions to UG +* Wrote the `Quick Start` section. +* Created table of content. +* Wrote example usage for `Add`, `Check` and `List` commands. +* Drafted cheat sheet for commands. + +### Contributions to DG +* Wrote explanation for the implementation of Command classes. +* Added object and sequence diagrams used in explaining flow of updating equipment. +* Defined some terms in glossary. +* Wrote instructions for manual testing. + +### Contributions to team-based tasks +* Took meeting minutes and tracked to-do tasks for the team. + +### Review/mentoring contributions +* Pull requests reviewed (only listed those with significant comments/discussion): + 1. [Reviewed implementation of Equipment related classes](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/29) + 2. [Reviewed implementation of Parser](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/31) + 3. [Reviewed Storage implementation](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/53) + 4. [Reviewed usage of Pair class in EquipmentManager](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/63) + 5. [Reviewed implementation of Date format](https://github.com/AY2122S2-CS2113-F12-2/tp/pull/124) + +### Contributions beyond the project team +* Filed bug reports for another team [here](https://github.com/AY2122S2-CS2113T-T09-1/tp/issues/101) and [here](https://github.com/AY2122S2-CS2113T-T09-1/tp/issues/109) \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..19e86fe56e --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.Duke + diff --git a/src/main/java/seedu/Pair.java b/src/main/java/seedu/Pair.java new file mode 100644 index 0000000000..223f6ecf93 --- /dev/null +++ b/src/main/java/seedu/Pair.java @@ -0,0 +1,54 @@ +package seedu; + +import java.util.AbstractMap; +import java.util.Objects; + +/** + * Implementation of Pair which is not available in Java 11. Referenced from https://stackoverflow.com/a/59945161 + * + * @param the class of the key + * @param the class of the value + */ +public class Pair { + + K key; + V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public void setValue(V value) { + this.value = value; + } + + public V getValue() { + return value; + } + + public void setKey(K key) { + this.key = key; + } + + public K getKey() { + return key; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Pair pair = (Pair) o; + return key.equals(pair.key) && value.equals(pair.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } +} diff --git a/src/main/java/seedu/command/AddCommand.java b/src/main/java/seedu/command/AddCommand.java new file mode 100644 index 0000000000..c6881025d2 --- /dev/null +++ b/src/main/java/seedu/command/AddCommand.java @@ -0,0 +1,92 @@ +package seedu.command; + +import seedu.equipment.Equipment; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.EquipmentType; +import seedu.parser.MissingAttributeException; + +import java.util.ArrayList; + +/** + * Subclass of Command. Handles adding of equipment to equipmentInventory. + */ +public class AddCommand extends ModificationCommand { + public static final String COMMAND_WORD = "add"; + public static final String COMMAND_DESCRIPTION = ": Adds a Equipment to the equipmentInventory. " + + System.lineSeparator() + + "Parameters: n/`ITEM NAME` s/`SERIAL NUMBER` t/`TYPE` c/`COST` pf/`PURCHASED FROM` pd/`PURCHASED DATE`" + + System.lineSeparator() + + "Example: " + + "add n/`SpeakerB` s/`S1404115ASF` t/`Speaker` c/`1000` pf/`Loud_Technologies` pd/`2022-02-23`"; + public static final String DUPLICATE_ITEM_ERROR = "There is already an item with this serial number: %1$s"; + public static final String ATTRIBUTE_NOT_SET_ERROR = "Unable to add. " + + "One or more than one of the attributes of Equipment is not specified."; + private static final int EXPECTED_NUMBER_OF_TAGS = 6; + + /** + * constructor for AddCommand. Initialises successMessage and usageReminder from Command + * + * @param commandStrings parsed user input which contains details of equipment to be added + */ + public AddCommand(ArrayList commandStrings) throws MissingAttributeException { + super(commandStrings); + successMessage = "Equipment successfully added: %1$s, serial number %2$s"; + usageReminder = COMMAND_WORD + COMMAND_DESCRIPTION; + + if (commandStrings.size() < EXPECTED_NUMBER_OF_TAGS) { + throw new MissingAttributeException(ATTRIBUTE_NOT_SET_ERROR); + } else { + prepareModification(); + } + + } + + /** + * Adds equipment with details given in COMMAND_STRINGS. + * + * @return CommandResult with message from execution of this command + */ + public CommandResult execute() { + if (!checkAttributes()) { + return new CommandResult(ATTRIBUTE_NOT_SET_ERROR + + System.lineSeparator() + + usageReminder); + } + + try { + addEquipment(commandStrings); + } catch (DuplicateSerialNumberException e) { + return new CommandResult(String.format(DUPLICATE_ITEM_ERROR, serialNumber)); + } catch (NullPointerException | NumberFormatException e) { + return new CommandResult(INCORRECT_COST_FORMAT); + } catch (IllegalArgumentException e) { + return new CommandResult(INCORRECT_ENUM_TYPE); + } + + return new CommandResult(String.format(successMessage, equipmentName, serialNumber)); + } + + /** + * Uses information from the ArrayList to create a new Equipment and add it to the inventory. + * + * @param userInput ArrayList of String which contains the individual attributes of the equipment to be added + */ + public void addEquipment(ArrayList userInput) throws DuplicateSerialNumberException { + + Equipment newEquipment = new Equipment(equipmentName, serialNumber, equipmentType, cost, purchasedFrom, + purchasedDate); + equipmentManager.addEquipment(newEquipment); + } + + /** + * Checks if all the attributes of Equipment to be added have been set. There should be no null values. + * @return boolean to indicate whether all attributes are set. + */ + public boolean checkAttributes() { + if (equipmentName == null || serialNumber == null || equipmentType == null || cost == null + || purchasedFrom == null || purchasedDate == null) { + return false; + } + return true; + } +} diff --git a/src/main/java/seedu/command/ByeCommand.java b/src/main/java/seedu/command/ByeCommand.java new file mode 100644 index 0000000000..2fa8857e19 --- /dev/null +++ b/src/main/java/seedu/command/ByeCommand.java @@ -0,0 +1,19 @@ +package seedu.command; + +public class ByeCommand extends Command { + public static final String COMMAND_WORD = "bye"; + public static final String COMMAND_DESCRIPTION = ": Exits the application. " + + System.lineSeparator() + + "Parameters: NIL" + System.lineSeparator() + + "Example: " + + "bye"; + public static final String EXIT_MESSAGE = "Bye, See you again!"; + + /** + * Exits the application. + * @return CommandResult with exit message. + */ + public CommandResult execute() { + return new CommandResult(EXIT_MESSAGE); + } +} diff --git a/src/main/java/seedu/command/CheckCommand.java b/src/main/java/seedu/command/CheckCommand.java new file mode 100644 index 0000000000..e4222407e8 --- /dev/null +++ b/src/main/java/seedu/command/CheckCommand.java @@ -0,0 +1,103 @@ +package seedu.command; + +import seedu.Pair; +import seedu.equipment.Equipment; +import seedu.equipment.EquipmentType; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Locale; + +/** + * Subclass of Command. Handles checking of details of a specified equipment from equipmentInventory. + */ +public class CheckCommand extends Command { + private final ArrayList commandStrings; + public static final String COMMAND_WORD = "check"; + public static final String COMMAND_DESCRIPTION = ": Check the details of the equipments that matches the specified" + + " parameter. " + + System.lineSeparator() + + "Parameters: parameter/`PARAMETER_VALUE`" + System.lineSeparator() + + "Example: " + + "check n/`MixerC` or check s/`SM57-1` or check t/`MICROPHONE`"; + + /** + * constructor for CheckCommand. Initialises successMessage and usageReminder from Command + * + * @param commandStrings parsed user input which contains details of equipment to be viewed + */ + public CheckCommand(ArrayList commandStrings) { + this.commandStrings = commandStrings; + successMessage = "Here are the equipment matching to '%1$s':" + System.lineSeparator(); + usageReminder = COMMAND_WORD + COMMAND_DESCRIPTION; + } + + /** + * Gives details of equipment specified by name. + * + * @return CommandResult with message from execution of this command + */ + public CommandResult execute() { + ArrayList equipment; + try { + Pair checkPair = generateCheckPair(); + equipment = equipmentManager.checkEquipment(checkPair); + } catch (DateTimeParseException e) { + return new CommandResult(INVALID_DATE_MESSAGE); + } catch (NumberFormatException e) { + return new CommandResult(INCORRECT_COST_FORMAT); + } catch (IllegalArgumentException e) { + return new CommandResult(INCORRECT_ENUM_TYPE); + } + + return new CommandResult(String.format(successMessage, commandStrings.get(0)), equipment); + } + + /** + * Generates a Pair containing the attribute for the check to be based on. + * Pair is a class that was implemented to match each attribute to its value. + * By using pair, we are able to specify the attribute to be checked. + * @return Pair of value to be checked and its matching attribute. + * @throws NumberFormatException if cost is invalid + * @throws IllegalArgumentException if EquipmentType is invalid + * @throws DateTimeParseException if date is not in YYYY-MM-DD format + * + */ + public Pair generateCheckPair() throws AssertionError, NumberFormatException, IllegalArgumentException, + DateTimeParseException { + Pair pair = null; + + String s = commandStrings.get(0); + int delimiterPos = s.indexOf('/'); + // the case where delimiterPos = -1 is impossible as + // ARGUMENT_FORMAT and ARGUMENT_TRAILING_FORMAT regex requires a '/' + assert delimiterPos != -1 : "Each args will need to include minimally a '/' to split arg and value upon"; + String argType = s.substring(0, delimiterPos); + String argValue = s.substring(delimiterPos + 1); + switch (argType) { + case "n": + pair = new Pair<>("itemName", argValue); + break; + case "pd": + pair = new Pair<>("purchasedDate", LocalDate.parse(argValue)); + break; + case "t": + pair = new Pair<>("type", EquipmentType.valueOf(argValue.toUpperCase(Locale.ROOT))); + break; + case "pf": + pair = new Pair<>("purchasedFrom", argValue); + break; + case "c": + pair = new Pair<>("cost", Double.valueOf(argValue)); + break; + case "s": + pair = new Pair<>("serialNumber", argValue); + break; + default: + System.out.println("`" + argValue + "` not accepted for type " + argType + ": Unrecognised Tag"); + } + + return pair; + } +} diff --git a/src/main/java/seedu/command/Command.java b/src/main/java/seedu/command/Command.java new file mode 100644 index 0000000000..3a0b284832 --- /dev/null +++ b/src/main/java/seedu/command/Command.java @@ -0,0 +1,39 @@ +package seedu.command; + +import seedu.equipment.EquipmentManager; +import seedu.equipment.EquipmentType; +import seedu.storage.Storage; + +/** + * Abstract parent class for all types of commands. + */ +// The Command class as well as all of its subclasses was created with reference to +// https://github.com/nus-cs2113-AY2122S2/personbook/tree/master/src/main/java/seedu/personbook/commands +public abstract class Command { + protected String successMessage; + public String usageReminder; + protected EquipmentManager equipmentManager; + protected Storage storage; + public static final String INCORRECT_ENUM_TYPE = "Equipment Type needs to be one of:" + System.lineSeparator() + + EquipmentType.getAllTypes(); + public static final String INCORRECT_COST_FORMAT = "Please enter numbers only for cost and omit symbols"; + public static final String INVALID_SERIAL_NUMBER = "No such serial number, please enter an existing " + + "serial number"; + public static final String INVALID_DATE_MESSAGE = "Date needs to be able to be parsed, make sure you're using " + + "YYYY-MM-DD format"; + public static final String MISSING_SERIAL_NUMBER = "Serial Number is required to run this command"; + + public abstract CommandResult execute(); + + public String getUsageReminder() { + return usageReminder; + } + + public void setEquipmentManager(EquipmentManager equipmentManager) { + this.equipmentManager = equipmentManager; + } + + public void setStorage(Storage storage) { + this.storage = storage; + } +} diff --git a/src/main/java/seedu/command/CommandResult.java b/src/main/java/seedu/command/CommandResult.java new file mode 100644 index 0000000000..bd6ffc12ef --- /dev/null +++ b/src/main/java/seedu/command/CommandResult.java @@ -0,0 +1,51 @@ +package seedu.command; + +import seedu.equipment.Equipment; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Represents the result of a command execution. + */ +// Created with reference to +// https://github.com/nus-cs2113-AY2122S2/personbook/tree/master/src/main/java/seedu/personbook/commands +public class CommandResult { + private final String resultToShow; + private final ArrayList relevantEquipment; + + public CommandResult(String result) { + resultToShow = result; + relevantEquipment = null; + } + + public CommandResult(String result, ArrayList equipmentList) { + resultToShow = result; + relevantEquipment = equipmentList; + } + + public String getResultToShow() { + return resultToShow; + } + + public ArrayList getRelevantEquipment() { + return relevantEquipment; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CommandResult that = (CommandResult) o; + return resultToShow.equals(that.resultToShow) && Objects.equals(relevantEquipment, that.relevantEquipment); + } + + @Override + public int hashCode() { + return Objects.hash(resultToShow, relevantEquipment); + } +} diff --git a/src/main/java/seedu/command/DeleteCommand.java b/src/main/java/seedu/command/DeleteCommand.java new file mode 100644 index 0000000000..5eb231f7f6 --- /dev/null +++ b/src/main/java/seedu/command/DeleteCommand.java @@ -0,0 +1,81 @@ +package seedu.command; + +import java.util.ArrayList; + +/** + * Subclass of Command. Handles deleting of equipment from equipmentInventory. + */ +public class DeleteCommand extends Command { + private final ArrayList commandStrings; + public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_DESCRIPTION = ": Deletes the equipment with the specified serial number. " + + System.lineSeparator() + + "Parameters: s/`SERIAL_NUMBER`" + System.lineSeparator() + + "Example: " + + "delete s/`SM57-1`"; + public static final String ONLY_SN_ACCEPTED = "Only serial number accepted for deleting equipment"; + + /** + * constructor for DeleteCommand. Initialises successMessage and usageReminder from Command. + * + * @param commandStrings parsed user input which contains details of equipment to be deleted + */ + public DeleteCommand(ArrayList commandStrings) { + this.commandStrings = commandStrings; + successMessage = "Equipment successfully deleted: %1$s, serial number %2$s"; + usageReminder = COMMAND_WORD + COMMAND_DESCRIPTION; + } + + /** + * Deletes equipment specified by serial number. + * + * @return CommandResult with message from execution of this command + */ + public CommandResult execute() { + String equipmentName; + String serialNumber; + + try { + serialNumber = prepareDelete(); + equipmentName = equipmentManager.getEquipmentList().get(serialNumber.toLowerCase()).getItemName(); + } catch (NullPointerException e) { + return new CommandResult(INVALID_SERIAL_NUMBER); + } catch (AssertionError e) { + return new CommandResult(ONLY_SN_ACCEPTED); + } + + equipmentManager.deleteEquipment(serialNumber); + + return new CommandResult(String.format(successMessage, equipmentName, serialNumber)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof DeleteCommand)) { + return false; + } + DeleteCommand otherDeleteCommand = (DeleteCommand) other; + return this.commandStrings.equals(otherDeleteCommand.commandStrings); + } + + /** + * Asserts that the argument value received is indeed a serial number and removes the '/' from the argument. + * @return String containing serial number of equipment to be deleted. + * @throws AssertionError if argument tag is not 's/' + */ + protected String prepareDelete() throws AssertionError { + String argString = commandStrings.get(0); + int delimiterPos = argString.indexOf('/'); + // the case where delimiterPos = -1 is impossible as + // ARGUMENT_FORMAT and ARGUMENT_TRAILING_FORMAT regex requires a '/' + assert delimiterPos != -1 : "Each args will need to include minimally a '/' to split arg and value upon"; + String argType = argString.substring(0, delimiterPos); + String argValue = argString.substring(delimiterPos + 1); + assert argType.equals("s") : ONLY_SN_ACCEPTED; + + return argValue; + } +} diff --git a/src/main/java/seedu/command/HelpCommand.java b/src/main/java/seedu/command/HelpCommand.java new file mode 100644 index 0000000000..8b2c17886b --- /dev/null +++ b/src/main/java/seedu/command/HelpCommand.java @@ -0,0 +1,36 @@ +package seedu.command; + +/** + * Subclass of Command. Shows information of the available commands to users. + */ +public class HelpCommand extends Command { + public static final String COMMAND_WORD = "help"; + public static final String COMMAND_DESCRIPTION = ": Shows details of available commands to users. " + + System.lineSeparator() + + "Parameters: NIL" + System.lineSeparator() + + "Example: " + + "help"; + + /** + * List all available commands and their usage examples. + * @return CommandResult with description of all available commands for users to use. + */ + public CommandResult execute() { + return new CommandResult(AddCommand.COMMAND_WORD + AddCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + DeleteCommand.COMMAND_WORD + DeleteCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + UpdateCommand.COMMAND_WORD + UpdateCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + ListCommand.COMMAND_WORD + ListCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + CheckCommand.COMMAND_WORD + CheckCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + SaveCommand.COMMAND_WORD + SaveCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + ByeCommand.COMMAND_WORD + ByeCommand.COMMAND_DESCRIPTION + + System.lineSeparator() + + System.lineSeparator() + HelpCommand.COMMAND_WORD + HelpCommand.COMMAND_DESCRIPTION); + + } +} diff --git a/src/main/java/seedu/command/IncorrectCommand.java b/src/main/java/seedu/command/IncorrectCommand.java new file mode 100644 index 0000000000..c0274640d8 --- /dev/null +++ b/src/main/java/seedu/command/IncorrectCommand.java @@ -0,0 +1,45 @@ +package seedu.command; + +/** + * Represents an incorrect command. Upon execution, provide some feedback to user + * + * @author Shun Yao + */ +public class IncorrectCommand extends Command { + + public final String feedbackToUser; + + public IncorrectCommand(String feedbackToUser) { + this.feedbackToUser = feedbackToUser; + } + + @Override + public CommandResult execute() { + return new CommandResult(feedbackToUser); + } + + /** + * Implement equals for JUnit comparisons + * Source: https://www.geeksforgeeks.org/overriding-equals-method-in-java/ + * @param o Any object + * @return If two commands are the same + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } + + /* Check if o is an instance of IncorrectCommand or not + "null instanceof [type]" also returns false */ + if (!(o instanceof IncorrectCommand)) { + return false; + } + + // typecast o to IncorrectCommand so that we can compare data members + IncorrectCommand c = (IncorrectCommand) o; + + // Compare the data members and return accordingly + return this.feedbackToUser.equals(c.feedbackToUser); + } + +} diff --git a/src/main/java/seedu/command/ListCommand.java b/src/main/java/seedu/command/ListCommand.java new file mode 100644 index 0000000000..96c97185eb --- /dev/null +++ b/src/main/java/seedu/command/ListCommand.java @@ -0,0 +1,67 @@ +package seedu.command; + +import seedu.equipment.Equipment; +import seedu.equipment.EquipmentType; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Subclass of Command. Handles listing of all equipment or listing of equipment of specified type. + */ +public class ListCommand extends Command { + private final ArrayList commandStrings; + public static final String COMMAND_WORD = "list"; + public static final String COMMAND_DESCRIPTION = ": Prints a list of all equipment in the inventory. " + + System.lineSeparator() + + "Parameters: NIL" + System.lineSeparator() + + "Example: " + + "list"; + + /** + * constructor for ListCommand with NO specified type. Initialises successMessage and usageReminder from Command + */ + public ListCommand() { + this.commandStrings = null; + successMessage = "TOTAL QUANTITY OF EQUIPMENT: %1$d" + System.lineSeparator(); + usageReminder = COMMAND_WORD + COMMAND_DESCRIPTION; + } + + + /** + * List all equipment. + * + * @return CommandResult with message from execution of this command + */ + public CommandResult execute() { + int listSize; + ArrayList equipmentArrayList; + equipmentArrayList = equipmentManager.listEquipment(); + listSize = equipmentArrayList.size(); + + return new CommandResult(String.format(successMessage, listSize), equipmentArrayList); + } + + /** + * Implement equals for JUnit comparisons + * Source: https://www.geeksforgeeks.org/overriding-equals-method-in-java/ + * @param o Any object + * @return If two commands are the same + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListCommand that = (ListCommand) o; + return Objects.equals(commandStrings, that.commandStrings); + } + + @Override + public int hashCode() { + return Objects.hash(commandStrings); + } +} diff --git a/src/main/java/seedu/command/ModificationCommand.java b/src/main/java/seedu/command/ModificationCommand.java new file mode 100644 index 0000000000..88efa03025 --- /dev/null +++ b/src/main/java/seedu/command/ModificationCommand.java @@ -0,0 +1,122 @@ +package seedu.command; + +import seedu.equipment.EquipmentType; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Objects; + +/** + * Parent class to AddCommand and UpdateCommand which have use for the same prepareModification method. + */ +public class ModificationCommand extends Command { + public static final String IMPLEMENTED_BY_CHILD = "Execute method for Modification should be implemented by " + + "child classes AddCommand and UpdateCommand"; + protected final ArrayList commandStrings; + protected String serialNumber; + protected String equipmentName = null; + protected LocalDate purchasedDate = null; + protected EquipmentType equipmentType = null; + protected String purchasedFrom = null; + protected Double cost = null; + + public ModificationCommand(ArrayList commandStrings) { + this.commandStrings = commandStrings; + } + + public CommandResult execute() { + return new CommandResult(IMPLEMENTED_BY_CHILD); + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public void setEquipmentName(String equipmentName) { + this.equipmentName = equipmentName; + } + + public void setPurchasedDate(String purchasedDate) throws DateTimeParseException { + this.purchasedDate = LocalDate.parse(purchasedDate); + } + + public void setEquipmentType(String equipmentType) throws IllegalArgumentException { + this.equipmentType = EquipmentType.valueOf(equipmentType.toUpperCase(Locale.ROOT)); + } + + public void setPurchasedFrom(String purchasedFrom) { + this.purchasedFrom = purchasedFrom; + } + + public void setCost(String cost) throws NumberFormatException { + this.cost = Double.valueOf(cost); + } + + /** + * Set up ModificationCommand with arguments required to update a given item + * + *

Should multiple arguments specifying the same argument parameter (e.g. 'c/1000' and 'c/2000') be given, + * the previous arguments passed in will be overwritten by the most recent parameter ('c/2000' in example). + * + * @throws NumberFormatException if cost is invalid + * @throws IllegalArgumentException if EquipmentType is invalid + * + */ + protected void prepareModification() throws AssertionError, NumberFormatException, IllegalArgumentException, + DateTimeParseException { + for (String s : commandStrings) { + int delimiterPos = s.indexOf('/'); + // the case where delimiterPos = -1 is impossible as + // ARGUMENT_FORMAT and ARGUMENT_TRAILING_FORMAT regex requires a '/' + assert delimiterPos != -1 : "Each args will need to include minimally a '/' to split arg and value upon"; + String argType = s.substring(0, delimiterPos); + String argValue = s.substring(delimiterPos + 1); + switch (argType) { + case "n": + setEquipmentName(argValue); + break; + case "pd": + setPurchasedDate(argValue); + break; + case "t": + setEquipmentType(argValue); + break; + case "pf": + setPurchasedFrom(argValue); + break; + case "c": + setCost(argValue); + break; + case "s": + setSerialNumber(argValue); + break; + default: + System.out.println("`" + argValue + "` not accepted for type " + argType + ": Unrecognised Tag"); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ModificationCommand that = (ModificationCommand) o; + return serialNumber.equals(that.serialNumber) + && Objects.equals(equipmentName, that.equipmentName) + && Objects.equals(purchasedDate, that.purchasedDate) + && Objects.equals(equipmentType, that.equipmentType) + && Objects.equals(purchasedFrom, that.purchasedFrom) + && Objects.equals(cost, that.cost); + } + + @Override + public int hashCode() { + return Objects.hash(serialNumber, equipmentName, purchasedDate, equipmentType, purchasedFrom, cost); + } +} diff --git a/src/main/java/seedu/command/SaveCommand.java b/src/main/java/seedu/command/SaveCommand.java new file mode 100644 index 0000000000..c44d44ae32 --- /dev/null +++ b/src/main/java/seedu/command/SaveCommand.java @@ -0,0 +1,23 @@ +package seedu.command; + +/** + * Subclass of Command. Saves the current state of application into a JSON file. + */ +public class SaveCommand extends Command { + public static final String COMMAND_WORD = "save"; + public static final String COMMAND_DESCRIPTION = ": Saves current state of application. " + + System.lineSeparator() + + "Parameters: NIL" + System.lineSeparator() + + "Example: " + + "save"; + public static final String SUCCESSFUL_SAVE = "Successfully saved."; + + /** + * Saves the current state of application into JSON file. + * @return CommandResult with successful save message. + */ + public CommandResult execute() { + storage.saveData(equipmentManager); + return new CommandResult(SUCCESSFUL_SAVE); + } +} diff --git a/src/main/java/seedu/command/UpdateCommand.java b/src/main/java/seedu/command/UpdateCommand.java new file mode 100644 index 0000000000..20f91c30ca --- /dev/null +++ b/src/main/java/seedu/command/UpdateCommand.java @@ -0,0 +1,133 @@ +package seedu.command; + +import seedu.Pair; + +import java.util.ArrayList; +import java.util.Objects; + +public class UpdateCommand extends ModificationCommand { + public static final String UPDATE_FAILURE_MESSAGE = "Equipment was not updated successfully."; + public static final String COMMAND_WORD = "update"; + public static final String COMMAND_DESCRIPTION = ": Updates the equipment with the specified serial number. " + + System.lineSeparator() + + "Parameters in [square brackets] are optional." + + System.lineSeparator() + + "Parameters: s/`SERIAL NUMBER` [n/`ITEM NAME`] [t/`TYPE`] [c/`COST`] [pf/`PURCHASED FROM`] " + + "[pd/`PURCHASED DATE`]" + + System.lineSeparator() + + "Example: " + + "update s/`SM57-1` n/`SpeakerC` c/`2510` pd/`2022-08-21`"; + + + /** + * constructor for UpdateCommand. Initialises successMessage and usageReminder from Command + */ + public UpdateCommand(ArrayList commandStrings) { + super(commandStrings); + successMessage = "Equipment successfully updated for serial number %1$s," + + System.lineSeparator() + + "Updated details are: %2$s"; + usageReminder = COMMAND_WORD + COMMAND_DESCRIPTION; + + prepareModification(); + } + + /** + * Updates the equipment of specified serial number with the given update values. + * @return different CommandResult is returned depending on situation. The different CommandResult + * represent the situations where serial number is not specified, update of equipment failed + * or update is successful. + */ + @Override + public CommandResult execute() { + if (getSerialNumber() == null) { + return new CommandResult(MISSING_SERIAL_NUMBER); + } + + ArrayList> updatePairs = generateUpdatePairs(); + if (!equipmentManager.updateEquipment(serialNumber, updatePairs)) { + return new CommandResult(UPDATE_FAILURE_MESSAGE); + } + + return new CommandResult(String.format(successMessage, serialNumber, generateUpdateString())); + } + + public String getSerialNumber() { + return serialNumber; + } + + /** + * Generates an ArrayList of Pair. + * Pair is a class that was implemented to match each attribute to its value. + * By using pair, we are able to update only the values that have been specified to be updated. + * @return ArrayList of the Pair of update values and their matching attributes. + */ + public ArrayList> generateUpdatePairs() { + ArrayList> pairs = new ArrayList<>(); + + if (equipmentName != null) { + pairs.add(new Pair<>("itemName", equipmentName)); + } + if (equipmentType != null) { + pairs.add(new Pair<>("type", equipmentType)); + } + if (cost != null) { + pairs.add(new Pair<>("cost", cost)); + } + if (purchasedDate != null) { + pairs.add(new Pair<>("purchasedDate", purchasedDate)); + } + if (purchasedFrom != null) { + pairs.add(new Pair<>("purchasedFrom", purchasedFrom)); + } + + return pairs; + } + + /** + * Generates the String which specifies which attributes have been updated. + * @return String containing update details. + */ + public String generateUpdateString() { + String updateDetails = ""; + if (equipmentName != null) { + updateDetails = updateDetails + System.lineSeparator() + "New name: " + equipmentName; + } + if (equipmentType != null) { + updateDetails = updateDetails + System.lineSeparator() + "New type: " + equipmentType; + } + if (cost != null) { + updateDetails = updateDetails + System.lineSeparator() + "New cost: " + cost; + } + if (purchasedDate != null) { + updateDetails = updateDetails + System.lineSeparator() + "New purchase date: " + purchasedDate; + } + if (purchasedFrom != null) { + updateDetails = updateDetails + System.lineSeparator() + "New purchased from: " + purchasedFrom; + } + + return updateDetails; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UpdateCommand that = (UpdateCommand) o; + return serialNumber.equals(that.serialNumber) + && Objects.equals(equipmentName, that.equipmentName) + && Objects.equals(purchasedDate, that.purchasedDate) + && Objects.equals(equipmentType, that.equipmentType) + && Objects.equals(purchasedFrom, that.purchasedFrom) + && Objects.equals(cost, that.cost); + } + + @Override + public int hashCode() { + return Objects.hash(serialNumber, equipmentName, purchasedDate, equipmentType, purchasedFrom, cost); + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 5c74e68d59..737af95237 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -1,21 +1,66 @@ package seedu.duke; +import seedu.command.Command; +import seedu.command.CommandResult; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.EquipmentManager; +import seedu.parser.Parser; +import seedu.ui.TextUi; +import seedu.storage.Storage; + import java.util.Scanner; public class Duke { + private TextUi ui; + private static EquipmentManager equipmentInventory = new EquipmentManager(); + private static Storage storage = new Storage(); + private static int commandCount = 0; + /** * Main entry-point for the java.duke.Duke application. */ public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); + Duke duke = new Duke(); + + duke.start(); + storage.loadData(equipmentInventory); + duke.runCommandLoop(); + storage.saveData(equipmentInventory); + } + /** + * Initialises the required objects and loads up the data from storage and show welcome message to user. + */ + private void start() { + ui = new TextUi(); + ui.showWelcomeMessage(); + } + + private void runCommandLoop() { Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); + Parser parser = new Parser(); + String userCommand; + Command command; + CommandResult result; + do { + userCommand = in.nextLine(); + command = parser.parseCommand(userCommand); + result = executeCommand(command); + ui.showResultToUser(result); + commandCount++; + if (commandCount % 5 == 0) { + storage.saveData(equipmentInventory); + System.out.print(System.lineSeparator()); + System.out.println("Auto-saved"); + } + } while (!userCommand.equals("bye")); + } + + private CommandResult executeCommand(Command command) { + command.setEquipmentManager(equipmentInventory); + command.setStorage(storage); + CommandResult result = command.execute(); + + return result; } } diff --git a/src/main/java/seedu/equipment/DuplicateSerialNumberException.java b/src/main/java/seedu/equipment/DuplicateSerialNumberException.java new file mode 100644 index 0000000000..46e10480f0 --- /dev/null +++ b/src/main/java/seedu/equipment/DuplicateSerialNumberException.java @@ -0,0 +1,6 @@ +package seedu.equipment; + +public class DuplicateSerialNumberException extends Exception { + public DuplicateSerialNumberException() { + } +} diff --git a/src/main/java/seedu/equipment/Equipment.java b/src/main/java/seedu/equipment/Equipment.java new file mode 100644 index 0000000000..0d03b75fad --- /dev/null +++ b/src/main/java/seedu/equipment/Equipment.java @@ -0,0 +1,112 @@ +package seedu.equipment; + +import java.time.LocalDate; +import java.util.Objects; + +public class Equipment { + private String itemName; + private String serialNumber; + private EquipmentType type; + private double cost; + private String purchasedFrom; + private LocalDate purchasedDate; + + /** + * Constructor for the Equipment object. + * @param itemName Name of the equipment in String. + * @param serialNumber Unique serial number of equipment in String. + * @param type Type of the equipment in EquipmentType enum. + * @param cost Cost of the equipment in double. + * @param purchasedFrom Where the equipment was purchased in String. + * @param purchasedDate When the equipment was purchased in String. + */ + public Equipment(String itemName, String serialNumber, EquipmentType type, double cost, String purchasedFrom, + LocalDate purchasedDate) { + this.itemName = itemName; + this.serialNumber = serialNumber; + this.type = type; + this.cost = cost; + this.purchasedFrom = purchasedFrom; + this.purchasedDate = purchasedDate; + } + + public String getItemName() { + return this.itemName; + } + + public void setItemName(String itemName) { + this.itemName = itemName; + } + + public String getSerialNumber() { + return this.serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public EquipmentType getType() { + return this.type; + } + + public void setType(EquipmentType type) { + this.type = type; + } + + public double getCost() { + return this.cost; + } + + public void setCost(double cost) { + this.cost = cost; + } + + public String getPurchasedFrom() { + return this.purchasedFrom; + } + + public void setPurchasedFrom(String purchasedFrom) { + this.purchasedFrom = purchasedFrom; + } + + public LocalDate getPurchasedDate() { + return this.purchasedDate; + } + + public void setPurchasedDate(LocalDate purchasedDate) { + this.purchasedDate = purchasedDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Equipment equipment = (Equipment) o; + return Double.compare(equipment.cost, cost) == 0 + && itemName.equals(equipment.itemName) + && serialNumber.equals(equipment.serialNumber) + && type == equipment.type + && purchasedFrom.equals(equipment.purchasedFrom) + && purchasedDate.equals(equipment.purchasedDate); + } + + @Override + public int hashCode() { + return Objects.hash(itemName, serialNumber, type, cost, purchasedFrom, purchasedDate); + } + + @Override + public String toString() { + return "serialNumber=" + serialNumber + "," + + "itemName=" + itemName + "," + + "type=" + type + "," + + "cost=" + cost + "," + + "purchasedFrom=" + purchasedFrom + "," + + "purchasedDate=" + purchasedDate; + } +} diff --git a/src/main/java/seedu/equipment/EquipmentManager.java b/src/main/java/seedu/equipment/EquipmentManager.java new file mode 100644 index 0000000000..4fb33b74a1 --- /dev/null +++ b/src/main/java/seedu/equipment/EquipmentManager.java @@ -0,0 +1,172 @@ +package seedu.equipment; + +import seedu.Pair; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; + +public class EquipmentManager { + private final HashMap equipmentList; + + public EquipmentManager() { + this.equipmentList = new HashMap<>(); + } + + /** + * Creates an Equipment object using the parameters and adds it to the equipmentList. + * + * @param itemName Name of the equipment in String. + * @param serialNumber Unique serial number of equipment in String. + * @param type Type of the equipment in EquipmentType enum. + * @param cost Cost of the equipment in double. + * @param purchasedFrom Where the equipment was purchased in String. + * @param purchasedDate When the equipment was purchased in String. + * @throws DuplicateSerialNumberException If the serial number exists in equipmentList. + */ + public void addEquipment(String itemName, String serialNumber, EquipmentType type, double cost, + String purchasedFrom, LocalDate purchasedDate) throws DuplicateSerialNumberException { + if (!equipmentList.containsKey(serialNumber.toLowerCase())) { + Equipment equipment = new Equipment(itemName, serialNumber, type, cost, purchasedFrom, purchasedDate); + equipmentList.put(serialNumber.toLowerCase(), equipment); + } else if (equipmentList.containsKey(serialNumber.toLowerCase())) { + throw new DuplicateSerialNumberException(); + } + } + + /** + * Adds the Equipment object into the equipmentList. + * + * @param equipment Equipment object. + * @throws DuplicateSerialNumberException If the serial number exists in equipmentList. + */ + public void addEquipment(Equipment equipment) throws DuplicateSerialNumberException { + String serialNumber = equipment.getSerialNumber(); + if (!equipmentList.containsKey(serialNumber.toLowerCase())) { + equipmentList.putIfAbsent(equipment.getSerialNumber().toLowerCase(), equipment); + } else { + throw new DuplicateSerialNumberException(); + } + } + + /** + * Searches the equipmentList for Equipments with the item name given. + * + * @param checkParam Name of Equipment in String. + * @return An ArrayList of Equipments with the same item name as the parameter. + */ + public ArrayList checkEquipment(Pair checkParam) { + ArrayList listOfEquipments = new ArrayList<>(); + String arg = checkParam.getKey(); + switch (arg) { + case "itemName": + for (Equipment equipment : equipmentList.values()) { + if (equipment.getItemName().contains((String) checkParam.getValue())) { + listOfEquipments.add(equipment); + } + } + break; + case "serialNumber": + String serialNumber = (String) checkParam.getValue(); + listOfEquipments.add(equipmentList.get(serialNumber.toLowerCase())); + break; + case "type": + for (Equipment equipment : equipmentList.values()) { + if (equipment.getType().equals(checkParam.getValue())) { + listOfEquipments.add(equipment); + } + } + break; + case "cost": + for (Equipment equipment : equipmentList.values()) { + if (equipment.getCost() == (double) checkParam.getValue()) { + listOfEquipments.add(equipment); + } + } + break; + case "purchasedFrom": + for (Equipment equipment : equipmentList.values()) { + if (equipment.getPurchasedFrom().contains((String) checkParam.getValue())) { + listOfEquipments.add(equipment); + } + } + break; + case "purchasedDate": + for (Equipment equipment : equipmentList.values()) { + if (equipment.getPurchasedDate().equals(checkParam.getValue())) { + listOfEquipments.add(equipment); + } + } + break; + default: + break; + } + return listOfEquipments; + } + + /** + * Creates an ArrayList of all Equipment Objects. + * + * @return An ArrayList of all Equipment Objects in equipmentList. + */ + public ArrayList listEquipment() { + return new ArrayList<>(equipmentList.values()); + } + + public HashMap getEquipmentList() { + return equipmentList; + } + + /** + * Updates the Equipment corresponding to the serial number given. + * Updates are given in the form of ArrayList of Pairs. + * + * @param serialNumber Serial number of Equipment in String. + * @param updatePairs Updates for Equipment with key being an Equipment + * attribute and value being the updated attribute. + * @return Boolean value based on whether the update succeeded or not. + */ + public boolean updateEquipment(String serialNumber, ArrayList> updatePairs) { + if (!equipmentList.containsKey(serialNumber.toLowerCase())) { + return false; + } + Equipment updatedEquipment = equipmentList.get(serialNumber.toLowerCase()); + for (Pair updates : updatePairs) { + String key = updates.getKey(); + switch (key) { + case "itemName": + updatedEquipment.setItemName((String) updates.getValue()); + break; + case "type": + try { + updatedEquipment.setType((EquipmentType) updates.getValue()); + } catch (IllegalArgumentException e) { + return false; + } + break; + case "cost": + updatedEquipment.setCost((Double) updates.getValue()); + break; + case "purchasedDate": + updatedEquipment.setPurchasedDate((LocalDate) updates.getValue()); + break; + case "purchasedFrom": + updatedEquipment.setPurchasedFrom((String) updates.getValue()); + break; + default: + break; + } + } + return true; + } + + /** + * Deletes the Equipment with the given serial number. + * + * @param serialNumber Serial number of Equipment in String. + */ + public void deleteEquipment(String serialNumber) { + equipmentList.remove(serialNumber.toLowerCase()); + } +} diff --git a/src/main/java/seedu/equipment/EquipmentType.java b/src/main/java/seedu/equipment/EquipmentType.java new file mode 100644 index 0000000000..f0bc8d95eb --- /dev/null +++ b/src/main/java/seedu/equipment/EquipmentType.java @@ -0,0 +1,13 @@ +package seedu.equipment; + +public enum EquipmentType { + MICROPHONE, SPEAKER, STAND, CABLE; + + public static String getAllTypes() { + StringBuilder sb = new StringBuilder(); + for (EquipmentType type : EquipmentType.values()) { + sb.append(type.name() + ", "); + } + return sb.toString(); + } +} diff --git a/src/main/java/seedu/parser/IncompleteCommandException.java b/src/main/java/seedu/parser/IncompleteCommandException.java new file mode 100644 index 0000000000..9d509b9c44 --- /dev/null +++ b/src/main/java/seedu/parser/IncompleteCommandException.java @@ -0,0 +1,9 @@ +package seedu.parser; + +public class IncompleteCommandException extends Exception { + public static final String NO_PARAMETERS_FOUND = "No parameters found!"; + + public IncompleteCommandException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/parser/MissingAttributeException.java b/src/main/java/seedu/parser/MissingAttributeException.java new file mode 100644 index 0000000000..8147c2df6b --- /dev/null +++ b/src/main/java/seedu/parser/MissingAttributeException.java @@ -0,0 +1,7 @@ +package seedu.parser; + +public class MissingAttributeException extends Exception { + public MissingAttributeException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/parser/Parser.java b/src/main/java/seedu/parser/Parser.java new file mode 100644 index 0000000000..e8b4bb17f9 --- /dev/null +++ b/src/main/java/seedu/parser/Parser.java @@ -0,0 +1,284 @@ +package seedu.parser; + +import java.time.format.DateTimeParseException; +import java.util.ArrayList; + +import seedu.command.AddCommand; +import seedu.command.UpdateCommand; +import seedu.command.ListCommand; +import seedu.command.IncorrectCommand; +import seedu.command.CheckCommand; +import seedu.command.DeleteCommand; +import seedu.command.HelpCommand; +import seedu.command.Command; +import seedu.command.SaveCommand; +import seedu.command.ByeCommand; + +import java.util.Collections; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parse user input + * Referenced https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java + */ +public class Parser { + + /** + * Format to parse command by breaking it up into two segments: command and arguments: these can then be separately + * passed into arguments. + */ + public static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)\\s+(?.+)"); + /** + * Defines regex used to match argument pairs in the command line. + * + *

An argument pair is defined as an argument tag, a slash "/", minimally a backtick "`", + * an argument value and minimally a final backtick "`". + * + *

See also {@link Parser#MODIFICATION_ARGUMENT_FORMAT} for Regex use and demonstration. + */ + private static final String ARGUMENT_PAIR_REGEX = + "(?:[sntcSNTC]|[pP][fF]|[pP][dD])" // non-capturing group for argument tag + + "\\/" // argument delimiter + + "`+" // backticks to enclose string + + "[\\w\\s\\-\\.\\$]+" // actual argument value + + "`+"; // backticks to enclose string + /** + * Extracts first n-1 tags for debugging and assumes that the last tag contains the whole string, refer to: + * See also {@link Parser#MODIFICATION_ARGUMENT_FORMAT} for Regex use and demonstration. + */ + public static final Pattern MODIFICATION_ARGUMENT_TRAILING_FORMAT = Pattern.compile( + "(?" + ARGUMENT_PAIR_REGEX + ")$"); + public static final Pattern DELETE_COMMAND_FORMAT = Pattern.compile("^(?" + "[Ss]" + + "\\/" // argument delimiter + + "`+" // backticks to enclose string + + "[\\w\\s\\-\\.]+" // actual argument value + + "`+" // backticks to enclose string + + ")$"); + public static final String UNRECOGNISED_COMMAND_MESSAGE = "Command word not recognised. " + System.lineSeparator() + + "Please use one of the following: " + + AddCommand.COMMAND_WORD + ", " + UpdateCommand.COMMAND_WORD + ", " + ListCommand.COMMAND_WORD + ", " + + CheckCommand.COMMAND_WORD + ", " + DeleteCommand.COMMAND_WORD + ", " + HelpCommand.COMMAND_WORD + ", " + + SaveCommand.COMMAND_WORD + ", " + ByeCommand.COMMAND_WORD + "."; + public static final String MISSING_COMMAND_WORD_DELIMITER_MESSAGE = UNRECOGNISED_COMMAND_MESSAGE + + System.lineSeparator() + "If including additional arguments, please separate them with a space."; + + /** + * Interpret the command requested by the user and returns a corresponding Command object. + * + * @param userInput Raw string of input values + * @return command of parent class Command with parameters specified + */ + public Command parseCommand(String userInput) { + ArrayList commandAndArgument; + ArrayList args; + try { + commandAndArgument = splitCommandTerm(userInput); + } catch (IncompleteCommandException e) { + return new IncorrectCommand(MISSING_COMMAND_WORD_DELIMITER_MESSAGE); + } + + // only arguments is trimmed because commandWord is split on the first space + String commandWord = commandAndArgument.get(0); + String arguments = commandAndArgument.get(1); + if (arguments != null) { + arguments = arguments.trim(); + } + + switch (commandWord) { + case AddCommand.COMMAND_WORD: + try { + args = extractArguments(arguments); + return new AddCommand(args); + } catch (IncompleteCommandException e) { + return new IncorrectCommand(AddCommand.COMMAND_WORD + AddCommand.COMMAND_DESCRIPTION); + } catch (NumberFormatException e) { + return new IncorrectCommand(Command.INCORRECT_COST_FORMAT); + } catch (IllegalArgumentException e) { + return new IncorrectCommand(Command.INCORRECT_ENUM_TYPE); + } catch (MissingAttributeException e) { + return new IncorrectCommand(e.getMessage() + + System.lineSeparator() + + AddCommand.COMMAND_WORD + + AddCommand.COMMAND_DESCRIPTION); + } catch (DateTimeParseException e) { + return new IncorrectCommand(Command.INVALID_DATE_MESSAGE); + } + case CheckCommand.COMMAND_WORD: + try { + args = prepareCheck(arguments); + return new CheckCommand(args); + } catch (IncompleteCommandException e) { + return new IncorrectCommand(CheckCommand.COMMAND_WORD + CheckCommand.COMMAND_DESCRIPTION); + } + case DeleteCommand.COMMAND_WORD: + try { + args = prepareDelete(arguments); + return new DeleteCommand(args); + } catch (IncompleteCommandException e) { + return new IncorrectCommand(DeleteCommand.COMMAND_WORD + DeleteCommand.COMMAND_DESCRIPTION); + } + case UpdateCommand.COMMAND_WORD: + try { + args = extractArguments(arguments); + return new UpdateCommand(args); + } catch (IncompleteCommandException e) { + return new IncorrectCommand(UpdateCommand.COMMAND_WORD + UpdateCommand.COMMAND_DESCRIPTION); + } catch (NumberFormatException e) { + return new IncorrectCommand(Command.INCORRECT_COST_FORMAT); + } catch (IllegalArgumentException e) { + return new IncorrectCommand(Command.INCORRECT_ENUM_TYPE); + } catch (DateTimeParseException e) { + return new IncorrectCommand(Command.INVALID_DATE_MESSAGE); + } + case ListCommand.COMMAND_WORD: + return new ListCommand(); + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + case SaveCommand.COMMAND_WORD: + return new SaveCommand(); + case ByeCommand.COMMAND_WORD: + return new ByeCommand(); + default: + return new IncorrectCommand(UNRECOGNISED_COMMAND_MESSAGE); + } + + } + + /** + * Break down a command into the command term to be parsed and the remainder of the arguments. + * Assumes command term and remainder arguments are delimited by minimally one space. + * + *

If first element is list, help, save or bye, + * remainder arguments can be empty, in which case a null second object will be passed in. + * If there exists a second element in such a case, it is split as per normal, but it will be ignored in + * {@link Parser#parseCommand(String)}. + * + * @param userInput String to be split into substrings + * @return ArrayList of 2 String, first element command term and second element arguments + * @throws IncompleteCommandException if no space is found + */ + public ArrayList splitCommandTerm(String userInput) throws IncompleteCommandException { + ArrayList resultArrayList = new ArrayList<>(); + userInput = userInput.trim(); + + // Checks for list/help/save/bye command word first + switch (userInput.toLowerCase(Locale.ROOT)) { + case ListCommand.COMMAND_WORD: + resultArrayList.add(ListCommand.COMMAND_WORD); + resultArrayList.add(null); + return resultArrayList; + case HelpCommand.COMMAND_WORD: + resultArrayList.add(HelpCommand.COMMAND_WORD); + resultArrayList.add(null); + return resultArrayList; + case SaveCommand.COMMAND_WORD: + resultArrayList.add(SaveCommand.COMMAND_WORD); + resultArrayList.add(null); + return resultArrayList; + case ByeCommand.COMMAND_WORD: + resultArrayList.add(ByeCommand.COMMAND_WORD); + resultArrayList.add(null); + return resultArrayList; + default: + break; + } + + // Match terms against syntax + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput); + // Guard against no match + if (!matcher.matches()) { + throw new IncompleteCommandException("Could not find space delimiter between command and arguments!"); + } + // Match and return ArrayList appropriately + resultArrayList.add(matcher.group("commandWord").toLowerCase(Locale.ROOT)); + resultArrayList.add(matcher.group("arguments")); + return resultArrayList; + } + + /** + * Prepare argument for CheckCommand by matching the preceding "n/" prefix, setting the prefix to lowercase, + * remove backticks and verifying that there are no additional tags. + * + * @param args String to be split into substrings + * @return ArrayList of one element (assumes rest of string is item name) + * @throws IncompleteCommandException if no match found + */ + protected ArrayList prepareCheck(String args) throws IncompleteCommandException { + final Matcher matcher = CHECK_COMMAND_FORMAT.matcher(args); + if (!matcher.matches()) { + throw new IncompleteCommandException("Check command values are incomplete or missing!"); + } + String argumentPair = reformatArgumentPair(matcher.group("itemName")); + return new ArrayList<>(Collections.singleton(argumentPair)); + } + + /** + * Prepare argument for DeleteCommand by matching the preceding "s/" prefix, setting the prefix to lowercase, + * remove backticks and verifying that there are no additional tags. + * + * @param args String to be split into substrings + * @return ArrayList of one element (assumes rest of string is serial number) + * @throws IncompleteCommandException if no match found + */ + protected ArrayList prepareDelete(String args) throws IncompleteCommandException { + final Matcher matcher = DELETE_COMMAND_FORMAT.matcher(args); + if (!matcher.matches()) { + throw new IncompleteCommandException("Delete command values are incomplete or missing!"); + } + String argumentPair = reformatArgumentPair(matcher.group("serialNumber")); + return new ArrayList<>(Collections.singleton(argumentPair)); + } + + /** + * Splits main arguments into split tags with each substring by setting the prefix to lowercase + * and removing backticks. + * + * @param args String to be split into substrings + * @return ArrayList of two elements + * @throws IncompleteCommandException if no parameters found + */ + protected ArrayList extractArguments(String args) throws IncompleteCommandException { + int lastIndex = 0; + String argumentToAdd; + String argument; + ArrayList splitArguments = new ArrayList<>(); + try { + Matcher matcher = MODIFICATION_ARGUMENT_FORMAT.matcher(args); + while (matcher.find()) { + argument = matcher.group(); + argumentToAdd = reformatArgumentPair(argument.trim()); + splitArguments.add(argumentToAdd); + lastIndex = matcher.end(); + } + matcher.usePattern(MODIFICATION_ARGUMENT_TRAILING_FORMAT); + matcher.find(lastIndex); + argument = matcher.group(); + argumentToAdd = reformatArgumentPair(argument.trim()); + splitArguments.add(argumentToAdd); + } catch (IllegalStateException e) { + throw new IncompleteCommandException(IncompleteCommandException.NO_PARAMETERS_FOUND); + } + + return splitArguments; + } + + private static String reformatArgumentPair(String argument) { + int slashIndex = argument.indexOf("/"); + String newString = argument.substring(0, slashIndex).toLowerCase(Locale.ROOT) + argument.substring(slashIndex); + return newString.replace("`", ""); + } + +} diff --git a/src/main/java/seedu/storage/LocalDateAdapter.java b/src/main/java/seedu/storage/LocalDateAdapter.java new file mode 100644 index 0000000000..6f4a1ed389 --- /dev/null +++ b/src/main/java/seedu/storage/LocalDateAdapter.java @@ -0,0 +1,44 @@ +package seedu.storage; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.time.LocalDate; + +class LocalDateAdapter extends TypeAdapter { + /** + * Write a LocalDate to a JsonWriter. + * Source: https://stackoverflow.com/questions/39192945/serialize-java-8-localdate-as-yyyy-mm-dd-with-gson + * @param jsonWriter the JsonWriter to write to. + * @param localDate the LocalDate to write. + * @throws IOException if an error occurs while writing. + */ + @Override + public void write(final JsonWriter jsonWriter, final LocalDate localDate) throws IOException { + if (localDate == null) { + jsonWriter.nullValue(); + } else { + jsonWriter.value(localDate.toString()); + } + } + + /** + * Read a LocalDate from a JsonReader. + * Source: https://stackoverflow.com/questions/39192945/serialize-java-8-localdate-as-yyyy-mm-dd-with-gson + * @param jsonReader the JsonReader to read from. + * @return the LocalDate read. + * @throws IOException if an error occurs while reading. + */ + @Override + public LocalDate read(final JsonReader jsonReader) throws IOException { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + return null; + } else { + return LocalDate.parse(jsonReader.nextString()); + } + } +} diff --git a/src/main/java/seedu/storage/Storage.java b/src/main/java/seedu/storage/Storage.java new file mode 100644 index 0000000000..ed2feeeb4e --- /dev/null +++ b/src/main/java/seedu/storage/Storage.java @@ -0,0 +1,72 @@ +package seedu.storage; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.Equipment; +import seedu.equipment.EquipmentManager; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + + +public class Storage { + public static final String FILE_NOT_FOUND_ERROR_MESSAGE = "Save file not found: " + + "a new file will be created after this session."; + public static final String SAVE_ERROR_MESSAGE = "An error occurred while saving!"; + public static final String DUPLICATE_SERIAL_NUMBER_ERROR = "Duplicate serial number found!"; + public static final String path = "./equipments.json"; + private static final String EMPTY_FILE_ERROR_MESSAGE = "File is empty. Added Equipments will be saved in the file."; + private static final String INVALID_DATE_ERROR_MESSAGE = "Invalid date format found in the file. " + + "Please check the file and try again. Do not proceed unless you want to rewrite the file."; + private final Gson gson = new GsonBuilder() + .setPrettyPrinting() + .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) + .create(); + + /** + * Loads data from ./equipments.json + * + * @param equipmentManager EquipmentManager object to put all the Equipments in ./equipments.json. + */ + public void loadData(EquipmentManager equipmentManager) { + try { + Reader reader = Files.newBufferedReader(Path.of(path)); + List equipments = gson.fromJson(reader, new TypeToken>() {}.getType()); + for (Equipment equipment : equipments) { + equipmentManager.addEquipment(equipment); + } + } catch (IOException e) { + System.out.println(FILE_NOT_FOUND_ERROR_MESSAGE); + } catch (DuplicateSerialNumberException e) { + System.out.println(DUPLICATE_SERIAL_NUMBER_ERROR); + } catch (NullPointerException e) { + System.out.println(EMPTY_FILE_ERROR_MESSAGE); + } catch (DateTimeException e) { + System.out.println(INVALID_DATE_ERROR_MESSAGE); + } + } + + /** + * Saves the Equipments in equipmentManager into a json file. + * + * @param equipmentManager EquipmentManager object with Equipments that will be saved to the json file. + */ + public void saveData(EquipmentManager equipmentManager) { + List equipments = new ArrayList<>(equipmentManager.getEquipmentList().values()); + try { + Writer writer = Files.newBufferedWriter(Path.of(path)); + gson.toJson(equipments, writer); + writer.close(); + } catch (IOException e) { + System.out.println(SAVE_ERROR_MESSAGE); + } + } +} diff --git a/src/main/java/seedu/ui/TextUi.java b/src/main/java/seedu/ui/TextUi.java new file mode 100644 index 0000000000..34275719da --- /dev/null +++ b/src/main/java/seedu/ui/TextUi.java @@ -0,0 +1,44 @@ +package seedu.ui; + +import seedu.command.CommandResult; +import seedu.equipment.Equipment; + +/** + * Text UI for the application, referenced from + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/ui/TextUi.java + */ +public class TextUi { + private static final String LOGO = " _ _" + + " \n" + + " (_) | | \n" + + " ___ __ _ _ _ _ _ __ _ __ ___ ___ _ __ | |_ _ __ ___ __ _ _ __ __ _ __ _ ___ _ __ \n" + + " / _ \\/ _` | | | | | '_ \\| '_ ` _ \\ / _ \\ '_ \\| __| '_ ` _ \\ / _` " + + "| '_ \\ / _` |/ _` |/ _ \\ '__|\n" + + " | __/ (_| | |_| | | |_) | | | | | | __/ | | | |_| | | | | | (_| | | | | (_| | (_| | __/ | \n" + + " \\___|\\__, |\\__,_|_| .__/|_| |_| |_|\\___|_| " + + "|_|\\__|_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| \n" + + " | | | | __/ | \n" + + " |_| |_| |___/"; + + public void showWelcomeMessage() { + System.out.println("Hello from" + System.lineSeparator() + LOGO); + System.out.println("What do you want to do? Enter `help` to see what you can do."); + } + + /** + * Shows the result of command execution to user. + * @param result CommandResult returned from the execution of a command. + */ + public void showResultToUser(CommandResult result) { + System.out.println(result.getResultToShow()); + int indexShown = 1; + if (result.getRelevantEquipment() != null) { + for (Equipment equipment : result.getRelevantEquipment()) { + System.out.print(indexShown + ". "); + System.out.println(equipment); + indexShown++; + } + } + } + +} diff --git a/src/test/java/seedu/command/AddCommandTest.java b/src/test/java/seedu/command/AddCommandTest.java new file mode 100644 index 0000000000..c4d3a1491b --- /dev/null +++ b/src/test/java/seedu/command/AddCommandTest.java @@ -0,0 +1,113 @@ +package seedu.command; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.Equipment; +import seedu.equipment.EquipmentManager; +import seedu.equipment.EquipmentType; +import seedu.parser.IncompleteCommandException; +import seedu.parser.MissingAttributeException; +import seedu.parser.Parser; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +class AddCommandTest { + AddCommand addCommand; + ArrayList userInput = new ArrayList<>( + Arrays.asList( + "n/Speaker B", + "s/S1404115ASF", + "t/SPEAKER", + "c/1000", + "pf/Loud Technologies", + "pd/2022-02-23") + ); + + @Test + void execute_duplicateSerialNumber_exceptionThrown() throws DuplicateSerialNumberException, + MissingAttributeException { + addCommand = new AddCommand(userInput); + addCommand.setEquipmentManager(new EquipmentManager()); + EquipmentManager equipmentManager = addCommand.equipmentManager; + equipmentManager.addEquipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + + CommandResult expectedResult = + new CommandResult("There is already an item with this serial number: S1404115ASF"); + CommandResult actualResult = addCommand.execute(); + assertEquals(expectedResult, actualResult); + } + + @Test + void execute_incorrectCostFormat_exceptionThrown() { + assertThrows(NumberFormatException.class, () -> new AddCommand(new ArrayList<>( + Arrays.asList("n/Speaker B", + "s/S1404115ASF", + "t/SPEAKER", + "c/$1000", + "pf/Loud Technologies", + "pd/2022-02-23") + ))); + } + + @Test + void execute_incorrectEnumType_exceptionThrown() { + assertThrows(IllegalArgumentException.class, () -> new AddCommand(new ArrayList<>( + Arrays.asList("n/Speaker B", + "s/S1404115ASF", + "t/SPEAKERS", + "c/1000", + "pf/Loud Technologies", + "pd/2022-02-23") + ))); + } + + @Test + void addEquipment_validArrayListString_success() throws DuplicateSerialNumberException, MissingAttributeException { + addCommand = new AddCommand(userInput); + addCommand.setEquipmentManager(new EquipmentManager()); + EquipmentManager equipmentManager = addCommand.equipmentManager; + int equipmentListOriginalSize = equipmentManager.getEquipmentList().size(); + Equipment expectedEquipment = new Equipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + + addCommand.addEquipment(userInput); + Equipment actualEquipment = equipmentManager.listEquipment().get(equipmentListOriginalSize); + + assertEquals(equipmentListOriginalSize + 1, equipmentManager.getEquipmentList().size()); + assertEquals(expectedEquipment, actualEquipment); + } + + @Test + void checkAttributes_allAttributesSet_true() throws MissingAttributeException { + addCommand = new AddCommand(userInput); + boolean actualResult = addCommand.checkAttributes(); + + assertTrue(actualResult); + } + + @Test + void checkAttributes_oneOrMoreNulls_false_exceptionThrown() { + assertThrows(MissingAttributeException.class, () -> new AddCommand(new ArrayList<>( + Arrays.asList("n/Speaker B", "s/S1404115ASF", "t/SPEAKER", "pf/Loud Technologies") + ))); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/command/CheckCommandTest.java b/src/test/java/seedu/command/CheckCommandTest.java new file mode 100644 index 0000000000..c37b630546 --- /dev/null +++ b/src/test/java/seedu/command/CheckCommandTest.java @@ -0,0 +1,87 @@ +package seedu.command; + +import org.junit.jupiter.api.Test; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.Equipment; +import seedu.equipment.EquipmentManager; +import seedu.equipment.EquipmentType; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class CheckCommandTest { + CheckCommand checkCommand; + + @Test + void execute_validEquipmentName_success() { + checkCommand = new CheckCommand(new ArrayList<>( + Arrays.asList("n/Speaker B") + )); + checkCommand.setEquipmentManager(new EquipmentManager()); + EquipmentManager equipmentManager = checkCommand.equipmentManager; + try { + equipmentManager.addEquipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + } catch (DuplicateSerialNumberException e) { + fail(); + } + + ArrayList listOfEquipments = new ArrayList<>(); + for (Equipment equipment : equipmentManager.getEquipmentList().values()) { + if (equipment.getItemName().equals("Speaker B")) { + listOfEquipments.add(equipment); + } + } + + CommandResult actualResult = checkCommand.execute(); + CommandResult expectedResult = new CommandResult( + "Here are the equipment matching to 'n/Speaker B':" + System.lineSeparator(), + listOfEquipments); + + assertEquals(expectedResult, actualResult); + } + + @Test + void execute_invalidEquipmentType_fail() { + checkCommand = new CheckCommand(new ArrayList<>( + Arrays.asList("t/hello") + )); + + CommandResult actualResult = checkCommand.execute(); + CommandResult expectedResult = new CommandResult(Command.INCORRECT_ENUM_TYPE); + + assertEquals(expectedResult, actualResult); + } + + @Test + void execute_invalidCost_fail() { + checkCommand = new CheckCommand(new ArrayList<>( + Arrays.asList("c/something") + )); + + CommandResult actualResult = checkCommand.execute(); + CommandResult expectedResult = new CommandResult(Command.INCORRECT_COST_FORMAT); + + assertEquals(expectedResult, actualResult); + } + + @Test + void execute_invalidDateFormat_fail() { + checkCommand = new CheckCommand(new ArrayList<>( + Arrays.asList("pd/2022") + )); + + CommandResult actualResult = checkCommand.execute(); + CommandResult expectedResult = new CommandResult(Command.INVALID_DATE_MESSAGE); + + assertEquals(expectedResult, actualResult); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/command/DeleteCommandTest.java b/src/test/java/seedu/command/DeleteCommandTest.java new file mode 100644 index 0000000000..480cef625f --- /dev/null +++ b/src/test/java/seedu/command/DeleteCommandTest.java @@ -0,0 +1,74 @@ +package seedu.command; + +import org.junit.jupiter.api.Test; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.EquipmentManager; +import seedu.equipment.EquipmentType; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static seedu.command.DeleteCommand.ONLY_SN_ACCEPTED; + +class DeleteCommandTest { + DeleteCommand deleteCommand; + + @Test + void execute_validSerialNumber_success() { + deleteCommand = new DeleteCommand(new ArrayList<>( + Arrays.asList("s/S1404115ASF") + )); + deleteCommand.setEquipmentManager(new EquipmentManager()); + EquipmentManager equipmentManager = deleteCommand.equipmentManager; + try { + equipmentManager.addEquipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + } catch (DuplicateSerialNumberException e) { + fail(); + } + + int equipmentListSize = equipmentManager.getEquipmentList().size(); + assertEquals(1, equipmentListSize); + + CommandResult actualResult = deleteCommand.execute(); + CommandResult expectedResult = + new CommandResult("Equipment successfully deleted: Speaker B, serial number S1404115ASF"); + + assertEquals(expectedResult, actualResult); + } + + @Test + void execute_invalidSerialNumber_fail() { + deleteCommand = new DeleteCommand(new ArrayList<>( + Arrays.asList("s/S123445ASF") + )); + deleteCommand.setEquipmentManager(new EquipmentManager()); + + CommandResult actualResult = deleteCommand.execute(); + CommandResult expectedResult = + new CommandResult(Command.INVALID_SERIAL_NUMBER); + + assertEquals(expectedResult, actualResult); + } + + @Test + void execute_notSerialNumber_fail() { + deleteCommand = new DeleteCommand(new ArrayList<>( + Arrays.asList("n/Speaker3") + )); + deleteCommand.setEquipmentManager(new EquipmentManager()); + + CommandResult actualResult = deleteCommand.execute(); + CommandResult expectedResult = + new CommandResult(ONLY_SN_ACCEPTED); + + assertEquals(expectedResult, actualResult); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/command/ListCommandTest.java b/src/test/java/seedu/command/ListCommandTest.java new file mode 100644 index 0000000000..ea43d01a66 --- /dev/null +++ b/src/test/java/seedu/command/ListCommandTest.java @@ -0,0 +1,40 @@ +package seedu.command; + +import org.junit.jupiter.api.Test; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.EquipmentManager; +import seedu.equipment.EquipmentType; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class ListCommandTest { + ListCommand listCommand; + + @Test + void execute_noSpecifiedType_success() { + listCommand = new ListCommand(); + listCommand.setEquipmentManager(new EquipmentManager()); + EquipmentManager equipmentManager = listCommand.equipmentManager; + try { + equipmentManager.addEquipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + } catch (DuplicateSerialNumberException e) { + fail(); + } + + CommandResult actualResult = listCommand.execute(); + CommandResult expectedResult = new CommandResult("TOTAL QUANTITY OF EQUIPMENT: 1" + System.lineSeparator(), + equipmentManager.listEquipment()); + + assertEquals(expectedResult, actualResult); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/command/ModificationCommandTest.java b/src/test/java/seedu/command/ModificationCommandTest.java new file mode 100644 index 0000000000..51876a64d6 --- /dev/null +++ b/src/test/java/seedu/command/ModificationCommandTest.java @@ -0,0 +1,63 @@ +package seedu.command; + +import org.junit.jupiter.api.Test; +import seedu.parser.IncompleteCommandException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModificationCommandTest { + ModificationCommand modificationCommand; + + @Test + void prepareModification_missingSlashDelimiter_assertionErrorThrown() throws AssertionError { + ArrayList testArrayList = new ArrayList<>(Collections.singleton("thiswillnotwork")); + try { + modificationCommand = new ModificationCommand(testArrayList); + modificationCommand.prepareModification(); + } catch (AssertionError error) { + assertEquals("Each args will need to include minimally a '/' to split arg and value upon", + error.getMessage()); + } + } + + @Test + void prepareModification_missingSerialNumber_exceptionThrown() throws IncompleteCommandException { + ArrayList testArrayList = new ArrayList<>(Arrays.asList( + "n/Speaker B", "t/SPEAKER", "c/1000", "pf/Loud Technologies", "pd/2022-02-23")); + UpdateCommand updateCommand = new UpdateCommand(testArrayList); + CommandResult expectedResult = new CommandResult("Serial Number is required to run this command"); + assertEquals(expectedResult, updateCommand.execute()); + } + + @Test + void prepareModification_mostRecentArgValueUsed_success() throws IncompleteCommandException { + ArrayList testArrayList = new ArrayList<>(Arrays.asList( + "s/S1404115ASF", "n/Speaker B", "n/Speaker A")); + ModificationCommand expectedCommand = new ModificationCommand(new ArrayList<>( + Arrays.asList("s/S1404115ASF", "n/Speaker A") + )); + expectedCommand.prepareModification(); + ModificationCommand actualCommand = new ModificationCommand(testArrayList); + actualCommand.prepareModification(); + assertEquals(expectedCommand, actualCommand); + } + + @Test + void prepareModification_typeEnumsChangedToCaps_success() throws IncompleteCommandException { + ArrayList testArrayList = new ArrayList<>(Arrays.asList( + "s/S1404115ASF", "t/sPEAker", "n/Speaker A")); + ModificationCommand expectedCommand = new ModificationCommand(new ArrayList<>( + Arrays.asList("s/S1404115ASF", "t/SPEAKER", "n/Speaker A") + )); + expectedCommand.prepareModification(); + ModificationCommand actualCommand = new ModificationCommand(testArrayList); + expectedCommand.prepareModification(); + actualCommand.prepareModification(); + assertEquals(expectedCommand, actualCommand); + } + +} \ No newline at end of file diff --git a/src/test/java/seedu/command/UpdateCommandTest.java b/src/test/java/seedu/command/UpdateCommandTest.java new file mode 100644 index 0000000000..8fb60a67ab --- /dev/null +++ b/src/test/java/seedu/command/UpdateCommandTest.java @@ -0,0 +1,68 @@ +package seedu.command; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.Pair; +import seedu.equipment.DuplicateSerialNumberException; +import seedu.equipment.EquipmentManager; +import seedu.equipment.EquipmentType; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class UpdateCommandTest { + EquipmentManager equipmentManager = new EquipmentManager(); + UpdateCommand updateCommand; + + @BeforeEach + void setup() { + try { + equipmentManager.addEquipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + } catch (DuplicateSerialNumberException e) { + fail(); + } + + updateCommand = new UpdateCommand(new ArrayList<>( + Arrays.asList("s/S1404115ASF", "n/Speaker C", "c/2000", "pd/2022-01-26") + )); + updateCommand.setEquipmentManager(equipmentManager); + } + + @Test + void execute_validSerialNumber_success() { + CommandResult expectedResult = new CommandResult("Equipment successfully updated for serial number S1404115ASF," + + System.lineSeparator() + + "Updated details are: " + + System.lineSeparator() + + "New name: Speaker C" + + System.lineSeparator() + + "New cost: 2000.0" + + System.lineSeparator() + + "New purchase date: 2022-01-26"); + + CommandResult actualResult = updateCommand.execute(); + + assertEquals(expectedResult, actualResult); + } + + @Test + void generateUpdatePairs_nonNullUpdates_success() { + ArrayList> expectedResult = new ArrayList<>(); + expectedResult.add(new Pair<>("itemName", "Speaker C")); + expectedResult.add(new Pair<>("cost", 2000.0)); + expectedResult.add(new Pair<>("purchasedDate", LocalDate.parse("2022-01-26"))); + + ArrayList> actualResult = updateCommand.generateUpdatePairs(); + + assertEquals(expectedResult, actualResult); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/equipment/EquipmentManagerTest.java b/src/test/java/seedu/equipment/EquipmentManagerTest.java new file mode 100644 index 0000000000..47ef843a9d --- /dev/null +++ b/src/test/java/seedu/equipment/EquipmentManagerTest.java @@ -0,0 +1,51 @@ +package seedu.equipment; + +import org.junit.jupiter.api.Test; +import seedu.Pair; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class EquipmentManagerTest { + @Test + public void updateEquipment_success() throws DuplicateSerialNumberException { + EquipmentManager equipmentManager = new EquipmentManager(); + equipmentManager.addEquipment("Speaker B", + "S1404115ASF", + EquipmentType.valueOf("SPEAKER"), + 1000, + "Loud Technologies", + LocalDate.parse("2022-02-23")); + ArrayList> updates = new ArrayList<>(); + updates.add(new Pair<>("itemName", "Speaker A")); + updates.add(new Pair<>("type", EquipmentType.STAND)); + updates.add(new Pair<>("cost", 2000.0)); + updates.add(new Pair<>("purchasedDate", LocalDate.parse("2022-03-17"))); + updates.add(new Pair<>("purchasedFrom", "Louder Technologies")); + equipmentManager.updateEquipment("S1404115ASF", updates); + HashMap equipments = equipmentManager.getEquipmentList(); + Equipment expectedEquipment = new Equipment("Speaker A", + "S1404115ASF", + EquipmentType.valueOf("STAND"), + 2000, + "Louder Technologies", + LocalDate.parse("2022-03-17")); + assertEquals(expectedEquipment, equipments.get("S1404115ASF".toLowerCase())); + } + + @Test + public void updateEquipment_keyNotFound() { + ArrayList> updates = new ArrayList<>(); + updates.add(new Pair<>("itemName", "Speaker A")); + updates.add(new Pair<>("type", "STAND")); + updates.add(new Pair<>("cost", "2000")); + updates.add(new Pair<>("purchaseDate", LocalDate.parse("2022-03-17"))); + updates.add(new Pair<>("purchaseFrom", "Louder Technologies")); + EquipmentManager equipmentManager = new EquipmentManager(); + assertFalse(equipmentManager.updateEquipment("WRONG SERIAL NUMBER", updates)); + } +} diff --git a/src/test/java/seedu/parser/ParserTest.java b/src/test/java/seedu/parser/ParserTest.java new file mode 100644 index 0000000000..af6423b0e3 --- /dev/null +++ b/src/test/java/seedu/parser/ParserTest.java @@ -0,0 +1,261 @@ +package seedu.parser; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import seedu.command.AddCommand; +import seedu.command.Command; +import seedu.command.DeleteCommand; +import seedu.command.IncorrectCommand; +import seedu.command.ListCommand; +import seedu.command.UpdateCommand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +class ParserTest { + + Parser parser; + + @BeforeEach + void setup() { + parser = new Parser(); + } + + @Test + void splitCommandTerm_validCommand_success() throws IncompleteCommandException { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("add", "n/`ITEM_NAME` s/`SERIAL_NUMBER` t/`TYPE` c/`COST` pf/`PURCHASED_FROM` " + + "pd/`PURCHASED_DATE`") + ); + ArrayList actualResult = parser.splitCommandTerm( + "add n/`ITEM_NAME` s/`SERIAL_NUMBER` t/`TYPE` c/`COST` pf/`PURCHASED_FROM` " + + "pd/`PURCHASED_DATE`"); + assertEquals(expectedResult, actualResult); + } + + @Test + void splitCommandTerm_listCommandOnly_success() throws IncompleteCommandException { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("list", null) + ); + ArrayList actualResult = parser.splitCommandTerm( + "list"); + assertEquals(expectedResult, actualResult); + } + + @Test + void splitCommandTerm_listCommandExtraWordsRetained_success() throws IncompleteCommandException { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("list", "t/`SPEAKER`") + ); + ArrayList actualResult = parser.splitCommandTerm( + "list t/`SPEAKER`"); + assertEquals(expectedResult, actualResult); + } + + @Test + void splitCommandTerm_noSpaceDelimiter_exceptionThrown() { + ArrayList unexpectedResult = new ArrayList<>( + Arrays.asList("add", "n/`ITEM_NAME`s/`SERIAL_NUMBER`t/`TYPE`c/`COST`pf/`PURCHASED_FROM`" + + "pd/`PURCHASED_DATE`") + ); + try { + ArrayList actualResult = parser.splitCommandTerm( + "addn/`ITEM_NAME`s/`SERIAL_NUMBER`t/`TYPE`c/`COST`pf/`PURCHASED_FROM`pd/`PURCHASED_DATE`"); + assertEquals(unexpectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals("Could not find space delimiter between command and arguments!", e.getMessage()); + } + } + + @Test + void prepareCheck_viewStringWithSpaces_success() throws IncompleteCommandException { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("n/Speaker B") + ); + ArrayList actualResult = parser.prepareCheck( + "n/`Speaker B`"); + assertEquals(expectedResult, actualResult); + } + + @Test + void prepareCheck_checkUsingSerialNum_success() throws IncompleteCommandException { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("s/Speaker B") + ); + ArrayList actualResult = parser.prepareCheck( + "s/`Speaker B`"); + assertEquals(expectedResult, actualResult); + } + + @Test + void prepareCheck_missingFrontBackTick_exceptionThrown() { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("n/Speaker B") + ); + try { + ArrayList actualResult = parser.prepareCheck( + "n/`Speaker B"); + assertEquals(expectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals("Check command values are incomplete or missing!", e.getMessage()); + } + } + + @Test + void prepareCheck_missingBackBackTick_exceptionThrown() { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("n/Speaker B") + ); + try { + ArrayList actualResult = parser.prepareCheck( + "n/Speaker B`"); + assertEquals(expectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals("Check command values are incomplete or missing!", e.getMessage()); + } + } + + @Test + void prepareDelete_deleteStringWithSpaces_success() throws IncompleteCommandException { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("s/SM58 - 1") + ); + ArrayList actualResult = parser.prepareDelete( + "s/`SM58 - 1`"); + assertEquals(expectedResult, actualResult); + } + + @Test + void prepareDelete_wrongArgumentTag_exceptionThrown() { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("n/Speaker B") + ); + try { + ArrayList actualResult = parser.prepareDelete( + "n/Speaker B"); + assertEquals(expectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals("Delete command values are incomplete or missing!", e.getMessage()); + } + } + + @Test + void prepareDelete_missingFrontBackTick_exceptionThrown() { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("s/Speaker B") + ); + try { + ArrayList actualResult = parser.prepareDelete( + "s/`Speaker B"); + assertEquals(expectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals("Delete command values are incomplete or missing!", e.getMessage()); + } + } + + @Test + void prepareDelete_missingBackBackTick_exceptionThrown() { + ArrayList expectedResult = new ArrayList<>( + Arrays.asList("s/Speaker B") + ); + try { + ArrayList actualResult = parser.prepareDelete( + "s/Speaker B`"); + assertEquals(expectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals("Delete command values are incomplete or missing!", e.getMessage()); + } + } + + @Test + void extractArguments_noSpaceBeforeTypeSlashDelimiterFound_exceptionThrown() { + ArrayList expectedResult = new ArrayList<>(Arrays.asList( + "n/Speaker B", "t/Speaker", "c/1000", "pf/Loud Technologies", "pd/2022-02-23")); + try { + ArrayList actualResult = parser.extractArguments( + "n/`Speaker B`t/`Speaker`c/`1000`pf/`Loud Technologies`pd/`2022-02-23`"); + assertEquals(expectedResult, actualResult); + fail(); + } catch (IncompleteCommandException e) { + assertEquals(IncompleteCommandException.NO_PARAMETERS_FOUND, e.getMessage()); + } + } + + @Test + void extractArguments_idealArgumentPairs_success() throws IncompleteCommandException { + ArrayList actualResult = parser.extractArguments("S/`S1404115Ax` n/`Speaker B` " + + "c/`1000` Pf/`Loud Technologies` PD/`2022-02-23` t/`Speaker`"); + ArrayList expectedResult = new ArrayList<>(Arrays.asList("s/S1404115Ax", + "n/Speaker B", "c/1000", "pf/Loud Technologies", "pd/2022-02-23", "t/Speaker")); + assertEquals(actualResult, expectedResult); + } + + @Test + void extractArguments_wrongArgTypesIgnored_success() throws IncompleteCommandException { + ArrayList actualResult = parser.extractArguments("s/`S1404115ASF` rand/`SpeakerC` " + + "c/`2510` name/`blahblah` pd/`2022-08-21`"); + ArrayList expectedResult = new ArrayList<>(Arrays.asList("s/S1404115ASF", + "c/2510", "pd/2022-08-21")); + assertEquals(actualResult, expectedResult); + } + + @Test + void extractArguments_noCorrectArgTypes_exceptionThrown() { + Throwable exception = assertThrows(IncompleteCommandException.class, () -> parser.extractArguments( + "x/`Speaker B` a/`Speaker` b/`1000` d/`Loud Technologies` e/`2022-02-23`")); + assertEquals(IncompleteCommandException.NO_PARAMETERS_FOUND, exception.getMessage()); + } + + @Test + void parseCommand_deleteCommand_success() { + Command testCommand = parser.parseCommand("delete s/`S1234567E`"); + Command expectedCommand = new DeleteCommand(new ArrayList<>(Collections.singleton("s/S1234567E"))); + assertEquals(expectedCommand, testCommand); + } + + @Test + void parseCommand_trailingWhiteSpace_success() { + Command testCommand = parser.parseCommand("delete s/`S1234567E` "); + Command expectedCommand = new DeleteCommand(new ArrayList<>(Collections.singleton("s/S1234567E"))); + assertEquals(expectedCommand, testCommand); + } + + @Test + void parseCommand_deleteCommandWrongArgType_exceptionCaught() { + Command expectedCommand = new IncorrectCommand(DeleteCommand.COMMAND_WORD + + DeleteCommand.COMMAND_DESCRIPTION); + Command testCommand = parser.parseCommand("delete x/`S1234567E`"); + assertEquals(expectedCommand, testCommand); + } + + @Test + void parseCommand_updateCommandIncorrectDateFormat_exceptionCaught() { + Command expectedCommand = new IncorrectCommand(UpdateCommand.COMMAND_WORD + + UpdateCommand.COMMAND_DESCRIPTION); + Command testCommand = parser.parseCommand("update s/`S1234567E` pd `2022-13-23`"); + assertEquals(expectedCommand, testCommand); + } + + @Test + void parseCommand_dollarSignAccepted_exceptionThrown() throws MissingAttributeException { + Command testCommand = parser.parseCommand("add n/`SpeakerB` s/`S1404115ASF` t/`Speaker` c/`$200.20` " + + "pf/`Loud_Technologies` pd/`2022-02-23`"); + Command expectedCommand = new IncorrectCommand(Command.INCORRECT_COST_FORMAT); + assertEquals(expectedCommand, testCommand); + } + +} \ No newline at end of file