diff --git a/.gitignore b/.gitignore index 2873e189e1..e98eb440b9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +logs/exitCommandLogs.log +logs/findCommandLogs.log +logs/helpCommandLogs.log +logs/listCommandLogs.log +logs/* +*.log diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..b607faeb14 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.syslib.Syslib + diff --git a/README.md b/README.md index f82e2494b7..d728605460 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Duke project template + -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. +SysLib is a CLI Library Management software for system librarians especially those who are fast typists. Given below are instructions on how to use it. ## Setting up in Intellij @@ -8,22 +8,41 @@ Prerequisites: JDK 11 (use the exact version), update Intellij to the most recen 1. **Ensure Intellij JDK 11 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 11 in a previous Intellij project. 1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -1. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: - ``` - > Task :compileJava - > Task :processResources NO-SOURCE - > Task :classes - - > Task :Duke.main() - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - - What is your name? - ``` +1. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/syslib/Syslib.java` file, right-click it, and choose `Run Syslib.main()`. If the setup is correct, you should see something like the below: + +``` +____________________________________________________________ +Data directory does not exist. Creating now... +Storage file does not exist. Creating now... +Loaded 0 resources and 0 events! +____________________________________________________________ + ..................... + -##@*+*@*++++++++++#@++## + .@. @-=%= *#-+% + :@ @+- :----------. .=#% + :@ @. *%----------@- =% + :@ @. #* @= =% + :@ @. #* *: :+ + :@ @. *%-----. .=+****+-. + :@ @. :-----.-#*-. .:-*#- + :@ @. .%+. .@*#+.*%. + :@ @: %= %* +@.=% + :@ @*#*. -@ *###***+. @- + :@ .@:.=@... -@ .+*#*#### @- + :@#*++++++++. %=.%+ +# +% + :@. =++++++++-.%*.+%*@. *%. + %+ ........ =#*-:: .-*%= + =*************. .=+****+-. + ____ _ _ _ ____ _ ___ +/ ___| _ _ ___| | (_) |__ / ___| | |_ _| +\___ \| | | / __| | | | '_ \ | | | | | | + ___) | |_| \__ \ |___| | |_) | | |___| |___ | | +|____/ \__, |___/_____|_|_.__/ \____|_____|___| + |___/ + +Hello! What would you like to do? +____________________________________________________________ +``` Type some word and press enter to let the execution proceed to the end. ## Build automation using Gradle diff --git a/build.gradle b/build.gradle index ea82051fab..957c529559 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("seedu.syslib.Syslib") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("syslib") archiveClassifier.set("") } @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/data/storage.txt b/data/storage.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..3d3ec817d1 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,8 @@ # 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 | +|-------------------------------------------------------------------------|:-------------:|:-----------------------------------------:|:---------------------------------:| +| ![yingxia_portfolio.jpg](team/pictures/yingxia_portfolio.jpg) | Loke Ying Xia | [Github](https://github.com/yingx9) | [Portfolio](team/yingx9.md) | +| ![benjaminng_portfolio.jpeg](team/pictures/benjaminng_portfolio.jpeg) | Benjamin Ng | [Github](https://github.com/bnjm2000) | [Portfolio](team/bnjm2000.md) | +| ![joanneang_portfolio.jpg](team/pictures/joanneang_portfolio.jpg) | Joanne Ang | [Github](https://github.com/JoanneJo) | [Portfolio](team/joannejo.md) | +| ![ashokbalaji_portfolio.jpg](./team/pictures/ashokbalaji_portfolio.jpg) | Ashok Balaji | [Github](https://github.com/000verflow) | [Portfolio](team/000verflow.md) | +| ![wuxingyu_protfolio.jpeg](./team/pictures/wuxingyu_portfolio.jpeg) | Wu Xingyu | [Github](https://github.com/DavinciDelta) | [Portfolio](team/davincidelta.md) | \ No newline at end of file diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..e14cc02cd5 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,1387 @@ # Developer Guide +## Table of Contents +* [About This guide](#about-this-guide) +* [Acknowledgements](#acknowledgements) +* [Setting Up](#setting-up--getting-started) +* [Design and Implementation](#design--implementation) + * [Architecture Overview](#architecture-overview--return-to-contents) + * [Component Overview](#component-overview--return-to-contents) + * [UI Component](#ui-component) + * [Parser Component](#parser-component) + * [Command Component](#command-component) + * [Storage Component](#storage-component) + * [Implementation](#implementation--return-to-contents) + * [Add Resources](#add-resource-feature--return-to-contents) + * [Find Resources](#find-resource-feature--return-to-contents) + * [Show Resources](#show-resources-feature--return-to-contents) + * [List Resources](#listing-resources-feature--return-to-contents) + * [Edit Resources](#edit-command-feature--return-to-contents) + * [Add Events](#event-add-feature--return-to-contents) + * [List Events](#event-list-feature--return-to-contents) + * [Delete Events](#event-delete-feature--return-to-contents) +* [Product Scope](#product-scope--return-to-contents) +* [User Stories](#user-stories--return-to-contents) +* [Use Cases](#use-cases--return-to-contents) +* [Non-Functional Requirements](#non-functional-requirements--return-to-contents) +* [Glossary](#glossary--return-to-contents) +* [Manual Testing](#instructions-for-manual-testing--return-to-contents) + +
+ +## About this guide +This developer guide serves as a documentation of the development of Syslib, an application that was created to help librarians to manage their work. + +This technical document is meant for current and future developers of Syslib as a reference point on the design, implementation, and other technical and non-technical aspects of the application. + ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +1. Reused [Style.puml](https://github.com/se-edu/addressbook-level3/blob/master/docs/diagrams/style.puml) and [CommandResult](https://github.com/se-edu/addressbook-level3/blob/master/src/main/java/seedu/address/logic/commands/CommandResult.java) class from [AddressBook](https://github.com/se-edu/addressbook-level3) with slight modifications. +2. Adapted `Command` and `Parser` structure from teammate Wu Xingyu's [iP codebase](https://github.com/DavinciDelta/ip). + +## Setting Up & Getting Started +1. Fork the repo at [GitHub](https://github.com/AY2324S1-CS2113T-W11-1/tp). +2. [Clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) the fork onto your computer. +3. Using [Intellij IDEA](https://www.jetbrains.com/idea/download/) (Recommended) or an IDE of your choice: + - Ensure your IDE is configured to use **JDK 11** as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk). + - Import the project as a **Gradle project** as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). + - **Verify** the setup: + - Run `Syslib`. On IntelliJ, you can right-click Syslib class and `Run Syslib.main()` or `Shift` + `F10`. + - You should see the following greeting logo and message: + +
+ +``` +____________________________________________________________ +Data directory does not exist. Creating now... +Storage file does not exist. Creating now... +Loaded 0 resources and 0 events! +____________________________________________________________ + ..................... + -##@*+*@*++++++++++#@++## + .@. @-=%= *#-+% + :@ @+- :----------. .=#% + :@ @. *%----------@- =% + :@ @. #* @= =% + :@ @. #* *: :+ + :@ @. *%-----. .=+****+-. + :@ @. :-----.-#*-. .:-*#- + :@ @. .%+. .@*#+.*%. + :@ @: %= %* +@.=% + :@ @*#*. -@ *###***+. @- + :@ .@:.=@... -@ .+*#*#### @- + :@#*++++++++. %=.%+ +# +% + :@. =++++++++-.%*.+%*@. *%. + %+ ........ =#*-:: .-*%= + =*************. .=+****+-. + ____ _ _ _ ____ _ ___ +/ ___| _ _ ___| | (_) |__ / ___| | |_ _| +\___ \| | | / __| | | | '_ \ | | | | | | + ___) | |_| \__ \ |___| | |_) | | |___| |___ | | +|____/ \__, |___/_____|_|_.__/ \____|_____|___| + |___/ + +Hello! What would you like to do? +____________________________________________________________ +> +``` + +You are now ready to begin developing! If you would like to exit Syslib, type `exit` to exit the program. + +
+ +## Design & Implementation +## Architecture Overview | [Return to contents](#table-of-contents) + + + +The above diagram is a simplified overview of Syslib and its components. More details can be found in the following sections. + +**Main components of SysLib Architecture** + +SysLib currently consists of five main components: + +- `Syslib`: Initializes other components and acts as the "launching pad" by executing important methods +- `UI`: Handles User Interaction such as getting user input +- `Parser`: Parsing User Input +- `Command`: Command Executor +- `Storage`: Loads data from file in hard disk, and saves data to hard disk on program exit + +
+ +**How the architecture components interact with each other** + +The following diagram shows an overview of how components in Syslib work and interact with each other. + + + +1. After all components have been initialized, `Syslib` calls upon the `Storage` component to load any data from `storage.txt`. +2. `Storage` fills two lists, ResourcesList and EventsList, with the read data. Both lists are returned to Syslib. +3. Syslib creates a GenericList containing of these filled lists. +4. Afterwhich, `UI` is called to get and return user input. +5. Syslib passes the returned user input to `Parser` for processing. +6. `Parser` calls the appropriate command based on the user input, for example `AddCommand`. +7. `Command` carries out necessary actions and returns a `CommandResult` object which holds the message to be displayed to user. `Parser` prints the feedback to user. +8. Once the user input has been processed and executed, Syslib calls `Storage` to save the current state of the lists to `storage.txt`. +9. `Storage` retrieves the lists in `Parser` and writes to `storage.txt`. + +
+ +## Component Overview | [Return to contents](#table-of-contents) + +### UI Component + +`UI` component consists of a single `UI` Class. +which manages interaction (prompting for user input and displaying results +of commands/methods being called) between the user and the application. + +How the UI class works: +* Upon the initialization of `SysLib`, the `UI` class will call the `showWelcomeMessage` method to display the greeting messages to + the user. +* Additionally, local files storing the data of previously saved work of `SysLib` will also be loaded into the program through the initialization of the `Storage` Class. Thereafter, + the loading statuses of these files are also displayed by calling the `showLoadMessage()` method. + * If `storage.txt` does not exist, it will also call the `showNoFileFoundMessage()` to let the user know a new `storage.txt` file will be created. + * However, if `storage.txt` already exists, `showFileFoundMessage()` will be called instead. +* `UI` class is also responsible for getting the user input by calling the `readCommand()` + method, which will then parse the input by sending it to the `Parser` class. The `Parser` class will process + the input and make use of the `Command` class to execute the command. +* UI also prints Resource details in a formatted table using the `showResourcesDetails` method. +* Help messages are also printed in the `UI` class by calling `showHelpMessage()` method. +* Lastly, when the user exits the program, the `showExitMessage()` method will be called to indicate that the + user has successfully exited the program. + +
+ +### Parser Component + +The parsing for a generic command can be seen here: + + + +For some commands that does not require arguments (etc: `help`, `exit`), `parseArgument()` +and `validateStatement()` will not be called + +If an invalid command is given, the nearest command will be searched for via the `suggest` function in `suggestParser`. +If the nearest command is similar to what the user typed, it will be suggested + +For more details on each Command, check them out [below](#implementation--return-to-contents) + +
+ +### Command Component +The `Command` component is linked to the `Parser` component. +After the `Parser` filters out the correct command, that particular command will be executed. + +Each `Command` uses a `GenericList` to get Events or Resources to manipulate. +Then the commands will generate a `CommandResult` to give informative feedback to the user. + + + +
+ +### Storage Component + +This component of Syslib is mainly responsible for reading and writing application data from and to files stored on the user’s filesystem. This is to allow the user to retain the data he/she has entered into Syslib and be able to continue using the data when he/she starts Syslib the next time. + +The following class diagram shows how the storage component’s classes and how it interacts with some other components and classes in Syslib: + + + +On the user’s local filesystem, the organisation of the application files are as follows: + +``` + data/ // Primary folder for storage + └── storage.txt // Text file containing a list of resources and events saved. + logs/ // Folder for logs of each command + ├── findCommandLogs.log // Log file containing logs created by the Find Command + ├── addCommandLogs.log // Log file containing logs created by the Add Command + ├── eventCommandLogs.log // Log file containing logs created by the Event Commands + ├── helpCommandLogs.log // Log file containing logs created by the Help Command + ├── listCommandLogs.log // Log file containing logs created by the List Command + ├── Storage.log // Log file containing logs created by the Storage uses + └── summaryCommandLogs.log // Log file containing logs created by the Summary Command +``` + +
+ +## Implementation | [Return to contents](#table-of-contents) +This section provides details on how certain features are implemented. + +### Add Resource Feature | [Return to contents](#table-of-contents) + +The `add` feature is responsible for processing user commands to add a new resource to SysLib. It is facilitated by +the `AddCommand` component. It works with `Parser` and `Command` components to parse and validate the user input. +The new resource is stored internally in `resourcesList` as a `Book`, `EBook`, `CD`, `Magazine`, `EMagazine`, `Newspaper`, +or `ENewspaper` depending on the type of resources specified by `tag`. + +There are seven types of `Resource`: +- Book +- EBook +- CD +- Magazine +- EMagazine +- Newspaper +- ENewspaper + +`add` has eighteen possible options: + +- **Book** has four options: + - `add /i [isbn] /t [title] /a [author] /tag [tag] ` + - `add /i [isbn] /t [title] /a [author] /tag [tag] /g [genre]` + - `add /i [isbn] /t [title] /a [author] /tag [tag] /s [status]` + - `add /i [isbn] /t [title] /a [author] /tag [tag] /g [genre] /s [status]` +- **EBook** has four options: + - `add /i [isbn] /t [title] /a [author] /tag [tag] /l [link]` + - `add /i [isbn] /t [title] /a [author] /tag [tag] /l [link] /g [genre]` + - `add /i [isbn] /t [title] /a [author] /tag [tag] /l [link] /s [status]` + - `add /i [isbn] /t [title] /a [author] /tag [tag] /l [link] /g [genre] /s [status]` +- **CD** has two options: + - `add /i [isbn] /t [title] /c [creator] /ty [type] /tag [tag]` + - `add /i [isbn] /t [title] /c [creator] /ty [type] /tag [tag] /s [status]` +- **Magazine** has two options: + - `add /i [isbn] /t [title] /b [brand] /is [issue] /tag [tag]` + - `add /i [isbn] /t [title] /b [brand] /is [issue] /tag [tag] /s [status]` +- **EMagazine** has two options: + - `add /i [isbn] /t [title] /b [brand] /is [issue] /tag [tag] /l [link]` + - `add /i [isbn] /t [title] /b [brand] /is [issue] /tag [tag] /l [link] /s [status]` +- **Newspaper** has two options: + - `add /i [isbn] /t [title] /p [publisher] /ed [edition] /tag [tag]` + - `add /i [isbn] /t [title] /p [publisher] /ed [edition] /tag [tag] /s [status]` +- **ENewspaper** has two options: + - `add /i [isbn] /t [title] /p [publisher] /ed [edition] /tag [tag] /l [link]` + - `add /i [isbn] /t [title] /p [publisher] /ed [edition] /tag [tag] /l [link] /s [status]` + +#### Implementation + +It implements the following operations: + +- `AddCommand#execute(statement: String, container: GenericList)` -- Validates and adds the new resource into the resource list. +- `Parser#parseAddCommand(statement: String)` -- Parses the input command to extract the tag indicating the type of resource. +- `Parser#checkFirstItem(statement: String)` -- Parses the input command to ensure that the first item in the input command is valid. +- `AddCommand#addResource*(statement: String, container: GenericList)` -- Adds a new resource to the resource list after validation. +- `ParseResource*#parseAddResource*(statement: String)` -- Parses the input command to ensure that it follows the right format for the particular resource. +- `ParseResource*#parseResource*Args(statement: String)` -- Parses the input command to ensure that all attributes required by the particular resource are parsed. +- `ParseResource#hasUnusedSlash(statement: String)` -- Parses the input command to ensure that there are no extra slashes. +- `ParseResource*#hasInvalidArgument(statement: String)` -- Parses the input command to ensure that there are no invalid arguments. +- `ParseAttribute#parseAttribute*(statement: String)`-- Parses the input command to ensure that each attribute follows the right format. +- `ParseResource#countDuplicate(statement: String, pattern: String)` -- Counts the number of matches found for the same argument. +- `ParseResource*#checkEmptyResource*Args(args: String[])` -- Parses the attribute to ensure that all attributes are not empty. +- `ParseResource*#checkEmptyArg(args: String[])` -- Parses the arguments to ensure that every argument is valid. +- `CreateResource#createResource*(values: String[], resourceID: int)` -- Creates a new resource with the given information. +- `ParseResource*#resetResource*Args()` - Clears the previous attributes to receive new attributes for new resource. + +#### Sequence Diagram +The following sequence diagram shows the interactions between objects for the add feature: + + + +It shows the interaction between seven main classes +- Parser + - Parser + - ParseResource* (ParseBook / ParseEBook / ParseCD / ParseMagazine / ParseEMagazine / ParseNewspaper / ParseENewspaper) + - ParseResource + - ParseAttribute +- Command + - CommandResult + - AddCommand +- CreateResource + +#### Example Usage Scenario + +Step 1. The user inputs the command: `add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag B /g Fiction /s lost`. + +Step 2. The `Parser` processes the command and calls `AddCommand#execute(statement: String, container: GenericList)` +with `/i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag B /g Fiction /s lost`. + +Step 3. `AddCommand` receives the command and calls `Parser#parseAddCommand(statement: String)` to extract the tag. + +Step 4. The first thing `Parser` does is to call `Parser#checkFirstItem(statement: String)` to ensure that there are no +unaccepted components in the input. + +Step 5. Since the `tag` argument in the input command indicates that it is a book, the `AddCommand` calls +`AddCommand#addBook(statement: String, container: GenericList)`. + +Step 6. Before adding the book, validation needs to be done so `ParseBook#parseAddBook(statement: String)` was called +to ensure that the overall input is in the correct format and the required data is provided. + +Step 7. After `ParseBook` receives the command, it calls `ParseBook#parseBookArgs(statement: String)` to start +the process of parsing every attribute of the resource. + +Step 8. `ParseBook` performs the following operations to validate the overall input: +- Calls `ParseResource#hasUnusedSlash(statement: String)` to ensure that there are no slashes that should not appear. +- Calls `ParseResource*#hasInvalidArgument(statement: String)` to ensure that all arguments given are valid. + +Step 9. `ParseBook` performs the following operations to validate each data in the input: +- Calls `ParseAttribute#parseIsbn(statement: String)` to validate and extract ISBN. +- Calls `ParseAttribute#parseTitle(statement: String)` to validate and extract title. +- Calls `ParseAttribute#parseAuthor(statement: String)` to validate and extract author. +- Calls `ParseAttribute#parseTag(statement: String)` to validate and extract tag. +- Calls `ParseAttribute#parseGenre(statement: String)` to validate and extract genres. +- Calls `ParseAttribute#parseStatus(statement: String)` to validate and extract status. +The above calls will each call `ParseResource#countDuplicate(statement: String, pattern: String)` to ensure that the +same argument does not appear more than once. These extracted attributes form `args: String[]`. + +Step 10. After `ParseBook` checked if optional attributes were provided by the user, it checks whether there were empty +attributes by calling `ParseResource*#checkEmptyResource*Args(args: String[])`. `ParseResource*#checkEmptyArg(args: String[])` +will then be called to check if every argument is valid. After that, all the data are forwarded to `AddCommand`. + +Step 11. `AddCommand` receives the arguments and calls `CreateResource#createBook(values: String[], resourceID: int` to +craft a new book with the validated data. + +Step 12. The newly created book is then added to the `resourcesList`. + +Step 13. Calls to `ParserResource#resetBookArgs()` prepares the arguments list for new processes. + +Step 14. Feedback to users are then sent to `CommandResult` to be displayed to the users. + +#### Activity Diagram +The following activity diagram shows an overview of how the activities flow when a user executes the add command: + + + +Step 1. First, it goes into `execute()` where all the functions for adding a resource occur. +Step 2. An information is added to the log to record the start of execution. +Step 3. Next, `parseAddCommand()` will identify the type of resource to add. +Step 4. If it is a book, it will call `addBook()` where validation and addition of book occurs. +If it is none of the listed resources, the add function will stop. +Step 5. `parseAddBook()` and `parseBookArgs()` will validate every part of the input. +Step 6. If it manages to pass all the validation, it will proceed to the next step. Else, the add function will stop. +Step 7. A resource is created by passing the parsed data to `createBook()`. +Step 8. Finally, this newly created book will be added to the `resourceList`. +Step 9. A message will be displayed to indicate the successful addition of resource. +Step 10. Lastly, an information is added to the log to record the resource that has been added. + +#### Note +- The word "Resource*" can be replaced by any of the resources + - For example, ParseResource* and CreateResource* can be interpreted as ParseBook and CreateBook for the process of adding a book +- The word "Attribute*" can be replaced by any of the attributes + - For example, parseAttribute* can be interpreted as parseTag or parseIsbn or parseTitle, etc + +
+ +### Show Resources Feature | [Return to contents](#table-of-contents) + +Show resources feature is facilitated by the `UI` component. It makes use of a class `ResourceDisplayFormatter` in `UI` to show the details of all resources stored in `ResourcesList` of the `Parser` component, sorted by resource type. + +#### Implementation + +Show Resources feature implements the following operations: +- `UI#showResourcesDetails(resourcesList: List)` — Displays a table showing details of all resources sorted by resource type. +- `Resource#checkColumnsWidth(columnsWidth: List)` — Checks the length of certain resource attributes against the width of columns and adjusts width if displaying the resource attribute would break the alignment +- `Resource#toTableFormat(formatString: String, tableFormatter: Formatter)` — Formats a string of aligned resource details + +Show Resources feature can be used when the user wants a list of resources and their details, such as when executing the `list` command or showing the results of `find` command. + +The following sequence diagram shows how the show resources feature works in a scenario where the ListCommand calls showResourcesDetails() method. + + + +
+ +**ResourceDisplayFormatter class:** + +1. A List< Resource > resourcesList is passed in to `showResourcesDetails` method from the calling function. It can be the resourcesList retrieved from the GenericList in memory which contains the data of all resources, or a custom resourcesList containing filtered resources. +2. A new `ResourceDisplayFormatter` is instantiated and the constructor calls `buildDisplayHeader()` to create a table header. +3. `buildDisplayHeader()` then calls `checkColumnsWidth()` for every resource in `resourcesList`. It passes an integer array containing the current column width for each resource attribute (Title...Author..et cetera). +4. `checkColumnsWidth()` adjusts the width as needed depending on the length of the resource attributes and returns to Formatter +5. A format specifier is created with the columns' width. Example: `"%-5s %-20s ..."` +6. Formatter objects are created for each resource type: Book, Newspapers, Magazine, CDs. + +**UI Class:** +7. Now the UI loops through the resourcesList and calls `setDisplayFormatter(Resource)` to add the formatted string to its respective display formatter. +8. A final call to `getFinalDisplayFormat()` returns the final formatted message of the table and all the resource details as `messageToDisplay` +9. `messageToDisplay` is returned to the calling function to be printed to user or for testing. + +
+ +### Find Resource Feature | [Return to contents](#table-of-contents) + +The `find` command allows users to search for resources based on specified filters such as author (`/a`), ISBN (`/i`), ID (`/id`), and title (`/t`). The results will show all resources that match any of the given filters. + +> For non-book resources, `author` refers to `publisher`,`creator` and `brand` for Newspapers, CD's and Magazines +> respectively. + +`find` has the following options: +- `find /id [ID]` +- `find /t [TITLE]` +- `find /a [AUTHOR/PUBLISHER/BRAND/CREATOR]` +- `find /i [ISBN]` + +Multiple filters can also be combined: + +- `find /t [TITLE] /a [AUTHOR/PUBLISHER/BRAND/CREATOR]` + +#### Implementation + +Upon receiving the `find` command, the system will: + +1. Parse the filters and their associated values. +2. Filter the resources based on the given filters. +3. Display the matching resources. + +#### Example Usage Scenario + +**Step 1.** The user inputs the command: `find /a "F. Scott Fitzgerald"` + +**Step 2.** The `UI` component forwards the input to `SYSLIB`, which then sends it to the `PARSER`. + +**Step 3.** The `PARSER` processes the command, extracts the `author` filter, and retrieves all resources written by "F. Scott Fitzgerald". + +**Step 4.** The matching resources are displayed to the user. + +
+ +#### Sequence Diagram + + + +
+ +### Examples for Testing + +1. **Find by Author** + - Test case: `find /a "F. Scott Fitzgerald"` + + Expected: All resources written by F. Scott Fitzgerald are displayed. + +2. **Find by ISBN** + - Test case: `find /i "9780061120084"` + + Expected: The resource with ISBN "9780061120084" is displayed, which should be "To Kill a Mockingbird" by Harper Lee. + +3. **Find by ID** + - Test case: `find /id 2` + + Expected: The resource with ID "2" is displayed, which should be "To Kill a Mockingbird" by Harper Lee. + +4. **Find by Title** + - Test case: `find /t "The Great Gatsby"` + + Expected: The resource titled "The Great Gatsby" is displayed. + +5. **Combining Filters** + - Test case: `find /a "F. Scott Fitzgerald" /t "The Great Gatsby"` + + Expected: Resources that match both the title "The Great Gatsby" and the author "F. Scott Fitzgerald" are displayed. + +
+ +### Listing Resources Feature | [Return to contents](#table-of-contents) + +The `list` command is facilitated by `Parser` and `UI` component to show the details of all resources in `resourcesList`. Furthermore, **filter** options can be provided to only list specific resources that match the given filters. + +`list` has five possible options: +- `list` +- `list /tag [tag]` +- `list /g [genre]` +- `list /s [status]` +- `list /tag [tag] /g [genre] /s [status]` + +Arguments in italics are filter options and are **optional**. + + +#### Implementation + +ListCommand implements the following operations: +- `ListCommmand#execute(statement: String, container: GenericList)` — Executes and carries out list feature operations +- `ListCommand#filterResources(givenParameters: String[], resourcesList: List>)` — Filters resources based on given filter values + + +Given below is an example usage scenario where a user enters the input `list /tag B` to list all Resources with tag `B` +and the corresponding sequence diagram. + + + +When a user enters `list /tag B`, the Parser retrieves the parameters from the input and +calls the `execute` function of ListCommand, passing the argument given: `/tag B`. + +ListCommand then calls `parseArg` and `validate` from `Command`, which checks if the parameters are valid. If it passes + the checks, `filterResources` is called to begin the filtering process. First it calls `hasFilters()` to check if the user + selected any filters `[tag/genre/status]` or none. + +If hasFilters returns `true`, it filters the `resourcesList` with the given keywords. Resources matching the filters are added to a temporary list called `matchedResources`. After all resources has been checked, `matchedResources` list is passed to `showResourcesDetails()`, a method called from `UI` to display the details +of the resources. + +If hasFilters returns `false`, it skips the filtering process and displays the details of all the resources. + +Finally, `ListCommand` instantiates the `CommandResult` class with a string `feedbackToUser`, which is returned to the `Parser` which will `print(commandResult.feedbackToUser)` to show the resource details. + +
+ +### Edit Command Feature | [Return to contents](#table-of-contents) + +The `edit` command is facilitated by `Parser` component to update the attributes of any resource type. Users can edit all attributes except ID, Tag, and Received Date, and must provide at least one argument to edit when calling the `edit` command. + + +#### Implementation + +EditCommand implements the following operations: +- `EditCommand#execute(statement: String, container: GenericList)` — Executes and handles editing of a resource +- `EditCommand#editResources(foundResource: Resource, givenParameters: String[], resourcesList: List)` — Validates parameters and updates a resource details + +#### Usage Scenario + +EditCommand may be used when the user would like to update a resource [Book/Newspaper/CDs/Magazines/...] et cetera. Below is an activity diagram showing the flow of SysLib when a user enters `"edit /id 1 /t NEWTITLE /a NEWAUTHOR"`. The diagram starts off with EditCommand's `execute()` method. + + + +First, `EditCommand` checks if the user has given at least one argument to edit. For example, `/t` or `/a`. If true, it searches for the resource that has the ID matching the ID given by the user. In this case, it searches for `id == 1`. + +Once found, it moves on to the `editResouce()` method, which contains a switch case that further calls the appropriate edit function based on the resource type. For example, if the resource is a Book or EBook, `validateBookParameters()` and `editBook()` is called. Else, it checks if it's a `CD` and so on. + +The edit methods updates the resource with all the details the user has provided. In this case, `title` is updated to `NEWTITLE` and `author` is updated to `NEWAUTHOR`. + +Finally, the resource list currently in memory is updated with the new resource details by calling `resourcesList.set(resourceIndex, updatedResource)`. + +
+ +### Event Add Feature | [Return to contents](#table-of-contents) + +The `eventadd` feature is responsible for processing user commands to add an `Event` to SysLib. It is facilitated by +the `EventAddCommand` component. It works with `Parser` and `Command` components to parse and validate the user input. +The new book is stored internally in `eventList` as a `Event`. + +`eventadd` has two options: +- `eventadd /t [title] /date [date]` +- `eventadd /t [title] /date [date] /desc [description]` + +#### Implementation + +It implements the following operations: + +- `EVENTADDCOMMAND#parseArgument(statement: String)` -- Parses the input command to extract relevant information. +- `EVENTADDCOMMAND#validate(statement: String, values: String[])` -- Validates the input statement to ensure that it is valid. +- `EVENTADDCOMMAND#binarySearch(container: GenericList, date: Date)` -- Search for the correct index of event list to add the event. + +#### Example Usage Scenario + +Step 1. The user inputs the command: `eventadd /t birthday /date 10 Dec 2001` + +Step 2. The `UI` component forwards the input to `SYSLIB`, which in turn passes it to the `PARSER`. + +Step 3. The `PARSER` processes the command and determines that it contains a valid key (`eventadd`). It then calls +`EVENTADDCOMMAND#execute(statement: String, this: Parser)` with the input command. + +Step 4. The `EVENTADDCOMMMAND` component receives the command and performs the following operations: +- Calls `EVENTADDCOMMAND#parseArgument(statement: String)` to extract values for title, date and description. +- Calls `EVENTADDCOMMAND#validate(statement: String, values: String[])` to ensure the validity of the input command. + +Step 5. The `COMMAND` component processes the input command to ensure that it meets the required format and constraints. +It prepares the argument values for further processing. + +Step 6. The `EVENTADDCOMMMAND` also calls the component: +- Calls `EVENTADDCOMMAND#binarySearch(container: GenericList, date: Date)` to find the correct index based on the date. +The whole eventList is sorted by date order. + +Step 7. The newly created event is forwarded to the `PARSER` to be added to the `eventList`. + +Sequence Diagram: + + +
+ +### Event List Feature | [Return to contents](#table-of-contents) + +The `eventlist` command works with the `Parser` and `Command` component to execute the correct action. +This feature is responsible for listing out the events in eventList. +It is facilitated by the `EventListCommand` component. + +`eventlist` has one option. + +#### Implementation + +It implements the following operations: + +- `EVENTLISTCOMMAND#isEmpty()` -- Check user input has only 'eventlist' + +#### Example Usage Scenario + +Step 1. The user inputs the command: `eventlist` + +Step 2. The `UI` component forwards the input to `SYSLIB`, which in turn passes it to the `PARSER`. + +Step 3. The `PARSER` processes the command and determines that it contains a valid key (`eventlist`). It then calls +`EVENTLISTCOMMAND#execute(statement: String, this: Parser)` with the input command. + +Step 4. The `EVENTLISTCOMMMAND` component receives the command and performs the following operations: +- Calls `EVENTLISTCOMMAND#isEmpty()` to check if the user input any additional redundant arguments. + +Step 5. The `EVENTLISTCOMMAND` then outputs the events in the eventList. + +
+ +
+ +### Event Delete Feature | [Return to contents](#table-of-contents) + +The `eventdelete` feature is responsible for processing user commands to delete an event to SysLib. +It is facilitated by the `EventDeleteCommand` component. +It works with `Parser` and `Command` components to parse and validate the input. +The `Event` is removed from `eventsList`. + +`eventdelete` has one options: +- eventdelete /id [index] + +#### Implementation + +It implements the following operations: + +- `EVENTDELETECOMMAND#parseArgument(statement: String)` -- Parses the input command to extract relevant information. +- `EVENTDELETECOMMAND#validate(statement: String, values: String[])` -- Validates the input statement. +- `EVENTDELETECOMMAND#parseCalendarInt(value: String, container: GenerticList)` -- Validate the index given. + +#### Example Usage Scenario + +Step 1. The user inputs the command: `eventdelete /id 0` + +Step 2. The `UI` component forwards the input to `SYSLIB`, which in turn passes it to the `PARSER`. + +Step 3. The `PARSER` processes the command and determines that it contains a valid key (`eventadd`). It then calls +`EVENTDELETECOMMAND#execute(statement: String, this: Parser)` with the input command. + +Step 4. The `EVENTDELETECOMMAND` component receives the command and performs the following operations: +- Calls `EVENTDELETECOMMAND#parseArgument(statement: String)` to extract values for title, date and description. +- Calls `EVENTDELETECOMMAND#validate(statement: String, values: String[])` to ensure the validity of the input command. + +Step 5. The `COMMAND` component processes the input command to ensure that it meets the required format and constraints. +It prepares the argument values for further processing. + +Step 6. The `EVENTDELETECOMMAND` also calls the component: +- Calls `EVENTDELETECOMMAND#parseCalendarInt(value: String, container: GenerticList)` to see if the index is an integer and that +it is within range of eventsList + +Step 7. The selected event at the index is then deleted from the eventsList. + +
+ +
+ +### Event Edit Feature +The `eventedit` command with the `Parser` and `Command` component to execute the correct action. +This feature is responsible for editing the events in eventsList as given by the user. +It is facilitated by the `EventsListCommand` component. + +`eventedit` has three option: +- `eventedit /t [title] /date [date]` +- `eventedit /t [title] /desc [description]` +- `eventedit /t [title] /date [date] /desc [description]` + +#### Implementation + +It implements the following operations: + +- `EVENTEDITCOMMAND#parseArgument(statement: String)` -- Parses the input command to extract relevant information. +- `EVENTEDITCOMMAND#validate(statement: String, values: String[])` -- Validates the input statement to ensure that it is valid. +- `EVENTEDITCOMMAND#binarySearch(container: GenericListt, date: Date)` -- Search for the correct index of event list to add the event. + +#### Example Usage Scenario + +Step 1. The user inputs the command: `eventedit /i 0 /date 23 Dec 2023` + +Step 2. The `UI` component forwards the input to `SYSLIB`, which in turn passes it to the `PARSER`. + +Step 3. The `PARSER` processes the command and determines that it contains a valid key (`evenedit`). It then calls +`EVENTEDITCOMMAND#execute(statement: String, container: GenerticList)` with the input command. + +Step 4. The `EVENTEDITCOMMMAND` component receives the command and performs the following operations: +- Calls `EVENTEDITCOMMAND#parseArgument(statement: String)` to extract values for the attributes to edit. +- Calls `EVENTEDITCOMMAND#validate(statement: String, values: String[])` to ensure the validity of the input command. + +Step 5. The `COMMAND` component processes the input command to ensure that it meets the required format and constraints. +It prepares the argument values for further processing. + +Step 6. The `EVENTEDITCOMMMAND` also calls the component: +- Calls `EVENTEDITCOMMAND#binarySearch(container: GenericList, date: Date)` to find the correct index based on the date. + The whole eventsList is sorted by date order. + +Step 7. The old event is removed from `eventsList`. + +Step 8. The edited event is forwarded to the `PARSER` to be inserted to the `eventsList` in accordance to the date. + +Sequence Diagram: + + +
+ +### Summary Feature +The `summary` command with the `Parser` and `UI` component to execute the correct action. +This feature is responsible for showing a summary of the Resources with a visually helpful bar as well as the next 3 events. +It is facilitated by the `SummaryCommand` component. + +`summary` has one option. + +#### Implementation + +It implements the following operations: + +- `SUMMARYCOMMAND#isEmpty()` -- Check user input has only 'summary' + +#### Example Usage Scenario + +Step 1. The user inputs the command: `summary` + +Step 2. The `UI` component forwards the input to `SYSLIB`, which in turn passes it to the `PARSER`. + +Step 3. The `PARSER` processes the command and determines that it contains a valid key (`summary`). It then calls +`EVENTLISTCOMMAND#execute(statement: String, container: GenerticList)` with the input command. + +Step 4. The `SUMMARYCOMMAND` component receives the command and performs the following operations: +- Calls `SUMMARYCOMMAND#isEmpty()` to check if the user input any additional redundant arguments. + +Step 5. The `SUMMARYCOMMAND` then gets each Resource from the ResourcesList and checks to see if it is an `instanceof`: +- Book +- EBook +- CD +- Magazine +- EMagazine +- Newspaper +- ENewspaper + +and counts as it does so. + +Step 5. It then gets all Events from the EventsList. + +Step 6. The `SUMMARYCOMMMAND` also calls the component: +- Calls `SUMMARYCOMMAND#getUpcomingEvents(events: List, 3: int)` to get the 3 upcoming events. + +Step 7. The `SUMMARYCOMMMAND` also calls the component: +- Calls `SUMMARYCOMMAND#generateBar(:int)` to get the size of the bar for each Resource. + +Step 8. The SUMMARYCOMMAND then outputs the Resource with a bar to indicate count and the next 3 upcoming events. + +Sequence Diagram: + + +
+ +## Product Scope | [Return to contents](#table-of-contents) + +### Target User Profile + +System librarians who prefer CLI over GUI and are responsible for inventory and event management. + +- Needs to manage events and inventory with significant number of resources e.g. books +- Is a fast typist + +### Value Proposition + +To provide a platform to help system librarians to quickly find the information they need in their everyday job. + +SysLib CLI is a robust command-line tool designed for fast typists librarians to efficiently handle inventory and events. + +With quick command-based actions, they can manage library's resources and events seamlessly. Administrative tasks are simplified, so they can focus on serving patrons better. + +
+ +## User Stories | [Return to contents](#table-of-contents) + +| Version | As a ... | I want to ... | So that I can ... | +|---------|-----------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| +| v1.0 | librarian | view a list of books that the library has | have an overview of all the books | +| v1.0 | librarian | add new books to our inventory by entering their title, author, ISBN, and publication year | keep our collection up-to-date | +| v1.0 | librarian | delete books from the database | let the patrons know our library no longer carries it | +| v1.0 | librarian | tag a book as physical medium like newspapers, audio cds, books or online like eMagazines | patrons can know what type are available | +| v1.0 | librarian | have a help function | know the commands of this programme | +| v1.0 | librarian | quickly find out how many books we have of a particular author and the names of the books | know how many books are related to the author | +| v2.0 | librarian | import/export our library's inventory data to a file format | easily share with other libraries to be used for backup and archival purposes. | +| v2.0 | librarian | find a to-do item by name | locate a to-do without having to go through the entire list | +| v2.0 | librarian | get the status of an item | know if it is available | +| v2.0 | librarian | update the details of a resource | fix any mistakes and maintain consistency | +| v2.1 | librarian | add in different types of resources | differentiate between resources such as books, cds, magazines, newspapers, and electronic versions | + +
+ +## Use Cases | [Return to contents](#table-of-contents) + +(For all use cases below, the System is the SysLib and the Actor is the user, unless specified otherwise) + +### Use case: Add a book + +#### MSS +1. User requests to add a book +2. SysLib adds the book + + Use case ends. + +#### Extensions +- 1a. The given `ISBN` is invalid. + - 1ai. SysLib displays an error message. + + Use case ends. + +- 1b. Insufficient data given. + - 1bi. SysLib displays an error message. + + Use case ends. + +
+ +### Use case: Edit a resource + +#### MSS + +1. User requests to list resources +2. SysLib shows a table of resources and their details +3. User requests to edit a resource by specifying their ID shown in list. +4. SysLib updates resource with new given details. + + Use case ends. + +#### Extensions + +- 1a. List is empty + - SysLib shows a message there are no resources to list. + - Use case ends. + +- 3a. Provided ID does not exist + - SysLib shows a message stating resource not found. + - Use case ends. + +- 3b. User does not provide any arguments to edit + - SysLib shows a message prompting for at least one argument. + - Use case ends. + +- 3c. User provides the wrong arguments for the type of resource they specified + - SysLib shows an invalid argument message and displays the right arguments for the resource type + - Use case ends. + +
+ +### Use case: Delete a resource + +#### MSS + +1. User requests to list resources +2. SysLib shows a table of resources and their details +3. User requests to delete a resource by specifying their ID shown in list. +4. SysLib removes the specified resource from the list. + + Use case ends. + +#### Extensions + +- 1a. List is empty + - SysLib shows a message there are no resources to list. + - Use case ends. + +- 3a. Provided ID does not exist + - SysLib shows a message stating resource not found. + - Use case ends. + +- 3b. User does not provide any arguments to edit + - SysLib shows a message prompting for at least one argument. + - Use case ends. + +- 3c. User provides the wrong arguments for the type of resource they specified + - SysLib shows an invalid argument message and displays the right arguments for the resource type + - Use case ends. + +
+ +### Use case: Find a resource + +#### MSS + +1. User requests to list resources +2. SysLib shows a table of resources and their details +3. User requests to Find a resource by specifying their ID shown in list. +4. SysLib returns the resource with the ID specified by the user. + + Use case ends. + +#### Extensions + +- 1a. List is empty + - SysLib shows a message there are no resources to list. + - Use case ends. + +- 3a. Provided ID does not exist + - SysLib shows a message stating resource not found. + - Use case ends. + +- 3b. User does not provide any arguments to edit + - SysLib shows a message prompting for at least one argument. + - Use case ends. + +- 3c. User provides the wrong arguments for the type of resource they specified + - SysLib shows an invalid argument message and displays the right arguments for the resource type + - Use case ends. + +
+ +### Use case: Add an event + +#### MSS +1. User requests to add a event +2. SysLib adds the event + + Use case ends. + +#### Extensions +- 1a. The given `date` is invalid. + - 1ai. SysLib displays an error message. + - Use case ends. + +- 1b. Insufficient data given. + - 1bi. SysLib displays an error message. + - Use case ends. + +
+ +### Use case: Delete an event + +#### MSS +1. User requests to list the events with `eventlist` +2. SysLib shows the list of events +3. User requests to delete a specific event based on `id` +4. Syslib deletes the event + + Use case ends. + +#### Extensions +- 2a. The list is empty. + - 1ai. SysLib displays the message for empty list. + - Use case ends. + +- 3a. The given index is invalid + - 1bi. SysLib displays an error message. + - Use case resumes at step 2. + +
+ +### Use case: Edit an event + +#### MSS +1. User requests to list the events with `eventlist` +2. SysLib shows the list of events +3. User requests to edit a specific event based on `id` +4. Syslib edits the event + + Use case ends. + +#### Extensions +- 2a. The list is empty. + - 1ai. SysLib displays the message for empty list. + - Use case ends. + +- 3a. The given index is invalid + - 1bi. SysLib displays an error message. + - Use case resumes at step 2. + +- 3b. Insufficient data given. + - 1bi. SysLib displays an error message. + - Use case resumes at step 2. + +
+ +## Non-Functional Requirements | [Return to contents](#table-of-contents) + +- Should work on any mainstream OS as long as it has Java 11 or above installed. +- Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +- The system should respond to user input within 3 seconds under normal operating conditions. +- A user with above-average typing speed for regular English text (i.e., not code, not system admin commands) +should be able to accomplish most of the tasks faster using commands than using the mouse. +- All user data relating to resources and events will be stored in a .txt file in the same folder as this jar file. + +## Glossary | [Return to contents](#table-of-contents) + +* *Resource* - A generic term for items in library inventory. + * Further divided into: Book, EBook, Magazines, EMagazines, Newspapers, ENewspapers, and CDs. + +
+ +## Instructions for Manual Testing | [Return to contents](#table-of-contents) + +> Note: These instructions only provide a starting point for testers to work on; Testers are expected to do more _exploratory_ testing. + +### Launch & Shutdown + +1. Initial launch + 1. Download the jar file and copy it into an empty folder. + 2. Open the command prompt / terminal and run `java -jar SysLib.jar`. You should see the following welcome message: + +``` +____________________________________________________________ +Data directory does not exist. Creating now... +Storage file does not exist. Creating now... +Loaded 0 resources and 0 events! +____________________________________________________________ + ..................... + -##@*+*@*++++++++++#@++## + .@. @-=%= *#-+% + :@ @+- :----------. .=#% + :@ @. *%----------@- =% + :@ @. #* @= =% + :@ @. #* *: :+ + :@ @. *%-----. .=+****+-. + :@ @. :-----.-#*-. .:-*#- + :@ @. .%+. .@*#+.*%. + :@ @: %= %* +@.=% + :@ @*#*. -@ *###***+. @- + :@ .@:.=@... -@ .+*#*#### @- + :@#*++++++++. %=.%+ +# +% + :@. =++++++++-.%*.+%*@. *%. + %+ ........ =#*-:: .-*%= + =*************. .=+****+-. + ____ _ _ _ ____ _ ___ +/ ___| _ _ ___| | (_) |__ / ___| | |_ _| +\___ \| | | / __| | | | '_ \ | | | | | | + ___) | |_| \__ \ |___| | |_) | | |___| |___ | | +|____/ \__, |___/_____|_|_.__/ \____|_____|___| + |___/ + +Hello! What would you like to do? +____________________________________________________________ +``` + +2. Shutdown + 1. Enter the command `exit` into the program. + 2. Close the command prompt / terminal. + + +Example response: + +``` +Thanks for using SysLib! We have saved the current resources and events. +See you next time! +____________________________________________________________ +``` + +### Adding a Book +1. Add a book + 1. Test case: `add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag B` + + Expected: A book with ISBN: 9783161484100, Title: Crime and Punishment, Author: Dostoevsky, and Status: AVAILABLE + is created and added into the list. A message with details of the added book is displayed to acknowledge that the + book has been added successfully. A warning is given to tell the user that the Status and Genre was not given. + + ``` + Attention: Status is not stated. Status set to default: AVAILABLE. + Attention: Genre is not stated. Genre not set. + This book is added: + [B] ID: 1 Title: Crime and Punishment ISBN: 9783161484100 Author: Dostoevsky Genre: - Status: AVAILABLE Received Date: 12 Nov 2023 + ____________________________________________________________ + ``` + + 2. Test case: `add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag B /g Fiction /s lost` + + Expected: A book with ISBN: 9783161484100, Title: Crime and Punishment, Author: Dostoevsky, Genre: Fiction, and + Status: LOST is created and added into the list. A message with details of the added book is displayed to + acknowledge that the book has been added successfully. + + ``` + This book is added: + [B] ID: 2 Title: Crime and Punishment ISBN: 9783161484100 Author: Dostoevsky Genre: Fiction Status: LOST Received Date: 12 Nov 2023 + ____________________________________________________________ + ``` + + 3. Test case: `add /i CAP123 /t Crime and Punishment /a Dostoevsky /tag B` + + Expected: No book is added. An error message is displayed to indicate that the ISBN is invalid. + + ``` + Please enter a valid ISBN with 13 digits. + ____________________________________________________________ + ``` + 4. Test case: `add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag A` + + Expected: No book is added. An error message is displayed to indicate that the tag is invalid. + + ``` + Please enter a valid tag. + ____________________________________________________________ + ``` + 5. Test case: `add /tag B` + + Expected: No book is added. An error message displayed to indicate that the input is incomplete. + + ``` + Please enter a valid ISBN with 13 digits. + ____________________________________________________________ + ``` + 6. Test case: `add /i 9783161484100 /t Crime and Punishment /t Crime and Punishment2 /a Dostoevsky /tag B` + + Expected: No book is added. An error message displayed to indicate that there are multiple titles. + + ``` + Please enter only 1 title. + ____________________________________________________________ + ``` + +### Listing Resources + +1. List all resources + 1. Prerequisites: At least one resource present in list + + 2. Test case: `list` + + Expected: A table showing details of current resources, in order of BOOKS, MAGAZINE, CDs, and NEWSPAPERS. + + +2. List when no resources are in list + 1. Prerequisites: No resources currently in SysLib + + 2. Test case: `list` + + Expected: An error message saying "There are currently 0 resources." + + ``` + There are currently no Resources in Syslib! + ____________________________________________________________ + ``` + +3. List resources with filter options + + 1. Test case: `list /tag B ` + + Expected: A table showing details of `Book` resources with tag `B`, or a message stating no resources found or empty list if applicable. + + 2. Test case: `list /tag B /s AVAILABLE` + + Expected: A table showing details of `Book` resources with tag `B` and status `AVAILABLE`, or a message stating no resources found or empty list if applicable. + + + +4. Other incorrect commands to try: list X, list /tag , ... + + Expected: Invalid argument message. + + ``` + Unknown variable/command: x, avoid using '/' in names/variables + ____________________________________________________________ + ``` + +### Editing a Resource + +1. Edit a resource + 1. Prerequisite: A list containing at least one resource. `list` all the resources to see their `ids`. + 2. Test case: `edit /id 1 /t NEWTILE /a AUTHOR` + + Expected: An edit success message displaying the new details of the edited resource, IF resource with `id 1` is a Book (Author is a Book argument). Else, error message saying wrong arguments and showing the right arguments. + + ``` + Successfully updated! Your updated resource: + + [B] ID: 1 Title: NEWTILE ISBN: 9783161484100 Author: AUTHOR Genre: - Status: AVAILABLE Received Date: 13 Nov 2023 + ____________________________________________________________ + + ``` + + 3. Test case: `edit /id 1 /c NEWCREATOR` + + Expected: An edit success message displaying the new details of the edited resource, IF resource with `id 1` is a CD (Creator is a CD argument). Else, error message saying wrong arguments and showing the right arguments. + +2. Other incorrect commands to try: `edit /id 0 X`, `edit /t` , ... + + Expected: Invalid argument message. + + ``` + Please provide at least one detail to edit! + For Books: /t TITLE /a AUTHOR /g GENRES /s STATUS /i ISBN + For EBooks: /t TITLE /a AUTHOR /g GENRES /s STATUS /l LINK /i ISBN + ____________________________________________________________ + For Magazines: /t TITLE /b BRAND /is ISSUE /s STATUS /i ISBN + For EMagazines: /t TITLE /b BRAND /is ISSUE /s STATUS /l LINK /i ISBN + ____________________________________________________________ + For CDs: /t TITLE /c CREATOR /ty TYPE /s STATUS /i ISBN + ____________________________________________________________ + For Newspapers: /t TITLE /p PUBLISHER /ed EDITION /s STATUS /i ISBN + For ENewspapers: /t TITLE /p PUBLISHER /ed EDITION /s STATUS /l LINK /i ISBN + ____________________________________________________________ + ``` + +### Deleting Resources +1. Delete a resource + 1. Prerequisites: At least one resource present. + 2. Test case: `delete /id 1` + + Expected: Resource with ID 1 is removed + + ``` + Looking for ID: 1... + This resource is removed: + [B] ID: 1 Title: Crime and Punishment ISBN: 9783161484100 Author: Dostoevsky Genre: null Status: AVAILABLE Received Date: 13 Nov 2023 + ____________________________________________________________ + ``` + +2. Delete a resource that is not there + 1. Prerequisites: Resource is non-existent + 2. Test case: `delete /id 10` + + Expected: Error message saying the resource with given ID is not found. + + ``` + Looking for ID: 10... + No resources with id matching 10 + ____________________________________________________________ + ``` + +### Adding an Event +1. Add an Event + 1. Test case: `eventadd /t New Year /date 1 Jan 2024` + + Expected: An Event with Title: New Year, Date: 1 Jan 2024, Description: null is + created and added into the list. A message with details of the added event is displayed to acknowledge that the + event has been added successfully. + + ``` + Event inserted at: 0 + 0: New Year | 01 Jan 2024 | null + ____________________________________________________________ + ``` + + 2. Test case: `eventadd /t Meeting /date 23 Dec 2023 /desc Board Meeting` + + Expected: An Event with Title: Meeting, Date: 23 Dec 2023, Description: Board Meeting is + created and added into the list. A message with details of the added event is displayed to acknowledge that the + event has been added successfully. + + ``` + Event inserted at: 0 + 0: Meeting | 23 Dec 2023 | Board Meeting + ____________________________________________________________ + ``` + + 3. Test case: `eventadd /date 23 Dec 2023` + + Expected: No event is added. An error message is displayed to indicate that the Title is missing. + + ``` + t is missing in the argument! + ____________________________________________________________ + ``` + 4. Test case: `eventadd /t Meeting` + + Expected: No event is added. An error message is displayed to indicate that the date is missing. + + ``` + date is missing in the argument! + ____________________________________________________________ + ``` + +### Listing Events + +1. List all events + 1. Prerequisites: At least one event present in list + + 2. Test case: `eventlist` + + Expected: A list showing details of current events, in order of its date. + + ``` + This is the current event list: + 0: Meeting | 23 Dec 2023 | Board Meeting + 1: New Year | 01 Jan 2024 | null + ____________________________________________________________ + ``` + +2. List when no resources are in list + 1. Prerequisites: No events currently in SysLib + + 2. Test case: `eventlist` + + Expected: An error message saying "There event list is empty." + + ``` + There are currently no Events in Syslib! + ____________________________________________________________ + ``` + +3. List with unexpected arguments + 1. Test case: `eventlist /t title` + + Expected: An error message saying "'eventlist' command does not require arguments!" + + ``` + 'eventlist' command does not require arguments! + ____________________________________________________________ + ``` + +### Editing an Event + +1. Edit a resource + 1. Prerequisite: A list containing at least one event. Use `eventlist` to see their `ids`. + 2. Test case: `eventedit /id 0 /t Board Meeting` -## Design & implementation + Expected: An edit success message displaying the new details of the edited event. + + ``` + Event edited successfully. New event details: + 0: Board Meeting | 23 Dec 2023 | Board Meeting + ____________________________________________________________ + ``` -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} + 2. Test case: `eventedit /id 0 /t Meeting /date 22 Dec 2023 /desc Board Meeting with CEO` + Expected: An edit success message displaying the new details of the edited event. + + ``` + Event edited successfully. New event details: + 0: Meeting | 22 Dec 2023 | Board Meeting with CEO + ____________________________________________________________ + ``` -## Product scope -### Target user profile +2. Edit id given but no parameters were given + 1. Test case: `eventedit /id 0` + + Expected: An error message saying "Event was not edited as nothing was changed." + + ``` + Event was not edited as nothing was changed. + ____________________________________________________________ + ``` -{Describe the target user profile} +3. Invalid id was given + 1. Test case: `eventedit /id 0` -### Value proposition + Expected: An error message saying "Event was not edited as nothing was changed." + + ``` + Invalid event index + ____________________________________________________________ + ``` -{Describe the value proposition: what problem does it solve?} +### Deleting Event +1. Delete an event + 1. Prerequisites: At least one resource present. + 2. Test case: `eventdelete /id 0` -## User Stories + Expected: Event with ID 0 is removed -|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| + ``` + This event is removed: + Meeting | 22 Dec 2023 | Board Meeting with CEO + ____________________________________________________________ + ``` -## Non-Functional Requirements +2. Delete an event that is not there + 1. Prerequisites: Event is non-existent + 2. Test case: `eventdelete /id 10` -{Give non-functional requirements} + Expected: Error message saying the event with given ID is not found. -## Glossary + ``` + Index is out of range of the event list! + ____________________________________________________________ + ``` + +### Saving data -* *glossary item* - Definition +The following are some test cases for you to try: -## Instructions for manual testing +> **Important!** These test cases are done on the assumption that storage.txt file is empty. +> If you have some data written into these files or modified storage.txt, please do the following prior to conducting the test cases mentioned below: +> +>(1) If Syslib is running, exit the application. +> +>(2) Backup your existing `storage.txt` file in the `data` directory. +> +>(3) Delete the `data` directory (not your backup!) +> +>(4) Start Syslib to generate a fresh `storage.txt` in `data` directory. -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +| Test Case | Command | Expected result | +|:--------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1. Write new data into `storage.txt`. | (a) `add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag B `
(b) `add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /tag B /g Fiction /s lost`
(c) `add /i 9780763630189 /t Frankenstein /c Mary Shelley /ty Audio Book /tag cd` | The following two lines are added to `storage.txt` on exit:

![storage.txt](images/StorageNewResources.png) | +| 2. Update data in `storage.txt`. | `edit /id 1 /t NEWTITLE /a AUTHOR` | The book `Crime and Punishment` with id `1` has new title: `NEWTITLE`, new author `AUTHOR`. `storage.txt` should look something like this:

![storage.txt](images/StorageEditResource.png) | +| 3. Delete data from `storage.txt`. | `delete /id 1` | The book `Crime and Punishment` with id `1` will be removed:

![storage.txt](images/StorageDeleteResource.png) | \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..f8fab3f1e1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,7 @@ -# Duke + + +SysLib CLI is a robust command-line tool designed for system librarians to efficiently handle inventory and events. With quick command-based actions, manage your library's resources and events seamlessly. Simplify administrative tasks, so you can focus on serving patrons better. -{Give product intro here} Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..2cf1cc2b25 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,719 @@ # User Guide + -## Introduction +## Contents +* [Introduction](#introduction--return-to-contents) +* [Quick Start](#quick-start--return-to-contents) +* [Features](#features--return-to-contents) + * [Save your work](#managing-your-resources-and-events-with-ease) + * [Resources](#resources) + * [Add a Listing: `add`](#add-a-listing-add--return-to-contents) + * [Delete a Listing: `delete`](#delete-a-listing-delete--return-to-contents) + * [List Items: `list`](#list-all-items-list--return-to-contents) + * [Find Listing by Search Parameter: `find`](#find-specific-listings-find--return-to-contents) + * [Edit a Listing: `edit`](#edit-a-listing-edit--return-to-contents) + * [Events](#events) + * [Event Adding: `eventadd`](#event-adding-eventadd--return-to-contents) + * [Event Listing: `eventlist`](#event-listing-eventlist--return-to-contents) + * [Event Delete: `eventdelete`](#event-delete-eventdelete--return-to-contents) + * [Event Edit: `eventedit`](#event-edit-eventedit--return-to-contents) + * [Summary of Resources & Events: `summary`](#summary-summary--return-to-contents) + * [Exiting the Program : `exit`](#exiting-the-program--exit--return-to-contents) + * [Get Help: `help`](#viewing-help--help--return-to-contents) +* [FAQ](#faq--return-to-contents) +* [Known Issues](#known-issues--return-to-contents) +* [Command Summary](#command-summary--return-to-contents) -{Give a product intro} +
-## Quick Start +## Introduction | [Return to Contents](#contents) -{Give steps to get started quickly} +Welcome to the SysLib User Guide: your all-in-one document to learn how to use SysLib to manage your work and responsibilities as a librarian. -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +Our user guide is for every system librarian whether you're a beginner, novice, or expert in using a CLI library management software. -## Features +From viewing, adding, searching, and many more, SysLib provides all the features you need to optimize your work from hours to seconds. In no time, you will be typing intuitive commands to manage your library resources and upcoming events! -{Give detailed description of each feature} +Without further ado, let's get started with how to navigate the guide! -### Adding a todo: `todo` -Adds a new item to the list of todo items. +### How to Use the User Guide -Format: `todo n/TODO_NAME d/DEADLINE` +Information about how to use the guide (e.g. how to navigate the document, meaning of icons and formatting used) -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +**Important pointers to take note of:** -Example of usage: +1. **Format for Commands**: +- Capital letters - placeholders for your input +- Small letters - exact commands to enter +- / - indicates the type of information you are entering +- [] - optional arguments -`todo n/Write the rest of the User Guide d/next week` +2. **Recommended Terminals** -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +The following table lists down the operating systems and their respective terminals that Syslib CLI has been tested on to +work. -## FAQ +| Operating System | Version | Recommended Terminal | +|:------------------|:-------------------------------|:--------------------------------------------------------------------------------------------------------------------| +| Microsoft Windows | Windows 10 2004 and above | Windows Terminal ([User Guide](https://docs.microsoft.com/en-us/windows/terminal/)) | +| Apple macOS | macOS 10.15 Catalina and above | Terminal ([User Guide](https://support.apple.com/en-sg/guide/terminal/apd5265185d-f365-44cb-8b09-71a064a42125/mac)) | +| Ubuntu Linux | Ubuntu 20.04.3 (LTS) and above | Bash Terminal ([User Guide](https://ubuntu.com/tutorials/command-line-for-beginners#3-opening-a-terminal)) | -**Q**: How do I transfer my data to another computer? +> **⚠️ IMPORTANT:** +> +>If your operating system is not listed in the table above, it means our application has not been tested on it. We therefore cannot guarantee that the application will work as intended. We highly encourage you to use one of the recommended operating systems in the table above. We apologise for any inconvenience caused. -**A**: {your answer here} +
+ +## Quick Start | [Return to Contents](#contents) +1. Make sure that you have Java 11 or above installed on your computer. + - Open terminal and type `java --version` + - [How do I check the version of my Java](https://www.java.com/en/download/help/version_manual.html) +2. [Download the latest release](https://github.com/AY2324S1-CS2113T-W11-1/tp/releases/tag/v2.1) of `Syslib.jar` +3. Copy the `Syslib.jar` file into a folder on its own. +4. Open a command terminal, type `cd `, where `` refers to the directory to the `Syslib.jar` file. +5. Run the following command: `java -jar Syslib.jar`. You should see the following welcome screen. + +``` +____________________________________________________________ +Data directory does not exist. Creating now... +Storage file does not exist. Creating now... +Loaded 0 resources and 0 events! +____________________________________________________________ + ..................... + -##@*+*@*++++++++++#@++## + .@. @-=%= *#-+% + :@ @+- :----------. .=#% + :@ @. *%----------@- =% + :@ @. #* @= =% + :@ @. #* *: :+ + :@ @. *%-----. .=+****+-. + :@ @. :-----.-#*-. .:-*#- + :@ @. .%+. .@*#+.*%. + :@ @: %= %* +@.=% + :@ @*#*. -@ *###***+. @- + :@ .@:.=@... -@ .+*#*#### @- + :@#*++++++++. %=.%+ +# +% + :@. =++++++++-.%*.+%*@. *%. + %+ ........ =#*-:: .-*%= + =*************. .=+****+-. + ____ _ _ _ ____ _ ___ +/ ___| _ _ ___| | (_) |__ / ___| | |_ _| +\___ \| | | / __| | | | '_ \ | | | | | | + ___) | |_| \__ \ |___| | |_) | | |___| |___ | | +|____/ \__, |___/_____|_|_.__/ \____|_____|___| + |___/ + +Hello! What would you like to do? +____________________________________________________________ +``` + +
+ +## Features | [Return to Contents](#contents) +> Syslib CLI allows System Librarians to create Resources (Books, Magazines, Newspapers, CDs, eBooks, eMagazines, eNewspapers) as well as Events. + +### Managing Your Resources and Events with Ease + +Our intuitive system ensures that you never lose track of your valuable resources and events. Here's how it works: + +#### 📁 Automatic Saving on Exit +- **Peace of Mind:** Every resource and event you add to the current list is automatically saved when you exit the program. Rest easy knowing your data is secure. + +``` +> exit +Thanks for using SysLib! We have saved the current resources and events. +See you next time! +____________________________________________________________ +``` + +#### 🔄 Seamless Loading on Startup +- **Instant Access:** Each time you start the program, we automatically load your resources and events from the last session. Your information is always at your fingertips. + +``` +____________________________________________________________ +Data directory exists. +Storage file exists. +Loaded 2 resources and 1 events! +____________________________________________________________ +``` + + +#### 📍 File Location +- **Find Your Data Easily:** Your saved data resides in a file named `storage.txt`, conveniently located in the `data` directory. + - Path to your file: `data/storage.txt` +``` +> Syslib.jar +> data/ // Primary folder for storage +> └── storage.txt // Text file containing a list of resources and events saved. +``` +Happy organizing! + + +
+ +## Resources +### Add a Listing: `add` | [Return to Contents](#contents) + +Adding New Resources to Your Library Inventory + +Easily expand your library's collection with our streamlined process for adding new resources. Here's everything you need to know: + +**Resource Tags - Identify Your Resources** + +Choose the right tag to classify each new addition: +- `[B]` for **Books** +- `[EB]` for **eBooks** +- `[CD]` for **CDs** +- `[M]` for **Magazines** +- `[EM]` for **eMagazines** +- `[N]` for **Newspapers** +- `[EN]` for **eNewspapers** + + +**Electronic Versions** +- Easy Identification: Anything with an `E` is an electronic version. +- For example, `[EB]` is an eBook, `[EM]` is an eMagazine, and `[EN]` is an eNewspaper. + +**Status Types - Keep Track of Availability** +Set the status to keep your inventory organized: +- `AVAILABLE` for items ready to be checked out. +- `BORROWED` for items currently with users. +- `LOST` for items that are missing. + +> **📚 Note:** +> +> Default Status +> - **Automatic Setting:** If you don't specify a status, we'll automatically set it to `AVAILABLE`. +> +> ISBN requirements +> - **13-Digit Requirement:** Ensure the ISBN is exactly 13 digits for proper cataloging +> +> Use of Slash ('/') +> - Slash ('/') can only be used in two situations. +> 1. To indicate the type of information you are entering. +> The required indications are in the 'Format' part of the commands. +> e.g. /i for ISBN, /t for Title, /tag for Tag, etc. +> 2. When it is wrapped with words. +> e.g. www.abc.com/def, Frankenstein/the Modern Prometheus +> - Examples of invalid use of slash ('/'): +> - /isbn +> - www.abc.com/ +> - Frankenstein/ the Modern Prometheus +> - Frankenstein /the Modern Prometheus +> - Frankenstein / the Modern Prometheus + +**Quick Tips** +- 🌟 **Double-check your tags and ISBN** for accurate categorization. +- 💡 **Regularly update the status** of your resources to reflect their current state. + +The specific commands for each resource types can be seen below: + +### Add Book + +Format: `add /i ISBN /t TITLE /a AUTHOR /tag b [/g GENRE /s STATUS]` + +**Example input:** +``` +add /i 9780763630189 /t Frankenstein /a Mary Shelley /tag b +add /i 9780763630188 /t Moby Dick /a Herman Melville /tag b /g Adventure, Fiction +add /i 9780763630187 /t Harry Squatter /a J.K. /tag b /g History /s lost +``` +**Example output:** +``` +This book is added: +[B] ID: 3 Title: Harry Squatter ISBN: 9780763630187 Author: J.K. Genre: History Status: LOST Received Date: 11 Nov 2023 +____________________________________________________________ +``` + +> **📚 Note:** +> - Multiple genres are allowed for a single resource. You can separate the different genres using comma `,`. +> e.g. Sci-Fi, Fantasy, Comedy + +
+ +### Add eBook + +Format: `add /i ISBN /t TITLE /a AUTHOR /tag eb /l LINK [/g GENRE /s STATUS]` + +**Example input:** +``` +add /i 9780763630189 /t Frankenstein /a Mary Shelley /tag eb /l frankenstein.com +add /i 9780763630188 /t Moby Dick /a Herman Melville /tag eb /l www.mobyd.com /g Adventure, Fiction +add /i 9780763630187 /t Harry Squatter /a J.K. /tag eb /l www.jk.com/harrysquatter /g History /s lost +``` +**Example output:** +``` +Attention: Status is not stated. Status set to default: AVAILABLE. +This e-book is added: +[EB] ID: 2 Title: Frankenstein ISBN: 9780763630189 Author: Mary Shelley Genre: - Link: frankenstein.com +____________________________________________________________ +``` + +> **📚 Note:** +> - Multiple genres are allowed for a single resource. You can separate the different genres using comma `,`. +> - e.g. Sci-Fi, Fantasy, Comedy + +### Add CD +Format: `add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag cd [/s STATUS]` + +**Example input:** +``` +add /i 9780763630189 /t Frankenstein /c Mary Shelley /ty Audio Book /tag cd +add /i 9770763630236 /t Mayday /c Kim Bondi /ty Video Recording /tag cd /s borrowed +add /i 9760763630369 /t Performing Arts in Singapore /c Evelyn Lim /ty Oral Interview /tag cd /s available +``` +**Example output:** +``` +This CD is added: +[CD] ID: 8 Title: Mayday ISBN: 9770763630236 Creator: Kim Bondi Type: Video Recording Status: BORROWED +____________________________________________________________ +``` + +
+ +### Add Magazine +Format: `add /i ISBN /t TITLE /b BRAND /is ISSUE /tag m [/s STATUS]` + +**Example input:** +``` +add /i 9781234567913 /t Tech Trends /b Wired Tech /is Volume 22, Issue 3 /tag m +add /i 9781234567944 /t Cozy Living /b Better Homes Publishing /is Home Edition, May 2023 /tag m /s LOST +add /i 9781234567951 /t Market Movers /b Forbes Publications /is Quarterly Report, Q2 2023 /tag m /s BORROWED +``` +**Example output:** +``` +This magazine is added: +[M] ID: 11 Title: Cozy Living ISBN: 9781234567944 Brand: Better Homes Publishing Issue: Home Edition, May 2023 Status: LOST +____________________________________________________________ +``` + +### Add eMagazine +Format: `add /i ISBN /t TITLE /b BRAND /is ISSUE /tag em /l LINK [/s STATUS]` + +**Example input:** +``` +add /i 9781234567913 /t Tech Trends /b Wired Tech /is Volume 22, Issue 3 /tag em /l www.wiredtech.com/techtrends +add /i 9781234567944 /t Cozy Living /b Better Homes Publishing /is Home Edition, May 2023 /tag em /l www.cozyliving.net /s lost +add /i 9781234567951 /t Market Movers /b Forbes Publications /is Quarterly Report, Q2 2023 /tag em /l forbes.com /s available +``` +**Example output:** +``` +This e-magazine is added: +[EM] ID: 15 Title: Market Movers ISBN: 9781234567951 Brand: Forbes Publications Issue: Quarterly Report, Q2 2023 Link: forbes.com +____________________________________________________________ +``` + +
+ +### Add Newspaper +Format: `add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag n [/s STATUS]` +**Example input:** +``` +add /i 9730763630288 /t City Herald /p Metro Media Group /ed Morning Edition, March 15 2023 /tag n +add /i 9730763630277 /t Sports Daily /p Sports Press International /ed Daily Sports Wrap, March 15 2023 /tag n /s lost +add /i 9730763630266 /t Community Chronicle /p Local News Network /ed Weekly Community News, March 13, 2023 /tag n /s available + +``` +**Example output:** +``` +Attention: Status is not stated. Status set to default: AVAILABLE. +This newspaper is added: +[N] ID: 16 Title: City Herald ISBN: 9730763630288 Publisher: Metro Media Group Edition: Morning Edition, March 15 2023 Status: AVAILABLE +``` + +### Add eNewspaper +Format: `add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag en /l LINK [/s STATUS]` + +**Example input:** +``` +add /i 9730763630288 /t City Herald /p Metro Media Group /ed Morning Edition, March 15 2023 /tag en /l https://www.cityherald.com/march15-2023 +add /i 9730763630277 /t Sports Daily /p Sports Press International /ed Daily Sports Wrap, March 15 2023 /tag en /l dailysports.com /s available +add /i 9730763630266 /t Community Chronicle /p Local News Network /ed Weekly Community News, March 13, 2023 /tag en /l www.lcn.com/news/031323 /s available +``` + +**Example output:** +``` +This e-newspaper is added: +[EN] ID: 20 Title: Sports Daily ISBN: 9730763630277 Publisher: Sports Press International Edition: Daily Sports Wrap, March 15 2023 Link: dailysports.com +____________________________________________________________ +``` + +
+ +### Delete a Listing: `delete` | [Return to Contents](#contents) + +Deletes the resource with the specified ID from the library inventory. You can find the ID using the `list` command. + +Format: `delete /id ID` + +**Example input:** +``` +delete /id 1 +``` +**Example input:** +``` +Looking for ID: 1... +This resource is removed: +[B] ID: 1 Title: Moby Dick ISBN: 9780763630188 Author: Herman Melville Genre: Adventure, Fiction Status: LOST +____________________________________________________________ +``` + +### List All Items: `list` | [Return to Contents](#contents) + + +The `list` command displays every resource in the library along with their details and categorized by their type `Book`, `Magazine`,`CD`, or `Newspaper`, giving you a quick and neat overview of all the resources in one place. + +Looking for a more specific list? `list` also offers you the capability to **filter** for a specific **tag**, **genre**, or **status**, generating a tailored list for your needs. + +**Format:** `list [/tag TAG /g GENRE /s STATUS]` + +> **📚 Note:** +>- Including more than one filter will list resources that satisfy **ALL** given filters. +>- For example, `list /tag B /g Horror` will list Books with Horror genre. +>- You can only specify one keyword per filter. +> - **ALLOWED:** /g Horror +> - **NOT ALLOWED:** /g Horror, Fiction + +**Potential Issues:** + +You may face an issue where you are unable to see the list, or it's difficult to read the table due to the display alignment. Kindly click [here](#list-table-looks-messy-or-unable-to-see-the-full-details-return-to-list-feature) to jump to the Known Issues section to solve any issues regarding the list. + +
+ +**Example input:** +``` +list +list /tag B +list /g Thrill +list /s Available +list /tag B /g Fiction +list /tag B /g Fiction /s Available +``` + +**Example output:** + +![ExampleOutput](images\List Screenshots\listexampleoutput.png) + +
+ +### Find Specific Listings: `find` | [Return to Contents](#contents) + +**Find What You Need, Fast!** + +Our advanced search capabilities make it easy to locate the resources you need. Here's how you can make the most out of our search tool: + +- **Flexible Options:** Find resources using a variety of identifiers: + - **Title:** Pinpoint resources by their titles. + - **Author:** Search for books by their authors. + - **ISBN:** Use this unique identifier for precise book searches. + - **ID:** Every resource has an ID for quick identification. + + +- **Targeted Results:** Combine multiple filters in your search. We'll show you results that match **ALL** your specified criteria for pinpoint precision. + +> **📚 Note:** +> - `AUTHOR` also refers to the following: +> - For **Newspapers**, `publisher` will be used. +> - For **CDs**, `creator` will be used. +> - For **Magazines**, `brand` will be used. + +**Quick Tips** +- 💡 **Familiarize yourself with the search terms** for different resource types for efficient searching. + + +Discover exactly what you're looking for, effortlessly! + +**Format:** `find [/t TITLE OR /i ISBN OR /a AUTHOR/PUBLISHER/BRAND/CREATOR OR /id ID]` + +**Example input:** +``` +find /t Moby Dick +find /i 9780763630188 +find /a J. K. Rowling +find /id 123456789 +find /id 123456789 /i 9780763630188 +find /a Vogue +``` + +**Example output:** +![img.png](images/findcommandoutput.png) + +
+ +### Edit a Listing: `edit` | [Return to Contents](#contents) + +We all know the horror of making a typo and having to delete and add a resource again—it's absolutely _dreadful_. + +Fear not! Update a resource's details using the `edit` command and fix your typos in a flash. + +**Format:** `edit /id ID /argumentname ARGUMENT [/argumentname2 ARGUMENT2..]` + +> **📚 Note:** +> +> - If you have forgotten the **ID**, execute `list` to locate your target resource and ID. +> - At least **one** argument to edit must be given. +> - You can edit multiple details in one go by specifying multiple arguments. E.g `edit /id 1 /t NEWTITLE /a NEWAUTHOR` updates title and author. + +**Argument Names:** + +Argument names differ based on resource type. The table below shows the argument names you can enter for each resource type. + +| Type | Argument Names | Notes | +|--------------------------|----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Book
eBook | /t TITLE
/a AUTHOR
/g GENRES
/s STATUS
/l LINK
/i ISBN | **/g GENRES:**
If you're inputting **multiple genres**, separate them by with comma `,`.

For example: `/g Horror, Fantasy`
**/l LINK:**
For eBook only. | +| Magazine
eMagazine | /t TITLE
/b BRAND
/is ISSUE
/s STATUS
/l LINK
/i ISBN | **/l LINK:**
For eMagazine only. | +| Newspaper
eNewspaper | /t TITLE
/p PUBLISHER
/ed EDITION
/s STATUS
/l LINK
/i ISBN | **/l LINK:**
For eNewspaper only. | +| CD | /t TITLE
/c CREATOR
/ty TYPE
/s STATUS
/i ISBN | | + +For example, if you would like to update a eBook, you can edit the attributes given under the **Argument Names** column, that is the title, author, genre, status, link, and isbn. + +**Example input:** + +``` +edit /id 1 /t NEW_TITLE +edit /id 1 /t NEW TITLE /a NEW AUTHOR /g Horror, Fiction /s LOST /i 1231231231234 +edit /id 2 /c NEW CREATOR /ty NEW TYPE +edit /id 3 /b NEW BRAND /is NEW ISSUE +edit /id 4 /p NEW PUSBLISHER /ed NEW EDITION +``` + +**Example output:** +``` +Successfully updated! Your updated resource: + +[B] ID: 3 Title: Mary ISBN: 123 Author: John Genre: Horror, Adventure Status: LOST +____________________________________________________________ +``` +
+ +## Events +>**📚 Note:** +> - Events are stored separately from resources +> - They are stored in chronological order (events that are happening sooner are closer to index 0) + +### Event Adding: `eventadd` | [Return to Contents](#contents) +>**📚 Note:** +> - `desc` is optional for all events +> - Any event without description will be shown as `null` + +Adds an event to the database. + +Format: `eventadd /t TITLE /date DATE [/desc DESCRIPTION]` + +**Example input:** +``` +eventadd /t Fan meetup for xxx /date 11 Jan 2010 +eventadd /t Meet and Greet for xxx /date 10 Jan 2010 /desc buffet style +``` + +**Example output:** +``` +Event inserted at: 0 +0: Fan meetup for xxx | 11 Jan 2010 | null +____________________________________________________________ +``` + +### Event Listing: `eventlist` | [Return to Contents](#contents) + +Displays all events in the database. + +Format: `eventlist` + +**Example input:** +``` +eventlist +``` + +**Example output:** +``` +This is the current event list: +0: Meet and Greet for xxx | 10 Jan 2010 | buffet style +1: Fan meetup for xxx | 11 Jan 2010 | null +____________________________________________________________ +``` + +
+ +### Event Delete: `eventdelete` | [Return to Contents](#contents) +>**📚 Note:** +> - INDEX starts from 0 and can be viewed by calling `eventlist` +> - INDEX might change as those with earlier dates are inserted first + +Deletes an event from the database based on the index provided. + +Format: `eventdelete /id INDEX` + +**Example input:** +``` +eventdelete /id 0 +``` + +**Example output:** +``` +This event is removed: +Meet and Greet for xxx | 10 Jan 2010 | buffet style +____________________________________________________________ +``` + +### Event Edit: `eventedit` | [Return to Contents](#contents) +>**📚 Note:** +> - INDEX starts from 0 and can be viewed by calling `eventlist` +> - INDEX might change as those with earlier dates are sorted first + +Edits attributes of an event based on information provided. + +Format: `eventedit /id INDEX [/t TITLE /date DATE /desc DESCRIPTION]` + +**Example input:** +``` +eventedit /id 2 /t NEW TITLE +eventedit /id 0 /t NEW TITLE /date 23 Jan 2024 /desc NEW DESCRIPTION +``` + +**Example output:** +``` +Event edited successfully. New event details: +0: NEW TITLE | 23-01-2024 | NEW DESCRIPTION +____________________________________________________________ +``` + +
+ +### Summary: `summary` | [Return to Contents](#contents) +Provide a summary of resources added and upcoming 3 events + +Format: `summary` + +**Example input:** +``` +summary +``` + +**Example output** +``` +Summary of Resources: +Total Resources: 26 +Total Books: [████████████████] 12 +Total CDs: [███] 2 +Total Magazines: [██████] 4 +Total E-Books: [█] 1 +Total E-Magazines: [████] 3 +Total Newspapers: [███] 2 +Total E-Newspapers: [███] 2 + +Summary of Events: +Total Events: 7 +Upcoming Events (Next 3): +1. Storey telling session | 21 Dec 2023 | null +2. Maintenance | 21 Dec 2023 | null +3. New Year | 01 Jan 2024 | null +____________________________________________________________ +``` +### Exiting the Program : `exit` | [Return to Contents](#contents) +Exits the program, all data in resource list and event list will be saved to a storage file. + +Format: `exit` + +Example: +``` +> exit +Thanks for using SysLib! We have saved the current resources and events. +See you next time! +____________________________________________________________ +``` + +
+ + +### Viewing Help : `help` | [Return to Contents](#contents) +Displays a list of available commands with examples and their syntax format. + +Format: `help` + +Example: + +![helpexampleoutput](images/helpexampleoutput.png) + +## FAQ | [Return to Contents](#contents) + +Q: How do I download Java 11 on my computer? +A: Follow the guide [here](https://www.codejava.net/java-se/download-and-install-java-11-openjdk-and-oracle-jdk#:~:text=Head%20to%20Java%20SE%20Development,download%20the%20file%20jdk%2D11.0.)! + +Q: How do I open command terminal? + +A: +- For **Windows** users, click **Start** and search for **Command Prompt**. + +- For **Mac** users, press `Command ⌘` + `Space` +on your keyboard to open Spotlight and type **Terminal** in the search field, then click **Terminal**. + +Q: What should I do if I encounter an error or bug? +A: Go to your [github issue page](https://github.com/AY2324S1-CS2113T-W11-1/tp/issues) and submit an issue +including the following: + - Steps to report bugs + - logs or error messages. + +We will get back to you as quickly as possible! + +
+ +## Known Issues | [Return to Contents](#contents) + +### List Table looks messy or unable to see the full details: [[Return to list feature]](#list-all-items-list--return-to-contents) + +Upon executing `list`, you may encounter an issue where the table is out of alignment: + +![ListProblem.png](images/List%20Screenshots/listproblemimg.png) + +This issue is due to the **window size** of your command line terminal and occurs when you have long details in your resources. + +**Solutions:** + +You can try any of the following solutions to fix this issue: +- Make your terminal **full screen** by clicking the square on the top right. + + ![ListProblem.png](images/List%20Screenshots/listimage.png) + +- **Resize** your window by: + 1. Move your cursor to bottom right corner of your terminal window until you see an icon with double arrows like: ![resizeicon.png](images/List Screenshots/resizeicon.png) + 2. Drag your cursor down until you see the full table + + +- Decrease your **font size** by: + + - Right-click your terminal and click "Properties" + + ![img_2.png](images/List%20Screenshots/terminalimg.png) + + - Click on "Font" tab and select a smaller font size that suits your display. + + ![img_3.png](images/List%20Screenshots/fontsize.png) + +
+ +## Command summary | [Return to Contents](#contents) + +| Action | Command | +|-----------------------|----------------------------------------------------------------------------------| +| Add Book | `add /i ISBN /t TITLE /a AUTHOR /tag b [/g GENRE /s STATUS]` | +| Add eBook | `add /i ISBN /t TITLE /a AUTHOR /tag eb /l LINK [/g GENRE /s STATUS]` | +| Add CD | `add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag cd [/s STATUS]` | +| Add Magazine | `add /i ISBN /t TITLE /b BRAND /is ISSUE /tag m [/s STATUS]` | +| Add eMagazine | `add /i ISBN /t TITLE /b BRAND /is ISSUE /tag em /l LINK [/s STATUS]` | +| Add Newspaper | `add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag n [/s STATUS]` | +| Add eNewspaper | `add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag en /l LINK [/s STATUS]` | +| Delete Listing | `delete /id ` | +| Listing All Items | `list [/tag /g /s ]` | +| Find Specific Listing | `find [/t OR /i <ISBN of item> OR /a AUTHOR OR /id ID]` | +| Edit a Listing | `edit /id ID /argument1 <ARGUMENT1> [/argument2 <ARGUMENT2>]...` | +| Event Add | `eventadd /t TITLE /date DATE [/desc DESCRIPTION]` (Format for date DD-MM-YYYY) | +| Event Delete | `eventdelete /id INDEX` | +| Event Listing | `eventlist` | +| Event Edit | `eventedit /id INDEX [/date DATE /desc DESCRIPTION]`(Format for date DD-MM-YYYY) | +| Summary | `summary` | +| View Help | `help` | +| Exit | `exit` | -## Command Summary -{Give a 'cheat sheet' of commands here} -* Add todo `todo n/TODO_NAME d/DEADLINE` diff --git a/docs/diagram/AddActivityDiagram.puml b/docs/diagram/AddActivityDiagram.puml new file mode 100644 index 0000000000..ebdb1ca130 --- /dev/null +++ b/docs/diagram/AddActivityDiagram.puml @@ -0,0 +1,88 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:execute(); +:Add Info to Log; +:parseAddCommand(); +if (isBook?) then (yes) + :addBook(); + :parseAddBook(); + :parseBookArgs(); + if (isValid?) then (yes) + :createBook(); + else (no) + stop + endif + +else if (isEBook?) then (yes) + :addEBook(); + :parseAddEBook(); + :parseEBookArgs(); + if (isValid?) then (yes) + :createEBook(); + else (no) + stop + endif + +else if (isCD?) then (yes) + :addCD(); + :parseAddCD(); + :parseCDArgs(); + if (isValid?) then (yes) + :createCD(); + else (no) + stop + endif + +else if (isMagazine?) then (yes) + :addMagazine(); + :parseAddMagazine(); + :parseMagazineArgs(); + if (isValid?) then (yes) + :createMagazine(); + else (no) + stop + endif + +else if (isEMagazine?) then (yes) + :addEMagazine(); + :parseAddEMagazine(); + :parseEMagazineArgs(); + if (isValid?) then (yes) + :createEMagazine(); + else (no) + stop + endif + +else if (isNewspaper?) then (yes) + :addNewspaper(); + :parseAddNewspaper(); + :parseNewspaperArgs(); + if (isValid?) then (yes) + :createNewspaper(); + else (no) + stop + endif + +else if (isENewspaper?) then (yes) + :addENewspaper(); + :parseAddENewspaper(); + :parseENewspaperArgs(); + if (isValid?) then (yes) + :createENewspaper(); + else (no) + stop + endif + +else (no) + stop +endif + +:Add to Resource List; +:Display Add Message; +:Add Info to Log; + +stop + +@enduml diff --git a/docs/diagram/AddSequenceDiagram.puml b/docs/diagram/AddSequenceDiagram.puml new file mode 100644 index 0000000000..f405a12d20 --- /dev/null +++ b/docs/diagram/AddSequenceDiagram.puml @@ -0,0 +1,105 @@ +@startuml +!include Style.puml +skinparam ArrowFontStyle plain + +box Parser +participant ":Parser" as PARSER PARSER_COLOR +participant ":ParseResource*" as PARSERESOURCE PARSER_COLOR +participant ":ParseResource" as PARSE PARSER_COLOR +participant ":ParseAttribute" as PARSEATTRIBUTE PARSER_COLOR +end box + +box Command +participant ":CommandResult" as COMMANDRESULT COMMAND_COLOR +participant ":AddCommand" as ADDCOMMAND COMMAND_COLOR +end box + +participant ":CreateResource*" as CREATE CREATE_COLOR + +activate PARSER PARSER_COLOR +PARSER -[PARSER_COLOR]> ADDCOMMAND : execute(input, container) +activate ADDCOMMAND COMMAND_COLOR + +ADDCOMMAND -[COMMAND_COLOR]> PARSER : parseAddCommand(input) + +PARSER -[PARSER_COLOR]> PARSER : checkFirstItem(input) +activate PARSER PARSER_COLOR +PARSER --[PARSER_COLOR]> PARSER : tag: String +deactivate PARSER + +PARSER --[PARSER_COLOR]> ADDCOMMAND : tag: String + +alt resource* + ADDCOMMAND -[COMMAND_COLOR]> ADDCOMMAND : addResource*(input, container) + activate ADDCOMMAND COMMAND_COLOR + + ADDCOMMAND -[COMMAND_COLOR]> PARSERESOURCE : parseAddResource*(input) + activate PARSERESOURCE PARSER_COLOR + + PARSERESOURCE -[PARSER_COLOR]> PARSERESOURCE : parseResource*Args(input) + activate PARSERESOURCE PARSER_COLOR + + PARSERESOURCE -[PARSER_COLOR]> PARSE : hasUnusedSlash(input) + activate PARSE PARSER_COLOR + PARSE --[PARSER_COLOR]> PARSERESOURCE : hasUnusedSlash: boolean + deactivate PARSE + + PARSERESOURCE -[PARSER_COLOR]> PARSERESOURCE : hasInvalidArgurment(input) + activate PARSERESOURCE PARSER_COLOR + PARSERESOURCE --[PARSER_COLOR]> PARSERESOURCE : hasInvalidArgurment: boolean + deactivate PARSERESOURCE + + PARSERESOURCE -[PARSER_COLOR]> PARSEATTRIBUTE : parseAttribute*(input) + activate PARSEATTRIBUTE PARSER_COLOR + + PARSEATTRIBUTE -[PARSER_COLOR]> PARSERESOURCE : countDuplicate(input, pattern) + activate PARSEATTRIBUTE PARSER_COLOR + PARSERESOURCE --[PARSER_COLOR]> PARSEATTRIBUTE : hasDuplicate: int + deactivate PARSEATTRIBUTE + + PARSEATTRIBUTE --[PARSER_COLOR]> PARSERESOURCE + deactivate PARSEATTRIBUTE + + PARSERESOURCE --[PARSER_COLOR]> PARSERESOURCE : isOptionalMatching: Boolean[] + deactivate PARSERESOURCE + + PARSERESOURCE -[PARSER_COLOR]> PARSERESOURCE : checkEmptyResource*Args(args) + activate PARSERESOURCE PARSER_COLOR + + PARSERESOURCE -[PARSER_COLOR]> PARSERESOURCE : checkEmptyArg(args) + activate PARSERESOURCE PARSER_COLOR + PARSERESOURCE --[PARSER_COLOR]> PARSERESOURCE + deactivate PARSERESOURCE + + PARSERESOURCE --[PARSER_COLOR]> PARSERESOURCE + deactivate PARSERESOURCE + + PARSERESOURCE --[PARSER_COLOR]> ADDCOMMAND : values: String[] + + ADDCOMMAND -[COMMAND_COLOR]> CREATE : createResource*(values, resourceID) + activate CREATE CREATE_COLOR + CREATE --[CREATE_COLOR]> ADDCOMMAND : newResource*: Resource* + deactivate CREATE + + ADDCOMMAND -[COMMAND_COLOR]> PARSERESOURCE : resetResource*Args() + PARSERESOURCE --[PARSER_COLOR]> ADDCOMMAND + deactivate PARSERESOURCE + + ADDCOMMAND --[COMMAND_COLOR]> ADDCOMMAND + deactivate ADDCOMMAND + + create COMMANDRESULT + ADDCOMMAND -[COMMAND_COLOR]> COMMANDRESULT : feedbackToUser: String + + activate COMMANDRESULT COMMAND_COLOR + COMMANDRESULT --[COMMAND_COLOR]> PARSER + deactivate COMMANDRESULT + +else else + ADDCOMMAND --[COMMAND_COLOR]> PARSER + deactivate ADDCOMMAND +end + +deactivate PARSER + +@enduml \ No newline at end of file diff --git a/docs/diagram/Architecture.puml b/docs/diagram/Architecture.puml new file mode 100644 index 0000000000..8284d3294c --- /dev/null +++ b/docs/diagram/Architecture.puml @@ -0,0 +1,34 @@ +@startuml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> +!include style.puml + +Package " "<<Rectangle>>{ + Class UI UI_COLOR + Class Parser PARSER_COLOR + Class Command COMMAND_COLOR + Class Syslib #grey + Class Storage STORAGE_COLOR +} + +Class "<$user>" as User STORAGE_COLOR_T2 +Class "<$documents>" as File UI_COLOR_T1 + +User ..> UI + +Syslib -[#grey]-> UI +Syslib -[#grey]-> Parser + + +Syslib -[#grey]-> Storage + +Parser -[#blue]-> Command +Parser ..>Storage +Command ..>Parser + +Storage -[#red]>Parser + +Storage ..>File +File ..>Storage +@enduml \ No newline at end of file diff --git a/docs/diagram/CommandClassDiagram.puml b/docs/diagram/CommandClassDiagram.puml new file mode 100644 index 0000000000..89794b9b38 --- /dev/null +++ b/docs/diagram/CommandClassDiagram.puml @@ -0,0 +1,49 @@ +@startuml + +class Parser { + - resourceList: List<Resource> + - eventList: List<Event> + - container: GenericList<Resource, Event> + - commandProcessor: HashMap<String, Command> + - commands: ArrayList<String> + + + processUserResponse(response: String): void + + removeFirstWord(response: String): String + + parseAddCommand(statement: String): String + + getStatusFromString(statusString: String): Status +} + +abstract class Command { + - args: String[] + - required: boolean[] + {abstract} + execute(statement: String, container: GenericList<Resource, Event>): CommandResult + + validateStatement(statement: String, values: String[]): void + + checkDuplicate(statement: String, pointer: int): String + + parseArgument(statement: String): String[] + + getMatch(statement: String, pointer: int): String + + checkMatch(matched: String, pointer: int): void + + parseInt(value: String): int +} + +class GenericList<R, E> { + - resourceList: List<R> + - eventList: List<E> + + + GenericList(resourceList: List<R>, eventList: List<E>) + + getResourceList(): List<R> + + getEventList(): List<E> + + setResourceList(resourceList: List<R>): void + + setEventList(eventList: List<E>): void +} + +class CommandResult { + + feedbackToUser: String + + + CommandResult(feedbackToUser: String) +} + +Parser "1" -down-> "*" Command : uses +Command "*" -right-> "1" GenericList : uses +Command "*" -down-> "1" CommandResult : produces + +@enduml diff --git a/docs/diagram/ComponentDiagram.puml b/docs/diagram/ComponentDiagram.puml new file mode 100644 index 0000000000..9605d4ae50 --- /dev/null +++ b/docs/diagram/ComponentDiagram.puml @@ -0,0 +1,55 @@ +@startuml +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +skinparam ArrowFontStyle plain + +participant ":Syslib" as Syslib #GREY +participant ":Storage" as Storage STORAGE_COLOR +participant ":Parser" as Parser PARSER_COLOR +participant ":UI" as UI UI_COLOR +participant ":Command" as Command COMMAND_COLOR + +activate Syslib #GREY +Syslib -[#GREY]>Storage: Load data file + +activate Storage STORAGE_COLOR +Storage --[STORAGE_COLOR]> Syslib: Lists with loaded data +deactivate Storage + +Syslib -[#GREY]>Parser: Set Lists to returned lists +activate Parser PARSER_COLOR +deactivate Parser + +Syslib -[#GREY]>UI: Get user input +activate UI UI_COLOR +UI --[UI_COLOR]> Syslib: String User Input +deactivate UI + +Syslib -[#GREY]> Parser : Process User Response +activate Parser PARSER_COLOR + +Parser -[PARSER_COLOR]> Command: Execute Appropriate Command +activate Command COMMAND_COLOR +Command --[COMMAND_COLOR]>Parser: feedback to user +deactivate Command +deactivate Parser +Syslib -[#GREY]> Storage: Save current data to file +activate Storage STORAGE_COLOR +Storage -[STORAGE_COLOR]> Parser: Get data in lists +activate Parser PARSER_COLOR +Parser --> Storage +deactivate Parser +deactivate Storage + + + + + + + + + +@enduml \ No newline at end of file diff --git a/docs/diagram/EditActivityDiagram.puml b/docs/diagram/EditActivityDiagram.puml new file mode 100644 index 0000000000..9661c98149 --- /dev/null +++ b/docs/diagram/EditActivityDiagram.puml @@ -0,0 +1,46 @@ + @startuml +'https://plantuml.com/activity-diagram-beta + +start +:execute(); +if (hasOneArg?) then (true) + :findResourceByID(); + if (foundResouce?) then (yes) + :editResource(); + + if (Book?) then (yes) + :validateBook(); + :editBook(); + + else if (CD?) then (yes) + :validateCD(); + :editCD(); + else if (Magazine?) then (yes) + :validateMagazine(); + :editMagazine(); + else if (Newspaper?) then (yes) + :validateNewspaper(); + :editNewspaper(); + else (no) + stop + endif + :Update Resource List; + stop + + else (no) + stop + + + endif + + +else (false) + stop + + + +endif + + + +@enduml diff --git a/docs/diagram/EventAdd.puml b/docs/diagram/EventAdd.puml new file mode 100644 index 0000000000..0f39456c93 --- /dev/null +++ b/docs/diagram/EventAdd.puml @@ -0,0 +1,48 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +actor User +participant "Parser" as Parser PARSER_COLOR +participant "EventAddCommand" as Command COMMAND_COLOR +participant "EventList" as EventList DATA_COLOR + +User -> Parser : inputs statement +activate Parser +Parser -> Command : execute(statement, parser) +activate Command + +Command -> Command : parseArgument(statement) +activate Command +Command --> Command : return +deactivate Command + +Command -> Command : validateStatement(statement, values) +activate Command +Command --> Command : return +deactivate Command + +Command -> Command : parseDate(values[1]) +activate Command +Command --> Command : currentDate +deactivate Command + +Command -> Command : binarySearch(parser, currentDate) +activate Command +Command --> Command : index +deactivate Command + +Command -> EventList : add(index, new Event(...)) +activate EventList +EventList --> Command : return +deactivate EventList +Command --> Parser : new CommandResult(feedbackToUser) +deactivate Command + +Parser --> User : result +deactivate Parser + +@enduml diff --git a/docs/diagram/EventEdit.puml b/docs/diagram/EventEdit.puml new file mode 100644 index 0000000000..36af6d2d18 --- /dev/null +++ b/docs/diagram/EventEdit.puml @@ -0,0 +1,81 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +actor User +participant "UI" as UI UI_COLOR +participant ":Parser" as Parser PARSER_COLOR +participant ":EventEditCommand" as EditCommand COMMAND_COLOR +participant ":Command" as Command COMMAND_COLOR_T2 +participant ":EventList" as EventList DATA_COLOR + +User -> UI : statement +activate UI UI_COLOR +UI -> Parser : process(statement) +activate Parser PARSER_COLOR +Parser -> EditCommand : execute(statement, container) +activate EditCommand COMMAND_COLOR + +EditCommand -> Command : parseArgument(statement) +activate Command COMMAND_COLOR_T2 +Command --> EditCommand : String[] +deactivate Command + +EditCommand -> Command : validateStatement(statement, values) +activate Command COMMAND_COLOR_T2 +Command --> EditCommand : return +deactivate Command +destroy Command + +EditCommand -> EditCommand : parseInt(values[0]) +activate EditCommand COMMAND_COLOR +EditCommand --> EditCommand : index:int +deactivate EditCommand + +EditCommand -> EventList : getEventList() +activate EventList DATA_COLOR +EventList -> EventList : get(index) +activate EventList DATA_COLOR +EventList --> EventList : :Event +deactivate EventList DATA_COLOR +EventList --> EditCommand : oldEvent:Event +deactivate EventList DATA_COLOR + +EditCommand -> EventList : getEventList() +activate EventList DATA_COLOR +EventList -> EventList : remove(index) +activate EventList DATA_COLOR +EventList --> EventList : return +deactivate EventList DATA_COLOR +EventList --> EditCommand : return +deactivate EventList DATA_COLOR + +EditCommand -> EditCommand : binarySearch(container, date) +activate EditCommand COMMAND_COLOR +EditCommand --> EditCommand : idx:int +deactivate EditCommand + +EditCommand -> EventList : getEventList() +activate EventList DATA_COLOR +EventList -> EventList : add(idx,editedEvent) +activate EventList DATA_COLOR +EventList --> EventList : return +deactivate EventList DATA_COLOR +EventList --> EditCommand : return +deactivate EventList DATA_COLOR +destroy EventList + +EditCommand --> UI : showline() +deactivate EditCommand +destroy EditCommand + +deactivate Parser +destroy Parser +deactivate UI +destroy UI + + +@enduml diff --git a/docs/diagram/FindSequenceDiagram.puml b/docs/diagram/FindSequenceDiagram.puml new file mode 100644 index 0000000000..43f2d992cf --- /dev/null +++ b/docs/diagram/FindSequenceDiagram.puml @@ -0,0 +1,59 @@ +@startuml +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +skinparam ArrowFontStyle plain + +Actor User as user USER_COLOR +participant ":UI" as UI UI_COLOR +participant ":FindCommand" as FINDCOMMAND COMMAND_COLOR +participant ":GenericList<Resource, Event>" as GENERICLIST STORAGE_COLOR +participant ":Resource" as RESOURCE PARSER_COLOR + +user -[USER_COLOR]> UI : "find '/t Title1'" +activate UI UI_COLOR + +UI -[UI_COLOR]> FINDCOMMAND : execute() +activate FINDCOMMAND COMMAND_COLOR + +FINDCOMMAND -[COMMAND_COLOR]> FINDCOMMAND: parseArgument() +activate FINDCOMMAND COMMAND_COLOR +FINDCOMMAND --[COMMAND_COLOR]> FINDCOMMAND: String[] values +deactivate FINDCOMMAND COMMAND_COLOR + +FINDCOMMAND -[COMMAND_COLOR]> FINDCOMMAND: validateStatement(values) +activate FINDCOMMAND COMMAND_COLOR +FINDCOMMAND --[COMMAND_COLOR]> FINDCOMMAND: return +deactivate FINDCOMMAND COMMAND_COLOR + +alt allValuesNull + FINDCOMMAND --[COMMAND_COLOR]> UI: INVALID_ARGUMENT_MESSAGE +else !allValuesNull + FINDCOMMAND -[COMMAND_COLOR]> GENERICLIST: getResourcesList() + activate GENERICLIST STORAGE_COLOR + GENERICLIST --[STORAGE_COLOR]> FINDCOMMAND: List<Resource> resources + deactivate GENERICLIST STORAGE_COLOR + destroy GENERICLIST + + loop for each Resource in resources + FINDCOMMAND -[COMMAND_COLOR]> RESOURCE: isResourceMatch(values) + activate RESOURCE PARSER_COLOR + RESOURCE --[PARSER_COLOR]> FINDCOMMAND: boolean match + deactivate RESOURCE PARSER_COLOR + destroy RESOURCE + + alt match + FINDCOMMAND -[COMMAND_COLOR]> FINDCOMMAND : Add to matchedResources + end + end + + FINDCOMMAND --[COMMAND_COLOR]> UI: displayResults(matchedResources) + deactivate UI UI_COLOR + deactivate FINDCOMMAND COMMAND_COLOR + destroy FINDCOMMAND + destroy UI +end + +@enduml diff --git a/docs/diagram/ListSequenceDiagram.puml b/docs/diagram/ListSequenceDiagram.puml new file mode 100644 index 0000000000..d5ad2c2f36 --- /dev/null +++ b/docs/diagram/ListSequenceDiagram.puml @@ -0,0 +1,61 @@ +@startuml +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +skinparam ArrowFontStyle plain + +participant ":Parser" as PARSER PARSER_COLOR + +box Command +participant ":CommandResult" as COMMANDRESULT COMMAND_COLOR +participant ":ListCommand" as LISTCOMMAND COMMAND_COLOR +participant ":Command" as COMMAND COMMAND_COLOR + +end box + +participant ":UI" as UI UI_COLOR + +PARSER -[PARSER_COLOR]> LISTCOMMAND: execute("/tag B") +activate PARSER PARSER_COLOR + +activate LISTCOMMAND COMMAND_COLOR + + +LISTCOMMAND -[COMMAND_COLOR]> COMMAND : parseArg("/tag B) +activate COMMAND COMMAND_COLOR +COMMAND --[COMMAND_COLOR]> LISTCOMMAND : String[] values +deactivate +LISTCOMMAND -[COMMAND_COLOR]> COMMAND : validate("/tag B", values) +activate COMMAND COMMAND_COLOR +COMMAND --[COMMAND_COLOR]> LISTCOMMAND : +deactivate COMMAND COMMAND_COLOR + + +LISTCOMMAND -[COMMAND_COLOR]> LISTCOMMAND : filterResources() +activate LISTCOMMAND COMMAND_COLOR + +LISTCOMMAND -[COMMAND_COLOR]> LISTCOMMAND : hasFilters() +activate LISTCOMMAND COMMAND_COLOR +LISTCOMMAND --[COMMAND_COLOR]> LISTCOMMAND +deactivate LISTCOMMAND COMMAND_COLOR + + +LISTCOMMAND -[COMMAND_COLOR]> UI : showResourcesDetails(matchedResources) +activate UI UI_COLOR +UI --[UI_COLOR]> LISTCOMMAND : messageToDisplay +deactivate UI +deactivate LISTCOMMAND +create COMMANDRESULT +LISTCOMMAND -[COMMAND_COLOR]> COMMANDRESULT : feedbackToUser +deactivate LISTCOMMAND COMMAND_COLOR +activate COMMANDRESULT COMMAND_COLOR +COMMANDRESULT --[COMMAND_COLOR]> PARSER +deactivate COMMANDRESULT + + + + + +@enduml \ No newline at end of file diff --git a/docs/diagram/Parsing.puml b/docs/diagram/Parsing.puml new file mode 100644 index 0000000000..f7bc567182 --- /dev/null +++ b/docs/diagram/Parsing.puml @@ -0,0 +1,37 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +actor User +participant "Parser" as Parser PARSER_COLOR +participant "Command" as Command DATA_COLOR + +User -> Parser : inputs statement +activate Parser + +Parser -> Command : execute(statement) +activate Command + +Command -> Command : parseArgument(statement) +activate Command +Command -> Command : getMatch(statement, pointer) +Command -> Command : checkMatch(matched, pointer) +Command --> Command : return +deactivate Command + +Command -> Command : validateStatement(statement, values) + +activate Command +Command -> Command : checkDuplicate(statement, pointer) +Command --> Command : return + +deactivate Command +Command --> Parser : CommandResult +deactivate Command + +Parser --> User : result +deactivate Parser +@enduml diff --git a/docs/diagram/ShowResourcesSequenceDiagram.puml b/docs/diagram/ShowResourcesSequenceDiagram.puml new file mode 100644 index 0000000000..992a4f4828 --- /dev/null +++ b/docs/diagram/ShowResourcesSequenceDiagram.puml @@ -0,0 +1,65 @@ +@startuml +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +skinparam ArrowFontStyle plain + +participant ":ListCommand" as ListCommand COMMAND_COLOR +participant ":UI" as UI UI_COLOR +participant ":ResourceDisplayFormatter" as ResourceDisplayFormatter PARSER_COLOR +participant ":Resource" as Resource #grey + + + +ListCommand -[COMMAND_COLOR]>UI : showResourcesDetails(list) +activate UI UI_COLOR +create ResourceDisplayFormatter +UI -[UI_COLOR]>ResourceDisplayFormatter : ResourceDisplayFormatter(list) +activate ResourceDisplayFormatter + +ResourceDisplayFormatter -[PARSER_COLOR]> ResourceDisplayFormatter : buildDisplayHeader(resourcesList) +activate ResourceDisplayFormatter + +ResourceDisplayFormatter -[PARSER_COLOR]> Resource : checkColumnsWidth(columnsWidth) +activate Resource +Resource --> ResourceDisplayFormatter : columnsWidth +deactivate Resource + +ResourceDisplayFormatter --> ResourceDisplayFormatter : displayFormat +deactivate ResourceDisplayFormatter + + +ResourceDisplayFormatter -[PARSER_COLOR]> ResourceDisplayFormatter : buildFormatter(displayFormat) +activate ResourceDisplayFormatter +ResourceDisplayFormatter --> ResourceDisplayFormatter : Formatter +deactivate ResourceDisplayFormatter + + +ResourceDisplayFormatter --> UI : resourceDisplayFormatter +deactivate ResourceDisplayFormatter + +loop until end of ResourceList +UI -[UI_COLOR]>ResourceDisplayFormatter : setDisplayFormatter(Resource) +activate ResourceDisplayFormatter +ResourceDisplayFormatter -[PARSER_COLOR]> Resource : toTableFormat(displayFormat) +activate Resource +Resource --> ResourceDisplayFormatter : resourceDetails +deactivate Resource +ResourceDisplayFormatter --[PARSER_COLOR]> UI : resourceDisplayFormatter +deactivate ResourceDisplayFormatter +end + +UI -[UI_COLOR]>ResourceDisplayFormatter : getFinalDisplayFormat() +activate ResourceDisplayFormatter +ResourceDisplayFormatter --[PARSER_COLOR]> UI : messageToDisplay +deactivate ResourceDisplayFormatter +ResourceDisplayFormatter -[hidden]> UI : messageToDisplay + +UI --[UI_COLOR]> ListCommand : messageToDisplay +deactivate UI +destroy ResourceDisplayFormatter + + +@enduml \ No newline at end of file diff --git a/docs/diagram/Storage.puml b/docs/diagram/Storage.puml new file mode 100644 index 0000000000..0e4f09b503 --- /dev/null +++ b/docs/diagram/Storage.puml @@ -0,0 +1,43 @@ +@startuml +'https://plantuml.com/class-diagram + +class Storage { + - filePath : String + - container : GenericList<Resource, Event> + __ + + Storage(filePath : String, container : GenericList<Resource, Event>) + + load(resources : List<Resource>, events : List<Event>) : void + + save() : void + - ensureFileExists() : void + - createResource(data : String[], id : int) : Resource + - createEvent(data : String[]) : Event + - getResourceSaveFormat(resourceToSave : Resource) : String + - getEventSaveFormat(eventToSave : Event) : String + {static} - setupLogger() : void +} + +class GenericList<R, E> { + - resourcesList : List<R> + - eventsList : List<E> + -- + + GenericList(resourcesList: List<R>, eventsList: List<E>) + + getResourcesList() : List<R> + + getEventsList() : List<e> + + setResourcesList(resourcesList: List<R>): void + + setEventsList(eventsList: List<E>): void +} + +class Resource { +} + +class Event { + +} + +' Relationships +Storage "1" -- "1" GenericList : contains +GenericList "1" - "*" Resource : contains +GenericList "1" - "*" Event : contains + + +@enduml diff --git a/docs/diagram/Style.puml b/docs/diagram/Style.puml new file mode 100644 index 0000000000..23f19c5b93 --- /dev/null +++ b/docs/diagram/Style.puml @@ -0,0 +1,85 @@ +/' + 'Commonly used styles and colors across diagrams. + 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more + 'comprehensive list of skinparams. + '/ + + +'T1 through T4 are shades of the original color from lightest to darkest + +!define SYSLIB_COLOR #grey + +!define UI_COLOR #1D8900 +!define UI_COLOR_T1 #83E769 +!define UI_COLOR_T2 #3FC71B +!define UI_COLOR_T3 #166800 +!define UI_COLOR_T4 #0E4100 + +!define PARSER_COLOR #3333C4 +!define PARSER_COLOR_T1 #C8C8FA +!define PARSER_COLOR_T2 #6A6ADC +!define PARSER_COLOR_T3 #1616B0 +!define PARSER_COLOR_T4 #101086 + +!define STORAGE_COLOR #9D0012 +!define STORAGE_COLOR_T1 #F97181 +!define STORAGE_COLOR_T2 #E41F36 +!define STORAGE_COLOR_T3 #7B000E +!define STORAGE_COLOR_T4 #51000A + +!define COMMAND_COLOR #A38300 +!define COMMAND_COLOR_T1 #FFE374 +!define COMMAND_COLOR_T2 #EDC520 +!define COMMAND_COLOR_T3 #806600 +!define COMMAND_COLOR_T4 #544400 + +!define DATA_COLOR #544400 + +!define USER_COLOR #000000 + +!define CREATE_COLOR #CE5A67 + +skinparam Package { + BackgroundColor #FFFFFF + BorderThickness 1 + FontSize 16 +} + +skinparam Class { + FontColor #FFFFFF + FontSize 15 + BorderThickness 1 + BorderColor #FFFFFF + StereotypeFontColor #FFFFFF + FontName Arial +} + +skinparam Actor { + BorderColor USER_COLOR + Color USER_COLOR + FontName Arial +} + +skinparam Sequence { + MessageAlign center + BoxFontSize 15 + BoxPadding 0 + BoxFontColor #FFFFFF + FontName Arial +} + +skinparam Participant { + FontColor #FFFFFFF + Padding 20 +} + +skinparam ArrowFontStyle bold +skinparam MinClassWidth 50 +skinparam ParticipantPadding 10 +skinparam Shadowing false +skinparam DefaultTextAlignment center +skinparam packageStyle Rectangle + +hide footbox +hide members +hide circle \ No newline at end of file diff --git a/docs/diagram/SummarySequenceDiagram.puml b/docs/diagram/SummarySequenceDiagram.puml new file mode 100644 index 0000000000..e47327fc56 --- /dev/null +++ b/docs/diagram/SummarySequenceDiagram.puml @@ -0,0 +1,58 @@ +@startuml +!include Style.puml +!include <office/Concepts/globe_internet> +!include <office/Concepts/documents> +!include <office/Users/user> + +skinparam ArrowFontStyle plain + +Actor User as user USER_COLOR +participant ":UI" as UI UI_COLOR +participant ":Parser" as PARSER PARSER_COLOR +participant ":SummaryCommand" as SummaryCommand COMMAND_COLOR +participant ":ResourceList" as Resource COMMAND_COLOR_T2 +participant ":EventList" as Event COMMAND_COLOR_T3 + +user -[USER_COLOR]> UI : "summary" +activate UI UI_COLOR +UI -> PARSER : process("summary") +deactivate +destroy UI +activate PARSER PARSER_COLOR +PARSER -> SummaryCommand : execute(statement, container) +activate SummaryCommand COMMAND_COLOR + +loop for each Resource in resourcesList + SummaryCommand -> Resource : getResourcesList() + activate Resource COMMAND_COLOR_T2 + Resource -> Resource : isinstanceof + activate Resource COMMAND_COLOR_T2 + Resource --> Resource + deactivate Resource + Resource --> SummaryCommand + deactivate Resource + destroy Resource +end + +SummaryCommand -> Event : getEventsList() +activate Event COMMAND_COLOR_T3 +Event --> SummaryCommand : events:List<Event> +deactivate Event +destroy Event + +SummaryCommand -> SummaryCommand : getUpcomingEvents(events, 3); +activate SummaryCommand COMMAND_COLOR +SummaryCommand --> SummaryCommand : UpcomingEvents:List<Event> +deactivate SummaryCommand + +loop for each Resource type +SummaryCommand -> SummaryCommand : generateBar(:int) +activate SummaryCommand COMMAND_COLOR +SummaryCommand --> SummaryCommand : :String +deactivate SummaryCommand +end +SummaryCommand --> PARSER : :String +deactivate SummaryCommand +destroy SummaryCommand +destroy PARSER +@enduml diff --git a/docs/images/AddActivityDiagram.png b/docs/images/AddActivityDiagram.png new file mode 100644 index 0000000000..2190c9b23b Binary files /dev/null and b/docs/images/AddActivityDiagram.png differ diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png new file mode 100644 index 0000000000..6e449a1bfb Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png new file mode 100644 index 0000000000..7df855c3e0 Binary files /dev/null and b/docs/images/ArchitectureDiagram.png differ diff --git a/docs/images/CommandClassDiagram.png b/docs/images/CommandClassDiagram.png new file mode 100644 index 0000000000..fab14575a2 Binary files /dev/null and b/docs/images/CommandClassDiagram.png differ diff --git a/docs/images/ComponentDiagram.png b/docs/images/ComponentDiagram.png new file mode 100644 index 0000000000..804ec2a4d2 Binary files /dev/null and b/docs/images/ComponentDiagram.png differ diff --git a/docs/images/EditActivityDiagram.png b/docs/images/EditActivityDiagram.png new file mode 100644 index 0000000000..f569904915 Binary files /dev/null and b/docs/images/EditActivityDiagram.png differ diff --git a/docs/images/EventAdd.png b/docs/images/EventAdd.png new file mode 100644 index 0000000000..4de2f539af Binary files /dev/null and b/docs/images/EventAdd.png differ diff --git a/docs/images/EventEditDiagram.png b/docs/images/EventEditDiagram.png new file mode 100644 index 0000000000..dc606fd3d2 Binary files /dev/null and b/docs/images/EventEditDiagram.png differ diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png new file mode 100644 index 0000000000..2857ce76f0 Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ diff --git a/docs/images/List Screenshots/fontsize.png b/docs/images/List Screenshots/fontsize.png new file mode 100644 index 0000000000..00223b5ba8 Binary files /dev/null and b/docs/images/List Screenshots/fontsize.png differ diff --git a/docs/images/List Screenshots/listexampleoutput.png b/docs/images/List Screenshots/listexampleoutput.png new file mode 100644 index 0000000000..a15d6993fe Binary files /dev/null and b/docs/images/List Screenshots/listexampleoutput.png differ diff --git a/docs/images/List Screenshots/listimage.png b/docs/images/List Screenshots/listimage.png new file mode 100644 index 0000000000..af2b093330 Binary files /dev/null and b/docs/images/List Screenshots/listimage.png differ diff --git a/docs/images/List Screenshots/listproblemimg.png b/docs/images/List Screenshots/listproblemimg.png new file mode 100644 index 0000000000..5232eaeab5 Binary files /dev/null and b/docs/images/List Screenshots/listproblemimg.png differ diff --git a/docs/images/List Screenshots/resizeicon.png b/docs/images/List Screenshots/resizeicon.png new file mode 100644 index 0000000000..bc56136be5 Binary files /dev/null and b/docs/images/List Screenshots/resizeicon.png differ diff --git a/docs/images/List Screenshots/terminalimg.png b/docs/images/List Screenshots/terminalimg.png new file mode 100644 index 0000000000..14790090c8 Binary files /dev/null and b/docs/images/List Screenshots/terminalimg.png differ diff --git a/docs/images/ListSequenceDiagram.png b/docs/images/ListSequenceDiagram.png new file mode 100644 index 0000000000..0f89431170 Binary files /dev/null and b/docs/images/ListSequenceDiagram.png differ diff --git a/docs/images/Parsing.png b/docs/images/Parsing.png new file mode 100644 index 0000000000..415170c8e4 Binary files /dev/null and b/docs/images/Parsing.png differ diff --git a/docs/images/ShowResourcesSequenceDiagram.png b/docs/images/ShowResourcesSequenceDiagram.png new file mode 100644 index 0000000000..9c64eea7c9 Binary files /dev/null and b/docs/images/ShowResourcesSequenceDiagram.png differ diff --git a/docs/images/StorageDeleteResource.png b/docs/images/StorageDeleteResource.png new file mode 100644 index 0000000000..ac5d69a7fe Binary files /dev/null and b/docs/images/StorageDeleteResource.png differ diff --git a/docs/images/StorageDiagram.png b/docs/images/StorageDiagram.png new file mode 100644 index 0000000000..be50afcea9 Binary files /dev/null and b/docs/images/StorageDiagram.png differ diff --git a/docs/images/StorageEditResource.png b/docs/images/StorageEditResource.png new file mode 100644 index 0000000000..4a25a7aef6 Binary files /dev/null and b/docs/images/StorageEditResource.png differ diff --git a/docs/images/StorageNewResources.png b/docs/images/StorageNewResources.png new file mode 100644 index 0000000000..15f15b3348 Binary files /dev/null and b/docs/images/StorageNewResources.png differ diff --git a/docs/images/SummaryDiagram.png b/docs/images/SummaryDiagram.png new file mode 100644 index 0000000000..db59dca85c Binary files /dev/null and b/docs/images/SummaryDiagram.png differ diff --git a/docs/images/SysLib Logo.png b/docs/images/SysLib Logo.png new file mode 100644 index 0000000000..6ff0f03f02 Binary files /dev/null and b/docs/images/SysLib Logo.png differ diff --git a/docs/images/findcommandoutput.png b/docs/images/findcommandoutput.png new file mode 100644 index 0000000000..f9ba7edcb2 Binary files /dev/null and b/docs/images/findcommandoutput.png differ diff --git a/docs/images/helpexampleoutput.png b/docs/images/helpexampleoutput.png new file mode 100644 index 0000000000..c3331cc117 Binary files /dev/null and b/docs/images/helpexampleoutput.png differ diff --git a/docs/team/000verflow.md b/docs/team/000verflow.md new file mode 100644 index 0000000000..3b25cd9341 --- /dev/null +++ b/docs/team/000verflow.md @@ -0,0 +1,85 @@ +# Velusamy Sathiakumar Ashok Balaji - Project Portfolio Page + +## Overview + +SysLib is a CLI Library Management software for system librarians especially those who are fast typists. + +From viewing, adding, searching, editing, deleting and saving, SysLib provides all the features needed to manage library resources and events. + +### Summary of Contributions + +#### Code contributed + +View the code I contributed via the tp Code Dashboard link [here](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=000verflow&breakdown=true). + + +#### Enhancements implemented + +**Enhancements**: +1. Classes and methods to format display and messages to users. +2. Find command to search and return resources that match all given arguments. +3. Storage class that can save and load resources and events. +4. GenericList class to act as a container for both Resource lists and Event lists. + + +**Details:** + +**1. Classes and methods to format display and messages to users** + +Main classes and methods implemented: +`Syslib` class +`UI` class + +Created the Syslib and UI class's to ensure proper OOP used for running the software, taking inputs from users, and displaying output through the UI class. + +**2. Find feature for resources** + +Main implementation: `FindCommand` class + +Implemented the **Find** feature that will return resources that match given filters such as `author`, `title`, `id` and `isbn`. +Only returns results that match `ALL` the given filters not `ANY`. + +**3. Storage Class** + +Main implementation: `Storage` class + +Implemented the Storage class which allows users to `save` resources and events that have been added to the list. The resources and events will be loaded back into the appropriate lists, when the program is run again. +The storage class is filled with exception catching mechanisms to ensure minimal bugs in case of corrupted data. + +**4. GenericList Class** + +Main Implementation: `GenericList` class + +Implemented the GenericList class which allows us to store 2 lists of Resources and Events separately. This allows us to handle loading, storing and command handling efficiently. +GenericList currently is just used as a container for both types of lists. + +#### Contributions to the UG: + +Sections contributed: +- Overall structure including Contents, Quick start, Features. +- Add command +- Find Command +- Command Summary +- Save your work section +- Finding bugs in peer's sections + +#### Contributions to the DG: + +Sections contributed: +- Storage Class section and Storage class diagram +- Find Command section and FindCommand sequence diagram +- Manual testing for data saving +- Structural changes +- Introduction and getting started + +#### Contributions to team-based tasks + +- Reviewed multiple peer pull request +- Help troubleshoot when checks fail on GitHub +- Bug tested 2.0 and 2.1 Jar's + +#### Review/mentoring contributions: + +- Helped test other group's project during PE-D, creating 5 bug issues +- Peer reviewed other team's developers guide in their [PR]("https://github.com/nus-cs2113-AY2324S1/tp/pull/8") + diff --git a/docs/team/bnjm2000.md b/docs/team/bnjm2000.md new file mode 100644 index 0000000000..42a6c17434 --- /dev/null +++ b/docs/team/bnjm2000.md @@ -0,0 +1,150 @@ +# Benjamin Ng - Project Portfolio Page + +## Overview + +SysLib is a command-line interface (CLI) software designed for system librarians, particularly those with proficient typing skills. +It offers a comprehensive set of functions, including viewing, adding, searching, editing, deleting, and saving, to effectively oversee library resources and events. + +### Summary of Contributions + +#### Code contributed + +View the code I contributed via the tp Code Dashboard link [here](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=bnjm2000&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos). + + +#### Enhancements implemented + +**Enhancements**: +1. Methods to display messages to users and keep outputs consistent +2. Summary Feature for Resources +3. Edit feature for Events +4. Status of attribute for resources + + +**Details:** + +**1. Methods to display messages to users** + +Main methods implemented: + +`showHelpMessage()` and `showExitMessage()` + +Also ensured that all outputs are consistent with a divider line so as to easily +differentiate between a user input or program output (much harder than it sounds.) + +**2. Summary feature** + +Main implementation: `SummaryCommand` class + +Implemented the **summary** feature. + +Summarises all resources that are currently in the database by item type and helpfully displays it into a bar based on quantity for easy viewing. + +Also summarises all events, shows the total number of events and shows the 3 upcoming events in order. + +**3. Edit feature for Events** + +Main implementation: + +`EventEditCommand` class + +Implemented the `eventedit` feature for all events. + +Edit feature supports editing: +- **For all events:** +- Able to edit any attribute of any `Event` except the assigned ID: + - Title + - Date + - Description + +Implemented validation and error checking for EventEditCommand, which prevents changing of any attribute of no new changes were input. +In that scenario, prints a message to the user to say that no attributes were changed + +Ensures that if the Date attribute of the `Event` was to be changed, the edited `Event` would be slot in in accordance to the date. + +**4. Status attribute for Resources** + +Main implementation: `getStatus()`, `setStatus()` in `Resource` class + +Implemented methods in Resource class which sets the status of the resource when executing `addCommand` (defaults to AVAILABLE when no input is given). +This attribute indicates the status of the resource. To do so, an enumerator was created with `AVAILABLE`, `BORROWED` OR `LOST` was created. + +Also allowed resource's status to be able to be edited later. + +**4. Help Command** + +Main implementation: `HelpCommand` class + +Allows users to key in `help` so give the users a basic understanding of the commands available as well as to lead +the users to the User Guide for more information if they would like. + +Allowed for the commands to be easily be understood as well as a short explanation of the commands and easy-to-read examples. + +**5. Exit Command** + +Main implementation: `ExitCommand` class + +Implemented the **exit** feature to allow the user to exit the program safely. +Prints a message to the user to indicate that their stored information has been saved and they are exiting the program. + +``` +Thanks for using SysLib! We have saved the current resources and events. +See you next time! +____________________________________________________________ +``` + +#### Contributions to the UG: + +Sections contributed: +- Introduction +- Quick-start +- Notes on Status +- Sample input and response +- EventEdit Command +- Summary Command +- Exit Command +- Help Command +- Consistency in sample input and outputs (updates UG when codebase changes) + + +#### Contributions to the DG: + +Sections contributed: + +- EventEdit Command section and ListCommand sequence diagram +- Summary Command section and Edit activity diagram +- Status for Resources +- Instructions for manual testing: + - Help + - Add (example outputs) + - List (example outputs) + - Delete (example outputs) + - Exit + - EventAdd (example outputs) + - EventEdit + - EventList (example outputs) + - EventDelete (example outputs) + - Summary + + +#### Contributions to team-based tasks + +- Helped managed some issues and milestones by using keywords and closing when completed. +- Bug test 2.0 .jar file and created issues based on bugs found. +- Help troubleshoot when checks fail on GitHub. +- Conscientiously checked on the code to ensure consistency in coding standards and outputs. +- Updates UG and DG of the changes when a change is made to the output. +- Ensures that all outputs returns with a line separator to ensure easy distinguishability between user input and program output +- Ensured `help` command, DG and UG are up-to-date with sample input and outputs whenever changed. +- Tested Syslib to ensure everything works the same on macOS. + +#### Review/mentoring contributions: + +- Reviewed multiple peer pull requests. +- Gave constructive and witty comments to improve teammates' morale. +- Assisted in debugging own group's code. + +#### Contributions beyond the project team: + +- Helped test other group's project during PE-D, creating 8 bug issues +- Peer reviewed other team's developers guide in their [PR](https://github.com/nus-cs2113-AY2324S1/tp/pull/11/files#diff-1a95edf069a4136e9cb71bee758b0dc86996f6051f0d438ec2c424557de7160b) \ No newline at end of file diff --git a/docs/team/davincidelta.md b/docs/team/davincidelta.md new file mode 100644 index 0000000000..928ae53068 --- /dev/null +++ b/docs/team/davincidelta.md @@ -0,0 +1,118 @@ +# Wu Xingyu - Project Portfolio Page + +## Overview + +SysLib is a CLI Library Management software for system librarians. + +From viewing, adding, searching, editing, deleting and saving, SysLib provides all the features needed to manage library resources and events. +### Summary of Contributions + +#### Code contributed + +View the code I contributed via the tp Code Dashboard link [here](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=DavinciDelta&breakdown=true). + +#### Enhancements implemented +1. Command and Delete Command for resources +2. Parser and suggestParser Class +3. EventAdd, EventList, EventDelete Command for events + +**Details:** + +**1. Command and Delete Command for resources** + +Main classes and methods implemented: +- `Command` class +- `DeleteCommand` class +- `execute()` in `Command` class +- `parseArugment()`, `getMatch` and `checkMatch` in `Command` class +- `validate()` and `checkDuplicate` in `Command` class +- `getReason()` in `Command` class +- `parseInt()` in `Command` class + +Implemented a class `Command` which is an abstract class to `execute()` each command + +User keys in a command followed by its arguments using "/". +For each command they have their own required arguments, +so this list, `args`, differs for each command, +but the implementation is the same. + +Thus `parseArgument()` function will be called to first to get these arguments, +which loops through the list of `args` that the command requires, +then `getMatch()` is called to check if the arguments exist in the user input, +which calls `checkMatch()` to ensure no issues with "/" in arguments. + +After `validate()` is called to verify the user input against the arguments received, +this then calls `checkDuplicate()` to check for duplicate calls of the same argument. + +At the end, `validate()` checks for additional random variables/commands. +If there is, `getReason()` will be called to verify the reason for error to give more +informative feedback. +If the additional command/variable resemble an argument, a suggested argument will be given. + +`parseInt()` is by some commands to check for an input of a valid integer. + +`DeleteCommand` class is an implementation of `Command` that get takes in the id of the resource +to be deleted, the id is parsed through the `parseInt()` function + +**2. Parser and SuggestParser Class** + +`Parser` class includes: + +- `commandProcessor` hashmap +- `eventList` and `resourceList` ArrayList +- `processUserResponse` function +- `removeFirstWord` function +- `suggest` in `SuggestParser` class + +`commandProcessor` stores the String variable of the command as a key and the Command variable as the value. +Thus by using `removeFirstWord` and taking this word, we can compare it to the keys in `commandProcessor` to get the right command to execute. + +`eventList` and `resourceList` are the ArrayList to store the `event` and `resource` that is currently in the database. +It can be used by other commands to add/edit/delete informations. + +`processUserResponse` makes use of the `commandProcessor` and `removeFirstWord` to call the right command. +If no valid command is found, the `suggest` function in `SuggestParser` will be called to try to find the nearest valid command that the user may be trying to call. + +**3. EventAdd, EventList, EventDelete Command for events** + +Using the `eventList` in Parser to store events, we can use these functions below to manipulate events: + +- `EventAdd` +- `EventList` +- `EventDelete` + +`EventAdd` takes in title, date and description(optional) of the event and store this in the eventList. +This event is then stored at the index where it fits chronologically(earlier dates first). + +`EventList` list out all the existing events in chronological order. + +`EventDelete` deletes the event based on the given index. + +#### Contributions to the UG: + +- Delete Command +- EventAdd Command +- EventList Command +- EventDelete Command +- FAQ + +#### Contributions to the DG: + +- User stories and use cases +- Parsing components and sequence diagram +- Command components and class diagram +- Events, including EventAdd, EventList and EventDelete +- EventAdd sequence diagram + +#### Contributions to team-based tasks + +- Reviewed multiple peer pull requests +- Debug multiple PR that has merge problems +- Added the foundation for tp project +- Bug tested for 2.1 jar + +#### Review/mentoring contributions: + +- PE-D, creating 4 bug issues for [ChessMaster](https://github.com/AY2324S1-CS2113-T18-1/tp) and discuss potential bug fixes +- Review other teams' tp project + diff --git a/docs/team/joannejo.md b/docs/team/joannejo.md new file mode 100644 index 0000000000..f19ebfe87f --- /dev/null +++ b/docs/team/joannejo.md @@ -0,0 +1,106 @@ +# Joanne Ang - Project Portfolio Page + +## Overview + +SysLib is a command-line (CLI) library management software tailored for system librarians, especially those who are fast typists. +It offers a wide range of features to manage library resources and events, from adding, editing, viewing, searching, deleting, to saving data file. + + +### Summary of Contributions + +#### Code Contributed + +View the code I contributed [here](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=joannejo&breakdown=true). + + +#### Enhancements Implemented + +**Enhancements**: +1. Resource classes +2. Add feature for resources +3. Parser for adding resources +4. Exception class +5. Output of messages + +**Details**: + +**1. Resource classes** + +Implementation: `Book`, `EBook`, `CD`, `Magazine`, `EMagazine`, `Newspaper`, `ENewspaper` classes + +Implemented the different resource classes for each resource has different attributes. + +**2. Add feature for resources** + +Implementation: `AddCommand`, `CreateResource` classes + +Implemented the **add** feature to add resources into a list of all the resources in the library. + +**3. Parser for adding resources** + +Implementation: `ParseResource`, `ParseAttribute`, `ParseBook`, `ParseEBook`, `ParseCD`, `ParseMagazine`, `ParseEMagazine`, `ParseNewspaper`, `ParseENewspaper` classes & +`parseAddCommand()`, `checkFirstItem()` in `Parser` class + +Implemented the validation and error handling of user input for the **add** feature. +Checks the user input for all attributes in all resources. Returns error messages when invalid. + +**Validation checks for**: +- **Valid input** +- **Optional data** + - Breaks down large data to smaller valid data + +Limitations of input were reduced. + +**4. Exception class** + +Implementation: `SysLibException` class + +Catches **exceptions** related to SysLib and prevents program from exiting due to exceptions. +Returns error messages to user so that the exception can be prevented with the right user input. + +**5. Output of messages** + +Implementation: `Messages` class & `formatSeparatorLineDivider()` in `MessageFormatter` class + +Messages strings are stored in **Messages**. It consists of mainly error, warning and assertion messages. +**formatSeparatorLineDivider()** formats the messages neatly for output to user. + + +#### Contributions to the UG + +Sections contributed: +- How to Use the User Guide +- Add a Listing +- FAQ +- Command Summary + + +#### Contributions to the DG + +Sections contributed: +- Setting Up & Getting Started +- Add Resource Feature +- Add Feature Sequence Diagram +- Add Feature Activity Diagram +- Target User Profile +- Use Case: Add a book +- Instructions for Manual Testing + - Launch & Shutdown + - Adding a Book + +#### Contributions to Team-Based Tasks + +- Did general code enhancements +- Updated UG and DG not specific to feature +- Check consistency of documentations +- Managed issues + +#### Reviewing / Mentoring Contributions + +- Reviewed peer pull requests +- Bug tested during implementation + +#### Contributions Beyond the Project Team + +- Peer reviewed other team's project [here](https://github.com/nus-cs2113-AY2324S1/tp/pull/8) +- Tested other team's project during PE-D \ No newline at end of file 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/pictures/ashokbalaji_portfolio.jpg b/docs/team/pictures/ashokbalaji_portfolio.jpg new file mode 100644 index 0000000000..c78a32ba81 Binary files /dev/null and b/docs/team/pictures/ashokbalaji_portfolio.jpg differ diff --git a/docs/team/pictures/benjaminng_portfolio.jpeg b/docs/team/pictures/benjaminng_portfolio.jpeg new file mode 100644 index 0000000000..b6b668720f Binary files /dev/null and b/docs/team/pictures/benjaminng_portfolio.jpeg differ diff --git a/docs/team/pictures/joanneang_portfolio.jpg b/docs/team/pictures/joanneang_portfolio.jpg new file mode 100644 index 0000000000..b7c2d67f80 Binary files /dev/null and b/docs/team/pictures/joanneang_portfolio.jpg differ diff --git a/docs/team/pictures/wuxingyu_portfolio.jpeg b/docs/team/pictures/wuxingyu_portfolio.jpeg new file mode 100644 index 0000000000..b15c1c95f5 Binary files /dev/null and b/docs/team/pictures/wuxingyu_portfolio.jpeg differ diff --git a/docs/team/pictures/yingxia_portfolio.jpg b/docs/team/pictures/yingxia_portfolio.jpg new file mode 100644 index 0000000000..17e06cebc7 Binary files /dev/null and b/docs/team/pictures/yingxia_portfolio.jpg differ diff --git a/docs/team/yingx9.md b/docs/team/yingx9.md new file mode 100644 index 0000000000..980c7abc67 --- /dev/null +++ b/docs/team/yingx9.md @@ -0,0 +1,82 @@ +# Loke Ying Xia - Project Portfolio Page + +## Overview + +SysLib is a CLI Library Management software for system librarians especially those who are fast typists. + +From viewing, adding, searching, editing, deleting and saving, SysLib provides all the features needed to manage library resources and events. +### Summary of Contributions + +#### Code contributed + +View the code I contributed via the tp Code Dashboard link [here](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=yingx9&breakdown=true). + +#### Enhancements implemented + +**Enhancements**: +1. Classes and methods to format display and messages to users +2. List feature for resources +3. Edit feature for resources +4. Received Date attribute for resources + +**Details:** + +**1. Classes and methods to format display and messages to users** + +Main classes and methods implemented: +`ResourceDisplayFormatter` class, +`showResouces()` in `UI` class. +`checkColumnWidths()`, `toTableFormat()` in all resource class. + +**2. List feature for resources** + +Main implementation: `ListCommand` class. Implemented the **list** feature to show a list of all the resources in the library. +Further enhanced by implementing `filter` options to filter by `tag`, `genre`, and `status`, which shows resources that fit all given filters, to help librarians get a specific overview of resources in the library. + +**3. Edit feature for resources** + +Main implementation: `EditCommand` class. Implemented the **edit** feature for all resource type: Book, Magazine, Newspaper, CD, and their electronic versions. + +**4. Received Date attribute for resources** + +Main implementation: `getReceivedDate()`, `setReceivedDate()` in `Resource` class + +Implemented methods in Resource class which sets the date received to the current system time when executing `addCommand`. This attribute indicates the date entered the resource was entered into the system, hence it is not set by the user and instead takes the current system time. + +#### Contributions to the UG: + +Sections contributed: +- Introduction (First part before How to use User Guide section) +- List Command +- Edit Command +- Known Issues: List issues section + +#### Contributions to the DG: + +Sections contributed: +- Acknowledgement +- Overall architecture diagram and Component Diagram +- Architecture section (The section before UI Component) +- List Command section and ListCommand sequence diagram +- Edit Command section and Edit activity diagram +- Show Resources feature section and Show Resources sequence diagram +- Instructions for manual testing: List and Edit resource sections +- Glossary: Resource definition +- Use Case: Edit a resource + +#### Contributions to team-based tasks + +- Setup Team organization and repo +- Helped managed some issues by opening and closing when done +- Add on to Value proposition and Target Audience in Developer's Guide +- Bug test 2.0 jar file and create issues based on bugs found + +#### Review/mentoring contributions: + +- Reviewed multiple peer pull request such as commenting on sequence diagram. Example: [#166](https://github.com/AY2324S1-CS2113T-W11-1/tp/pull/166) +- Help troubleshoot when checks fail on GitHub Example: [#22](https://github.com/AY2324S1-CS2113T-W11-1/tp/pull/22), [#38](https://github.com/AY2324S1-CS2113T-W11-1/tp/pull/38) + +#### Contributions beyond the project team: + +- Helped test other group's project during PE-D, creating 9 bug issues +- Peer reviewed other team's developers guide in their [PR](https://github.com/nus-cs2113-AY2324S1/tp/pull/18/files) \ No newline at end of file diff --git a/logs/editCommandLogs.log b/logs/editCommandLogs.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/logs/exitCommandLogs.log b/logs/exitCommandLogs.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/logs/findCommandLogs.log b/logs/findCommandLogs.log new file mode 100644 index 0000000000..603dff920e --- /dev/null +++ b/logs/findCommandLogs.log @@ -0,0 +1,2 @@ +Nov 14, 2023 2:39:15 AM seedu.commands.FindCommand <init> +INFO: FindCommand instance created. diff --git a/logs/helpCommandLogs.log b/logs/helpCommandLogs.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/logs/listCommandLogs.log b/logs/listCommandLogs.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/java/seedu/commands/AddCommand.java b/src/main/java/seedu/commands/AddCommand.java new file mode 100644 index 0000000000..57d57c5a19 --- /dev/null +++ b/src/main/java/seedu/commands/AddCommand.java @@ -0,0 +1,222 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Book; +import seedu.data.resources.CD; +import seedu.data.resources.EBook; +import seedu.data.resources.Resource; +import seedu.data.resources.Magazine; +import seedu.data.resources.EMagazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.ENewspaper; +import seedu.exception.SysLibException; + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.data.CreateResource.createBook; +import static seedu.data.CreateResource.createEBook; +import static seedu.data.CreateResource.createCD; +import static seedu.data.CreateResource.createMagazine; +import static seedu.data.CreateResource.createEMagazine; +import static seedu.data.CreateResource.createNewspaper; +import static seedu.data.CreateResource.createENewspaper; +import static seedu.data.resources.Book.BOOK_TAG; +import static seedu.data.resources.CD.CD_TAG; +import static seedu.data.resources.EBook.EBOOK_TAG; +import static seedu.data.resources.EMagazine.EMAGAZINE_TAG; +import static seedu.data.resources.ENewspaper.ENEWSPAPER_TAG; +import static seedu.data.resources.Magazine.MAGAZINE_TAG; +import static seedu.data.resources.Newspaper.NEWSPAPER_TAG; +import static seedu.parser.resources.ParseBook.parseAddBook; +import static seedu.parser.resources.ParseBook.resetBookArgs; +import static seedu.parser.Parser.parseAddCommand; +import static seedu.parser.resources.ParseCD.parseAddCD; +import static seedu.parser.resources.ParseCD.resetCDArgs; +import static seedu.parser.resources.ParseEBook.parseAddEBook; +import static seedu.parser.resources.ParseEBook.resetEBookArgs; +import static seedu.parser.resources.ParseEMagazine.parseAddEMagazine; +import static seedu.parser.resources.ParseEMagazine.resetEMagazineArgs; +import static seedu.parser.resources.ParseENewspaper.parseAddENewspaper; +import static seedu.parser.resources.ParseENewspaper.resetENewspaperArgs; +import static seedu.parser.resources.ParseMagazine.parseAddMagazine; +import static seedu.parser.resources.ParseMagazine.resetMagazineArgs; +import static seedu.parser.resources.ParseNewspaper.parseAddNewspaper; +import static seedu.parser.resources.ParseNewspaper.resetNewspaperArgs; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_CONTAINER; +import static seedu.ui.Messages.ERROR_TAG; +import static seedu.ui.UI.LINEDIVIDER; + +public class AddCommand extends Command{ + public static final String TITLE_OPTION = "t"; + public static final String AUTHOR_OPTION = "a"; + public static final String TAG_OPTION = "tag"; + public static final String ISBN_OPTION = "i"; + public static final String GENRE_OPTION = "g"; + public static final String STATUS_OPTION = "s"; + public static final String LINK_OPTION = "l"; + public static final String CREATOR_OPTION = "c"; + public static final String BRAND_OPTION = "b"; + public static final String PUBLISHER_OPTION = "p"; + public static final String TYPE_OPTION = "ty"; + public static final String ISSUE_OPTION = "is"; + public static final String EDITION_OPTION = "ed"; + private static final int RESOURCEID_INCREMENT = 1; + private static final String WHITESPACE = " "; + private static final Logger ADDLOGGER = Logger.getLogger(AddCommand.class.getName()); + private static String feedbackToUser; + private int resourceID; + + static { + try { + String logDir = System.getProperty("user.dir") + "/logs"; + String logFile = logDir + "/addCommandLogs.log"; + File directory = new File(logDir); + if (!directory.exists()) { + directory.mkdir(); + } + + FileHandler fileHandler = new FileHandler(logFile, true); + fileHandler.setFormatter(new SimpleFormatter()); + ADDLOGGER.addHandler(fileHandler); + ADDLOGGER.setLevel(Level.INFO); + } catch (IOException e) { + ADDLOGGER.log(Level.SEVERE, "Error occured while initializing log."); + throw new RuntimeException(e); + } + } + + public AddCommand() { + args = new String[] {TAG_OPTION, ISBN_OPTION, TITLE_OPTION, AUTHOR_OPTION, GENRE_OPTION, + CREATOR_OPTION, BRAND_OPTION, PUBLISHER_OPTION, TYPE_OPTION, ISSUE_OPTION, EDITION_OPTION, LINK_OPTION, + STATUS_OPTION}; + required = new boolean[]{true, true, true, false, false, false, false, false, false, false, false, false, + false}; + } + + private void addBook(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddBook(statement); + Book newBook = createBook(values, resourceID); + container.getResourcesList().add(newBook); + System.out.println("This book is added:" + System.lineSeparator() + newBook.toString()); + resetBookArgs(); + ADDLOGGER.log(Level.INFO, "Added Book: " + newBook.toString()); + } + + private void addEBook(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddEBook(statement); + EBook newEBook = createEBook(values, resourceID); + container.getResourcesList().add(newEBook); + System.out.println("This e-book is added:" + System.lineSeparator() + newEBook.toString()); + resetEBookArgs(); + ADDLOGGER.log(Level.INFO, "Added E-Book: " + newEBook.toString()); + } + + private void addCD(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddCD(statement); + CD newCD = createCD(values, resourceID); + container.getResourcesList().add(newCD); + System.out.println("This CD is added:" + System.lineSeparator() + newCD.toString()); + resetCDArgs(); + ADDLOGGER.log(Level.INFO, "Added CD: " + newCD.toString()); + } + + private void addMagazine(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddMagazine(statement); + Magazine newMagazine = createMagazine(values, resourceID); + container.getResourcesList().add(newMagazine); + System.out.println("This magazine is added:" + System.lineSeparator() + newMagazine.toString()); + resetMagazineArgs(); + ADDLOGGER.log(Level.INFO, "Added Magazine: " + newMagazine.toString()); + } + + private void addEMagazine(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddEMagazine(statement); + EMagazine newEMagazine = createEMagazine(values, resourceID); + container.getResourcesList().add(newEMagazine); + System.out.println("This e-magazine is added:" + System.lineSeparator() + newEMagazine.toString()); + resetEMagazineArgs(); + ADDLOGGER.log(Level.INFO, "Added E-Magazine: " + newEMagazine.toString()); + } + + private void addNewspaper(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddNewspaper(statement); + Newspaper newNewspaper = createNewspaper(values, resourceID); + container.getResourcesList().add(newNewspaper); + System.out.println("This newspaper is added:" + System.lineSeparator() + newNewspaper.toString()); + resetNewspaperArgs(); + ADDLOGGER.log(Level.INFO, "Added Newspaper: " + newNewspaper.toString()); + } + + private void addENewspaper(String statement, GenericList<Resource, Event> container) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + String[] values = parseAddENewspaper(statement); + ENewspaper newENewspaper = createENewspaper(values, resourceID); + container.getResourcesList().add(newENewspaper); + System.out.println("This e-newspaper is added:" + System.lineSeparator() + newENewspaper.toString()); + resetENewspaperArgs(); + ADDLOGGER.log(Level.INFO, "Added E-Newspaper: " + newENewspaper.toString()); + } + + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) throws + IllegalStateException, NumberFormatException, SysLibException { + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + ADDLOGGER.log(Level.INFO, "Executing Add. Input Arguments: " + statement); + feedbackToUser = ""; + + StringBuilder spaceStatement = new StringBuilder(); + spaceStatement.append(WHITESPACE); + spaceStatement.append(statement); + + resourceID = container.getResourcesList().size() + RESOURCEID_INCREMENT; + String tag = parseAddCommand(String.valueOf(spaceStatement)); + + if (tag.equalsIgnoreCase(BOOK_TAG)) { + addBook(String.valueOf(spaceStatement), container); + } else if (tag.equalsIgnoreCase(EBOOK_TAG)) { + addEBook(String.valueOf(spaceStatement), container); + } else if (tag.equalsIgnoreCase(CD_TAG)) { + addCD(String.valueOf(spaceStatement), container); + } else if (tag.equalsIgnoreCase(MAGAZINE_TAG)) { + addMagazine(String.valueOf(spaceStatement), container); + } else if (tag.equalsIgnoreCase(EMAGAZINE_TAG)) { + addEMagazine(String.valueOf(spaceStatement), container); + } else if (tag.equalsIgnoreCase(NEWSPAPER_TAG)) { + addNewspaper(String.valueOf(spaceStatement), container); + } else if (tag.equalsIgnoreCase(ENEWSPAPER_TAG)) { + addENewspaper(String.valueOf(spaceStatement), container); + } else { + throw new SysLibException(ERROR_TAG); + } + System.out.println(LINEDIVIDER); + return new CommandResult(feedbackToUser); + } +} diff --git a/src/main/java/seedu/commands/Command.java b/src/main/java/seedu/commands/Command.java new file mode 100644 index 0000000000..70f96f6ecf --- /dev/null +++ b/src/main/java/seedu/commands/Command.java @@ -0,0 +1,165 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.parser.SuggestParser; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public abstract class Command { + protected String[] args; + protected boolean[] required; + public abstract CommandResult execute(String statement, GenericList<Resource, Event> container) throws + IllegalArgumentException, IllegalStateException, SysLibException; + + /** + * A method to check if statement given is valid compared to the values extracted + * @param statement The input of user + * @param values The extracted value(s) from the statement + * @throws IllegalArgumentException The invalid arguments given + */ + public void validateStatement(String statement, String[] values) throws IllegalArgumentException { + statement = statement.toLowerCase(); + for (int pointer = 0; pointer < args.length; pointer ++) { + if (values[pointer] != null) { + String arg = checkDuplicate(statement, pointer); + statement = statement.replaceAll(arg+ "\\s*" + Pattern.quote(values[pointer].toLowerCase()), ""); + } + } + if (!statement.isBlank()) { + String message = getReason(statement); + throw new IllegalArgumentException(message + SEPARATOR_LINEDIVIDER); + } + } + + /** + * A method to check if for duplicate argument calls + * @param statement The input of user + * @param pointer The index of args being checked + * @return The argument being checked + */ + public String checkDuplicate(String statement, int pointer) { + String arg = "/" + args[pointer] + " "; + int firstIndex = statement.indexOf(arg); + if (firstIndex == -1) { + return arg; + } + int secondIndex = statement.indexOf(arg, firstIndex + arg.length()); + if (secondIndex != -1) { + throw new IllegalArgumentException("Duplicate instances of " + arg + SEPARATOR_LINEDIVIDER); + } + return arg; + } + + /** + * A method to get information from user input to a list + * @param statement The input of user + * @return The list of information for each argument required + * @throws IllegalArgumentException The missing argument that is required + */ + public String[] parseArgument(String statement) throws IllegalArgumentException { + String[] orderedArgs = new String[args.length]; + for (int pointer = 0; pointer < args.length; pointer ++) { + orderedArgs[pointer] = getMatch(statement, pointer); + if (orderedArgs[pointer] == null && required[pointer]) { + throw new IllegalArgumentException(args[pointer] + " is missing in the argument!" + + SEPARATOR_LINEDIVIDER); + } + if (orderedArgs[pointer] != null && orderedArgs[pointer].isBlank()) { + throw new IllegalArgumentException(args[pointer] + " has a blank argument!" + + SEPARATOR_LINEDIVIDER); + } + } + return orderedArgs; + } + + /** + * Match the required argument to one in the user's input + * @param statement The user input + * @param pointer The index of the required argument + * @return The matched argument information, null otherwise + */ + + public String getMatch(String statement, int pointer) { + String key = args[pointer]; + Pattern pattern = Pattern.compile("/" + key + " (.+?)(?=\\s?/|$)", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(statement); + if (matcher.find()) { + String output = matcher.group(1).trim(); + checkMatch(output, pointer); + return output; + } + return null; + + } + + /** + * Check the matched strings is empty and considered the next argument it's variable + * @param matched The matched string + * @param pointer The argument + */ + public void checkMatch (String matched, int pointer){ + if (matched.contains("/")) { + throw new IllegalArgumentException("Avoid using '/' as names, your " + args[pointer] + + " may have been empty to give this error" + SEPARATOR_LINEDIVIDER); + } + } + + /** + * Get the reason for a failed command + * @param message the leftover variables/commands + * @return reason for failure + */ + public String getReason(String message) { + message = message.stripLeading(); + if (!message.startsWith("/")){ + return "Unknown variable/command:" + message; + } else { + message = message.substring(1); + List<String> variables = List.of(message.split("/")); + if (variables.size() > 1){ + if(parseInt(variables.get(0)) != -1){ + return "Unknown variable: " + message + ", avoid using '/' in arguments. " + "\n" + + "Dates are in the format of DD MMM YYYY, e.g. 25 Dec 2023"; + } else { + return "You need spacing in between arguments"; + } + } else { + List<String> values = List.of(message.split(" ")); + String output = ""; + if (values.size() > 1){ + output += "Invalid command and argument: /" + message; + } else { + output += "Invalid command: /" + message; + } + String probCommand = SuggestParser.suggest(values.get(0), Arrays.asList(args)); + if (probCommand != null){ + return output + "\n" + "Did you mean: /" + probCommand + "?"; + } + return output; + } + } + + } + public static int parseInt(String value) { + try { + int tempNum = Integer.parseInt(value); + if (0 <= tempNum) { + return tempNum; + } + throw new IllegalArgumentException ("The integer argument(s) given is not a valid number!" + + SEPARATOR_LINEDIVIDER); + } catch (NumberFormatException e) { + throw new IllegalArgumentException ("The integer argument(s) given is not a number!" + + SEPARATOR_LINEDIVIDER); + } + } +} + diff --git a/src/main/java/seedu/commands/CommandResult.java b/src/main/java/seedu/commands/CommandResult.java new file mode 100644 index 0000000000..9e4b6b2238 --- /dev/null +++ b/src/main/java/seedu/commands/CommandResult.java @@ -0,0 +1,12 @@ +package seedu.commands; + +public class CommandResult { + + public final String feedbackToUser; + + public CommandResult(String feedbackToUser) { + this.feedbackToUser = feedbackToUser; + } + +} + diff --git a/src/main/java/seedu/commands/DeleteCommand.java b/src/main/java/seedu/commands/DeleteCommand.java new file mode 100644 index 0000000000..ef9f518d45 --- /dev/null +++ b/src/main/java/seedu/commands/DeleteCommand.java @@ -0,0 +1,77 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public class DeleteCommand extends Command { + private static String feedbackToUser; + private static final Logger LOGGER = Logger.getLogger(DeleteCommand.class.getName()); + + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/DeleteCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + + public DeleteCommand(){ + args = new String[]{"id"}; + required = new boolean[]{true}; + LOGGER.info("DeleteCommand initialized."); + } + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) throws SysLibException { + LOGGER.info("Executing DeleteCommand"); + String[] values = parseArgument(statement); + int id = parseInt(values[0]); + assert id > 0; + if (container.getResourcesList().isEmpty()) { + LOGGER.info("DeleteCommand: List is empty"); + throw new SysLibException("There are currently no Resources in Syslib!" + SEPARATOR_LINEDIVIDER); + } + feedbackToUser = ""; + ArrayList<Resource> removals = new ArrayList<>(); + LOGGER.info("DeleteCommand: Looking for ID"); + System.out.println("Looking for ID: " + id + "..."); + for (Resource r: container.getResourcesList()) { + if (r.getId() == id) { + System.out.println("This resource is removed:"); + System.out.println(r + SEPARATOR_LINEDIVIDER); + removals.add(r); + LOGGER.info("DeleteCommand: Resource has been removed"); + + } + } + if (removals.isEmpty()) { + System.out.println("No resources with id matching " + id +SEPARATOR_LINEDIVIDER); + LOGGER.info("DeleteCommand: Resource ID not found"); + } else { + container.getResourcesList().removeAll(removals); + } + return new CommandResult(feedbackToUser); + } + +} diff --git a/src/main/java/seedu/commands/EditCommand.java b/src/main/java/seedu/commands/EditCommand.java new file mode 100644 index 0000000000..efd5001338 --- /dev/null +++ b/src/main/java/seedu/commands/EditCommand.java @@ -0,0 +1,393 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Book; +import seedu.data.resources.EBook; +import seedu.data.resources.CD; + +import seedu.data.resources.EMagazine; +import seedu.data.resources.ENewspaper; +import seedu.data.resources.Magazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.data.Status; + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.List; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.EditCommandMessages.BOOK_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.CD_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.INVALID_EDIT_ARGS; +import static seedu.ui.EditCommandMessages.MAGAZINE_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.MISSING_ARG_MESSAGE; +import static seedu.ui.EditCommandMessages.NEWSPAPERS_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.NOT_BOOK_ERROR; +import static seedu.ui.EditCommandMessages.NOT_CD_ERROR; +import static seedu.ui.EditCommandMessages.NOT_MAGAZINE_ERROR; +import static seedu.ui.EditCommandMessages.NOT_NEWSPAPER_ERROR; +import static seedu.ui.EditCommandMessages.RESOURCE_NOT_FOUND; +import static seedu.ui.EditCommandMessages.EDIT_SUCCESS; +import static seedu.ui.ListCommandMessages.STATUS_ERROR_MESSAGE; + +import static seedu.ui.MessageFormatter.formatLastLineDivider; +public class EditCommand extends Command{ + + private static final Logger EDIT_LOGGER = Logger.getLogger(EditCommand.class.getName()); + private static String feedbackToUser; + private static int resourceIndex; + + static { + FileHandler editFileHandler; + try { + String loggingDirectoryPath = System.getProperty("user.dir") + "/logs"; + String logFilePath = loggingDirectoryPath + "/editCommandLogs.log"; + File directory = new File(loggingDirectoryPath); + if (!directory.exists()) { + directory.mkdir(); + } + editFileHandler = new FileHandler(logFilePath, true); + + } catch (IOException e) { + EDIT_LOGGER.log(Level.SEVERE, "Failed to initialize edit logging handler."); + throw new RuntimeException(e); + } + editFileHandler.setFormatter(new SimpleFormatter()); + EDIT_LOGGER.addHandler(editFileHandler); + } + + public EditCommand(){ + args = new String[]{"id", "t", "a", "l", "g", "s", "c", "ty", "b", "is", "p", "ed", "i"}; + required = new boolean[]{true, false, false, false, false, false, false, false,false,false,false,false, false}; + } + + /** + * Validates parameters, finds and edits chosen resource, and returns a message to feedback to user. + */ + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws SysLibException, IllegalArgumentException { + feedbackToUser = ""; + EDIT_LOGGER.info("Edit Command execute with " + statement); + + String[] givenParameters = parseArgument(statement); + validateStatement(statement, givenParameters); + + List<Resource> resourcesList = container.getResourcesList(); + int givenArgsCount = countGivenArgs(givenParameters); + int givenIDNumber = parseInt(givenParameters[0]); + + Resource foundResource = findResourceByID(givenIDNumber, resourcesList); + + if (foundResource == null) { + feedbackToUser += RESOURCE_NOT_FOUND; + EDIT_LOGGER.warning(feedbackToUser); + return new CommandResult(feedbackToUser); + } + + Resource updatedResource = editResource(foundResource, givenParameters, givenArgsCount); + assert updatedResource != null; + assert resourceIndex < container.getResourcesList().size(); + + resourcesList.set(resourceIndex, updatedResource); + feedbackToUser += EDIT_SUCCESS + formatLastLineDivider(updatedResource.toString()); + + return new CommandResult(feedbackToUser); + } + + /** + * Counts the number of edit arguments given by user. + * Checks if user has given at least one edit argument. + * + * @throws SysLibException If argsCount <= 1. + */ + private int countGivenArgs(String[] givenParameters) throws SysLibException { + + int argsCount = 0; + + for (int i =1; i<givenParameters.length; i++) { + if (givenParameters[i] != null) { + argsCount++; + } + } + + boolean hasAtLeastOneArg = argsCount > 0; + + if (!hasAtLeastOneArg) { + EDIT_LOGGER.warning(MISSING_ARG_MESSAGE); + throw new SysLibException(MISSING_ARG_MESSAGE); + } + return argsCount; + } + + private Resource findResourceByID(int givenID, List<Resource> resourcesList) { + + Resource foundResource = null; + + for (int i=0; i < resourcesList.size(); i++) { + + Resource tempResource = resourcesList.get(i); + + int resourceID = tempResource.getId(); + if (resourceID==givenID) { + foundResource = tempResource; + resourceIndex = i; + break; + } + } + return foundResource; + } + + private Resource editResource(Resource foundResource, String[] givenParameters, int givenArgsCount) + throws SysLibException { + + //Check Title, Status, and ISBN first as all resources share these attributes regardless of type + if (givenParameters[1] != null) { + foundResource.setTitle(givenParameters[1]); + } + if (givenParameters[5] != null) { + foundResource.setStatus(getStatusFromString(givenParameters[5])); + } + if (givenParameters[12] != null) { + if (givenParameters[12].length() != 13) { + throw new SysLibException("ISBN must be 13 characters!"); + } + foundResource.setISBN(givenParameters[12]); + } + + String resourceTag = foundResource.getTag(); + + switch(resourceTag) { + case "B": + // Fallthrough + case "EB": + validateBookParameters(givenParameters, resourceTag, givenArgsCount); + foundResource = editBook(foundResource, givenParameters); + break; + case "CD": + validateCDParameters(givenParameters, givenArgsCount); + foundResource = editCD(foundResource, givenParameters); + break; + case "M": + // Fallthrough + case "EM": + validateMagazineParameters(givenParameters, resourceTag, givenArgsCount); + foundResource = editMagazine(foundResource, givenParameters); + break; + case "N": + // Fallthrough + case "EN": + validateNewspaperParameters(givenParameters, resourceTag, givenArgsCount); + foundResource = editNewspapers(foundResource, givenParameters); + break; + default: + throw new SysLibException("Invalid Resource!"); + } + + return foundResource; + } + + private void validateBookParameters(String[] givenParameters, String resourceTag, int givenArgsCount) + throws SysLibException { + + if (resourceTag.equals("B") && givenParameters[3] != null) { + throw new SysLibException(INVALID_EDIT_ARGS + BOOK_ARGS_MESSAGE); + } + + int[] indexToCheck = {1,2,3,4,5,12}; + + checkGivenParameters(givenArgsCount,givenParameters, indexToCheck, BOOK_ARGS_MESSAGE); + + } + + private void validateCDParameters(String[] givenParameters, int givenArgsCount) + throws SysLibException { + + int[] indexToCheck = {1,5,6,7,12}; + checkGivenParameters(givenArgsCount,givenParameters, indexToCheck, CD_ARGS_MESSAGE); + } + + private void validateNewspaperParameters(String[] givenParameters, String resourceTag, int givenArgsCount) + throws SysLibException { + + if (resourceTag.equals("N") && givenParameters[3] != null) { + throw new SysLibException(INVALID_EDIT_ARGS + NEWSPAPERS_ARGS_MESSAGE); + } + + int[] indexToCheck = {1,3,5,10,11,12}; + checkGivenParameters(givenArgsCount,givenParameters, indexToCheck, NEWSPAPERS_ARGS_MESSAGE); + } + + private void validateMagazineParameters(String[] givenParameters, String resourceTag, int givenArgsCount) + throws SysLibException { + + if (resourceTag.equals("M") && givenParameters[3] != null) { + throw new SysLibException(INVALID_EDIT_ARGS + MAGAZINE_ARGS_MESSAGE); + } + + int[] indexToCheck = {1,3,5,8,9,12}; + + checkGivenParameters(givenArgsCount,givenParameters, indexToCheck, MAGAZINE_ARGS_MESSAGE); + + } + + /** + * Checks if given parameters are valid for the chosen resource type. + * + * @param givenArgsCount Number of edit arguments the user gave. + * @param givenParameters Array that holds the values to edit to. + * @param indexToCheck Array that holds the indexes to check against in givenParameters array. + * @param argsMessage Message notifying users the valid arguments for their chosen resource type. + * @throws SysLibException If number of valid args found not equals to number of givenArgs. + */ + private void checkGivenParameters(int givenArgsCount, String[] givenParameters,int[] indexToCheck, + String argsMessage) throws SysLibException { + int argsCount = 0; + + for (int i=0;i<indexToCheck.length;i++) { + int index = indexToCheck[i]; + if (givenParameters[index] != null) { + argsCount++; + } + } + + if (argsCount != givenArgsCount) { + throw new SysLibException(INVALID_EDIT_ARGS + argsMessage); + } + } + private Book editBook(Resource foundResource, String[] givenParameters) throws SysLibException { + String newAuthor = givenParameters[2]; + String newLink = givenParameters[3]; + + Book bookResource; + + try { + bookResource= (Book) foundResource; + } catch (ClassCastException e) { + EDIT_LOGGER.warning(NOT_BOOK_ERROR); + throw new SysLibException(NOT_BOOK_ERROR); + } + if(newAuthor != null) { + bookResource.setAuthor(newAuthor); + } + + if (givenParameters[4] != null) { + String[] newGenres = givenParameters[4].split(", "); + bookResource.setGenre(newGenres); + } + + if (bookResource instanceof EBook) { + if (newLink != null) { + EBook eBookResource = (EBook) bookResource; + eBookResource.setLink(newLink); + bookResource = eBookResource; + } + } + return bookResource; + } + private CD editCD(Resource foundResource, String[] givenParameters) throws SysLibException { + String newCreator = givenParameters[6]; + String newType = givenParameters[7]; + + CD cdResource; + try { + cdResource= (CD) foundResource; + } catch (ClassCastException e){ + EDIT_LOGGER.warning(NOT_CD_ERROR); + throw new SysLibException(NOT_CD_ERROR); + } + + if (newCreator != null) { + cdResource.setCreator(newCreator); + } + + if (newType != null) { + cdResource.setType(newType); + } + return cdResource; + } + private Magazine editMagazine(Resource foundResource, String[] givenParameters) throws SysLibException { + String newLink = givenParameters[3]; + String newBrand = givenParameters[8]; + String newIssue = givenParameters[9]; + + Magazine magazineResource; + try { + magazineResource = (Magazine) foundResource; + } catch (ClassCastException e){ + EDIT_LOGGER.warning(NOT_MAGAZINE_ERROR); + throw new SysLibException(NOT_MAGAZINE_ERROR); + } + + if (newBrand != null) { + magazineResource.setBrand(newBrand); + } + + if (newIssue != null) { + magazineResource.setIssue(newIssue); + } + + if (magazineResource instanceof EMagazine) { + if (newLink != null) { + EMagazine eMagazineResource = (EMagazine) magazineResource; + eMagazineResource.setLink(newLink); + magazineResource = eMagazineResource; + } + } + return magazineResource; + } + + private Newspaper editNewspapers(Resource foundResource, String[] givenParameters) throws SysLibException { + + String newLink = givenParameters[3]; + String newPublisher = givenParameters[10]; + String newEdition = givenParameters[11]; + + Newspaper newspaperResource; + try { + newspaperResource = (Newspaper) foundResource; + } catch (ClassCastException e){ + EDIT_LOGGER.warning(NOT_NEWSPAPER_ERROR); + throw new SysLibException(NOT_NEWSPAPER_ERROR); + } + + if (newPublisher != null) { + newspaperResource.setPublisher(newPublisher); + } + + if (newEdition != null) { + newspaperResource.setEdition(newEdition); + } + + if (newspaperResource instanceof ENewspaper) { + if (newLink != null) { + ENewspaper eNewspaperResource = (ENewspaper) newspaperResource; + eNewspaperResource.setLink(newLink); + newspaperResource = eNewspaperResource; + } + } + + return newspaperResource; + } + + public static Status getStatusFromString(String statusString) throws SysLibException { + statusString = statusString.toLowerCase().trim(); + if (statusString.equals("borrowed")) { + return Status.BORROWED; + } else if (statusString.equals("lost")) { + return Status.LOST; + } else if (statusString.equals("available")) { + return Status.AVAILABLE; + } else { + throw new SysLibException(STATUS_ERROR_MESSAGE); + } + + } + +} diff --git a/src/main/java/seedu/commands/ExitCommand.java b/src/main/java/seedu/commands/ExitCommand.java new file mode 100644 index 0000000000..a6b89778fa --- /dev/null +++ b/src/main/java/seedu/commands/ExitCommand.java @@ -0,0 +1,56 @@ +package seedu.commands; + +import java.io.IOException; + +import java.util.logging.FileHandler; +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.logging.SimpleFormatter; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; + +import seedu.ui.UI; + +/** + * Command to print exit message + */ +public class ExitCommand extends Command{ + private static final Logger LOGGER = Logger.getLogger(ExitCommand.class.getName()); + private static String feedbackToUser; + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/exitCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + + } + + } + + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws IllegalArgumentException { + assert statement != null : "Statement to execute cannot be null"; + assert container != null : "Parser must not be null"; + feedbackToUser = ""; + LOGGER.log(Level.INFO, "Executing ExitCommand..."); + UI ui = new UI(); + ui.showExitMessage(); + return new CommandResult(feedbackToUser); + } + +} diff --git a/src/main/java/seedu/commands/FindCommand.java b/src/main/java/seedu/commands/FindCommand.java new file mode 100644 index 0000000000..416968a6c6 --- /dev/null +++ b/src/main/java/seedu/commands/FindCommand.java @@ -0,0 +1,252 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Book; +import seedu.data.resources.Magazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.Resource; +import seedu.data.resources.CD; +import seedu.exception.SysLibException; +import seedu.ui.UI; +import static seedu.ui.FindCommandMessages.INVALID_ARGUMENT_MESSAGE; +import static seedu.ui.FindCommandMessages.NO_RESOURCE_FOUND_MESSAGE; +import static seedu.ui.FindCommandMessages.RESOURCE_FOUND_MESSAGE; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.UI.showResourcesDetails; + +/** + * The FindCommand class is responsible for handling the "find" command within the application. + * It allows the user to search for resources in the system based on various criteria such as ID, ISBN, + * author/publisher/brand/creator, or title. It extends the Command class and overrides the execute method + * to perform the search operation. + */ +public class FindCommand extends Command { + public static final int FIRST_INDEX = 0; + public static final int SECOND_INDEX = 1; + public static final int THIRD_INDEX = 2; + public static final int FOURTH_INDEX = 3; + private static final Logger LOGGER = Logger.getLogger(FindCommand.class.getName()); + private static String feedbackToUser; + + protected String title; + protected String author; + protected String isbn; + protected String id; + protected UI ui; + + static { + setupLogger(); + } + + + /** + * Constructs a FindCommand object for handling find command instances. + */ + public FindCommand() { + args = new String[]{"id", "i", "a", "t"}; + required = new boolean[]{false, false, false, false}; + ui = new UI(); + LOGGER.info("FindCommand instance created."); + } + + /** + * Sets up the logger for this class. + */ + private static void setupLogger() { + try { + // remove logs from showing in stdout + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + FileHandler fileHandler = new FileHandler("logs/findCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTitle() { + return this.title; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthor() { + return this.author; + } + + public void setISBN(String isbn) { + this.isbn = isbn; + } + + public String getISBN() { + return this.isbn; + } + + public void setID(String id) { + this.id = id; + } + + public String getID() { + return this.id; + } + + /** + * Executes the find command using the provided statement and container. + * @param statement The command statement. + * @param container The container of resources and events. + * @return The result of the command execution. + * @throws IllegalArgumentException If the input statement is invalid. + * @throws SysLibException If there is an error in processing the find command. + */ + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws IllegalArgumentException, SysLibException { + assert container != null : "Container cannot be null"; + feedbackToUser = ""; + String[] values = parseArgument(statement); + validateStatement(statement, values); + + if (areAllValuesNull(values)) { + throw new IllegalArgumentException(INVALID_ARGUMENT_MESSAGE); + } + + List<Resource> matchedResources = filterResources(container.getResourcesList(), values); + displayResults(matchedResources); + + return new CommandResult(feedbackToUser); + + } + + private boolean areAllValuesNull(String[] values) { + for (String value : values) { + if (value != null) { + return false; + } + } + return true; + } + + private void displayResults(List<Resource> matchedResources) throws IllegalArgumentException, SysLibException{ + if (matchedResources.isEmpty()) { + LOGGER.info("No resources matched the given filters."); + System.out.println(NO_RESOURCE_FOUND_MESSAGE); + ui.showLine(); + } else { + LOGGER.info("Resources matched the given filters."); + System.out.println(RESOURCE_FOUND_MESSAGE); + System.out.println(showResourcesDetails(matchedResources)); + } + } + + /** + * Filters the resources based on the specified search criteria. + * @param resourceList The list of resources to filter. + * @param values The search criteria. + * @return A list of resources that match the criteria. + */ + public List<Resource> filterResources(List<Resource> resourceList, String[] values) { + List<Resource> matchedResources = new ArrayList<>(); + for (Resource resource : resourceList) { + if (isResourceMatch(resource, values)) { + LOGGER.info(String.format("Resource with name: %s matched given arguments.", resource.getTitle())); + matchedResources.add(resource); + } + } + return matchedResources; + } + + /** + * Determines if a resource matches the given search criteria. + * @param resource The resource to check. + * @param values The search criteria values. + * @return true if the resource matches the criteria, false otherwise. + */ + private boolean isResourceMatch(Resource resource, String[] values) { + try { + boolean isMatch = true; + String resourceType = resource.getTag(); + + if (values[FIRST_INDEX] != null && resource.getId() != Integer.parseInt(values[FIRST_INDEX])) { + if (Integer.parseInt(values[FIRST_INDEX]) < 0) { + throw new SysLibException("ID cannot be negative."); + } + isMatch = false; + } + + if (values[SECOND_INDEX] != null && !resource.getISBN().equalsIgnoreCase(values[SECOND_INDEX])) { + isMatch = false; + } + + switch (resourceType) { + case "B": + case "EB": + Book b = (Book) resource; + if (values[THIRD_INDEX] != null && !b.getAuthor().trim().equalsIgnoreCase((values[THIRD_INDEX]))) { + isMatch = false; + } + break; + case "M": + case "EM": + Magazine m = (Magazine) resource; + if (values[THIRD_INDEX] != null && !m.getBrand().trim().equalsIgnoreCase(values[THIRD_INDEX])) { + isMatch = false; + } + break; + case "N": + case "EN": + Newspaper n = (Newspaper) resource; + if (values[THIRD_INDEX] != null && !n.getPublisher().trim().equalsIgnoreCase(values[THIRD_INDEX])) { + isMatch = false; + } + break; + case "CD": + CD cd = (CD) resource; + if (values[THIRD_INDEX] != null && !cd.getCreator().trim().equalsIgnoreCase(values[THIRD_INDEX])) { + isMatch = false; + } + break; + + default: + throw new SysLibException("Unknown resource type found."); + } + + if (values[FOURTH_INDEX] != null && !resource.getTitle().equalsIgnoreCase(values[FOURTH_INDEX])) { + isMatch = false; + } + + // If all non-null criteria matched, add the book to the list + if (isMatch) { + LOGGER.info(String.format("Resource with name: %s matched given arguments.", resource.getTitle())); + } + return isMatch; + + + } catch (SysLibException SysLibEx) { + LOGGER.log(Level.SEVERE, "Find ID supplied is null"); + System.out.println(SysLibEx); + } + return false; + } +} diff --git a/src/main/java/seedu/commands/HelpCommand.java b/src/main/java/seedu/commands/HelpCommand.java new file mode 100644 index 0000000000..51cd1f64d3 --- /dev/null +++ b/src/main/java/seedu/commands/HelpCommand.java @@ -0,0 +1,65 @@ +package seedu.commands; + +import java.io.IOException; + +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; + +import seedu.ui.UI; + +/** + * Command to print help message + */ +public class HelpCommand extends Command { + private static final Logger LOGGER = Logger.getLogger(HelpCommand.class.getName()); + + private static String feedbackToUser; + + static { + // remove logs from showing in stdout + try { + + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + + if (handler instanceof java.util.logging.ConsoleHandler) { + + rootLogger.removeHandler(handler); + + } + + } + + FileHandler fileHandler = new FileHandler("logs/helpCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + + } catch (IOException e) { + + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + + } + } + + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws IllegalArgumentException { + feedbackToUser = ""; + assert statement != null : "Statement to execute cannot be null"; + assert container != null : "Parser must not be null"; + UI ui = new UI(); + LOGGER.log(Level.INFO, "Executing HelpCommand"); + ui.showHelpMessage(); + + return new CommandResult(feedbackToUser); + + } + +} diff --git a/src/main/java/seedu/commands/ListCommand.java b/src/main/java/seedu/commands/ListCommand.java new file mode 100644 index 0000000000..fb959a5f0a --- /dev/null +++ b/src/main/java/seedu/commands/ListCommand.java @@ -0,0 +1,139 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.Status; +import seedu.data.events.Event; +import seedu.data.resources.Resource; + +import seedu.exception.SysLibException; +import seedu.ui.ListCommandMessages; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; +import static seedu.ui.UI.showResourcesDetails; + +public class ListCommand extends Command { + + public static List<Resource> matchedResources; + private static final Logger LIST_LOGGER = Logger.getLogger(ListCommand.class.getName()); + private static String tagKeyword; + private static String genreKeyword; + private static String statusKeyword; + private static String feedbackToUser; + + static { + + FileHandler listFileHandler; + try { + String loggingDirectoryPath = System.getProperty("user.dir") + "/logs"; + String logFilePath = loggingDirectoryPath + "/listCommandLogs.log"; + File directory = new File(loggingDirectoryPath); + if (!directory.exists()) { + directory.mkdir(); + } + listFileHandler = new FileHandler(logFilePath, true); + + } catch (IOException e) { + LIST_LOGGER.log(Level.SEVERE, "Failed to initialize list logging handler."); + throw new RuntimeException(e); + } + listFileHandler.setFormatter(new SimpleFormatter()); + LIST_LOGGER.addHandler(listFileHandler); + } + + public ListCommand() { + args = new String[]{"tag", "g", "s"}; + required = new boolean[]{false, false, false}; + } + + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws SysLibException, IllegalArgumentException { + feedbackToUser = ""; + LIST_LOGGER.info("List Command execute with " + statement); + + String[] givenParameters = parseArgument(statement); + validateStatement(statement, givenParameters); + if (container.getResourcesList().isEmpty()) { + LIST_LOGGER.warning("ResourcesList is empty"); + throw new SysLibException("There are currently no Resources in Syslib!" + SEPARATOR_LINEDIVIDER); + } + filterResources(givenParameters, container.getResourcesList()); + LIST_LOGGER.info("List Command ends"); + return new CommandResult(feedbackToUser); + + } + + private void filterResources(String[] givenParameters, List<Resource> resourcesList) throws SysLibException { + + boolean hasFilters = hasFilters((givenParameters)); + boolean isTagEqualToKeyword = true; + boolean isGenreEqualToKeyword = true; + boolean isStatusEqualToKeyword = true; + + matchedResources = new ArrayList<>(); + + if (!hasFilters) { + feedbackToUser += ListCommandMessages.GENERIC_MESSAGE; + feedbackToUser += showResourcesDetails(resourcesList); + } else { + + for (Resource resource : resourcesList) { + + if (tagKeyword != null) { + String resourceTag = resource.getTag(); + resourceTag = resourceTag.toLowerCase(); + isTagEqualToKeyword = resourceTag.equals(tagKeyword); + } + + if (genreKeyword != null) { + isGenreEqualToKeyword = Resource.hasGenre(resource, genreKeyword); + } + + if (statusKeyword != null) { + Status resourceStatus = resource.getStatus(); + isStatusEqualToKeyword = statusKeyword.equals(resourceStatus.name()); + + } + + if (isTagEqualToKeyword && isGenreEqualToKeyword && isStatusEqualToKeyword) { + matchedResources.add(resource); + } + } + feedbackToUser += ListCommandMessages.FILTER_MESSAGE; + feedbackToUser += showResourcesDetails(matchedResources); + } + + } + + private static boolean hasFilters(String[] givenParameters) throws SysLibException { + tagKeyword = null; + genreKeyword = null; + statusKeyword = null; + + boolean hasFilters = true; + + if (givenParameters[0] != null) { + tagKeyword = givenParameters[0].toLowerCase(); + } + + if (givenParameters[1] != null) { + genreKeyword = givenParameters[1].toLowerCase(); + } + + if (givenParameters[2] != null) { + Status status = EditCommand.getStatusFromString(givenParameters[2]); + statusKeyword = status.name(); + } + return hasFilters; + } + +} diff --git a/src/main/java/seedu/commands/SummaryCommand.java b/src/main/java/seedu/commands/SummaryCommand.java new file mode 100644 index 0000000000..9492c1fba2 --- /dev/null +++ b/src/main/java/seedu/commands/SummaryCommand.java @@ -0,0 +1,199 @@ +package seedu.commands; + +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import seedu.data.resources.Book; +import seedu.data.resources.CD; +import seedu.data.resources.Magazine; +import seedu.data.resources.EBook; +import seedu.data.resources.EMagazine; +import seedu.data.resources.ENewspaper; +import seedu.data.resources.Newspaper; + +import java.io.IOException; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import java.util.Comparator; +import java.util.List; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; +import java.util.stream.Collectors; + +import static seedu.ui.Messages.ASSERT_CONTAINER; +import static seedu.ui.UI.LINEDIVIDER; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + + +public class SummaryCommand extends Command { + private static final Logger LOGGER = Logger.getLogger(SummaryCommand.class.getName()); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); + + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/summaryCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + /** + * Executes the Summary Command to retrieve and summarize information about resources and events in the system. + * + * @param statement The command statement. + * @param container The container that holds resources and events. + * @return A CommandResult containing the summary information. + * @throws SysLibException If a system library exception occurs. + */ + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws SysLibException { + + assert container != null : ASSERT_CONTAINER; + if (!statement.isEmpty()){ + LOGGER.warning("SummaryCommand was given arguments when none was expected"); + throw new IllegalArgumentException("'summary' command does not require arguments!" + + SEPARATOR_LINEDIVIDER); + } + LOGGER.info("Executing Summary Command."); + int totalResources = container.getResourcesList().size(); + LOGGER.info("Retrieved resourcelist size."); + int totalBooks = 0; + int totalCDs = 0; + int totalMagazines = 0; + int totalEBooks = 0; + int totalEMagazines = 0; + int totalNewspapers = 0; + int totalENewspapers = 0; + LOGGER.info("Initialized values."); + + LOGGER.info("Counting resources..."); + for (Resource resource : container.getResourcesList()) { + if (resource instanceof EBook) { + totalEBooks++; + } else if (resource instanceof CD) { + totalCDs++; + } else if (resource instanceof EMagazine) { + totalEMagazines++; + } else if (resource instanceof Book) { + totalBooks++; + } else if (resource instanceof Magazine) { + totalMagazines++; + } else if (resource instanceof ENewspaper) { + totalENewspapers++; + } else if (resource instanceof Newspaper) { + totalNewspapers++; + } + } + + List<Event> events = container.getEventsList(); + List<Event> upcomingEvents = getUpcomingEvents(events, 3); + + LOGGER.info("Drawing graph"); + StringBuilder graph = new StringBuilder(); + + int maxCount = Math.max(totalBooks, Math.max(totalCDs, Math.max(totalMagazines, Math.max(totalEBooks, + Math.max(totalEMagazines, Math.max(totalNewspapers, totalENewspapers)))))); + int maxBarLength = 20; + int bookBarLength = (int) (maxBarLength * ((double) totalBooks / maxCount)); + int cdBarLength = (int) (maxBarLength * ((double) totalCDs / maxCount)); + int magazineBarLength = (int) (maxBarLength * ((double) totalMagazines / maxCount)); + int eBookBarLength = (int) (maxBarLength * ((double) totalEBooks / maxCount)); + int eMagazineBarLength = (int) (maxBarLength * ((double) totalEMagazines / maxCount)); + int newspaperBarLength = (int) (maxBarLength * ((double) totalNewspapers / maxCount)); + int eNewspaperBarLength = (int) (maxBarLength * ((double) totalENewspapers / maxCount)); + + graph.append("Summary of Resources:").append(System.lineSeparator()); + graph.append("Total Resources: ").append(totalResources).append(System.lineSeparator()); + graph.append("Total Books: ").append(generateBar(bookBarLength)).append(" ") + .append(totalBooks).append(System.lineSeparator()); + graph.append("Total E-Books: ").append(generateBar(eBookBarLength)).append(" ") + .append(totalEBooks).append(System.lineSeparator()); + graph.append("Total Magazines: ").append(generateBar(magazineBarLength)).append(" ") + .append(totalMagazines).append(System.lineSeparator()); + graph.append("Total E-Magazines: ").append(generateBar(eMagazineBarLength)).append(" ") + .append(totalEMagazines).append(System.lineSeparator()); + graph.append("Total Newspapers: ").append(generateBar(newspaperBarLength)).append(" ") + .append(totalNewspapers).append(System.lineSeparator()); + graph.append("Total E-Newspapers: ").append(generateBar(eNewspaperBarLength)).append(" ") + .append(totalENewspapers).append(System.lineSeparator());; + graph.append("Total CDs: ").append(generateBar(cdBarLength)).append(" ") + .append(totalCDs).append(System.lineSeparator()); + + LOGGER.info("Summarizing events"); + + graph.append(System.lineSeparator()).append("Summary of Events:").append(System.lineSeparator()); + + graph.append("Total Events: ").append(events.size()).append(System.lineSeparator()); + + if (!events.isEmpty()) { + graph.append("Upcoming Events (Next 3):").append(System.lineSeparator()); + for (int i = 0; i < upcomingEvents.size(); i++) { + Event event = upcomingEvents.get(i); + graph.append(i + 1) + .append(". ") + .append(event.getName()) + .append(" | ") + .append(event.getDate().format(formatter)) + .append(" | ") + .append(event.getDescription()) + .append(System.lineSeparator()); + } + } + graph.append(LINEDIVIDER).append(System.lineSeparator()); + + return new CommandResult(graph.toString()); + } + + /** + * @param count raw count of data + * @return number of bars in square brackets based on count + */ + public String generateBar(int count) { + LOGGER.info("Generate bar method called"); + final int maxBarLength = 20; + int barLength = (int) (maxBarLength * ((double) count / 100)); + StringBuilder bar = new StringBuilder(); + + for (int i = 0; i < barLength; i++) { + bar.append("▓"); + } + + return "[" + bar.toString() + "]"; + + } + + /** + * @param events list of events + * @param count number of upcoming events to return + * @return list of upcoming events of value count. + */ + public List<Event> getUpcomingEvents(List<Event> events, int count) { + LOGGER.info("Getting "+ count + "upcoming events"); + + LocalDate today = LocalDate.now(); + + return events.stream() + .filter(event -> event.getDate().isAfter(today)) + .sorted(Comparator.comparing(Event::getDate)) + .limit(count) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/seedu/commands/events/EventAddCommand.java b/src/main/java/seedu/commands/events/EventAddCommand.java new file mode 100644 index 0000000000..73a79c942c --- /dev/null +++ b/src/main/java/seedu/commands/events/EventAddCommand.java @@ -0,0 +1,142 @@ +package seedu.commands.events; + +import seedu.commands.Command; +import seedu.commands.CommandResult; +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.Locale; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.UI.LINEDIVIDER; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public class EventAddCommand extends Command { + + private static String feedbackToUser; + private static final Logger LOGGER = Logger.getLogger(EventAddCommand.class.getName()); + + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/eventCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + public EventAddCommand() { + args = new String[]{"t", "date", "desc"}; + required = new boolean[]{true, true, false}; + LOGGER.info("EventAdd Command is created"); + } + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws IllegalArgumentException, IllegalStateException, SysLibException { + + feedbackToUser = ""; + String[] values = parseArgument(statement); + validateStatement(statement, values); + LocalDate currentDate = parseDate(values[1]); + int index = binarySearch(container, currentDate); + container.getEventsList().add(index, new Event(values[0], currentDate, values[2])); + System.out.println("Event inserted at: " + index); + System.out.println(index + ": " + container.getEventsList().get(index).toString()); + System.out.println(LINEDIVIDER); + LOGGER.info("Successfully added an event"); + + return new CommandResult(feedbackToUser); + } + + /** + * @param container Contains ResourceList and EventList. + * @param key date to search for. + * @return index to insert to. + */ + public static int binarySearch(GenericList<Resource, Event> container, LocalDate key) { + if (container.getEventsList().isEmpty()) { + return 0; + } + int low = 0; + int high = container.getEventsList().size() - 1; + + while (low <= high) { + int mid = (low + high)/2; + LocalDate midVal = container.getEventsList().get(mid).getDate(); + int cmp = midVal.compareTo(key); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return low; + } + + /** + * Parses a date string into a LocalDate object with a specific format. + * + * @param dateStr The date string to be parsed. + * @return The parsed LocalDate object. + * @throws IllegalArgumentException If the date string is in an invalid format. + */ + public static LocalDate parseDate(String dateStr) throws IllegalArgumentException { + DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("dd MMM yyyy") + .toFormatter(Locale.ENGLISH) + .withResolverStyle(ResolverStyle.SMART); + try { + dateStr = checkDate(dateStr); + return LocalDate.parse(dateStr, formatter); + } catch (DateTimeParseException e) { + LOGGER.info("failed date parsing"); + throw new IllegalArgumentException("Please enter a valid date in the format 'dd MMM yyyy'" + + SEPARATOR_LINEDIVIDER); + } + } + + /** + * Checks and formats a date string to ensure it is in the correct format. + * + * @param dateStr The date string to be checked. + * @return The formatted date string. + * @throws IllegalArgumentException If the date string is in an invalid format. + */ + public static String checkDate(String dateStr) throws IllegalArgumentException { + String[] temp = dateStr.split(" "); + if (temp.length != 3) { + LOGGER.info("failed checkDate function"); + throw new IllegalArgumentException("Please enter a valid date in the format 'dd MMM yyyy'" + + SEPARATOR_LINEDIVIDER); + } + int first = parseInt(temp[0]); + if (first < 10) { + return "0" + dateStr; + } + return dateStr; + } + +} diff --git a/src/main/java/seedu/commands/events/EventDeleteCommand.java b/src/main/java/seedu/commands/events/EventDeleteCommand.java new file mode 100644 index 0000000000..e1bb5a2f97 --- /dev/null +++ b/src/main/java/seedu/commands/events/EventDeleteCommand.java @@ -0,0 +1,78 @@ +package seedu.commands.events; + +import seedu.commands.Command; +import seedu.commands.CommandResult; +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.UI.LINEDIVIDER; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public class EventDeleteCommand extends Command { + + private static String feedbackToUser; + private static final Logger LOGGER = Logger.getLogger(EventDeleteCommand.class.getName()); + + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/eventCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + + public EventDeleteCommand() { + args = new String[]{"id"}; + required = new boolean[]{true}; + LOGGER.info("EventDelete Command created."); + } + + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws IllegalArgumentException, IllegalStateException, SysLibException { + feedbackToUser = ""; + String[] values = parseArgument(statement); + validateStatement(statement, values); + int index = parseCalendarInt(values[0], container); + System.out.println("This event is removed:"); + System.out.println(container.getEventsList().get(index).toString()); + System.out.println(LINEDIVIDER); + container.getEventsList().remove(index); + LOGGER.info("Event removed"); + return new CommandResult(feedbackToUser); + } + + public int parseCalendarInt(String value, GenericList<Resource, Event> container) throws SysLibException { + int index = parseInt(value); + int size = container.getEventsList().size(); + if (size == 0) { + LOGGER.warning("EventsList is empty"); + throw new SysLibException("There are currently no Events in Syslib!" + SEPARATOR_LINEDIVIDER); + } + if (index >= size || index < 0) { + LOGGER.warning("Index is out of range"); + throw new IllegalArgumentException("Index is out of range of the event list!" + SEPARATOR_LINEDIVIDER); + } + LOGGER.warning("Index found"); + return index; + } +} diff --git a/src/main/java/seedu/commands/events/EventEditCommand.java b/src/main/java/seedu/commands/events/EventEditCommand.java new file mode 100644 index 0000000000..1bfa8483af --- /dev/null +++ b/src/main/java/seedu/commands/events/EventEditCommand.java @@ -0,0 +1,217 @@ +/** + * EventEditCommand represents a command to edit an event in a list of events. + * It allows users to update the title, date, or description of an existing event. + * If no changes are specified, it informs the user that nothing was edited. + */ +package seedu.commands.events; + +import seedu.commands.Command; +import seedu.commands.CommandResult; +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.Locale; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.Messages.ASSERT_CONTAINER; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public class EventEditCommand extends Command { + + private static String feedbackToUser; + + private static final Logger LOGGER = Logger.getLogger(EventEditCommand.class.getName()); + + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/eventCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + + /** + * Constructs an EventEditCommand with default arguments and sets up logging. + */ + public EventEditCommand() { + args = new String[]{"id", "t", "date", "desc"}; + required = new boolean[]{true, false, false, false}; + LOGGER.info("EventEdit Command created"); + } + + /** + * Executes the EventEditCommand to edit an event in the provided container. + * + * @param statement The user's input statement. + * @param container The container containing events to be edited. + * @return A CommandResult containing feedback to the user. + * @throws IllegalArgumentException If the input statement or index is invalid. + * @throws IllegalStateException If the container is in an invalid state. + * @throws SysLibException If a system library exception occurs. + */ + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) + throws IllegalArgumentException, IllegalStateException, SysLibException { + + assert statement != null : ASSERT_STATEMENT; + assert container != null : ASSERT_CONTAINER; + + LOGGER.info("Executing EventEditCommand"); + feedbackToUser = ""; + String[] values = parseArgument(statement); + validateStatement(statement, values); + int size = container.getEventsList().size(); + if (size == 0) { + LOGGER.warning("EventsList is empty"); + throw new SysLibException("There are currently no Events in Syslib!" + SEPARATOR_LINEDIVIDER); + } + int index = parseInt(values[0]); + if (index < 0 || index >= container.getEventsList().size()) { + throw new IllegalArgumentException("Invalid event index" + SEPARATOR_LINEDIVIDER); + } + LOGGER.info("Getting old event"); + Event oldEvent = container.getEventsList().get(index); + + String title = values[1] != null ? values[1] : oldEvent.getName(); + LOGGER.info("Processed title change."); + + LocalDate date = values[2] != null ? parseDate(values[2]) : oldEvent.getDate(); + LOGGER.info("Processed date change."); + + String description = values[3] != null ? values[3] : oldEvent.getDescription(); + LOGGER.info("Processed description change."); + + Event editedEvent = new Event(title, date, description); + + container.getEventsList().remove(index); + LOGGER.info("Old event removed."); + + int idx = binarySearch(container, date); + container.getEventsList().add(idx,editedEvent); + LOGGER.info("New event added."); + + feedbackToUser = ""; + + if (values[1] == null && values[2] == null && values[3] == null) { + LOGGER.info("Print nothing changed."); + System.out.println("Event was not edited as nothing was changed." + SEPARATOR_LINEDIVIDER); + } else { + LOGGER.info("Print event changed."); + if (idx != index) { + LOGGER.info("Index changed"); + System.out.println("Event index has changed as the date was changed."); + } + System.out.println("Event edited successfully. New event details:" + System.lineSeparator() + + idx + ": " + editedEvent.toString() + SEPARATOR_LINEDIVIDER); + } + + return new CommandResult(feedbackToUser); + } + + /** + * @param container Contains ResourcesList and EventsList. + * @param key date to search for. + * @return index to insert to. + */ + public static int binarySearch(GenericList<Resource, Event> container, LocalDate key) { + LOGGER.info("binary search method activated."); + if (container.getEventsList().isEmpty()) { + return 0; + } + int low = 0; + int high = container.getEventsList().size() - 1; + + while (low <= high) { + int mid = (low + high)/2; + LocalDate midVal = container.getEventsList().get(mid).getDate(); + int cmp = midVal.compareTo(key); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return low; + } + + /** + * Parses a date string into a LocalDate object with a specific format. + * + * @param dateStr The date string to be parsed. + * @return The parsed LocalDate object. + * @throws IllegalArgumentException If the date string is in an invalid format. + */ + public static LocalDate parseDate(String dateStr) throws IllegalArgumentException { + + DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("dd MMM yyyy") + .toFormatter(Locale.ENGLISH) + .withResolverStyle(ResolverStyle.SMART); + + try { + + dateStr = checkDate(dateStr); + return LocalDate.parse(dateStr, formatter); + + } catch (DateTimeParseException e) { + + throw new IllegalArgumentException("Please enter a valid date in the format 'dd MMM yyyy'" + + SEPARATOR_LINEDIVIDER); + + } + + } + + /** + * Checks and formats a date string to ensure it is in the correct format. + * + * @param dateStr The date string to be checked. + * @return The formatted date string. + * @throws IllegalArgumentException If the date string is in an invalid format. + */ + public static String checkDate(String dateStr) throws IllegalArgumentException { + String[] temp = dateStr.split(" "); + if (temp.length != 3) { + + throw new IllegalArgumentException("Please enter a valid date in the format 'dd MMM yyyy'" + + SEPARATOR_LINEDIVIDER); + + } + + int first = parseInt(temp[0]); + if (first < 10) { + + return "0" + dateStr; + + } + + return dateStr; + } + +} diff --git a/src/main/java/seedu/commands/events/EventListCommand.java b/src/main/java/seedu/commands/events/EventListCommand.java new file mode 100644 index 0000000000..1d5573c3c6 --- /dev/null +++ b/src/main/java/seedu/commands/events/EventListCommand.java @@ -0,0 +1,69 @@ +package seedu.commands.events; + +import seedu.commands.Command; +import seedu.commands.CommandResult; +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.ui.UI.LINEDIVIDER; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public class EventListCommand extends Command { + + private static String feedbackToUser; + + private static final Logger LOGGER = Logger.getLogger(EventListCommand.class.getName()); + + static { + // remove logs from showing in stdout + try { + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + + FileHandler fileHandler = new FileHandler("logs/eventCommandLogs.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + + @Override + public CommandResult execute(String statement, GenericList<Resource, Event> container) throws + IllegalArgumentException, IllegalStateException, SysLibException { + feedbackToUser = ""; + if (!statement.isEmpty()) { + LOGGER.warning("EventList was given arguments when none was expected"); + throw new IllegalArgumentException("'eventlist' command does not require arguments!" + + SEPARATOR_LINEDIVIDER); + } + if (container.getEventsList().isEmpty()) { + LOGGER.info("EventList is empty."); + throw new SysLibException("There are currently no Events in Syslib!" + + SEPARATOR_LINEDIVIDER); + } else { + LOGGER.info("Printing events in EventList"); + System.out.println("This is the current event list:"); + for (int index = 0; index < container.getEventsList().size(); index += 1) { + System.out.println(index + ": " + container.getEventsList().get(index).toString()); + } + System.out.println(LINEDIVIDER); + LOGGER.info("EventList has finish printing."); + } + + return new CommandResult(feedbackToUser); + } +} diff --git a/src/main/java/seedu/data/CreateResource.java b/src/main/java/seedu/data/CreateResource.java new file mode 100644 index 0000000000..00701e2bda --- /dev/null +++ b/src/main/java/seedu/data/CreateResource.java @@ -0,0 +1,148 @@ +package seedu.data; + +import seedu.data.resources.Book; +import seedu.data.resources.Magazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.EBook; +import seedu.data.resources.EMagazine; +import seedu.data.resources.ENewspaper; +import seedu.data.resources.CD; +import seedu.exception.SysLibException; + +import java.util.ArrayList; + +import static seedu.parser.Parser.getStatusFromString; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ASSERT_ID; +import static seedu.ui.Messages.ATTENTION_GENRE; +import static seedu.ui.Messages.ERROR_INVALID_GENRE_CHARACTER; + +public class CreateResource { + public static Book createBook(String[] args, int id) throws IllegalStateException, NumberFormatException, + SysLibException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String author = args[2]; + Status status = getStatusFromString(args[4]); + + String genreString; + String[] genres = new String[1]; + ArrayList<String> parsedGenresList = new ArrayList<String>(); + String[] parsedGenres = new String[1]; + + if (args[3] != null) { + genreString = args[3]; + genres = genreString.split(",\\s*"); + + for (String genre : genres) { + if (genre.contains("[") | genre.contains("]")) { + throw new SysLibException(ERROR_INVALID_GENRE_CHARACTER); + } + + if (!genre.isEmpty()) { + parsedGenresList.add(genre); + } + } + + parsedGenres = new String[parsedGenresList.size()]; + parsedGenres = parsedGenresList.toArray(parsedGenres); + } + if (genres[0] == null) { + System.out.println(ATTENTION_GENRE); + } + + return new Book(title, isbn, author, parsedGenres, id, status); + } + + public static EBook createEBook(String[] args, int id) throws IllegalStateException, NumberFormatException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String author = args[2]; + Status status = getStatusFromString(args[4]); // Get the status from the provided string + String link = args[6]; + + String genre; + String[] genres = new String[1]; + if (args[3] != null) { + genre = args[3]; + genres = genre.split(", "); + } + + return new EBook(title, isbn, author, genres, id, status, link); + } + + public static CD createCD(String[] args, int id) throws IllegalStateException, NumberFormatException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String creator = args[2]; + String type = args[3]; + Status status = getStatusFromString(args[4]); + + return new CD(title, isbn, creator, type, id, status); + } + + public static Magazine createMagazine(String[] args, int id) throws IllegalStateException, NumberFormatException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String brand = args[2]; + String issue = args[3]; + Status status = getStatusFromString(args[4]); + + return new Magazine(title, isbn, brand, issue, id, status); + } + + public static EMagazine createEMagazine(String[] args, int id) throws IllegalStateException, NumberFormatException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String brand = args[2]; + String issue = args[3]; + Status status = getStatusFromString(args[4]); + String link = args[6]; + + return new EMagazine(title, isbn, brand, issue, id, status, link); + } + + public static Newspaper createNewspaper(String[] args, int id) throws IllegalStateException, + NumberFormatException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String publisher = args[2]; + String edition = args[3]; + Status status = getStatusFromString(args[4]); + + return new Newspaper(title, isbn, publisher, edition, id, status); + } + + public static ENewspaper createENewspaper(String[] args, int id) throws IllegalStateException, + NumberFormatException { + assert args != null : ASSERT_ARGUMENTS; + assert id > 0 : ASSERT_ID; + + String isbn = args[0]; + String title = args[1]; + String publisher = args[2]; + String edition = args[3]; + Status status = getStatusFromString(args[4]); + String link = args[6]; + + return new ENewspaper(title, isbn, publisher, edition, id, status, link); + } +} diff --git a/src/main/java/seedu/data/GenericList.java b/src/main/java/seedu/data/GenericList.java new file mode 100644 index 0000000000..65aa53391f --- /dev/null +++ b/src/main/java/seedu/data/GenericList.java @@ -0,0 +1,29 @@ +package seedu.data; + +import java.util.List; + +public class GenericList<R, E> { + private List<R> resourcesList; + private List<E> eventList; + + public GenericList(List<R> resourcesList, List<E> eventList) { + this.resourcesList = resourcesList; + this.eventList = eventList; + } + + public List<R> getResourcesList() { + return resourcesList; + } + + public List<E> getEventsList() { + return eventList; + } + + public void setResourcesList(List<R> resourcesList) { + this.resourcesList = resourcesList; + } + + public void setEventsList(List<E> eventList) { + this.eventList = eventList; + } +} diff --git a/src/main/java/seedu/data/Status.java b/src/main/java/seedu/data/Status.java new file mode 100644 index 0000000000..94ca446cae --- /dev/null +++ b/src/main/java/seedu/data/Status.java @@ -0,0 +1,7 @@ +package seedu.data; + +public enum Status { + AVAILABLE, + BORROWED, + LOST; +} diff --git a/src/main/java/seedu/data/events/Event.java b/src/main/java/seedu/data/events/Event.java new file mode 100644 index 0000000000..2c902cf729 --- /dev/null +++ b/src/main/java/seedu/data/events/Event.java @@ -0,0 +1,34 @@ +package seedu.data.events; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class Event { + private final String name; + private final LocalDate date; + private final String description; + + public Event(String name, LocalDate date, String description) { + this.name = name; + this.date = date; + this.description = description; + } + + public LocalDate getDate() { + return date; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); + return name + " | " + date.format(formatter) + " | " + description; + } +} diff --git a/src/main/java/seedu/data/resources/Book.java b/src/main/java/seedu/data/resources/Book.java new file mode 100644 index 0000000000..8c2676775a --- /dev/null +++ b/src/main/java/seedu/data/resources/Book.java @@ -0,0 +1,84 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class Book extends Resource { + public static final String BOOK_TAG = "B"; + private String author; + private String[] genres; + + public Book(String title, String isbn, String author, String[] genres, int id, Status status) { + super(title, isbn, id, status); + setTag(BOOK_TAG); + setAuthor(author); + setGenre(genres); + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String[] getGenre() { + return genres; + } + + public void setGenre(String[] genres) { + this.genres = genres; + } + + public String getGenreString() { + return String.join(", ", genres); + } + + @Override + public String toString() { + StringBuilder genreString = new StringBuilder(); + if (getGenre()[0] == null) { + genreString.append("-"); + } else { + genreString.append(java.util.Arrays.toString(getGenre()).replace("[", ""). + replace("]", "")); + } + + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + " Author: " + + getAuthor() + " Genre: " + genreString + " Status: " + getStatus().name() + " Received Date: " + + getDateReceived(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getAuthor(), + getGenreString(), "null", getStatus(), + getDateReceived()); + return tableFormatter; + } + + @Override + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + + columnsWidth = super.checkColumnsWidths(columnsWidth); + int authorLength = getAuthor().length(); + int genreLength = getGenreString().length(); + + if (authorLength > columnsWidth.get(4)) { + columnsWidth.set(4,authorLength+1); + } + + if (genreLength > columnsWidth.get(5)) { + columnsWidth.set(5,genreLength+1); + } + + return columnsWidth; + } + +} + diff --git a/src/main/java/seedu/data/resources/CD.java b/src/main/java/seedu/data/resources/CD.java new file mode 100644 index 0000000000..7ecbfc2472 --- /dev/null +++ b/src/main/java/seedu/data/resources/CD.java @@ -0,0 +1,68 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class CD extends Resource { + public static final String CD_TAG = "CD"; + private String creator; + private String type; + + public CD(String title, String isbn, String creator, String type, int id, Status status) { + super(title, isbn, id, status); + setTag(CD_TAG); + setCreator(creator); + setType(type); + setId(id); + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + + " Creator: " + getCreator() + " Type: " + getType() + " Status: " + getStatus().name(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getCreator(), + getType(), "null", getStatus(), + getDateReceived()); + return tableFormatter; + } + + @Override + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + columnsWidth = super.checkColumnsWidths(columnsWidth); + int creatorLength = getCreator().length(); + int typeLength = getType().length(); + + if (creatorLength > columnsWidth.get(4)) { + columnsWidth.set(4,creatorLength+1); + } + + if (typeLength > columnsWidth.get(5)) { + columnsWidth.set(5,typeLength+1); + } + + return columnsWidth; + } +} diff --git a/src/main/java/seedu/data/resources/EBook.java b/src/main/java/seedu/data/resources/EBook.java new file mode 100644 index 0000000000..cf502a294f --- /dev/null +++ b/src/main/java/seedu/data/resources/EBook.java @@ -0,0 +1,61 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class EBook extends Book{ + public static final String EBOOK_TAG = "EB"; + private String link; + + public EBook(String title, String isbn, String author, String[] genres, int id, Status status, String link) { + super(title, isbn, author, genres, id, status); + setTag(EBOOK_TAG); + setLink(link); + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + @Override + public String toString() { + StringBuilder genreString = new StringBuilder(); + if (getGenre()[0] == null) { + genreString.append("-"); + } else { + genreString.append(java.util.Arrays.toString(getGenre()).replace("[", ""). + replace("]", "")); + } + + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + " Author: " + + getAuthor() + " Genre: " + genreString + " Link: " + getLink(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getAuthor(), + getGenreString(), getLink(), getStatus(), + getDateReceived()); + return tableFormatter; + } + + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth){ + + int linkLength = getLink().length(); + + columnsWidth = super.checkColumnsWidths(columnsWidth); + + if (linkLength > columnsWidth.get(6)) { + columnsWidth.set(6,linkLength+1); + + } + return columnsWidth; + } +} diff --git a/src/main/java/seedu/data/resources/EMagazine.java b/src/main/java/seedu/data/resources/EMagazine.java new file mode 100644 index 0000000000..3c85373b91 --- /dev/null +++ b/src/main/java/seedu/data/resources/EMagazine.java @@ -0,0 +1,52 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class EMagazine extends Magazine { + public static final String EMAGAZINE_TAG = "EM"; + private String link; + + public EMagazine(String title, String isbn, String creator, String type, int id, Status status, String link) { + super(title, isbn, creator, type, id, status); + setTag(EMAGAZINE_TAG); + setLink(link); + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + @Override + public String toString() { + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + " Brand: " + + getBrand() + " Issue: " + getIssue() + " Link: " + getLink(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getBrand(), + getIssue(), getLink(), getStatus(), + getDateReceived()); + return tableFormatter; + } + + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + + int linkLength = getLink().length(); + + columnsWidth = super.checkColumnsWidths(columnsWidth); + + if (linkLength > columnsWidth.get(6)) { + columnsWidth.set(6,linkLength+1); + } + return columnsWidth; + } +} diff --git a/src/main/java/seedu/data/resources/ENewspaper.java b/src/main/java/seedu/data/resources/ENewspaper.java new file mode 100644 index 0000000000..bb20319542 --- /dev/null +++ b/src/main/java/seedu/data/resources/ENewspaper.java @@ -0,0 +1,53 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class ENewspaper extends Newspaper{ + public static final String ENEWSPAPER_TAG = "EN"; + private String link; + + public ENewspaper(String title, String isbn, String creator, String type, int id, Status status, String link) { + super(title, isbn, creator, type, id, status); + setTag(ENEWSPAPER_TAG); + setLink(link); + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + @Override + public String toString() { + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + + " Publisher: " + getPublisher() + " Edition: " + getEdition() + " Link: " + getLink(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getPublisher(), + getEdition(), getLink(), getStatus(), + getDateReceived()); + return tableFormatter; + } + + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + + int linkLength = getLink().length(); + + columnsWidth = super.checkColumnsWidths(columnsWidth); + + if (linkLength > columnsWidth.get(6)) { + columnsWidth.set(6,linkLength+1); + + } + return columnsWidth; + } +} diff --git a/src/main/java/seedu/data/resources/Magazine.java b/src/main/java/seedu/data/resources/Magazine.java new file mode 100644 index 0000000000..196f198984 --- /dev/null +++ b/src/main/java/seedu/data/resources/Magazine.java @@ -0,0 +1,67 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class Magazine extends Resource { + public static final String MAGAZINE_TAG = "M"; + private String brand; + private String issue; + + public Magazine(String title, String isbn, String brand, String issue, int id, Status status) { + super(title, isbn, id, status); + setTag(MAGAZINE_TAG); + setBrand(brand); + setIssue(issue); + setId(id); + } + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getIssue() { + return issue; + } + + public void setIssue(String issue) { + this.issue = issue; + } + + @Override + public String toString() { + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + " Brand: " + + getBrand() + " Issue: " + getIssue() + " Status: " + getStatus().name(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getBrand(), + getIssue(), "null", getStatus(), + getDateReceived()); + return tableFormatter; + } + + @Override + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + columnsWidth = super.checkColumnsWidths(columnsWidth); + int brandLength = getBrand().length(); + int issueLength = getIssue().length(); + + if (brandLength > columnsWidth.get(4)) { + columnsWidth.set(4,brandLength+1); + } + + if (issueLength > columnsWidth.get(5)) { + columnsWidth.set(5,issueLength+1); + } + + return columnsWidth; + } +} diff --git a/src/main/java/seedu/data/resources/Newspaper.java b/src/main/java/seedu/data/resources/Newspaper.java new file mode 100644 index 0000000000..7012fc8b79 --- /dev/null +++ b/src/main/java/seedu/data/resources/Newspaper.java @@ -0,0 +1,68 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.util.Formatter; +import java.util.List; + +public class Newspaper extends Resource { + public static final String NEWSPAPER_TAG = "N"; + private String publisher; + private String edition; + + public Newspaper(String title, String isbn, String publisher, String edition, int id, Status status) { + super(title, isbn, id, status); + setTag(NEWSPAPER_TAG); + setPublisher(publisher); + setEdition(edition); + setId(id); + } + public String getPublisher() { + return publisher; + } + + public void setPublisher(String publisher) { + this.publisher = publisher; + } + + public String getEdition() { + return edition; + } + + public void setEdition(String edition) { + this.edition = edition; + } + + @Override + public String toString() { + return "[" + getTag() + "] " + " ID: " + getId() + " Title: " + getTitle() + " ISBN: " + getISBN() + + " Publisher: " + getPublisher() + " Edition: " + getEdition() + " Status: " + getStatus().name(); + } + + @Override + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, getId(), + getTag(),getTitle(), getISBN(), getPublisher(), + getEdition(), "null", getStatus(), + getDateReceived()); + return tableFormatter; + } + + @Override + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + + columnsWidth = super.checkColumnsWidths(columnsWidth); + int publisherLength = getPublisher().length(); + int editionLength = getEdition().length(); + + if (publisherLength > columnsWidth.get(4)) { + columnsWidth.set(4,publisherLength+1); + } + + if (editionLength > columnsWidth.get(5)) { + columnsWidth.set(5,editionLength+1); + } + + return columnsWidth; + } +} diff --git a/src/main/java/seedu/data/resources/Resource.java b/src/main/java/seedu/data/resources/Resource.java new file mode 100644 index 0000000000..8168d5b204 --- /dev/null +++ b/src/main/java/seedu/data/resources/Resource.java @@ -0,0 +1,162 @@ +package seedu.data.resources; + +import seedu.data.Status; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Formatter; +import java.util.List; + +public class Resource { + private String title; + private boolean isBorrowed; + private Integer copies; + private String tag; + private String isbn; + private int id; + private Status status; + private LocalDateTime dateReceived; //To keep track of when the resource was entered into the system + + public Resource(String title, String isbn, int id, Status status) { + setTitle(title); + setBorrowed(false); + setISBN(isbn); + setCopies(1); + setTag(""); + setId(id); + setStatus(status); + setReceivedDate(); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean isBorrowed() { + return isBorrowed; + } + + public void setBorrowed(boolean borrowed) { + isBorrowed = borrowed; + } + + public Integer getCopies() { + return copies; + } + + public void setCopies(Integer copies) { + this.copies = copies; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getISBN() { + return isbn; + } + + public void setISBN(String isbn) { + this.isbn = isbn; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getDateReceived() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); + return dateReceived.format(formatter); + } + + public LocalDateTime getDateReceivedUnparsed() { + return dateReceived; + } + + public void setReceivedDate() { + dateReceived = LocalDateTime.now(); + } + + public void setReceivedDateCustom(LocalDateTime ldt) { + dateReceived = ldt; + } + + public String toString() { + return "[" + tag + "] " + title; + } + + /** + * Constructs and returns a string of resource details formatted with given format specifiers. + * + * @param formatString String containing format specifiers and messages to display. + * @param tableFormatter Formatter storing formatted string for display. + */ + + public Formatter toTableFormat(String formatString, Formatter tableFormatter) { + tableFormatter.format(formatString, "null", + id, title, isbn, "null", + "null", status, + getDateReceived()); + return tableFormatter; + } + public static boolean hasGenre(Resource resource, String genreKeyword) { + + boolean isBook = (resource instanceof Book); + if (!isBook){ + return false; + } + + Book bookResource = (Book) resource; + String[] genres = bookResource.getGenre(); + genreKeyword = genreKeyword.toLowerCase(); + + if (genres[0] == null && genreKeyword == "null") { + return true; + } else if (genres[0] == null) { + return false; + } + + for (int i =0;i< genres.length;i++) { + String genreName = genres[i].toLowerCase(); + if (genreName.equals(genreKeyword)) { + return true; + } + } + + return false; + } + public void setStatus(Status status) { + this.status = status; + } + + public Status getStatus() { + return status; + } + + /** + * Checks if attributes length are longer than the current width of columns for a table displaying its details. + * Updates columnsWidth if it is longer to ensure alignment is correct. + * + * @param columnsWidth List containing column width of each resource attribute. + */ + public List<Integer> checkColumnsWidths(List<Integer> columnsWidth) { + int titleLength = getTitle().length(); + if (titleLength > columnsWidth.get(2)) { + columnsWidth.set(2,titleLength+1); + } + return columnsWidth; + } + +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * 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?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/seedu/exception/SysLibException.java b/src/main/java/seedu/exception/SysLibException.java new file mode 100644 index 0000000000..08be23c95c --- /dev/null +++ b/src/main/java/seedu/exception/SysLibException.java @@ -0,0 +1,7 @@ +package seedu.exception; + +public class SysLibException extends Exception { + public SysLibException(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..7511026e64 --- /dev/null +++ b/src/main/java/seedu/parser/Parser.java @@ -0,0 +1,139 @@ +package seedu.parser; + +import seedu.commands.events.EventAddCommand; +import seedu.commands.events.EventDeleteCommand; +import seedu.commands.events.EventEditCommand; +import seedu.commands.events.EventListCommand; +import seedu.data.GenericList; +import seedu.data.Status; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; + +import seedu.commands.Command; +import seedu.commands.CommandResult; +import seedu.commands.AddCommand; +import seedu.commands.DeleteCommand; +import seedu.commands.FindCommand; +import seedu.commands.ListCommand; +import seedu.commands.HelpCommand; +import seedu.commands.ExitCommand; +import seedu.commands.EditCommand; +import seedu.commands.SummaryCommand; + +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ERROR_TAG; +import static seedu.ui.Messages.ERROR_INVALID_START; +import static seedu.ui.UI.LINEDIVIDER; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Parser { + + public List<Resource> resourcesList = new ArrayList<>(); + public List<Event> eventsList = new ArrayList<>(); + public GenericList<Resource, Event> container = new GenericList<>(resourcesList, eventsList); + + // @@author DavinciDelta + public HashMap<String, Command> commandProcessor = new HashMap<>() { + { + put("list", new ListCommand()); + put("delete", new DeleteCommand()); + put("find", new FindCommand()); + put("help", new HelpCommand()); + put("exit", new ExitCommand()); + put("add", new AddCommand()); + put("edit", new EditCommand()); + put("eventadd", new EventAddCommand()); + put("eventdelete", new EventDeleteCommand()); + put("eventedit", new EventEditCommand()); + put("eventlist", new EventListCommand()); + put("summary", new SummaryCommand()); + } + }; + + public ArrayList<String> commands = new ArrayList<>(commandProcessor.keySet()); + + // @@author DavinciDelta + public void processUserResponse(String response) { + String command = response.split(" ")[0].toLowerCase(); + if (commandProcessor.containsKey(command)) { + String statement = removeFirstWord(response); + try { + CommandResult commandResult = commandProcessor.get(command).execute(statement, container); + System.out.print(commandResult.feedbackToUser); + } catch (IllegalArgumentException | IllegalStateException | SysLibException e) { + System.out.println(e.getMessage()); + } + } else { + String suggestion = SuggestParser.suggest(command, commands); + System.out.println("no commands found. Enter \"help\" for a list of commands."); + if (suggestion != null) { + System.out.println("Did you mean: '" + suggestion + "'"); + } + System.out.println(LINEDIVIDER); + } + + } + + // @@author DavinciDelta + public static String removeFirstWord(String response) { + int index = response.indexOf(" "); + if (index == -1) { + return ""; + } + return response.substring(index + 1); + } + + // @@author JoanneJo + public static String parseAddCommand(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + checkFirstItem(statement); + + String inputPattern = "/tag (.+?)(?=\\s/|$)"; + + Pattern pattern = Pattern.compile(inputPattern); + Matcher matcher = pattern.matcher(statement); + boolean isMatching = matcher.find(); + + if (isMatching) { + return matcher.group(1).trim(); + } else { + throw new SysLibException(ERROR_TAG); + } + } + + // @@author JoanneJo + public static void checkFirstItem(String statement) throws SysLibException { + String wordPattern = "\\s/(.+?)"; + Matcher wordMatcher = Pattern.compile(wordPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isWordMatching = wordMatcher.matches(); + + if (!isWordMatching) { + throw new SysLibException(ERROR_INVALID_START); + } + } + + // @@author bnjm2000 + /** + * @param statusString input string status + * @return Status object + */ + public static Status getStatusFromString(String statusString) { + if (statusString != null) { + statusString = statusString.toLowerCase().trim(); + if (statusString.equalsIgnoreCase("borrowed")) { + return Status.BORROWED; + } else if (statusString.equalsIgnoreCase("lost")) { + return Status.LOST; + } + } + // Default to Available if the status is not provided or unrecognized + return Status.AVAILABLE; + } +} diff --git a/src/main/java/seedu/parser/SuggestParser.java b/src/main/java/seedu/parser/SuggestParser.java new file mode 100644 index 0000000000..c8be7a6550 --- /dev/null +++ b/src/main/java/seedu/parser/SuggestParser.java @@ -0,0 +1,56 @@ +package seedu.parser; + +import java.util.List; + +public class SuggestParser { + /** + * Suggest next possible argument for user to type + * @param input User input + * @param commands Possible arguments + * @return Closest match is there is one, else null + */ + public static String suggest(String input, List<String> commands) { + int minDistance = 2; + String closestMatch = null; + + for (String command : commands) { + int distance = levenshteinDistance(input, command); + if (distance < minDistance) { + minDistance = distance; + closestMatch = command; + } + } + return closestMatch; + } + + /** + * Compare 'distance' between 2 strings with the levenshtein formula + * @param first First string + * @param second Second string + * @return distance between the 2 strings + */ + public static int levenshteinDistance(String first, String second) { + int[][] array = new int[first.length() + 1][second.length() + 1]; + + for (int firstPointer = 0; firstPointer <= first.length(); firstPointer++) { + for (int secondPointer = 0; secondPointer <= second.length(); secondPointer++) { + if (firstPointer == 0) { + array[firstPointer][secondPointer] = secondPointer; + } else if (secondPointer == 0) { + array[firstPointer][secondPointer] = firstPointer; + } else { + int temp = 1; + if(first.charAt(firstPointer - 1) == second.charAt(secondPointer - 1) ) { + temp = 0; + } + int firstMin = Math.min(array[firstPointer - 1][secondPointer - 1] + temp, + array[firstPointer - 1][secondPointer] + 1); + array[firstPointer][secondPointer] = Math.min(firstMin, + array[firstPointer][secondPointer - 1] + 1); + } + } + } + return array[first.length()][second.length()]; + } + +} diff --git a/src/main/java/seedu/parser/resources/ParseAttribute.java b/src/main/java/seedu/parser/resources/ParseAttribute.java new file mode 100644 index 0000000000..54b4b9d9c4 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseAttribute.java @@ -0,0 +1,308 @@ +package seedu.parser.resources; + +import seedu.data.Status; +import seedu.exception.SysLibException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.parser.resources.ParseResource.countDuplicate; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ERROR_ISBN; +import static seedu.ui.Messages.ERROR_TITLE; +import static seedu.ui.Messages.ERROR_AUTHOR; +import static seedu.ui.Messages.ERROR_TAG; +import static seedu.ui.Messages.ERROR_GENRE; +import static seedu.ui.Messages.ERROR_CREATOR; +import static seedu.ui.Messages.ERROR_TYPE; +import static seedu.ui.Messages.ERROR_BRAND; +import static seedu.ui.Messages.ERROR_ISSUE; +import static seedu.ui.Messages.ERROR_PUBLISHER; +import static seedu.ui.Messages.ERROR_EDITION; +import static seedu.ui.Messages.ERROR_LINK; +import static seedu.ui.Messages.ERROR_STATUS; +import static seedu.ui.Messages.ATTENTION_STATUS; +import static seedu.ui.Messages.ERROR_DUPLICATE_ISBN; +import static seedu.ui.Messages.ERROR_DUPLICATE_TITLE; +import static seedu.ui.Messages.ERROR_DUPLICATE_AUTHOR; +import static seedu.ui.Messages.ERROR_DUPLICATE_TAG; +import static seedu.ui.Messages.ERROR_DUPLICATE_GENRE; +import static seedu.ui.Messages.ERROR_DUPLICATE_CREATOR; +import static seedu.ui.Messages.ERROR_DUPLICATE_TYPE; +import static seedu.ui.Messages.ERROR_DUPLICATE_BRAND; +import static seedu.ui.Messages.ERROR_DUPLICATE_ISSUE; +import static seedu.ui.Messages.ERROR_DUPLICATE_PUBLISHER; +import static seedu.ui.Messages.ERROR_DUPLICATE_EDITION; +import static seedu.ui.Messages.ERROR_DUPLICATE_LINK; +import static seedu.ui.Messages.ERROR_DUPLICATE_STATUS; + +public class ParseAttribute { + private static final String TITLE_ARG = "/t\\s"; + private static final String AUTHOR_ARG = "/a\\s"; + private static final String TAG_ARG = "/tag\\s"; + private static final String ISBN_ARG = "/i\\s"; + private static final String GENRE_ARG = "/g\\s"; + private static final String STATUS_ARG = "/s\\s"; + private static final String LINK_ARG = "/l\\s"; + private static final String CREATOR_ARG = "/c\\s"; + private static final String BRAND_ARG = "/b\\s"; + private static final String PUBLISHER_ARG = "/p\\s"; + private static final String TYPE_ARG = "/ty\\s"; + private static final String ISSUE_ARG = "/is\\s"; + private static final String EDITION_ARG = "/ed\\s"; + private static final int ISBN_LENGTH = 13; + public static void parseIsbn(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, ISBN_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_ISBN); + } + + String pattern = "(?=.*/i (\\S+)(?=\\s?/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_ISBN); + } + + boolean isDigit = matcher.group(1).trim().matches("\\d+"); + boolean isCorrectLength = matcher.group(1).trim().length() == ISBN_LENGTH; + + if (!isDigit | !isCorrectLength) { + throw new SysLibException(ERROR_ISBN); + } + } + + public static void parseTitle(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, TITLE_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_TITLE); + } + + String pattern = "(?=.*/t (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_TITLE); + } + } + + public static void parseAuthor(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, AUTHOR_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_AUTHOR); + } + + String pattern = "(?=.*/a (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_AUTHOR); + } + } + + public static void parseTag(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, TAG_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_TAG); + } + + String pattern = "(?=.*/tag (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_TAG); + } + } + + public static boolean parseGenre(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, GENRE_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_GENRE); + } + + String pattern = "(?=.*/g (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (isMatching) { + boolean isEmpty = matcher.group(1).trim().isEmpty(); + boolean isSpace = matcher.group(1).trim().matches("\\s+"); + + if (isEmpty || isSpace) { + throw new SysLibException(ERROR_GENRE); + } + } + + return matcher.find(); + } + + public static void parseCreator(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, CREATOR_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_CREATOR); + } + + String pattern = "(?=.*/c (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_CREATOR); + } + } + + public static void parseType(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, TYPE_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_TYPE); + } + + String pattern = "(?=.*/ty (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_TYPE); + } + } + + public static void parseBrand(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, BRAND_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_BRAND); + } + + String pattern = "(?=.*/b (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_BRAND); + } + } + + public static void parseIssue(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, ISSUE_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_ISSUE); + } + + String pattern = "(?=.*/is (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_ISSUE); + } + } + + public static void parsePublisher(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, PUBLISHER_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_PUBLISHER); + } + + String pattern = "(?=.*/p (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_PUBLISHER); + } + } + + public static void parseEdition(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, EDITION_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_EDITION); + } + + String pattern = "(?=.*/ed (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_EDITION); + } + } + + public static void parseLink(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, LINK_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_LINK); + } + + String pattern = "(?=.*/l (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (!isMatching) { + throw new SysLibException(ERROR_LINK); + } + } + + public static boolean parseStatus(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + boolean hasDuplicate = countDuplicate(statement, STATUS_ARG) > 1; + if (hasDuplicate) { + throw new SysLibException(ERROR_DUPLICATE_STATUS); + } + + String pattern = "(?=.*/s (.+?)(?=\\s/|$))"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isMatching = matcher.find(); + + if (isMatching) { + boolean isEmpty = matcher.group(1).trim().isEmpty(); + boolean isSpace = matcher.group(1).trim().matches("\\s+"); + + if (isEmpty || isSpace) { + throw new SysLibException(ERROR_STATUS); + } + + boolean isValidAvailableStatus = matcher.group(1).trim(). + equalsIgnoreCase(String.valueOf(Status.AVAILABLE)); + boolean isValidBorrowedStatus = matcher.group(1).trim().equalsIgnoreCase(String.valueOf(Status.BORROWED)); + boolean isValidLostStatus = matcher.group(1).trim().equalsIgnoreCase(String.valueOf(Status.LOST)); + + if (isValidAvailableStatus || isValidBorrowedStatus || isValidLostStatus) { + return isMatching; + } else { + throw new SysLibException(ERROR_STATUS); + } + } else { + System.out.println(ATTENTION_STATUS); + } + + return isMatching; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseBook.java b/src/main/java/seedu/parser/resources/ParseBook.java new file mode 100644 index 0000000000..4bdbdb8050 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseBook.java @@ -0,0 +1,147 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.AUTHOR_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.GENRE_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.AUTHOR_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.GENRE_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parseAuthor; +import static seedu.parser.resources.ParseAttribute.parseTag; +import static seedu.parser.resources.ParseAttribute.parseGenre; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_BOOK; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_BOOK; + +public class ParseBook { + public static String[] args = new String[6]; + public static String[] parseAddBook(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/a (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/g (.+?)(?=\\s/|$))?(?=.*/s (.+?)(?=\\s/|$))?.*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean[] isOptionalsMatching = parseBookArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // author + if (isOptionalsMatching[0]) { + args[3] = matcher.group(5).trim(); // genre + } + if (isOptionalsMatching[1]) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + + checkEmptyBookArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_BOOK); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_BOOK); + } + } + + public static Boolean[] parseBookArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parseAuthor(statement); + parseTag(statement); + + return new Boolean[]{parseGenre(statement), parseStatus(statement)}; + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, AUTHOR_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, GENRE_OPTION) || + Objects.equals(inputArg, STATUS_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(AUTHOR_ARG) || + arg.contains(TAG_ARG) || + arg.contains(GENRE_ARG) || + arg.contains(STATUS_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_BOOK); + } + } + } + } + + public static void checkEmptyBookArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[5].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_BOOK); + } + } + + public static void resetBookArgs() { + args = new String[6]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseCD.java b/src/main/java/seedu/parser/resources/ParseCD.java new file mode 100644 index 0000000000..552ca6c5b6 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseCD.java @@ -0,0 +1,144 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.CREATOR_OPTION; +import static seedu.commands.AddCommand.TYPE_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parseCreator; +import static seedu.parser.resources.ParseAttribute.parseType; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.CREATOR_ARG; +import static seedu.parser.resources.ParseResource.TYPE_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_CD; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_CD; + +public class ParseCD { + public static String[] args = new String[6]; + public static String[] parseAddCD(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/c (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/ty (.+?)(?=\\s/|$))(?=.*/s (.+?)(?=\\s/|$))?.*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean isStatusMatching = parseCDArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // creator + args[3] = matcher.group(5).trim(); // type + if (isStatusMatching) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + + checkEmptyCDArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_CD); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_CD); + } + } + + public static Boolean parseCDArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parseCreator(statement); + parseType(statement); + + return parseStatus(statement); + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, CREATOR_OPTION) || + Objects.equals(inputArg, TYPE_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, STATUS_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(CREATOR_ARG) || + arg.contains(TYPE_ARG) || + arg.contains(TAG_ARG) || + arg.contains(STATUS_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_CD); + } + } + } + } + + public static void checkEmptyCDArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[3].isEmpty() || args[5].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_CD); + } + } + + public static void resetCDArgs() { + args = new String[6]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseEBook.java b/src/main/java/seedu/parser/resources/ParseEBook.java new file mode 100644 index 0000000000..824a0062a5 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseEBook.java @@ -0,0 +1,155 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.AUTHOR_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.GENRE_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.commands.AddCommand.LINK_OPTION; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parseAuthor; +import static seedu.parser.resources.ParseAttribute.parseTag; +import static seedu.parser.resources.ParseAttribute.parseLink; +import static seedu.parser.resources.ParseAttribute.parseGenre; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.AUTHOR_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.GENRE_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.parser.resources.ParseResource.LINK_ARG; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_EBOOK; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_EBOOK; + +public class ParseEBook { + public static String[] args = new String[7]; + public static String[] parseAddEBook(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/a (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/g (.+?)(?=\\s/|$))?(?=.*/s (.+?)(?=\\s/|$))?" + + "(?=.*/l (.+?)(?=\\s/|$)).*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean[] isOptionalsMatching = parseEBookArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // author + if (isOptionalsMatching[0]) { + args[3] = matcher.group(5).trim(); // genre + } + if (isOptionalsMatching[1]) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + args[6] = matcher.group(7).trim(); // link + + checkEmptyEBookArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_EBOOK); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_EBOOK); + } + } + + public static Boolean[] parseEBookArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parseAuthor(statement); + parseTag(statement); + parseLink(statement); + + return new Boolean[]{parseGenre(statement), parseStatus(statement)}; + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, AUTHOR_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, GENRE_OPTION) || + Objects.equals(inputArg, STATUS_OPTION) || + Objects.equals(inputArg, LINK_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(AUTHOR_ARG) || + arg.contains(TAG_ARG) || + arg.contains(GENRE_ARG) || + arg.contains(STATUS_ARG) || + arg.contains(LINK_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_EBOOK); + } + } + } + } + + public static void checkEmptyEBookArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[5].isEmpty() || args[6].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_EBOOK); + } + } + + public static void resetEBookArgs() { + args = new String[7]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseEMagazine.java b/src/main/java/seedu/parser/resources/ParseEMagazine.java new file mode 100644 index 0000000000..b8117d0fdc --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseEMagazine.java @@ -0,0 +1,153 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.BRAND_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.ISSUE_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.commands.AddCommand.LINK_OPTION; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parseBrand; +import static seedu.parser.resources.ParseAttribute.parseIssue; +import static seedu.parser.resources.ParseAttribute.parseLink; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.BRAND_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.ISSUE_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.parser.resources.ParseResource.LINK_ARG; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_EMAGAZINE; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_EMAGAZINE; + +public class ParseEMagazine { + public static String[] args = new String[7]; + public static String[] parseAddEMagazine(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/b (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/is (.+?)(?=\\s/|$))(?=.*/s (.+?)(?=\\s/|$))?" + + "(?=.*/l (.+?)(?=\\s/|$)).*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean isStatusMatching = parseEMagazineArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // brand + args[3] = matcher.group(5).trim(); // issue + if (isStatusMatching) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + args[6] = matcher.group(7).trim(); // link + + checkEmptyEMagazineArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_EMAGAZINE); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_EMAGAZINE); + } + } + + public static Boolean parseEMagazineArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parseBrand(statement); + parseIssue(statement); + parseLink(statement); + + return parseStatus(statement); + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, BRAND_OPTION) || + Objects.equals(inputArg, ISSUE_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, STATUS_OPTION) || + Objects.equals(inputArg, LINK_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(BRAND_ARG) || + arg.contains(ISSUE_ARG) || + arg.contains(TAG_ARG) || + arg.contains(STATUS_ARG) || + arg.contains(LINK_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_EMAGAZINE); + } + } + } + } + + public static void checkEmptyEMagazineArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[3].isEmpty() || args[5].isEmpty() + || args[6].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_EMAGAZINE); + } + } + + public static void resetEMagazineArgs() { + args = new String[7]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseENewspaper.java b/src/main/java/seedu/parser/resources/ParseENewspaper.java new file mode 100644 index 0000000000..f5947e1d56 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseENewspaper.java @@ -0,0 +1,154 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.PUBLISHER_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.EDITION_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.commands.AddCommand.LINK_OPTION; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parsePublisher; +import static seedu.parser.resources.ParseAttribute.parseEdition; +import static seedu.parser.resources.ParseAttribute.parseLink; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.PUBLISHER_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.EDITION_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.parser.resources.ParseResource.LINK_ARG; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_ENEWSPAPER; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_ENEWSPAPER; + + +public class ParseENewspaper { + public static String[] args = new String[7]; + public static String[] parseAddENewspaper(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/p (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/ed (.+?)(?=\\s/|$))(?=.*/s (.+?)(?=\\s/|$))?" + + "(?=.*/l (.+?)(?=\\s/|$)).*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean isStatusMatching = parseEMagazineArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // publisher + args[3] = matcher.group(5).trim(); // edition + if (isStatusMatching) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + args[6] = matcher.group(7).trim(); // link + + checkEmptyENewspaperArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_ENEWSPAPER); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_ENEWSPAPER); + } + } + + public static Boolean parseEMagazineArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parsePublisher(statement); + parseEdition(statement); + parseLink(statement); + + return parseStatus(statement); + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, PUBLISHER_OPTION) || + Objects.equals(inputArg, EDITION_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, STATUS_OPTION) || + Objects.equals(inputArg, LINK_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(PUBLISHER_ARG) || + arg.contains(EDITION_ARG) || + arg.contains(TAG_ARG) || + arg.contains(STATUS_ARG) || + arg.contains(LINK_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_ENEWSPAPER); + } + } + } + } + + public static void checkEmptyENewspaperArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[3].isEmpty() || args[5].isEmpty() + || args[6].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_ENEWSPAPER); + } + } + + public static void resetENewspaperArgs() { + args = new String[7]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseMagazine.java b/src/main/java/seedu/parser/resources/ParseMagazine.java new file mode 100644 index 0000000000..785554f7c2 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseMagazine.java @@ -0,0 +1,144 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.BRAND_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.ISSUE_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parseBrand; +import static seedu.parser.resources.ParseAttribute.parseIssue; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.BRAND_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.ISSUE_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_MAGAZINE; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_MAGAZINE; + +public class ParseMagazine { + public static String[] args = new String[6]; + public static String[] parseAddMagazine(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/b (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/is (.+?)(?=\\s/|$))(?=.*/s (.+?)(?=\\s/|$))?.*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean isStatusMatching = parseMagazineArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // brand + args[3] = matcher.group(5).trim(); // issue + if (isStatusMatching) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + + checkEmptyMagazineArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_MAGAZINE); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_MAGAZINE); + } + } + + public static Boolean parseMagazineArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parseBrand(statement); + parseIssue(statement); + + return parseStatus(statement); + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, BRAND_OPTION) || + Objects.equals(inputArg, ISSUE_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, STATUS_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(BRAND_ARG) || + arg.contains(ISSUE_ARG) || + arg.contains(TAG_ARG) || + arg.contains(STATUS_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_MAGAZINE); + } + } + } + } + + public static void checkEmptyMagazineArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[3].isEmpty() || args[5].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_MAGAZINE); + } + } + + public static void resetMagazineArgs() { + args = new String[6]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseNewspaper.java b/src/main/java/seedu/parser/resources/ParseNewspaper.java new file mode 100644 index 0000000000..7bb339deb8 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseNewspaper.java @@ -0,0 +1,144 @@ +package seedu.parser.resources; + +import seedu.exception.SysLibException; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.commands.AddCommand.ISBN_OPTION; +import static seedu.commands.AddCommand.TITLE_OPTION; +import static seedu.commands.AddCommand.PUBLISHER_OPTION; +import static seedu.commands.AddCommand.TAG_OPTION; +import static seedu.commands.AddCommand.EDITION_OPTION; +import static seedu.commands.AddCommand.STATUS_OPTION; +import static seedu.parser.resources.ParseAttribute.parseIsbn; +import static seedu.parser.resources.ParseAttribute.parseTitle; +import static seedu.parser.resources.ParseAttribute.parsePublisher; +import static seedu.parser.resources.ParseAttribute.parseEdition; +import static seedu.parser.resources.ParseAttribute.parseStatus; +import static seedu.parser.resources.ParseResource.ISBN_ARG; +import static seedu.parser.resources.ParseResource.TITLE_ARG; +import static seedu.parser.resources.ParseResource.PUBLISHER_ARG; +import static seedu.parser.resources.ParseResource.TAG_ARG; +import static seedu.parser.resources.ParseResource.EDITION_ARG; +import static seedu.parser.resources.ParseResource.STATUS_ARG; +import static seedu.parser.resources.ParseResource.hasUnusedSlash; +import static seedu.ui.Messages.ASSERT_STATEMENT; +import static seedu.ui.Messages.ASSERT_ARGUMENTS; +import static seedu.ui.Messages.ERROR_FORMAT_NEWSPAPER; +import static seedu.ui.Messages.ERROR_UNUSED_SLASH; +import static seedu.ui.Messages.ERROR_INVALID_ARGUMENT; +import static seedu.ui.Messages.ERROR_EMPTY_NEWSPAPER; + +public class ParseNewspaper { + public static String[] args = new String[6]; + public static String[] parseAddNewspaper(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + try { + String inputPattern = "^(?=.*/i (.+?)(?=\\s/|$))(?=.*/t (.+?)(?=\\s/|$))(?=.*/p (.+?)(?=\\s/|$))" + + "(?=.*/tag (.+?)(?=\\s/|$))(?=.*/ed (.+?)(?=\\s/|$))(?=.*/s (.+?)(?=\\s/|$))?.*$"; + Matcher matcher = Pattern.compile(inputPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean isInputMatching = matcher.find(); + + Boolean isStatusMatching = parseNewspaperArgs(statement); + + if (isInputMatching) { + args[0] = matcher.group(1).trim(); // isbn + args[1] = matcher.group(2).trim(); // title + args[2] = matcher.group(3).trim(); // publisher + args[3] = matcher.group(5).trim(); // edition + if (isStatusMatching) { + args[4] = matcher.group(6).trim(); // status + } + args[5] = matcher.group(4).trim(); // tag + + checkEmptyNewspaperArgs(args); + } else { + throw new SysLibException(ERROR_FORMAT_NEWSPAPER); + } + + return args; + } catch (IllegalStateException e) { + throw new SysLibException(ERROR_FORMAT_NEWSPAPER); + } + } + + public static Boolean parseNewspaperArgs(String statement) throws SysLibException { + assert statement != null : ASSERT_STATEMENT; + + if (hasUnusedSlash(statement)) { + throw new SysLibException(ERROR_UNUSED_SLASH); + } + + if (hasInvalidArgument(statement)) { + throw new SysLibException(ERROR_INVALID_ARGUMENT); + } + + parseIsbn(statement); + parseTitle(statement); + parsePublisher(statement); + parseEdition(statement); + + return parseStatus(statement); + } + + public static boolean hasInvalidArgument(String statement) { + + ArrayList<String> inputArgs = new ArrayList<String>(); + + String pattern = "\\s/(\\S+)"; + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + while(matcher.find()) { + inputArgs.add(matcher.group(1).trim()); + } + + for (String inputArg : inputArgs) { + boolean isValidArg = Objects.equals(inputArg, ISBN_OPTION) || + Objects.equals(inputArg, TITLE_OPTION) || + Objects.equals(inputArg, PUBLISHER_OPTION) || + Objects.equals(inputArg, EDITION_OPTION) || + Objects.equals(inputArg, TAG_OPTION) || + Objects.equals(inputArg, STATUS_OPTION); + + if (!isValidArg) { + return true; + } + } + + return false; + } + + public static void checkEmptyArg(String[] args) throws SysLibException { + for (String arg : args) { + if (arg != null) { + boolean hasValidArg = arg.contains(ISBN_ARG) || + arg.contains(TITLE_ARG) || + arg.contains(PUBLISHER_ARG) || + arg.contains(EDITION_ARG) || + arg.contains(TAG_ARG) || + arg.contains(STATUS_ARG); + + if (hasValidArg) { + throw new SysLibException(ERROR_FORMAT_NEWSPAPER); + } + } + } + } + + public static void checkEmptyNewspaperArgs(String[] args) throws SysLibException { + assert args != null : ASSERT_ARGUMENTS; + + checkEmptyArg(args); + + if (args[0].isEmpty() || args[1].isEmpty() || args[2].isEmpty() || args[3].isEmpty() || args[5].isEmpty()) { + throw new SysLibException(ERROR_EMPTY_NEWSPAPER); + } + } + + public static void resetNewspaperArgs() { + args = new String[6]; + } +} diff --git a/src/main/java/seedu/parser/resources/ParseResource.java b/src/main/java/seedu/parser/resources/ParseResource.java new file mode 100644 index 0000000000..ddafb5e4c5 --- /dev/null +++ b/src/main/java/seedu/parser/resources/ParseResource.java @@ -0,0 +1,51 @@ +package seedu.parser.resources; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ParseResource { + public static final String TITLE_ARG = "/t "; + public static final String AUTHOR_ARG = "/a "; + public static final String TAG_ARG = "/tag "; + public static final String ISBN_ARG = "/i "; + public static final String GENRE_ARG = "/g "; + public static final String STATUS_ARG = "/s "; + public static final String LINK_ARG = "/l "; + public static final String CREATOR_ARG = "/c "; + public static final String BRAND_ARG = "/b "; + public static final String PUBLISHER_ARG = "/p "; + public static final String TYPE_ARG = "/ty "; + public static final String ISSUE_ARG = "/is "; + public static final String EDITION_ARG = "/ed "; + + public static int countDuplicate(String statement, String pattern) { + Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(statement); + + int count = 0; + while(matcher.find()) { + count++; + } + + return count; + } + + public static boolean hasUnusedSlash(String statement) { + String begPattern = "^/\\s"; + Matcher begMatcher = Pattern.compile(begPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean hasBegSlash = begMatcher.find(); + + String midPattern = "\\s/\\s"; + Matcher midMatcher = Pattern.compile(midPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean hasMidSlash = midMatcher.find(); + + String etyPattern = "/\\s"; + Matcher etyMatcher = Pattern.compile(etyPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean hasEtySlash = etyMatcher.find(); + + String endPattern = "/$"; + Matcher endMatcher = Pattern.compile(endPattern, Pattern.CASE_INSENSITIVE).matcher(statement); + boolean hasEndSlash = endMatcher.find(); + + return hasBegSlash | hasMidSlash | hasEtySlash | hasEndSlash; + } +} diff --git a/src/main/java/seedu/storage/Storage.java b/src/main/java/seedu/storage/Storage.java new file mode 100644 index 0000000000..5b01e110b6 --- /dev/null +++ b/src/main/java/seedu/storage/Storage.java @@ -0,0 +1,447 @@ +package seedu.storage; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.IllegalFormatException; +import java.util.List; +import java.util.Scanner; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import seedu.data.GenericList; +import seedu.data.Status; +import seedu.data.events.Event; +import seedu.data.resources.Book; +import seedu.data.resources.EBook; +import seedu.data.resources.EMagazine; +import seedu.data.resources.Magazine; +import seedu.data.resources.ENewspaper; +import seedu.data.resources.Newspaper; +import seedu.data.resources.CD; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.ui.UI; + +public class Storage { + public static final int FIRST_INDEX = 0; + public static final int SECOND_INDEX = 1; + public static final int THIRD_INDEX = 2; + public static final int FOURTH_INDEX = 3; + public static final int FIFTH_INDEX = 4; + public static final int SIXTH_INDEX = 5; + public static final int SEVENTH_INDEX = 6; + public static final int EIGHTH_INDEX = 7; + public static final int NINTH_INDEX = 8; + public static final int TENTH_INDEX = 9; + public static final int ELEVENTH_INDEX = 10; + private static final String RESOURCE_PREFIX = "R"; + private static final String EVENT_PREFIX = "E"; + private static final Logger LOGGER = Logger.getLogger(Storage.class.getName()); + + private final File dataFile; + private final String filePath; + private final GenericList<Resource, Event> container; + private final UI ui = new UI(); + + static { + setupLogger(); + } + + /** + * Constructs a Storage object for handling data persistence. + * + * @param filePath The path to the data file. + * @param container The container holding resources and events. + */ + public Storage(String filePath, GenericList<Resource, Event> container) { + this.filePath = filePath; + this.dataFile = new File(filePath); + this.container = container; + try { + ensureFileExists(); + } catch (SysLibException SLEx){ + System.out.println(SLEx); + } + } + + /** + * Sets up the logger for this class. + */ + private static void setupLogger() { + try { + // remove logs from showing in stdout + Logger rootLogger = Logger.getLogger(""); + for (java.util.logging.Handler handler : rootLogger.getHandlers()) { + if (handler instanceof java.util.logging.ConsoleHandler) { + rootLogger.removeHandler(handler); + } + } + FileHandler fileHandler = new FileHandler("logs/Storage.log", true); + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to set up log file handler", e); + } + } + + /** + * Ensures the data folder and storage file exists, creating it if necessary. + */ + private void ensureFileExists() throws SysLibException { + ui.showLine(); + try { + Path dataPath = Paths.get(System.getProperty("user.dir"), "data"); + + // create directory + if (Files.notExists(dataPath)) { + System.out.println("Data directory does not exist. Creating now..."); + LOGGER.info("Data directory created."); + Files.createDirectory(dataPath); + } else { + System.out.println("Data directory exists."); + LOGGER.info("Data directory already exists."); + } + Path storagePath = dataPath.resolve("storage.txt"); + + // create file + if (Files.notExists(storagePath)) { + System.out.println("Storage file does not exist. Creating now..."); + LOGGER.info("Storage file created."); + Files.createFile(storagePath); + } else { + System.out.println("Storage file exists."); + LOGGER.info("Storage file found."); + } + + } catch (IOException e) { + throw new SysLibException("Unable to create data directory or storage file. " + + "Please run again with administrative privileges."); + } + } + + + /** + * Loads resources and events from the data file. + * + * @param resources The list to store loaded resources. + * @param events The list to store loaded events. + * @throws SysLibException If there is an issue reading from the file or parsing data. + */ + public void load(List<Resource> resources, List<Event> events) throws SysLibException { + LOGGER.info("Starting load from storage file."); + try (Scanner dataScanner = new Scanner(dataFile)) { + int id = 0; + while (dataScanner.hasNext()) { + String dataLine = dataScanner.nextLine(); + String[] splitLineArguments = dataLine.split(" \\| "); + + if (splitLineArguments.length == 0) { + throw new SysLibException("Empty line in data file"); + } + + String dataType = splitLineArguments[FIRST_INDEX]; + switch (dataType) { + case RESOURCE_PREFIX: + Resource resource = createResource(splitLineArguments, ++id); + resources.add(resource); + break; + case EVENT_PREFIX: + Event event = createEvent(splitLineArguments); + events.add(event); + break; + default: + throw new SysLibException("Unknown data type: " + dataType); + } + } + } catch (IOException e) { + LOGGER.info("Unable to read storage file."); + throw new SysLibException("Error reading file: " + filePath); + } + LOGGER.info("Successfully loaded data."); + } + + /** + * Creates a Resource object from the parsed data line. + * + * @param data The parsed data line. + * @param id The ID for the resource. + * @return The created Resource object. + * @throws SysLibException If there is an issue parsing the data. + */ + private Resource createResource(String[] data, int id) throws SysLibException { + try { + String title = data[SECOND_INDEX]; + boolean isBorrowed = Boolean.parseBoolean(data[THIRD_INDEX]); + String isbn = data[FOURTH_INDEX]; + int copies = Integer.parseInt(data[FIFTH_INDEX]); + String tag = data[SIXTH_INDEX]; + Status status = Status.valueOf(data[SEVENTH_INDEX]); + LocalDateTime ldt = LocalDateTime.parse(data[EIGHTH_INDEX]); + + switch(tag) { + case "B": + String author = data[NINTH_INDEX]; + String[] genres = data[TENTH_INDEX].split(","); + Book bookToAdd = new Book(title, isbn, author, genres, id, status); + bookToAdd.setCopies(copies); + bookToAdd.setBorrowed(isBorrowed); + bookToAdd.setReceivedDateCustom(ldt); + return bookToAdd; + + case "EB": + String eauthor = data[NINTH_INDEX]; + String[] egenres = data[TENTH_INDEX].split(","); + String blink = data[ELEVENTH_INDEX]; + EBook ebookToAdd = new EBook(title, isbn, eauthor, egenres, id, status, blink); + ebookToAdd.setCopies(copies); + ebookToAdd.setBorrowed(isBorrowed); + ebookToAdd.setReceivedDateCustom(ldt); + return ebookToAdd; + + case "N": + String publisher = data[NINTH_INDEX]; + String edition = data[TENTH_INDEX]; + Newspaper newspaperToAdd = new Newspaper(title, isbn, publisher, edition, id, status); + newspaperToAdd.setCopies(copies); + newspaperToAdd.setBorrowed(isBorrowed); + newspaperToAdd.setReceivedDateCustom(ldt); + return newspaperToAdd; + + case "EN": + String epublisher = data[NINTH_INDEX]; + String eedition = data[TENTH_INDEX]; + String nlink = data[ELEVENTH_INDEX]; + ENewspaper enewspaperToAdd = new ENewspaper(title, isbn, epublisher, eedition, + id, status, nlink); + enewspaperToAdd.setCopies(copies); + enewspaperToAdd.setBorrowed(isBorrowed); + enewspaperToAdd.setReceivedDateCustom(ldt); + return enewspaperToAdd; + + case "M": + String brand = data[NINTH_INDEX]; + String issue = data[TENTH_INDEX]; + Magazine magazineToAdd = new Magazine(title, isbn, brand, issue, id, status); + magazineToAdd.setCopies(copies); + magazineToAdd.setBorrowed(isBorrowed); + magazineToAdd.setReceivedDateCustom(ldt); + return magazineToAdd; + + case "EM": + String ebrand = data[NINTH_INDEX]; + String eissue = data[TENTH_INDEX]; + String mlink = data[ELEVENTH_INDEX]; + EMagazine emagazineToAdd = new EMagazine(title, isbn, ebrand, eissue, id, + status, mlink); + emagazineToAdd.setCopies(copies); + emagazineToAdd.setBorrowed(isBorrowed); + emagazineToAdd.setReceivedDateCustom(ldt); + return emagazineToAdd; + + case "CD": + String creator = data[NINTH_INDEX]; + String type = data[TENTH_INDEX]; + CD cdToAdd = new CD(title, isbn, creator, type, id, status); + cdToAdd.setCopies(copies); + cdToAdd.setBorrowed(isBorrowed); + cdToAdd.setReceivedDateCustom(ldt); + return cdToAdd; + + default: + LOGGER.info("Data corrupted, unable to load."); + throw new SysLibException("Unknown resource type found, data corrupted."); + } + } catch (ClassCastException CCEx) { + LOGGER.info("Unable to create resource."); + throw new SysLibException("Error creating resource from data: " + CCEx.getMessage()); + } + } + + /** + * Creates an Event object from the parsed data line. + * + * @param data The parsed data line. + * @return The created Event object. + * @throws SysLibException If there is an issue parsing the data. + */ + private Event createEvent(String[] data) throws SysLibException { + try { + String name = data[SECOND_INDEX]; + String description = data[THIRD_INDEX]; + LocalDate eventld = LocalDate.parse(data[FOURTH_INDEX]); + Event eventToAdd = new Event(name, eventld, description); + return eventToAdd; + } catch (Exception e) { + LOGGER.info("Unable to create event from data."); + throw new SysLibException("Error creating event from data: " + e.getMessage()); + } + } + + /** + * Saves the current state of resources and events to the data file. + * + * @throws SysLibException If there is an issue writing to the file. + */ + public void save() throws SysLibException { + LOGGER.info("Starting to save data to storage file."); + try (FileWriter fw = new FileWriter(this.filePath)) { + for (Resource resource : container.getResourcesList()) { + String resourceSaveFormat = getResourceSaveFormat(resource); + fw.write(resourceSaveFormat); + } + for (Event event : container.getEventsList()) { + String eventSaveFormat = getEventSaveFormat(event); + fw.write(eventSaveFormat); + } + } catch (IOException e) { + LOGGER.info("Unable to write to storage file."); + throw new SysLibException("Error writing to file: " + filePath); + } + LOGGER.info("Successfully saved to storage file."); + } + + /** + * Generates a formatted string for saving a resource to the file. + * + * @param resourceToSave The resource to format. + * @return The formatted string. + * @throws SysLibException If there is an issue formatting the resource. + */ + private String getResourceSaveFormat(Resource resourceToSave) throws SysLibException { + String resourceSaveFormat; + try { + switch (resourceToSave.getTag()) { + case "B": // Book + Book book = (Book) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s%n", + book.getTitle(), + book.isBorrowed(), + book.getISBN(), + book.getCopies(), + book.getTag(), + book.getStatus(), + book.getDateReceivedUnparsed(), + book.getAuthor(), + String.join(",", book.getGenre())); + break; + case "EB": // eBook + EBook ebook = (EBook) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s | %s%n", + ebook.getTitle(), + ebook.isBorrowed(), + ebook.getISBN(), + ebook.getCopies(), + ebook.getTag(), + ebook.getStatus(), + ebook.getDateReceivedUnparsed(), + ebook.getAuthor(), + String.join(",", ebook.getGenre()), + ebook.getLink()); + break; + + case "CD": // CD + CD cd = (CD) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s%n", + cd.getTitle(), + cd.isBorrowed(), + cd.getISBN(), + cd.getCopies(), + cd.getTag(), + cd.getStatus(), + cd.getDateReceivedUnparsed(), + cd.getCreator(), + cd.getType()); + break; + + case "M": // Magazine + Magazine magazine = (Magazine) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s%n", + magazine.getTitle(), + magazine.isBorrowed(), + magazine.getISBN(), + magazine.getCopies(), + magazine.getTag(), + magazine.getStatus(), + magazine.getDateReceivedUnparsed(), + magazine.getBrand(), + magazine.getIssue()); + break; + + case "EM": // eMagazine + EMagazine emagazine = (EMagazine) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s | %s%n", + emagazine.getTitle(), + emagazine.isBorrowed(), + emagazine.getISBN(), + emagazine.getCopies(), + emagazine.getTag(), + emagazine.getStatus(), + emagazine.getDateReceivedUnparsed(), + emagazine.getBrand(), + emagazine.getIssue(), + emagazine.getLink()); + break; + + case "N": // Newspaper + Newspaper newspaper = (Newspaper) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s%n", + newspaper.getTitle(), + newspaper.isBorrowed(), + newspaper.getISBN(), + newspaper.getCopies(), + newspaper.getTag(), + newspaper.getStatus(), + newspaper.getDateReceivedUnparsed(), + newspaper.getPublisher(), + newspaper.getEdition()); + break; + + case "EN": // eNewspaper + ENewspaper enewspaper = (ENewspaper) resourceToSave; + resourceSaveFormat = String.format("R | %s | %b | %s | %d | %s | %s | %s | %s | %s | %s%n", + enewspaper.getTitle(), + enewspaper.isBorrowed(), + enewspaper.getISBN(), + enewspaper.getCopies(), + enewspaper.getTag(), + enewspaper.getStatus(), + enewspaper.getDateReceivedUnparsed(), + enewspaper.getPublisher(), + enewspaper.getEdition(), + enewspaper.getLink()); + break; + default: + LOGGER.info("Corrupted data in list. Can't save."); + throw new SysLibException("Unknown data type in list. Can't store it in file."); + } + + } catch (IllegalFormatException IFEx) { + LOGGER.info("Formatting error when trying to save.."); + throw new SysLibException("Error formatting resource for save: " + IFEx.getMessage()); + } + return resourceSaveFormat; + } + + /** + * Generates a formatted string for saving an event to the file. + * + * @param eventToSave The event to format. + * @return The formatted string. + */ + private String getEventSaveFormat(Event eventToSave) { + String eventSaveFormat = String.format("E | %s | %s | %s%n", + eventToSave.getName(), + eventToSave.getDescription(), + eventToSave.getDate()); + return eventSaveFormat; + } +} diff --git a/src/main/java/seedu/syslib/Syslib.java b/src/main/java/seedu/syslib/Syslib.java new file mode 100644 index 0000000000..5e4f99339a --- /dev/null +++ b/src/main/java/seedu/syslib/Syslib.java @@ -0,0 +1,71 @@ +package seedu.syslib; + +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.parser.Parser; +import seedu.storage.Storage; +import seedu.ui.UI; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class Syslib { + /** + * Main entry-point for the java.syslib.Syslib application. + */ + private static UI ui; + private static Parser parser; + private static Storage storage; + private static final String dataPath = System.getProperty("user.dir") + File.separator + "data"; + private static final String storagePath = dataPath + File.separator + "storage.txt"; + + + public Syslib(String filePath) throws SysLibException { + ui = new UI(); + parser = new Parser(); + + storage = new Storage(filePath, parser.container); + try { + List<Resource> resourceListLoad = new ArrayList<>(); + List<Event> eventListLoad = new ArrayList<>(); + storage.load(resourceListLoad, eventListLoad); + ui.showLoadMessage(filePath, resourceListLoad, eventListLoad); + + parser.container.setResourcesList(resourceListLoad); + parser.container.setEventsList(eventListLoad); + + + } catch (SysLibException SysLibEx) { + System.out.println(SysLibEx); + } + + } + + public static void main(String[] args) { + try { + new Syslib(storagePath).run(); + } catch (SysLibException SLEx) { + System.out.println(SLEx); + } + } + + public void run() { + ui.showWelcomeMessage(); + while (true) { + String response = ui.readCommand(); + parser.processUserResponse(response); + try { + storage.save(); + } catch (SysLibException SysLibEx) { + System.out.println(SysLibEx); + } + + + if (response.equalsIgnoreCase("exit")) { + break; + } + } + } +} diff --git a/src/main/java/seedu/ui/EditCommandMessages.java b/src/main/java/seedu/ui/EditCommandMessages.java new file mode 100644 index 0000000000..1a195d0518 --- /dev/null +++ b/src/main/java/seedu/ui/EditCommandMessages.java @@ -0,0 +1,32 @@ +package seedu.ui; + +import static seedu.ui.MessageFormatter.formatLastLineDivider; +import static seedu.ui.MessageFormatter.formatLineSeparator; +import static seedu.ui.MessageFormatter.formatSeparatorLineDivider; +import static seedu.ui.UI.LINEDIVIDER; +import static seedu.ui.UI.LINESEPARATOR; + +public class EditCommandMessages { + + public static final String INVALID_EDIT_ARGS = formatLineSeparator("Invalid edit arguments!"); + public static final String NEWSPAPERS_ARGS_MESSAGE = + formatLineSeparator("For Newspapers: /t TITLE /p PUBLISHER /ed EDITION /s STATUS /i ISBN") + + formatSeparatorLineDivider("For ENewspapers: /t TITLE /p PUBLISHER /ed EDITION " + + "/s STATUS /l LINK /i ISBN"); + public static final String BOOK_ARGS_MESSAGE = "For Books: /t TITLE /a AUTHOR /g GENRES /s STATUS /i ISBN" + + formatLastLineDivider("For EBooks: /t TITLE /a AUTHOR /g GENRES /s STATUS /l LINK /i ISBN"); + public static final String CD_ARGS_MESSAGE = formatLineSeparator("For CDs: /t TITLE /c CREATOR" + + " /ty TYPE /s STATUS /i ISBN") + LINEDIVIDER + LINESEPARATOR; + public static final String MAGAZINE_ARGS_MESSAGE = "For Magazines: /t TITLE /b BRAND /is ISSUE /s STATUS /i ISBN" + + formatLastLineDivider("For EMagazines: /t TITLE /b BRAND /is ISSUE /s STATUS /l LINK /i ISBN"); + + public static final String EDIT_SUCCESS = formatLineSeparator("Successfully updated! Your updated resource:"); + public static final String MISSING_ARG_MESSAGE = formatLineSeparator("Please provide at least " + + "one detail to edit!") + BOOK_ARGS_MESSAGE + MAGAZINE_ARGS_MESSAGE+ CD_ARGS_MESSAGE+ + NEWSPAPERS_ARGS_MESSAGE; + public static final String NOT_BOOK_ERROR = formatLastLineDivider("Your resource is not a book!"); + public static final String RESOURCE_NOT_FOUND = formatLastLineDivider("No such resource with given ID"); + public static final String NOT_CD_ERROR = formatLastLineDivider("Your resource is not a CD!"); + public static final String NOT_NEWSPAPER_ERROR = formatLastLineDivider("Your resource is not a Newspaper!"); + public static final String NOT_MAGAZINE_ERROR = formatLastLineDivider("Your resource is not a Magazine!"); +} diff --git a/src/main/java/seedu/ui/FindCommandMessages.java b/src/main/java/seedu/ui/FindCommandMessages.java new file mode 100644 index 0000000000..d497bc71f0 --- /dev/null +++ b/src/main/java/seedu/ui/FindCommandMessages.java @@ -0,0 +1,9 @@ +package seedu.ui; + +public class FindCommandMessages { + public static final String INVALID_ARGUMENT_MESSAGE = "Please use the format 'find [/t TITLE OR " + + "/i ISBN OR /a AUTHOR/PUBLISHER/BRAND/CREATOR OR /id ID]'" + System.lineSeparator() + + "____________________________________________________________"; + public static final String NO_RESOURCE_FOUND_MESSAGE = "There are no resources found matching the given filters."; + public static final String RESOURCE_FOUND_MESSAGE = "Here are resources that matched the given filters:"; +} diff --git a/src/main/java/seedu/ui/ListCommandMessages.java b/src/main/java/seedu/ui/ListCommandMessages.java new file mode 100644 index 0000000000..376527c087 --- /dev/null +++ b/src/main/java/seedu/ui/ListCommandMessages.java @@ -0,0 +1,12 @@ +package seedu.ui; + +import static seedu.ui.MessageFormatter.formatFirstLine; +import static seedu.ui.MessageFormatter.formatLastLineDivider; + +public class ListCommandMessages { + public static final String FILTER_MESSAGE = formatFirstLine("Listing resources matching given filters: "); + public static final String GENERIC_MESSAGE = formatFirstLine("Listing all resources in the Library:"); + public static final String ZERO_RESOURCES_MESSAGE = formatLastLineDivider("There are currently 0 resources."); + public static final String STATUS_ERROR_MESSAGE = formatLastLineDivider("Invalid Status! Status must be: " + + "AVAILABLE, BORROWED, OR LOST"); +} diff --git a/src/main/java/seedu/ui/MessageFormatter.java b/src/main/java/seedu/ui/MessageFormatter.java new file mode 100644 index 0000000000..96358f2a88 --- /dev/null +++ b/src/main/java/seedu/ui/MessageFormatter.java @@ -0,0 +1,30 @@ +package seedu.ui; + +import static seedu.ui.UI.LINESEPARATOR; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +/** + * Contains methods to format messages with commonly used line separators and dividers. + */ +public class MessageFormatter { + + public static String formatFirstLine(String message) { + return message + LINESEPARATOR + LINESEPARATOR; + } + public static String formatLineSeparator(String message) { + return message + LINESEPARATOR; + } + public static String formatLastLineDivider(String message) { + return LINESEPARATOR + message + SEPARATOR_LINEDIVIDER + LINESEPARATOR; + } + public static String formatSeparatorLineDivider(String message) { + return message + SEPARATOR_LINEDIVIDER; + } + + /** + * Formats a line dashed divider with provided length. + */ + public static String formatADivider(String dividerLength) { + return String.format(dividerLength + LINESEPARATOR, "-").replace(' ', '-'); + } +} diff --git a/src/main/java/seedu/ui/Messages.java b/src/main/java/seedu/ui/Messages.java new file mode 100644 index 0000000000..0a482d09e6 --- /dev/null +++ b/src/main/java/seedu/ui/Messages.java @@ -0,0 +1,79 @@ +package seedu.ui; + +import static seedu.ui.MessageFormatter.formatSeparatorLineDivider; + +public class Messages { + public static final String ASSERT_STATEMENT = "Statement should not be null"; + public static final String ASSERT_CONTAINER = "Container should not be null"; + public static final String ASSERT_ARGUMENTS = "Arguments should not be null"; + public static final String ASSERT_ID = "ID should be greater than 0"; + + public static final String ERROR_FORMAT_BOOK = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /a AUTHOR /tag TAG [/g GENRE /s STATUS]'."); + public static final String ERROR_FORMAT_EBOOK = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /a AUTHOR /tag TAG /l LINK [/g GENRE /s STATUS]'."); + public static final String ERROR_FORMAT_CD = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag TAG [/s STATUS]'."); + public static final String ERROR_FORMAT_MAGAZINE = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /b BRAND /is ISSUE /tag TAG [/s STATUS]'."); + public static final String ERROR_FORMAT_EMAGAZINE = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /b BRAND /is ISSUE /tag TAG /l LINK [/s STATUS]'."); + public static final String ERROR_FORMAT_NEWSPAPER = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag TAG [/s STATUS]'."); + public static final String ERROR_FORMAT_ENEWSPAPER = formatSeparatorLineDivider("Please use the format " + + "'add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag TAG /l LINK [/s STATUS]'."); + + public static final String ERROR_ISBN = formatSeparatorLineDivider("Please enter a valid ISBN with 13 digits."); + public static final String ERROR_TITLE = formatSeparatorLineDivider("Please enter a valid title."); + public static final String ERROR_AUTHOR = formatSeparatorLineDivider("Please enter a valid author."); + public static final String ERROR_TAG = formatSeparatorLineDivider("Please enter a valid tag."); + public static final String ERROR_GENRE = formatSeparatorLineDivider("Please enter a valid genre."); + public static final String ERROR_CREATOR = formatSeparatorLineDivider("Please enter a valid creator."); + public static final String ERROR_TYPE = formatSeparatorLineDivider("Please enter a valid type."); + public static final String ERROR_BRAND = formatSeparatorLineDivider("Please enter a valid brand."); + public static final String ERROR_ISSUE = formatSeparatorLineDivider("Please enter a valid issue."); + public static final String ERROR_PUBLISHER = formatSeparatorLineDivider("Please enter a valid publisher."); + public static final String ERROR_EDITION = formatSeparatorLineDivider("Please enter a valid edition."); + public static final String ERROR_LINK = formatSeparatorLineDivider("Please enter a valid link."); + public static final String ERROR_STATUS = formatSeparatorLineDivider("Please enter a valid status."); + + public static final String ATTENTION_STATUS = "Attention: Status is not stated. Status set to default: AVAILABLE."; + public static final String ATTENTION_GENRE = "Attention: Genre is not stated. Genre not set."; + + public static final String ERROR_EMPTY_BOOK = formatSeparatorLineDivider("Please enter the ISBN, title, author, " + + "and tag."); + public static final String ERROR_EMPTY_EBOOK = formatSeparatorLineDivider("Please enter the ISBN, title, author, " + + "link, and tag."); + public static final String ERROR_EMPTY_CD = formatSeparatorLineDivider("Please enter the ISBN, title, creator, " + + "type, and tag."); + public static final String ERROR_EMPTY_MAGAZINE = formatSeparatorLineDivider("Please enter the ISBN, title, " + + "brand, issue, and tag."); + public static final String ERROR_EMPTY_EMAGAZINE = formatSeparatorLineDivider("Please enter the ISBN, title, " + + "brand, issue, link, and tag."); + public static final String ERROR_EMPTY_NEWSPAPER = formatSeparatorLineDivider("Please enter the ISBN, title, " + + "publisher, edition, and tag."); + public static final String ERROR_EMPTY_ENEWSPAPER = formatSeparatorLineDivider("Please enter the ISBN, title, " + + "publisher, edition, link, and tag."); + + public static final String ERROR_DUPLICATE_ISBN = formatSeparatorLineDivider("Please enter only 1 ISBN."); + public static final String ERROR_DUPLICATE_TITLE = formatSeparatorLineDivider("Please enter only 1 title."); + public static final String ERROR_DUPLICATE_AUTHOR = formatSeparatorLineDivider("Please enter only 1 author."); + public static final String ERROR_DUPLICATE_TAG = formatSeparatorLineDivider("Please enter only 1 tag."); + public static final String ERROR_DUPLICATE_GENRE = formatSeparatorLineDivider("Please enter only 1 genre."); + public static final String ERROR_DUPLICATE_CREATOR = formatSeparatorLineDivider("Please enter only 1 creator."); + public static final String ERROR_DUPLICATE_TYPE = formatSeparatorLineDivider("Please enter only 1 type."); + public static final String ERROR_DUPLICATE_BRAND = formatSeparatorLineDivider("Please enter only 1 brand."); + public static final String ERROR_DUPLICATE_ISSUE = formatSeparatorLineDivider("Please enter only 1 issue."); + public static final String ERROR_DUPLICATE_PUBLISHER = formatSeparatorLineDivider("Please enter only 1 publisher."); + public static final String ERROR_DUPLICATE_EDITION = formatSeparatorLineDivider("Please enter only 1 edition."); + public static final String ERROR_DUPLICATE_LINK = formatSeparatorLineDivider("Please enter only 1 link."); + public static final String ERROR_DUPLICATE_STATUS = formatSeparatorLineDivider("Please enter only 1 status."); + + public static final String ERROR_UNUSED_SLASH = formatSeparatorLineDivider("Please avoid unused '/'."); + public static final String ERROR_INVALID_ARGUMENT = formatSeparatorLineDivider( + "Please enter only valid arguments."); + public static final String ERROR_INVALID_GENRE_CHARACTER = formatSeparatorLineDivider( + "'[' and ']' are not allowed in genres."); + public static final String ERROR_INVALID_START = formatSeparatorLineDivider( + "Please use '/' to indicate the type of data entered."); +} diff --git a/src/main/java/seedu/ui/ResourceDisplayFormatter.java b/src/main/java/seedu/ui/ResourceDisplayFormatter.java new file mode 100644 index 0000000000..865c1e2fc1 --- /dev/null +++ b/src/main/java/seedu/ui/ResourceDisplayFormatter.java @@ -0,0 +1,149 @@ +package seedu.ui; + +import seedu.data.resources.Resource; + +import java.util.Arrays; +import java.util.Formatter; +import java.util.List; + +import static seedu.ui.MessageFormatter.formatADivider; +import static seedu.ui.UI.LINESEPARATOR; + +/** Handles display and formatting of resources to show to users as a table. **/ +public class ResourceDisplayFormatter { + + protected Formatter bookDisplayFormatter; + protected Formatter magazineDisplayFormatter; + protected Formatter cdDisplayFormatter; + protected Formatter newspaperDisplayFormatter; + private String customDivider; + private String displayFormat; + private List<Boolean> hasResourceTypeList; + + public ResourceDisplayFormatter(List<Resource> resourcesList) { + displayFormat = buildDisplayHeader(resourcesList); + bookDisplayFormatter = buildBookFormatter(displayFormat); + magazineDisplayFormatter = buildMagazineFormatter(displayFormat); + cdDisplayFormatter = buildCDFormatter(displayFormat); + newspaperDisplayFormatter = buildNewspaperFormatter(displayFormat); + + hasResourceTypeList = Arrays.asList(false, false, false, false); + } + + public void setBookDisplayFormatter(Resource resource) { + bookDisplayFormatter = resource.toTableFormat(displayFormat, bookDisplayFormatter); + hasResourceTypeList.set(0,true); + } + + public void setMagazineDisplayFormatter(Resource resource) { + magazineDisplayFormatter = resource.toTableFormat(displayFormat, magazineDisplayFormatter); + hasResourceTypeList.set(1,true); + } + + public void setCDDisplayFormatter(Resource resource) { + cdDisplayFormatter = resource.toTableFormat(displayFormat, cdDisplayFormatter); + hasResourceTypeList.set(2,true); + } + public void setNewspaperDisplayFormatter(Resource resource){ + newspaperDisplayFormatter = resource.toTableFormat(displayFormat, newspaperDisplayFormatter); + hasResourceTypeList.set(3,true); + } + + private Formatter buildBookFormatter(String displayFormat) { + Object[] bookArgs = {"ID", "Tag", "Title", "ISBN", "Author", "Genre", "Link", "Status", "Received Date"}; + String headerPadding = "%" + (customDivider.length()/2 + 6) + "s" + LINESEPARATOR; + String bookHeader = String.format(headerPadding, "[BOOKS]"); + Formatter bookDisplayFormatter = buildDisplayFormatter(displayFormat, bookArgs, bookHeader); + + return bookDisplayFormatter; + } + private Formatter buildMagazineFormatter(String displayFormat) { + Object[] magazineArgs = {"ID", "Tag", "Title", "ISBN", "Brand", "Issue", "Link", "Status", "Received Date"}; + String headerPadding = "%" + (customDivider.length()/2 + 10) + "s" + LINESEPARATOR; + String magazineHeader = String.format(headerPadding, "[MAGAZINES]"); + Formatter magazineDisplayFormatter = buildDisplayFormatter(displayFormat, magazineArgs, magazineHeader); + return magazineDisplayFormatter; + } + + private Formatter buildCDFormatter(String displayFormat) { + Object[] cdArgs = { "ID", "Tag", "Title", "ISBN", "Creator", "Type", "Link", "Status", "Received Date"}; + String headerPadding = "%" + (customDivider.length()/2 + 5) + "s" + LINESEPARATOR; + String cdHeader = String.format(headerPadding, "[CDS]"); + Formatter cdDisplayFormatter = buildDisplayFormatter(displayFormat, cdArgs, cdHeader); + return cdDisplayFormatter; + } + + private Formatter buildNewspaperFormatter(String displayFormat) { + Object[] newspaperArgs = {"ID", "Tag", "Title", "ISBN", "Publisher", "Edition", "Link", + "Status", "Received Date"}; + String headerPadding = "%" + (customDivider.length()/2 + 10) + "s" + LINESEPARATOR; + String newspaperHeader = String.format(headerPadding, "[NEWSPAPERS]"); + Formatter newspaperFormatter = buildDisplayFormatter(displayFormat, newspaperArgs, newspaperHeader); + return newspaperFormatter; + } + public Formatter buildDisplayFormatter(String displayFormat, Object[] displayArgs, String header) { + + Formatter displayFormatter = new Formatter(); + displayFormatter.format(header); + displayFormatter.format(customDivider); + displayFormatter.format(displayFormat, displayArgs); + displayFormatter.format(customDivider); + return displayFormatter; + + } + + public String buildDisplayHeader(List<Resource> resourcesList) { + + // Check columns at index 2, 4, 5, 6 as length is unrestricted + // Columns represent: + // ID, Tag, Title, ISBN, Author/Brand/Creator/Publisher, + // Genre/Issue/Type/Edition, Link, Status, Received Date + List<Integer> columnsWidth = Arrays.asList(7,5,20,14,25,20,15,10,15); + + int paddingLength = 0; + + for (Resource resource : resourcesList) { + columnsWidth = resource.checkColumnsWidths(columnsWidth); + } + + String displayFormat = ""; + + for (int i= 0; i<columnsWidth.size();i++) { + displayFormat += "%-" + columnsWidth.get(i) + "s"; + paddingLength += columnsWidth.get(i); + } + + displayFormat += LINESEPARATOR; + + customDivider = formatADivider("%-" + paddingLength + "s"); + + return displayFormat; + } + + /** + * Constructs the final display message by adding a resource type only if it contains at least one resource to show. + * + * @return messageToDisplay. + */ + public String getFinalDisplayFormat() { + + String messageToDisplay =""; + if (hasResourceTypeList.get(0)) { + messageToDisplay += bookDisplayFormatter + LINESEPARATOR; + } + + if (hasResourceTypeList.get(1)) { + messageToDisplay += magazineDisplayFormatter+ LINESEPARATOR; + } + + if (hasResourceTypeList.get(2)) { + messageToDisplay += cdDisplayFormatter+ LINESEPARATOR; + } + + if (hasResourceTypeList.get(3)) { + messageToDisplay += newspaperDisplayFormatter+ LINESEPARATOR; + } + return messageToDisplay; + } + +} diff --git a/src/main/java/seedu/ui/UI.java b/src/main/java/seedu/ui/UI.java new file mode 100644 index 0000000000..7ea591c750 --- /dev/null +++ b/src/main/java/seedu/ui/UI.java @@ -0,0 +1,163 @@ +package seedu.ui; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.data.resources.Magazine; +import seedu.data.resources.Book; +import seedu.data.resources.CD; +import seedu.data.resources.Newspaper; + +import seedu.exception.SysLibException; + +import java.util.List; +import java.util.Scanner; + +import static seedu.ui.MessageFormatter.formatLastLineDivider; + +public class UI { + public static final String LINESEPARATOR = System.lineSeparator(); + public static final String LINEDIVIDER = "____________________________________________________________"; + public static final String SEPARATOR_LINEDIVIDER = LINESEPARATOR + LINEDIVIDER; + + public static final String ZERO_RESOURCES_MESSAGE = formatLastLineDivider("There are currently 0 resources."); + protected static final String LOGO = + " ..................... \n" + + " -##@*+*@*++++++++++#@++## \n" + + " .@. @-=%= *#-+% \n" + + " :@ @+- :----------. .=#% \n" + + " :@ @. *%----------@- =% \n" + + " :@ @. #* @= =% \n" + + " :@ @. #* *: :+ \n" + + " :@ @. *%-----. .=+****+-. \n" + + " :@ @. :-----.-#*-. .:-*#- \n" + + " :@ @. .%+. .@*#+.*%. \n" + + " :@ @: %= %* +@.=% \n" + + " :@ @*#*. -@ *###***+. @- \n" + + " :@ .@:.=@... -@ .+*#*#### @- \n" + + " :@#*++++++++. %=.%+ +# +% \n" + + " :@. =++++++++-.%*.+%*@. *%. \n" + + " %+ ........ =#*-:: .-*%= \n" + + " =*************. .=+****+-. \n" + + " ____ _ _ _ ____ _ ___ \n" + + "/ ___| _ _ ___| | (_) |__ / ___| | |_ _| \n" + + "\\___ \\| | | / __| | | | '_ \\ | | | | | | \n" + + " ___) | |_| \\__ \\ |___| | |_) | | |___| |___ | | \n" + + "|____/ \\__, |___/_____|_|_.__/ \\____|_____|___| \n" + + " |___/ \n"; + + protected Scanner myScanner; + + public UI() { + this.myScanner = new Scanner(System.in); + } + + public void showWelcomeMessage() { + showLine(); + System.out.println("Syslib v2.1"); + System.out.println(LOGO); + System.out.println("Hello! What would you like to do?"); + showLine(); + } + + public void showExitMessage(){ + System.out.println("Thanks for using SysLib! We have saved the current resources and events."); + System.out.println("See you next time!"); + showLine(); + } + + public void showHelpMessage() { + System.out.println("Commands available:"); + System.out.println("[add] (Book) Adds a new book. " + + "(e.g. add /i ISBN /t TITLE /a AUTHOR /tag TAG [/g GENRE /s STATUS])"); + System.out.println("[add] (eBook) Adds a new eBook. " + + "(e.g. add /i ISBN /t TITLE /a AUTHOR /tag eb /l LINK [/g GENRE /s STATUS])"); + System.out.println("[add] (CD) Adds a new CD. " + + "(e.g. add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag cd [/s STATUS])"); + System.out.println("[add] (Magazine) Adds a new magazine. " + + "(e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag m [/s STATUS])"); + System.out.println("[add] (eMagazine) Adds a new eMagazine. " + + "(e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag em /l LINK [/s STATUS])"); + System.out.println("[add] (Newspaper) Adds a new newspaper. " + + "(e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag n [/s STATUS])"); + System.out.println("[add] (eNewspaper) Adds a new eNewspaper. " + + "(e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag en /l LINK [/s STATUS])"); + System.out.println("[delete] deletes the resource with the specified ID from the library inventory. " + + "(e.g. delete /id 123456789)"); + System.out.println("[list] lists all resources OR filter by certain tags, genre, or status. " + + "(e.g. list /tag B /g Fiction /s AVAILABLE)"); + System.out.println("[find] finds a resource by title, author, ISBN or given id. " + + "(e.g. find /i 9780763630188 /a AUTHOR)"); + System.out.println("[edit] edits a listing by entering its id to update its details. " + + "(e.g. edit /id 123 /t NEW_TITLE /a NEW_AUTHOR)"); + System.out.println("[eventadd] adds an event to the database. " + + "(e.g. eventadd /t TITLE /date 23 Dec 2023 [/desc DESCRIPTION])"); + System.out.println("[eventlist] lists out all events in the database. " + + "(e.g. eventlist)"); + System.out.println("[eventdelete] deletes an event from the database based on the index. " + + "(e.g. eventdelete /i INDEX)"); + System.out.println("[eventedit] edits an event in the event list based on the information given. " + + "(e.g. eventedit /i INDEX [/t TITLE /date DATE /desc DESCRIPTION])"); + System.out.println("[summary] shows a summary of all resources and the next 3 events. " + + "(e.g. summary)"); + System.out.println("[exit] displays a farewell message and exits the program. " + + "(e.g. exit)" + System.lineSeparator()); + System.out.println("For more information, please refer to our user guide at: https://bit.ly/SyslibUserGuide"); + showLine(); + } + + public String readCommand() { + System.out.print("> "); + return myScanner.nextLine(); + } + + public void showNoFileFoundMessage(String filePath) { + System.out.println("Storage file not found."); + System.out.println("Creating new data file @ " + filePath); + } + + public void showFileFoundMessage(String filePath) { + System.out.println("Storage file found @ " + filePath); + } + + public void showLoadMessage(String filepath, List<Resource> resourcelist, List<Event> eventlist) { + System.out.printf("Loaded %d resources and %d events!%n", resourcelist.size(), eventlist.size()); + } + + public void showLine() { + System.out.println(LINEDIVIDER); + } + + public static String showResourcesDetails(List<Resource> resourcesList) throws SysLibException { + + String messageToDisplay = ""; + + if (resourcesList.isEmpty()) { + messageToDisplay += ZERO_RESOURCES_MESSAGE; + return messageToDisplay; + } + + ResourceDisplayFormatter resourceDisplayFormatter = new ResourceDisplayFormatter(resourcesList); + + for (Resource resource : resourcesList) { + + if (resource instanceof Book) { + resourceDisplayFormatter.setBookDisplayFormatter(resource); + } else if (resource instanceof Magazine) { + resourceDisplayFormatter.setMagazineDisplayFormatter(resource); + } else if (resource instanceof CD ) { + resourceDisplayFormatter.setCDDisplayFormatter(resource); + } else if (resource instanceof Newspaper) { + resourceDisplayFormatter.setNewspaperDisplayFormatter(resource); + } else { + throw new SysLibException("Invalid resource!"); + } + + } + + messageToDisplay += resourceDisplayFormatter.getFinalDisplayFormat(); + + messageToDisplay += formatLastLineDivider("There are currently " + resourcesList.size() + " resource(s)."); + + return messageToDisplay; + } + +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/DukeTest.java similarity index 90% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/seedu/DukeTest.java index 2dda5fd651..e70c96f072 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/seedu/DukeTest.java @@ -1,4 +1,4 @@ -package seedu.duke; +package seedu; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/seedu/commands/AddCommandTest.java b/src/test/java/seedu/commands/AddCommandTest.java new file mode 100644 index 0000000000..c341e864bd --- /dev/null +++ b/src/test/java/seedu/commands/AddCommandTest.java @@ -0,0 +1,403 @@ +package seedu.commands; + +import org.junit.jupiter.api.Test; +import seedu.data.resources.Book; +import seedu.data.resources.EBook; +import seedu.data.resources.CD; +import seedu.data.resources.Magazine; +import seedu.data.resources.EMagazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.ENewspaper; +import seedu.data.Status; +import seedu.exception.SysLibException; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; +import static seedu.util.TestUtil.getCurrentDate; + +public class AddCommandTest { + private final Parser parser = new Parser(); + private final AddCommand addCommand = new AddCommand(); + + // ---------------------------------------------------------------------------------------------------- + // Test Resources with Valid Data + // ---------------------------------------------------------------------------------------------------- + @Test + public void addCommandValidBook() throws SysLibException { + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /a Daniel Keyes /tag B " + + "/g Non-Fiction, Biography /s LOST", parser.container); + + Book newBook = (Book) parser.container.getResourcesList().get(0); + + assertEquals(newBook.getId(), 1); + assertEquals(newBook.getTitle(), "The Minds of Billy Milligan"); + assertEquals(newBook.getAuthor(), "Daniel Keyes"); + assertEquals(newBook.getTag(), "B"); + assertEquals(newBook.getISBN(), "9783161484100"); + assertEquals(newBook.getGenreString(), "Non-Fiction, Biography"); + assertEquals(newBook.getStatus(), Status.LOST); + } + + @Test + public void addCommandValidEBook() throws SysLibException { + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /a Daniel Keyes /tag EB " + + "/g Non-Fiction, Biography /s LOST /l www.daniel.com/tmobm", parser.container); + + EBook newEBook = (EBook) parser.container.getResourcesList().get(0); + + assertEquals(newEBook.getId(), 1); + assertEquals(newEBook.getTitle(), "The Minds of Billy Milligan"); + assertEquals(newEBook.getAuthor(), "Daniel Keyes"); + assertEquals(newEBook.getTag(), "EB"); + assertEquals(newEBook.getISBN(), "9783161484100"); + assertEquals(newEBook.getGenreString(), "Non-Fiction, Biography"); + assertEquals(newEBook.getStatus(), Status.LOST); + assertEquals(newEBook.getLink(), "www.daniel.com/tmobm"); + } + + @Test + public void addCommandValidCD() throws SysLibException { + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /c Daniel Keyes /tag CD " + + "/ty Audio Book /s LOST", parser.container); + + CD newCD = (CD) parser.container.getResourcesList().get(0); + + assertEquals(newCD.getId(), 1); + assertEquals(newCD.getTitle(), "The Minds of Billy Milligan"); + assertEquals(newCD.getCreator(), "Daniel Keyes"); + assertEquals(newCD.getTag(), "CD"); + assertEquals(newCD.getISBN(), "9783161484100"); + assertEquals(newCD.getType(), "Audio Book"); + assertEquals(newCD.getStatus(), Status.LOST); + } + + @Test + public void addCommandValidMagazine() throws SysLibException { + addCommand.execute("/i 9783161484101 /t Exploring the Outer Space /b Astronomy 101 /tag M " + + "/is Planets Edition, January 2023 /s LOST", parser.container); + + Magazine newMagazine = (Magazine) parser.container.getResourcesList().get(0); + + assertEquals(newMagazine.getId(), 1); + assertEquals(newMagazine.getTitle(), "Exploring the Outer Space"); + assertEquals(newMagazine.getBrand(), "Astronomy 101"); + assertEquals(newMagazine.getTag(), "M"); + assertEquals(newMagazine.getISBN(), "9783161484101"); + assertEquals(newMagazine.getIssue(), "Planets Edition, January 2023"); + assertEquals(newMagazine.getStatus(), Status.LOST); + } + + @Test + public void addCommandValidEMagazine() throws SysLibException { + addCommand.execute("/i 9783161484101 /t Exploring the Outer Space /b Astronomy 101 /tag EM " + + "/is Planets Edition, January 2023 /s LOST /l www.astronomy.com", parser.container); + + EMagazine newEMagazine = (EMagazine) parser.container.getResourcesList().get(0); + + assertEquals(newEMagazine.getId(), 1); + assertEquals(newEMagazine.getTitle(), "Exploring the Outer Space"); + assertEquals(newEMagazine.getBrand(), "Astronomy 101"); + assertEquals(newEMagazine.getTag(), "EM"); + assertEquals(newEMagazine.getISBN(), "9783161484101"); + assertEquals(newEMagazine.getIssue(), "Planets Edition, January 2023"); + assertEquals(newEMagazine.getStatus(), Status.LOST); + assertEquals(newEMagazine.getLink(), "www.astronomy.com"); + } + + @Test + public void addCommandValidNewspaper() throws SysLibException { + addCommand.execute("/i 9783161484102 /t Freshest Lemon in Town /p Healthy Food /tag N " + + "/ed Fruity Fruits, 25 January 2023 /s LOST", parser.container); + + Newspaper newNewspaper = (Newspaper) parser.container.getResourcesList().get(0); + + assertEquals(newNewspaper.getId(), 1); + assertEquals(newNewspaper.getTitle(), "Freshest Lemon in Town"); + assertEquals(newNewspaper.getPublisher(), "Healthy Food"); + assertEquals(newNewspaper.getTag(), "N"); + assertEquals(newNewspaper.getISBN(), "9783161484102"); + assertEquals(newNewspaper.getEdition(), "Fruity Fruits, 25 January 2023"); + assertEquals(newNewspaper.getStatus(), Status.LOST); + } + + @Test + public void addCommandValidENewspaper() throws SysLibException { + addCommand.execute("/i 9783161484102 /t Freshest Lemon in Town /p Healthy Food /tag EN " + + "/ed Fruity Fruits, 25 January 2023 /s LOST /l https://www.lemon.com", parser.container); + + ENewspaper newENewspaper = (ENewspaper) parser.container.getResourcesList().get(0); + + assertEquals(newENewspaper.getId(), 1); + assertEquals(newENewspaper.getTitle(), "Freshest Lemon in Town"); + assertEquals(newENewspaper.getPublisher(), "Healthy Food"); + assertEquals(newENewspaper.getTag(), "EN"); + assertEquals(newENewspaper.getISBN(), "9783161484102"); + assertEquals(newENewspaper.getEdition(), "Fruity Fruits, 25 January 2023"); + assertEquals(newENewspaper.getStatus(), Status.LOST); + assertEquals(newENewspaper.getLink(), "https://www.lemon.com"); + } + + + // ---------------------------------------------------------------------------------------------------- + // Test Output + // ---------------------------------------------------------------------------------------------------- + @Test + public void addCommandOutput() throws SysLibException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /a Daniel Keyes /tag B " + + "/g Non-Fiction, Biography", parser.container); + + String output = outputStream.toString(); + + String expectedOutput = "Attention: Status is not stated. Status set to default: AVAILABLE." + + System.lineSeparator() + "This book is added:" + System.lineSeparator() + + "[B] ID: 1 Title: The Minds of Billy Milligan ISBN: 9783161484100 Author: Daniel Keyes Genre: " + + "Non-Fiction, Biography Status: AVAILABLE Received Date: " + getCurrentDate() + + SEPARATOR_LINEDIVIDER + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + + // ---------------------------------------------------------------------------------------------------- + // Test Attributes with Invalid Input + // ---------------------------------------------------------------------------------------------------- + @Test + public void addCommandInvalidIsbn() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i TMOBM " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + + } + + @Test + public void addCommandInvalidTag() { + assertThrows(SysLibException.class, () -> addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag ABC", parser.container)); + } + + @Test + public void addCommandInvalidGenre() { + assertThrows(SysLibException.class, () -> addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B /g [Biography]", parser.container)); + } + + @Test + public void addCommandInsufficientData() { + assertThrows(SysLibException.class, ()->addCommand.execute("/tag ", parser.container)); + } + + @Test + public void addCommandInvalidFormat() { + assertThrows(SysLibException.class, ()->addCommand.execute("BOOK /i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandMultipleTitle() { + assertThrows(SysLibException.class, ()->addCommand.execute( + "add /i 9783161484100 /t Crime and Punishment /t Crime and Punishment2 /a Dostoevsky /tag B" + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandMultipleAuthors() { + assertThrows(SysLibException.class, ()->addCommand.execute( + "add /i 9783161484100 /t Crime and Punishment /a Dostoevsky /a Dostoevsky2 /tag B" + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + + } + + @Test + public void addCommandInvalidArguments() { + assertThrows(SysLibException.class, ()->addCommand.execute( + "add /ddd /i 1234567890123 /t abc /a qqq /tag b", parser.container)); + + } + + @Test + public void addCommandInvalidArguments2() { + assertThrows(SysLibException.class, ()->addCommand.execute( + "add jesus /i 1234567890123 /t abc /a qqq /tag b", parser.container)); + + } + + @Test + public void addCommandInvalidArguments3() { + assertThrows(SysLibException.class, ()->addCommand.execute( + "add /// /i 1234567890123 /t abc /a qqq /tag b", parser.container)); + + } + + + // ---------------------------------------------------------------------------------------------------- + // Test Resource Attributes Validation + // ---------------------------------------------------------------------------------------------------- + @Test + public void addCommandDuplicateIsbn() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 /i 9783161484102 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandWrongFormatIsbn() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandDuplicateTitle() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds /t of Billy Milligan /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandWrongFormatTitle() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandDuplicateAuthor() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel /a Keyes /tag B", parser.container)); + } + + @Test + public void addCommandWrongFormatAuthor() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a /tag B", parser.container)); + } + + @Test + public void addCommandDuplicateTag() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B /tag EB", parser.container)); + } + + @Test + public void addCommandWrongFormatTag() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag ", parser.container)); + } + + @Test + public void addCommandDuplicateGenre() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B /g Non-Fiction /g Biography", parser.container)); + } + + @Test + public void addCommandWrongFormatGenre() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag B /g ", parser.container)); + } + + @Test + public void addCommandDuplicateCreator() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /c Daniel /c Keyes /tag CD /ty Audio Book", parser.container)); + } + + @Test + public void addCommandDuplicateType() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /c Daniel Keyes /tag CD /ty Audio /ty Book", parser.container)); + } + + @Test + public void addCommandDuplicateBrand() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484101 /t Exploring the " + + "Outer Space /b Astronomy /b 101 /tag M /is Planets Edition, January 2023 /s LOST", parser.container)); + } + + @Test + public void addCommandDuplicateIssue() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484101 /t Exploring the " + + "Outer Space /b Astronomy 101 /tag M /is Planets Edition, /is January 2023 /s LOST", parser.container)); + } + + @Test + public void addCommandDuplicatePublisher() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484102 /t Freshest Lemon " + + "in Town /p Healthy /p Food /tag N /ed Fruity Fruits, 25 January 2023 /s LOST", parser.container)); + } + + @Test + public void addCommandDuplicateEdition() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484102 /t Freshest Lemon " + + "in Town /p Healthy Food /tag N /ed Fruity Fruits, /ed 25 January 2023 /s LOST", parser.container)); + } + + @Test + public void addCommandDuplicateLink() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484102 /t Freshest Lemon " + + "in Town /p Healthy Food /tag EN /ed Fruity Fruits, 25 January 2023 /s LOST /l www.lemon.com " + + "/l www.watermelon.com", parser.container)); + } + + @Test + public void addCommandDuplicateStatus() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484102 /t Freshest Lemon " + + "in Town /p Healthy Food /tag N /ed Fruity Fruits, 25 January 2023 /s LOST /s BORROWED", + parser.container)); + } + + @Test + public void addCommandWrongFormatStatus() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484102 /t Freshest Lemon " + + "in Town /p Healthy Food /tag N /ed Fruity Fruits, 25 January 2023 /s ", parser.container)); + } + + @Test + public void addCommandWrongInvalidStatus() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484102 /t Freshest Lemon " + + "in Town /p Healthy Food /tag N /ed Fruity Fruits, 25 January 2023 /s DAMAGED ", parser.container)); + } + + + // ---------------------------------------------------------------------------------------------------- + // Test Resources with Invalid Input + // ---------------------------------------------------------------------------------------------------- + @Test + public void addCommandBookUnusedSlash() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan / / /a Daniel Keyes /tag B", parser.container)); + } + + @Test + public void addCommandBookInvalidArgument() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /c Dan /tag B", parser.container)); + } + + @Test + public void addCommandBookNoData() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a /tag B", parser.container)); + } + + @Test + public void addCommandEBookUnusedSlash() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag EB /l www.daniel.com/tmobm/", parser.container)); + } + + @Test + public void addCommandEBookInvalidArgument() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /c Dan /tag EB /l www.daniel.com/tmobm/", + parser.container)); + } + + @Test + public void addCommandEBookNoData() { + assertThrows(SysLibException.class, ()->addCommand.execute("/i 9783161484100 " + + "/t The Minds of Billy Milligan /a Daniel Keyes /tag EB /l ", parser.container)); + } +} diff --git a/src/test/java/seedu/commands/CommandExceptionTest.java b/src/test/java/seedu/commands/CommandExceptionTest.java new file mode 100644 index 0000000000..eab315cd2e --- /dev/null +++ b/src/test/java/seedu/commands/CommandExceptionTest.java @@ -0,0 +1,37 @@ +package seedu.commands; + +import org.junit.jupiter.api.Test; +import seedu.commands.events.EventAddCommand; +import seedu.parser.Parser; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CommandExceptionTest { + private final Parser parser = new Parser(); + private final EventAddCommand eventAddCommand = new EventAddCommand(); + + @Test + public void extraCommand() { + String input = "/t abc /date 22 May 2023 /jesus"; + assertThrows(IllegalArgumentException.class, () -> eventAddCommand.execute(input, parser.container)); + } + @Test + public void extraCommandVariable() { + String input = "/t abc /date 22 May 2023 /jesus save"; + assertThrows(IllegalArgumentException.class, () -> eventAddCommand.execute(input, parser.container)); + } + + @Test + public void extraVariable() { + String input = "jesus /t abc /date 22 May 2023"; + assertThrows(IllegalArgumentException.class, () -> eventAddCommand.execute(input, parser.container)); + } + + @Test + public void illegalDateFormat() { + String input = "/t abc /date 12/12/2024"; + assertThrows(IllegalArgumentException.class, () -> eventAddCommand.execute(input, parser.container)); + } + + +} diff --git a/src/test/java/seedu/commands/DeleteCommandTest.java b/src/test/java/seedu/commands/DeleteCommandTest.java new file mode 100644 index 0000000000..185b5c4a30 --- /dev/null +++ b/src/test/java/seedu/commands/DeleteCommandTest.java @@ -0,0 +1,67 @@ +package seedu.commands; + +import org.junit.jupiter.api.Test; +import seedu.exception.SysLibException; +import seedu.parser.Parser; +import static seedu.util.TestUtil.getCurrentDate; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DeleteCommandTest { + private final Parser parser = new Parser(); + private final AddCommand addCommand = new AddCommand(); + private final DeleteCommand deleteCommand = new DeleteCommand(); + @Test + public void deleteCommandValidData() throws SysLibException { + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /a Daniel Keyes /tag B" + , parser.container); + + deleteCommand.execute("/id 1", parser.container); + assertEquals(parser.resourcesList.size(), 0); + } + + @Test + public void deleteCommandOutput() throws SysLibException { + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /a Daniel Keyes /tag B" + , parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + deleteCommand.execute("/id 1", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Looking for ID: 1..." + System.lineSeparator()+ + "This resource is removed:" + System.lineSeparator() + + "[B] ID: 1 Title: The Minds of Billy Milligan ISBN: 9783161484100 Author: Daniel Keyes Genre: - " + + "Status: AVAILABLE " + "Received Date: " + getCurrentDate() + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + @Test + public void testdeleteCommandInvalidId() { + assertThrows(IllegalArgumentException.class, () -> deleteCommand.execute("", parser.container)); + } + + @Test + public void testDeleteCommandEmptyList() { + assertThrows(SysLibException.class, () -> deleteCommand.execute("/id 1", parser.container)); + } + + @Test + public void testDeleteCommandOutOfBound() throws SysLibException { + addCommand.execute("/i 9783161484100 /t The Minds of Billy Milligan /a Daniel Keyes /tag B" + , parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + deleteCommand.execute("/id 10", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Looking for ID: 10..." + System.lineSeparator() + + "No resources with id matching 10" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + } +} diff --git a/src/test/java/seedu/commands/EditCommandTest.java b/src/test/java/seedu/commands/EditCommandTest.java new file mode 100644 index 0000000000..0ecea5ce6d --- /dev/null +++ b/src/test/java/seedu/commands/EditCommandTest.java @@ -0,0 +1,239 @@ +package seedu.commands; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import seedu.data.resources.Book; +import seedu.data.resources.CD; +import seedu.data.resources.EBook; +import seedu.data.resources.EMagazine; +import seedu.data.resources.ENewspaper; +import seedu.data.resources.Magazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.parser.Parser; +import seedu.util.TestUtil; + +import static seedu.ui.EditCommandMessages.BOOK_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.INVALID_EDIT_ARGS; +import static seedu.ui.EditCommandMessages.MAGAZINE_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.MISSING_ARG_MESSAGE; +import static seedu.ui.EditCommandMessages.NEWSPAPERS_ARGS_MESSAGE; +import static seedu.ui.EditCommandMessages.RESOURCE_NOT_FOUND; +import static seedu.ui.EditCommandMessages.EDIT_SUCCESS; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.ui.ListCommandMessages.STATUS_ERROR_MESSAGE; +import static seedu.ui.MessageFormatter.formatLastLineDivider; + +public class EditCommandTest { + private static List<Resource> testResourceList = new ArrayList<>(); + private static Parser parser = new Parser(); + private List<Resource> emptyResourceList = new ArrayList<>(); + private TestUtil testUtil = new TestUtil(); + private Command editCommand = new EditCommand(); + + @BeforeAll + public static void setup() { + testResourceList = TestUtil.fillTestResourcesList(); + parser.container.setResourcesList(testResourceList); + + } + + @Test + public void testEditTitleBehavior() throws SysLibException { + Resource targetResource = testResourceList.get(0); + String expectedMessage = targetResource.toString(); + expectedMessage = expectedMessage.replace(targetResource.getTitle(), "NEW TITLE"); + executeEditSuccessBehavior("/id 2 /t NEW TITLE", expectedMessage ); + } + + @Test + public void testEditStatusBehavior() throws SysLibException { + Resource targetResource = testResourceList.get(0); + String expectedMessage = targetResource.toString(); + expectedMessage = expectedMessage.replace(targetResource.getStatus().name(), "LOST"); + executeEditSuccessBehavior("/id 2 /s LOST", expectedMessage ); + expectedMessage = expectedMessage.replace(targetResource.getStatus().name(), "AVAILABLE"); + executeEditSuccessBehavior("/id 2 /s AVAILABLE", expectedMessage ); + expectedMessage = expectedMessage.replace(targetResource.getStatus().name(), "BORROWED"); + executeEditSuccessBehavior("/id 2 /s BORROWED", expectedMessage ); + } + + @Test + public void testEditISBNBehavior() throws SysLibException { + Resource targetResource = testResourceList.get(0); + String expectedMessage = targetResource.toString(); + expectedMessage = expectedMessage.replace(targetResource.getISBN(), "1234567891234"); + executeEditSuccessBehavior("/id 2 /i 1234567891234", expectedMessage ); + } + + @Test + public void testEditBookAuthorBehavior() throws SysLibException { + Book targetBook = (Book) testResourceList.get(0); + String expectedMessage = targetBook.toString(); + expectedMessage = expectedMessage.replace(targetBook.getAuthor(), "NEW AUTHOR"); + executeEditSuccessBehavior("/id 2 /a NEW AUTHOR", expectedMessage); + } + + @Test + public void testEditBookGenreBehavior() throws SysLibException { + Book targetBook = (Book) testResourceList.get(0); + String expectedMessage = targetBook.toString(); + expectedMessage = expectedMessage.replace(targetBook.getGenreString(), "Horror, Action, Fantasy"); + executeEditSuccessBehavior("/id 2 /g Horror, Action, Fantasy",expectedMessage); + } + + @Test + public void testEditMagazineBrandBehavior() throws SysLibException { + Magazine targetMagazine = (Magazine) testResourceList.get(4); + String expectedMessage = targetMagazine.toString(); + expectedMessage = expectedMessage.replace(targetMagazine.getBrand(), "NEW BRAND"); + executeEditSuccessBehavior("/id 6 /b NEW BRAND",expectedMessage); + } + + @Test + public void testEditMagazineIssueBehavior() throws SysLibException { + Magazine targetMagazine = (Magazine) testResourceList.get(4); + String expectedMessage = targetMagazine.toString(); + expectedMessage = expectedMessage.replace(targetMagazine.getIssue(), "NEW ISSUE"); + executeEditSuccessBehavior("/id 6 /is NEW ISSUE",expectedMessage); + } + + + @Test + public void testEditCDCreatorBehavior() throws SysLibException { + CD targetCD = (CD) testResourceList.get(6); + String expectedMessage = targetCD.toString(); + expectedMessage = expectedMessage.replace(targetCD.getCreator(), "NEW CREATOR"); + executeEditSuccessBehavior("/id 8 /c NEW CREATOR",expectedMessage); + } + + @Test + public void testEditCDTypeBehavior() throws SysLibException { + CD targetCD = (CD) testResourceList.get(6); + String expectedMessage = targetCD.toString(); + expectedMessage = expectedMessage.replace(targetCD.getType(), "NEW TYPE"); + executeEditSuccessBehavior("/id 8 /ty NEW TYPE",expectedMessage); + } + + @Test + public void testEditNewspaperPublisherBehavior() throws SysLibException { + Newspaper targetNewspaper = (Newspaper) testResourceList.get(7); + String expectedMessage = targetNewspaper.toString(); + expectedMessage = expectedMessage.replace(targetNewspaper.getPublisher(), "NEW PUBLISHER"); + executeEditSuccessBehavior("/id 9 /p NEW PUBLISHER",expectedMessage); + } + + @Test + public void testEditNewspaperEditionBehavior() throws SysLibException { + Newspaper targetNewspaper = (Newspaper) testResourceList.get(7); + String expectedMessage = targetNewspaper.toString(); + expectedMessage = expectedMessage.replace(targetNewspaper.getEdition(), "NEW EDITION"); + executeEditSuccessBehavior("/id 9 /ed NEW EDITION",expectedMessage); + } + @Test + public void testEditEBookLinkBehavior() throws SysLibException { + EBook targetEBook = (EBook) testResourceList.get(3); + String expectedMessage = targetEBook.toString(); + expectedMessage = expectedMessage.replace(targetEBook.getLink(), "NEW LINK"); + executeEditSuccessBehavior("/id 5 /l NEW LINK",expectedMessage); + } + @Test + public void testEditEMagazineLinkBehavior() throws SysLibException { + EMagazine targetEMagazine = (EMagazine) testResourceList.get(5); + String expectedMessage = targetEMagazine.toString(); + expectedMessage = expectedMessage.replace(targetEMagazine.getLink(), "NEW LINK"); + executeEditSuccessBehavior("/id 7 /l NEW LINK",expectedMessage); + } + + @Test + public void testEditENewspaperLinkBehavior() throws SysLibException { + ENewspaper targetENewspaper = (ENewspaper) testResourceList.get(8); + String expectedMessage = targetENewspaper.toString(); + expectedMessage = expectedMessage.replace(targetENewspaper.getLink(), "NEW LINK"); + executeEditSuccessBehavior("/id 10 /l NEW LINK",expectedMessage); + } + + private void executeEditSuccessBehavior(String argument, String expectedMessage) throws SysLibException { + String outputMessage = testUtil.getOutputMessage(editCommand, argument, testResourceList); + expectedMessage = EDIT_SUCCESS + formatLastLineDivider(expectedMessage); + assertEquals(expectedMessage, outputMessage); + } + + + @Test + public void testEditResourceNotFound() throws SysLibException { + String outputMessage = testUtil.getOutputMessage(editCommand, "/id 123 /t NEWTITLE", emptyResourceList); + String expectedMessage = RESOURCE_NOT_FOUND; + assertEquals(expectedMessage, outputMessage); + } + + @Test + public void testNoArgumentGiven() { + executeAssertSysLibExceptionThrown("/id 123", MISSING_ARG_MESSAGE); + + } + + @Test + public void testNotCorrectResourceTypeError() { + List<Resource> dummyList = testUtil.addDummyResource(testResourceList); + parser.container.setResourcesList(dummyList); + executeAssertSysLibExceptionThrown("/id 1 /t dummyTitle", "Invalid Resource!"); + + } + + @Test + public void testEditStatusError() { + executeAssertSysLibExceptionThrown("/id 2 /s INVALIDSTATUS", + STATUS_ERROR_MESSAGE); + } + + @Test + public void testEditISBNError() { + executeAssertSysLibExceptionThrown("/id 2 /i invalid", + "ISBN must be 13 characters!"); + } + + @Test + public void testEditBookLinkError() { + executeAssertSysLibExceptionThrown("/id 2 /l dummyLink", + INVALID_EDIT_ARGS + BOOK_ARGS_MESSAGE); + } + + @Test + public void testEditNewspaperLinkError() { + executeAssertSysLibExceptionThrown("/id 9 /l dummyLink", + INVALID_EDIT_ARGS + NEWSPAPERS_ARGS_MESSAGE); + } + + @Test + public void testEditMagazineLinkError() { + executeAssertSysLibExceptionThrown("/id 6 /l dummyLink", + INVALID_EDIT_ARGS + MAGAZINE_ARGS_MESSAGE); + } + + @Test + public void testEditBookInvalidArgsGiven() { + executeAssertSysLibExceptionThrown("/id 2 /t TITLE /s LOST /p PUBLISHER /g GENRES /ed EDITION " + + "/c CREATOR /ty TYPE /b BRAND /is ISSUE", INVALID_EDIT_ARGS+BOOK_ARGS_MESSAGE); + } + + @Test + public void testEditBookInvalidIDGiven() { + assertThrows(IllegalArgumentException.class, () -> + editCommand.execute("/id -1 /t hello", parser.container)); + } + + private void executeAssertSysLibExceptionThrown(String arguments, String expectedMessage) { + SysLibException exception = assertThrows(SysLibException.class, ()->editCommand.execute( + arguments, parser.container)); + assertEquals(expectedMessage, exception.getMessage()); + } + +} diff --git a/src/test/java/seedu/commands/ExitCommandTest.java b/src/test/java/seedu/commands/ExitCommandTest.java new file mode 100644 index 0000000000..827ab8c64c --- /dev/null +++ b/src/test/java/seedu/commands/ExitCommandTest.java @@ -0,0 +1,30 @@ +package seedu.commands; + +import org.junit.jupiter.api.Test; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExitCommandTest { + @Test + void testExecute() { + Parser parser = new Parser(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + ExitCommand exitCommand = new ExitCommand(); + exitCommand.execute("", parser.container); + + String output = outputStream.toString(); + String expectedOutput = "Thanks for using SysLib! We have saved the current resources and events." + + System.lineSeparator() + + "See you next time!" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + +} diff --git a/src/test/java/seedu/commands/FindCommandTest.java b/src/test/java/seedu/commands/FindCommandTest.java new file mode 100644 index 0000000000..eead4c8cbd --- /dev/null +++ b/src/test/java/seedu/commands/FindCommandTest.java @@ -0,0 +1,140 @@ +package seedu.commands; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import seedu.data.resources.Book; +import seedu.data.resources.Magazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.CD; +import seedu.data.Status; +import seedu.exception.SysLibException; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FindCommandTest { + + private FindCommand findCommand; + private Parser parser; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + void setUp() { + findCommand = new FindCommand(); + parser = new Parser(); + + parser.container.getResourcesList().add(new Book("Title1", "ISBN1", "Author1", + new String[]{"horror"}, 1234, Status.AVAILABLE)); + parser.container.getResourcesList().add(new Magazine("Title2", "ISBN2", "VOGUE2", + "1234", 5678, Status.AVAILABLE)); + parser.container.getResourcesList().add(new Newspaper("Title3", "ISBN3", "Publisher3", + "1234", 9101, Status.AVAILABLE)); + parser.container.getResourcesList().add(new CD("Title4", "Creator4", "Creator4", + "1234", 1121, Status.AVAILABLE)); + + outContent.reset(); + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + void tearDown() { + System.setOut(originalOut); + } + + @Test + void testSetAndGetTitle() { + findCommand.setTitle("TitleTest"); + assertEquals("TitleTest", findCommand.getTitle()); + } + + @Test + void testSetAndGetAuthor() { + findCommand.setAuthor("AuthorTest"); + assertEquals("AuthorTest", findCommand.getAuthor()); + } + + @Test + void testSetAndGetISBN() { + findCommand.setISBN("ISBNTest"); + assertEquals("ISBNTest", findCommand.getISBN()); + } + + @Test + void testSetAndGetID() { + findCommand.setID("IDTest"); + assertEquals("IDTest", findCommand.getID()); + } + + @Test + void testExecuteWithInvalidFlag() { + assertThrows(IllegalArgumentException.class, () -> findCommand.execute("/x InvalidFlag", parser.container)); + } + + @Test + void testExecuteWithNoFilter() { + assertThrows(IllegalArgumentException.class, () -> findCommand.execute("", parser.container)); + } + + @Test + void testExecuteFindTitleMatch() throws SysLibException { + findCommand.execute("/t Title1", parser.container); + assertTrue(outContent.toString().contains("Title1")); + } + + @Test + void testExecuteFindAuthorMatch() throws SysLibException { + findCommand.execute("/a Author1", parser.container); + assertTrue(outContent.toString().contains("Author1")); + } + + @Test + void testExecuteFindISBNMatch() throws SysLibException { + findCommand.execute("/i ISBN1", parser.container); + assertTrue(outContent.toString().contains("ISBN1")); + } + + @Test + void testExecuteNoMatchesFound() throws SysLibException { + findCommand.execute("/t NonexistentTitle", parser.container); + assertTrue(outContent.toString().contains("There are no resources found matching the given filters.")); + } + + @Test + void testExecuteFindMagazineBrandMatch() throws SysLibException { + findCommand.execute("/a VOGUE2", parser.container); + assertTrue(outContent.toString().contains("VOGUE2")); + } + + @Test + void testExecuteFindNewspaperPublisherMatch() throws SysLibException { + findCommand.execute("/a Publisher3", parser.container); + assertTrue(outContent.toString().contains("Publisher3")); + } + + @Test + void testExecuteFindCDMatch() throws SysLibException { + findCommand.execute("/a Creator4", parser.container); + assertTrue(outContent.toString().contains("Creator4")); + } + + + @Test + void testExecuteMultipleFilters() throws SysLibException { + findCommand.execute("/t Title1 /a Author1", parser.container); + assertTrue(outContent.toString().contains("Title1")); + assertTrue(outContent.toString().contains("Author1")); + } + + @Test + void testExecuteInvalidFormat() { + assertThrows(IllegalArgumentException.class, () -> findCommand.execute("find /z Invalid", parser.container)); + } + +} diff --git a/src/test/java/seedu/commands/HelpCommandTest.java b/src/test/java/seedu/commands/HelpCommandTest.java new file mode 100644 index 0000000000..427d2fe0d7 --- /dev/null +++ b/src/test/java/seedu/commands/HelpCommandTest.java @@ -0,0 +1,65 @@ +package seedu.commands; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import seedu.parser.Parser; + +class HelpCommandTest { + + @Test + void testExecute() { + Parser parser = new Parser(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + HelpCommand helpCommand = new HelpCommand(); + helpCommand.execute("", parser.container); + + String output = outputStream.toString(); + String expectedOutput = "Commands available:" + + System.lineSeparator() + "[add] (Book) Adds a new book. " + + "(e.g. add /i ISBN /t TITLE /a AUTHOR /tag TAG [/g GENRE /s STATUS])" + + System.lineSeparator() + "[add] (eBook) Adds a new eBook. " + + "(e.g. add /i ISBN /t TITLE /a AUTHOR /tag eb /l LINK [/g GENRE /s STATUS])" + + System.lineSeparator() + "[add] (CD) Adds a new CD. " + + "(e.g. add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag cd [/s STATUS])" + + System.lineSeparator() + "[add] (Magazine) Adds a new magazine. " + + "(e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag m [/s STATUS])" + + System.lineSeparator() + "[add] (eMagazine) Adds a new eMagazine. " + + "(e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag em /l LINK [/s STATUS])" + + System.lineSeparator() + "[add] (Newspaper) Adds a new newspaper. " + + "(e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag n [/s STATUS])" + + System.lineSeparator() + "[add] (eNewspaper) Adds a new eNewspaper. " + + "(e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag en /l LINK [/s STATUS])"+ + System.lineSeparator() + + "[delete] deletes the resource with the specified ID from the library inventory. " + + "(e.g. delete /id 123456789)" + System.lineSeparator() + + "[list] lists all resources OR filter by certain tags, genre, or status. " + + "(e.g. list /tag B /g Fiction /s AVAILABLE)" + System.lineSeparator() + + "[find] finds a resource by title, author, ISBN or given id. " + + "(e.g. find /i 9780763630188 /a AUTHOR)" + System.lineSeparator() + + "[edit] edits a listing by entering its id to update its details. " + + "(e.g. edit /id 123 /t NEW_TITLE /a NEW_AUTHOR)" + System.lineSeparator() + + "[eventadd] adds an event to the database. " + + "(e.g. eventadd /t TITLE /date 23 Dec 2023 [/desc DESCRIPTION])" + System.lineSeparator() + + "[eventlist] lists out all events in the database. " + + "(e.g. eventlist)" + System.lineSeparator() + + "[eventdelete] deletes an event from the database based on the index. " + + "(e.g. eventdelete /i INDEX)" + System.lineSeparator() + + "[eventedit] edits an event in the event list based on the information given. " + + "(e.g. eventedit /i INDEX [/t TITLE /date DATE /desc DESCRIPTION])" + System.lineSeparator() + + "[summary] shows a summary of all resources and the next 3 events. " + + "(e.g. summary)" + System.lineSeparator() + + "[exit] displays a farewell message and exits the program. " + + "(e.g. exit)" + System.lineSeparator() + System.lineSeparator() + + "For more information, please refer to our user guide at: https://bit.ly/SyslibUserGuide" + + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } +} diff --git a/src/test/java/seedu/commands/ListCommandTest.java b/src/test/java/seedu/commands/ListCommandTest.java new file mode 100644 index 0000000000..e160435061 --- /dev/null +++ b/src/test/java/seedu/commands/ListCommandTest.java @@ -0,0 +1,99 @@ +package seedu.commands; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.parser.Parser; +import seedu.util.TestUtil; + +import static seedu.ui.ListCommandMessages.FILTER_MESSAGE; +import static seedu.ui.ListCommandMessages.STATUS_ERROR_MESSAGE; +import static seedu.ui.ListCommandMessages.ZERO_RESOURCES_MESSAGE; +import static seedu.commands.ListCommand.matchedResources; +import static seedu.ui.UI.showResourcesDetails; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +public class ListCommandTest { + + private static List<Resource> testResourcesList = new ArrayList<>(); + private static Parser parser = new Parser(); + private List<Resource> emptyResourceList = new ArrayList<>(); + private TestUtil testUtil = new TestUtil(); + private Command listCommand = new ListCommand(); + + @BeforeAll + public static void setup() throws SysLibException { + testResourcesList = TestUtil.fillTestResourcesList(); + parser.container.setResourcesList(testResourcesList); + + } + @Test + public void testEmptyListMessage() throws SysLibException { + assertThrows(SysLibException.class, ()->testUtil.getOutputMessage(listCommand, "", emptyResourceList)); + } + @Test + public void testNoTagArgBehavior() { + assertThrows(IllegalArgumentException.class, ()->listCommand.execute("/tag", parser.container)); + + } + @Test + public void testNoGenreArgBehavior() { + assertThrows(IllegalArgumentException.class, ()->listCommand.execute("/g", parser.container)); + + } + @Test + public void testNoStatusArgBehavior() { + assertThrows(IllegalArgumentException.class, ()->listCommand.execute("/s", parser.container)); + + } + @Test + public void testListByTagFilterBehavior() throws SysLibException { + executeListFilterBehavior("/tag B"); + } + @Test + public void testListByGenreFilterBehavior() throws SysLibException { + executeListFilterBehavior("/g Horror"); + executeListFilterBehavior("/g Adventure"); + executeListFilterBehavior("/g Fiction"); + executeListFilterBehavior("/g null"); + } + + @Test + public void testListByStatusFilterBehavior() throws SysLibException { + executeListFilterBehavior("/s AVAILABLE"); + executeListFilterBehavior("/s BORROWED"); + executeListFilterBehavior("/s LOST"); + + } + + public void executeListFilterBehavior(String argument) throws SysLibException { + String outputMessage = testUtil.getOutputMessage(listCommand, argument, testResourcesList); + String expectedMessage = FILTER_MESSAGE + showResourcesDetails(matchedResources); + assertEquals(expectedMessage, outputMessage); + + } + + @Test + public void testNoFilteredListDisplay() throws SysLibException { + String outputMessage = testUtil.getOutputMessage(listCommand, "/g Thriller", testResourcesList); + String expectedMessage = FILTER_MESSAGE; + expectedMessage += ZERO_RESOURCES_MESSAGE; + assertEquals(expectedMessage, outputMessage); + + } + + @Test + public void testInvalidStatusInput() { + executeAssertSysLibExceptionThrown("/s INVALIDSTATUS",STATUS_ERROR_MESSAGE); + } + + private void executeAssertSysLibExceptionThrown(String arguments, String expectedMessage) { + SysLibException exception = assertThrows(SysLibException.class, ()->listCommand.execute( + arguments, parser.container)); + assertEquals(expectedMessage, exception.getMessage()); + } +} diff --git a/src/test/java/seedu/commands/SummaryCommandTest.java b/src/test/java/seedu/commands/SummaryCommandTest.java new file mode 100644 index 0000000000..a83509b75a --- /dev/null +++ b/src/test/java/seedu/commands/SummaryCommandTest.java @@ -0,0 +1,112 @@ +package seedu.commands; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.parser.Parser; +import seedu.util.TestUtil; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +public class SummaryCommandTest { + + private static List<Resource> testResourcesList = new ArrayList<>(); + private static List<Event> testEventsList = new ArrayList<>(); + private static final Parser parser = new Parser(); + private final TestUtil testUtil = new TestUtil(); + private final Command summaryCommand = new SummaryCommand(); + @BeforeAll + public static void setup() throws SysLibException { + testResourcesList = TestUtil.fillTestResourcesList(); + testEventsList = TestUtil.fillTestEventsList(); + parser.container.setResourcesList(testResourcesList); + parser.container.setEventsList(testEventsList); + } + + @Test + public void testExcecute() throws SysLibException { + executeSummaryBehaviour(""); + } + + public void executeSummaryBehaviour(String argument) throws SysLibException { + String outputMessage = testUtil.getSummaryOutputMessage(summaryCommand, argument, parser.container); + String expectedMessage = "Summary of Resources:" + System.lineSeparator() + + "Total Resources: 9" + System.lineSeparator() + + "Total Books: [▓▓▓▓] 3" + System.lineSeparator() + + "Total E-Books: [▓] 1" + System.lineSeparator() + + "Total Magazines: [▓] 1" + System.lineSeparator() + + "Total E-Magazines: [▓] 1" + System.lineSeparator() + + "Total Newspapers: [▓] 1" + System.lineSeparator() + + "Total E-Newspapers: [▓] 1" + System.lineSeparator() + + "Total CDs: [▓] 1" + System.lineSeparator() + + System.lineSeparator() + + "Summary of Events:" + System.lineSeparator() + + "Total Events: 5" + System.lineSeparator() + + "Upcoming Events (Next 3):" + System.lineSeparator() + + "1. New Year 2024 | 01 Jan 2024 | Happy New Year" + System.lineSeparator() + + "2. April Fools | 01 Apr 2024 | Time for mischief" + System.lineSeparator() + + "3. Children's Day | 01 Oct 2024 | null" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedMessage, outputMessage); + + } + @Test + public void testExcecutewithArguments() throws IllegalArgumentException { + Parser parser2 = new Parser(); + assertThrows(IllegalArgumentException.class, () -> summaryCommand.execute("/t hi", parser2.container)); + } + @Test + public void testGenerateBar() { + SummaryCommand summaryCommand = new SummaryCommand(); + String bar = summaryCommand.generateBar(50); + assertEquals("[▓▓▓▓▓▓▓▓▓▓]", bar); + + String bar0 = summaryCommand.generateBar(0); + assertEquals("[]", bar0); + + String bar100 = summaryCommand.generateBar(100); + assertEquals("[▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓]", bar100); + + String bar25 = summaryCommand.generateBar(25); + assertEquals("[▓▓▓▓▓]", bar25); + } + + @Test + public void testGetUpcomingEvents() { + SummaryCommand summaryCommand = new SummaryCommand(); + + // Create a list of events for testing + List<Event> events = new ArrayList<>(); + LocalDate today = LocalDate.now(); + LocalDate tomorrow = today.plusDays(1); + LocalDate nextWeek = today.plusWeeks(1); + + events.add(new Event("Event 1", today, "Description 1")); + events.add(new Event("Event 2", tomorrow, "Description 2")); + events.add(new Event("Event 3", nextWeek, "Description 3")); + + // Test for 0 upcoming events + List<Event> upcomingEvents0 = summaryCommand.getUpcomingEvents(events, 0); + assertEquals(0, upcomingEvents0.size()); + + // Test for 2 upcoming events + List<Event> upcomingEvents2 = summaryCommand.getUpcomingEvents(events, 2); + assertEquals(2, upcomingEvents2.size()); + assertEquals("Event 2", upcomingEvents2.get(0).getName()); + assertEquals("Event 3", upcomingEvents2.get(1).getName()); + + // Test for 4 upcoming events when 3 available + List<Event> upcomingEvents4 = summaryCommand.getUpcomingEvents(events, 4); + assertEquals(2, upcomingEvents4.size()); + } + + +} diff --git a/src/test/java/seedu/commands/events/EventAddCommandTest.java b/src/test/java/seedu/commands/events/EventAddCommandTest.java new file mode 100644 index 0000000000..11cebde2e3 --- /dev/null +++ b/src/test/java/seedu/commands/events/EventAddCommandTest.java @@ -0,0 +1,108 @@ +package seedu.commands.events; + +import org.junit.jupiter.api.Test; +import seedu.exception.SysLibException; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class EventAddCommandTest { + private final Parser parser = new Parser(); + private final EventAddCommand eventAddCommand = new EventAddCommand(); + + @Test + public void testeventAddCommandValidData() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 Dec 2001 /desc testing 123", parser.container); + String output = parser.eventsList.get(0).toString(); + String expectedOutput = "testrun | 01 Dec 2001 | testing 123"; + assertEquals(output, expectedOutput); + } + + @Test + public void testeventAddCommandWithoutDescription() throws SysLibException { + eventAddCommand.execute("/t testrun2 /date 1 Dec 2001", parser.container); + String output = parser.eventsList.get(0).toString(); + String expectedOutput = "testrun2 | 01 Dec 2001 | null"; + assertEquals(output, expectedOutput); + } + + @Test + public void eventAddCommandOutput() throws SysLibException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventAddCommand.execute("/t testrun /date 1 DEC 2001 /desc testing 123", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Event inserted at: 0" + System.lineSeparator() + + "0: testrun | 01 Dec 2001 | testing 123" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + @Test + public void eventAddCommandOutputSoonerDate() throws SysLibException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventAddCommand.execute("/t testrun /date 1 DEC 2001 /desc testing 123", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Event inserted at: 0" + System.lineSeparator() + + "0: testrun | 01 Dec 2001 | testing 123" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + eventAddCommand.execute("/t testrun2 /date 1 NOV 2001", parser.container); + output = outputStream.toString(); + expectedOutput += "Event inserted at: 0" + System.lineSeparator() + + "0: testrun2 | 01 Nov 2001 | null" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + @Test + public void eventAddCommandInvalidDate() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/t testrun /date tmr /desc testing 123", parser.container)); + } + + @Test + public void eventAddCommandInvalidDateFormat() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/t testrun /date 23-12-2023", parser.container)); + } + + @Test + public void eventAddCommandInvalidTitle() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/t /date 12 Jan 2022", parser.container)); + } + + @Test + public void eventAddCommandInsufficientData() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute("/t ", parser.container)); + } + + @Test + public void eventAddCommandInsufficientData2() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute("/t Hello", parser.container)); + } + + @Test + public void eventAddCommandDuplicateTitle() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/t Hello /t Hello2 /date 20 Jan 2023", parser.container)); + } + + @Test + public void eventAddCommandDuplicateDate() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/t Hello /date 17 Jan 2023 /date 20 Jan 2023", parser.container)); + } + + @Test + public void eventAddCommandInvalidArguments() { + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "Invalid /t Hello /date 20 Jan 2023", parser.container)); + } +} diff --git a/src/test/java/seedu/commands/events/EventDeleteCommandTest.java b/src/test/java/seedu/commands/events/EventDeleteCommandTest.java new file mode 100644 index 0000000000..e2c1f088f7 --- /dev/null +++ b/src/test/java/seedu/commands/events/EventDeleteCommandTest.java @@ -0,0 +1,67 @@ +package seedu.commands.events; + +import org.junit.jupiter.api.Test; +import seedu.exception.SysLibException; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class EventDeleteCommandTest { + private final Parser parser = new Parser(); + private final EventAddCommand eventAddCommand = new EventAddCommand(); + private final EventDeleteCommand eventDeleteCommand = new EventDeleteCommand(); + + @Test + public void eventDeleteCommandOutput() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 dec 2001 /desc testing 123", parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventDeleteCommand.execute("/id 0", parser.container); + String output = outputStream.toString(); + String expectedOutput = "This event is removed:" + System.lineSeparator() + + "testrun | 01 Dec 2001 | testing 123" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + @Test + public void eventDeleteCommandInvalidIndex() { + assertThrows(IllegalArgumentException.class, ()->eventDeleteCommand.execute( + "/id aaaaa", parser.container)); + } + + @Test + public void eventDeleteCommandInsufficientData() { + assertThrows(IllegalArgumentException.class, ()->eventDeleteCommand.execute("/id ", parser.container)); + } + + @Test + public void eventDeleteCommandOutOfBounds() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 dec 2001 /desc testing 123", parser.container); + assertThrows(IllegalArgumentException.class, ()->eventDeleteCommand.execute("/id 1", parser.container)); + } + + @Test + public void eventDeleteCommandEmpty(){ + assertThrows(SysLibException.class, ()->eventDeleteCommand.execute("/id 0", parser.container)); + } + + @Test + public void eventDeleteDuplicateID() throws SysLibException{ + eventAddCommand.execute("/t test1 /date 1 dec 2001 /desc testing 123", parser.container); + eventAddCommand.execute("/t test2 /date 2 dec 2001 /desc testing 456", parser.container); + assertThrows(IllegalArgumentException.class, ()->eventDeleteCommand.execute("/id 0 /id 1", parser.container)); + } + + @Test + public void eventDeleteInvalidArgument() throws SysLibException{ + eventAddCommand.execute("/t test1 /date 1 dec 2001 /desc testing 123", parser.container); + eventAddCommand.execute("/t test2 /date 2 dec 2001 /desc testing 456", parser.container); + assertThrows(IllegalArgumentException.class, ()->eventDeleteCommand.execute("not /id 0", parser.container)); + } +} diff --git a/src/test/java/seedu/commands/events/EventEditCommandTest.java b/src/test/java/seedu/commands/events/EventEditCommandTest.java new file mode 100644 index 0000000000..0b6d251c47 --- /dev/null +++ b/src/test/java/seedu/commands/events/EventEditCommandTest.java @@ -0,0 +1,122 @@ +package seedu.commands.events; + +import org.junit.jupiter.api.Test; +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Resource; +import seedu.exception.SysLibException; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +public class EventEditCommandTest { + private final Parser parser = new Parser(); + private final EventAddCommand eventAddCommand = new EventAddCommand(); + private final EventEditCommand eventEditCommand = new EventEditCommand(); + private GenericList<Resource, Event> container; + + @Test + public void testInvalidIndex() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 dec 2001 /desc testing 123", parser.container); + assertThrows(IllegalArgumentException.class, + () -> eventEditCommand.execute("/id 10", parser.container)); + } + + @Test + public void testeventEditCommandOutputNothingChanged() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 dec 2001 /desc testing 123", parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventEditCommand.execute("/id 0", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Event was not edited as nothing was changed." + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + + @Test + void testExecuteWithDateChanges() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 dec 2001 /desc testing 123", parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventEditCommand.execute("/id 0 /t newtest /date 23 Dec 2023 /desc test456", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Event edited successfully. New event details:" + System.lineSeparator() + + "0: newtest | 23 Dec 2023 | test456" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + @Test + void testExecuteWithDateChange() throws SysLibException { + eventAddCommand.execute("/t test1 /date 1 dec 2001 /desc testing 123", parser.container); + eventAddCommand.execute("/t test2 /date 2 dec 2001 /desc testing 123", parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventEditCommand.execute("/id 0 /date 23 Dec 2023", parser.container); + String output = outputStream.toString(); + String expectedOutput = "Event index has changed as the date was changed." + System.lineSeparator() + + "Event edited successfully. New event details:" + System.lineSeparator() + + "1: test1 | 23 Dec 2023 | testing 123" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + eventEditCommand.execute("/id 1 /date 23 Nov 2001", parser.container); + output = outputStream.toString(); + expectedOutput += "Event index has changed as the date was changed." + System.lineSeparator() + + "Event edited successfully. New event details:" + System.lineSeparator() + + "0: test1 | 23 Nov 2001 | testing 123" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + } + + @Test + void testExecuteWithEmptyList() throws SysLibException { + assertThrows(SysLibException.class, () -> + eventEditCommand.execute("/id 0", parser.container)); + } + + @Test + void testParseDateWithInvalidFormat() { + assertThrows(IllegalArgumentException.class, () -> EventEditCommand.parseDate("2023-01-01")); + } + + @Test + void testCheckDate() { + String formattedDate = EventEditCommand.checkDate("1 Jan 2023"); + assertEquals("01 Jan 2023", formattedDate); + } + + @Test + void testCheckDateWithInvalidFormat() { + assertThrows(IllegalArgumentException.class, () -> EventEditCommand.checkDate("01-Jan-2023")); + } + + @Test + public void eventEditCommandDuplicateTitle() throws SysLibException { + eventAddCommand.execute("/t test1 /date 1 dec 2001 /desc testing 123", parser.container); + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/id 0 /t Hello /t Hello2 /date 20 Jan 2023", parser.container)); + } + + @Test + public void eventAddCommandDuplicateDate() throws SysLibException { + eventAddCommand.execute("/t test1 /date 1 dec 2001 /desc testing 123", parser.container); + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "/id 0 /t Hello /date 17 Jan 2023 /date 20 Jan 2023", parser.container)); + } + + @Test + public void eventAddCommandInvalidArguments() throws SysLibException { + eventAddCommand.execute("/t test1 /date 1 dec 2001 /desc testing 123", parser.container); + assertThrows(IllegalArgumentException.class, ()->eventAddCommand.execute( + "Invalid /id 0 /t Hello /date 20 Jan 2023", parser.container)); + } + +} diff --git a/src/test/java/seedu/commands/events/EventListCommandTest.java b/src/test/java/seedu/commands/events/EventListCommandTest.java new file mode 100644 index 0000000000..80dc5612cd --- /dev/null +++ b/src/test/java/seedu/commands/events/EventListCommandTest.java @@ -0,0 +1,45 @@ +package seedu.commands.events; + +import org.junit.jupiter.api.Test; +import seedu.exception.SysLibException; +import seedu.parser.Parser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class EventListCommandTest { + + private final Parser parser = new Parser(); + private final EventAddCommand eventAddCommand = new EventAddCommand(); + private final EventListCommand eventListCommand = new EventListCommand(); + + @Test + public void eventListCommandOutputEmpty() throws SysLibException { + assertThrows(SysLibException.class, ()->eventListCommand.execute("", parser.container)); + } + + @Test + public void eventListCommandOutputMultiple() throws SysLibException { + eventAddCommand.execute("/t testrun /date 1 dec 2001 /desc testing 123", parser.container); + eventAddCommand.execute("/t testrun2 /date 1 DEC 2002 /desc testing 1234", parser.container); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + eventListCommand.execute("", parser.container); + String output = outputStream.toString(); + String expectedOutput = "This is the current event list:" + System.lineSeparator() + + "0: testrun | 01 Dec 2001 | testing 123" + System.lineSeparator() + + "1: testrun2 | 01 Dec 2002 | testing 1234" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + } + + @Test + public void eventListCommandInvalidInput() { + assertThrows(IllegalArgumentException.class, ()->eventListCommand.execute( + "random", parser.container)); + } + +} diff --git a/src/test/java/seedu/parser/ParserTest.java b/src/test/java/seedu/parser/ParserTest.java new file mode 100644 index 0000000000..bbf159f0be --- /dev/null +++ b/src/test/java/seedu/parser/ParserTest.java @@ -0,0 +1,242 @@ +package seedu.parser; + +import org.junit.jupiter.api.Test; +import seedu.data.resources.Book; +import seedu.data.Status; +import seedu.data.resources.Resource; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.ui.UI.LINESEPARATOR; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; +import static seedu.util.TestUtil.getCurrentDate; + +class ParserTest { + + @Test + public void testProcessExitCommand() { + Parser parser = new Parser(); + String validResponse = "exit"; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + parser.processUserResponse(validResponse); + + System.setOut(System.out); + String output = outputStream.toString(); + + String expectedOutput = "Thanks for using SysLib! We have saved the current resources and " + + "events." + System.lineSeparator() + + "See you next time!" + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + } + + @Test + public void testProcessHelpCommand() { + Parser parser = new Parser(); + String validResponse = "help"; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + parser.processUserResponse(validResponse); + + System.setOut(System.out); + String output = outputStream.toString(); + + String expectedOutput = "Commands available:" + System.lineSeparator() + + "[add] (Book) Adds a new book. " + + "(e.g. add /i ISBN /t TITLE /a AUTHOR /tag TAG [/g GENRE /s STATUS])" + + System.lineSeparator() + "[add] (eBook) Adds a new eBook. " + + "(e.g. add /i ISBN /t TITLE /a AUTHOR /tag eb /l LINK [/g GENRE /s STATUS])" + + System.lineSeparator() + "[add] (CD) Adds a new CD. " + + "(e.g. add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag cd [/s STATUS])" + + System.lineSeparator() + "[add] (Magazine) Adds a new magazine. " + + "(e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag m [/s STATUS])" + + System.lineSeparator() + "[add] (eMagazine) Adds a new eMagazine. " + + "(e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag em /l LINK [/s STATUS])" + + System.lineSeparator() + "[add] (Newspaper) Adds a new newspaper. " + + "(e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag n [/s STATUS])" + + System.lineSeparator() + "[add] (eNewspaper) Adds a new eNewspaper. " + + "(e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag en /l LINK [/s STATUS])"+ + System.lineSeparator() + + "[delete] deletes the resource with the specified ID from the library inventory. " + + "(e.g. delete /id 123456789)" + System.lineSeparator() + + "[list] lists all resources OR filter by certain tags, genre, or status. " + + "(e.g. list /tag B /g Fiction /s AVAILABLE)" + System.lineSeparator() + + "[find] finds a resource by title, author, ISBN or given id. " + + "(e.g. find /i 9780763630188 /a AUTHOR)" + System.lineSeparator() + + "[edit] edits a listing by entering its id to update its details. " + + "(e.g. edit /id 123 /t NEW_TITLE /a NEW_AUTHOR)" + System.lineSeparator() + + "[eventadd] adds an event to the database. " + + "(e.g. eventadd /t TITLE /date 23 Dec 2023 [/desc DESCRIPTION])" + System.lineSeparator() + + "[eventlist] lists out all events in the database. " + + "(e.g. eventlist)" + System.lineSeparator() + + "[eventdelete] deletes an event from the database based on the index. " + + "(e.g. eventdelete /i INDEX)" + System.lineSeparator() + + "[eventedit] edits an event in the event list based on the information given. " + + "(e.g. eventedit /i INDEX [/t TITLE /date DATE /desc DESCRIPTION])" + System.lineSeparator() + + "[summary] shows a summary of all resources and the next 3 events. " + + "(e.g. summary)" + System.lineSeparator() + + "[exit] displays a farewell message and exits the program. " + + "(e.g. exit)" + System.lineSeparator() + System.lineSeparator() + + "For more information, please refer to our user guide at: https://bit.ly/SyslibUserGuide" + + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + @Test + public void testProcessUnknownCommand() { + Parser parser = new Parser(); + String validResponse = "bye"; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + + parser.processUserResponse(validResponse); + + System.setOut(System.out); + String output = outputStream.toString(); + + String expectedOutput = "no commands found. Enter \"help\" for a list of commands."; + expectedOutput += System.lineSeparator(); + expectedOutput += "____________________________________________________________"; + expectedOutput += System.lineSeparator(); + + assertEquals(expectedOutput, output); + } + + @Test + public void testProcessSuggestCommand() { + Parser parser = new Parser(); + String validResponse = "hel"; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + + parser.processUserResponse(validResponse); + + System.setOut(System.out); + String output = outputStream.toString(); + + String expectedOutput = "no commands found. Enter \"help\" for a list of commands."; + expectedOutput += System.lineSeparator(); + expectedOutput += "Did you mean: 'help'"; + expectedOutput += System.lineSeparator(); + expectedOutput += "____________________________________________________________"; + expectedOutput += System.lineSeparator(); + assertEquals(expectedOutput, output); + } + + @Test + public void testProcessCommands() { + //temporary fix + List<Resource> resources = new ArrayList<>(); + Book book = new Book("The Subtle Art of Not Giving a F*ck /a Mark Manson", "9780062457714", + "Mark Manson", new String[]{"Self-help"}, 2, Status.AVAILABLE); + resources.add(book); + //Test add + Parser parser = new Parser(); + String validResponse = "add /i 9781250255174 /t Surrounded by Idiots /a Thomas Erikson /tag B /g Self-help"; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + + parser.processUserResponse(validResponse); + + System.setOut(System.out); + String output = outputStream.toString(); + String expectedOutput = "Attention: Status is not stated. Status set to default: AVAILABLE." + + System.lineSeparator() + "This book is added:" + System.lineSeparator() + + "[B] ID: 1 Title: Surrounded by Idiots ISBN: 9781250255174 Author: Thomas Erikson " + + "Genre: Self-help Status: AVAILABLE Received Date: " + getCurrentDate() + + SEPARATOR_LINEDIVIDER + System.lineSeparator(); + assertEquals(expectedOutput, output); + //Add second book + validResponse = "add /i 9780062457714 /t The Subtle Art of Not Giving a F*ck " + + "/a Mark Manson /tag B /g Self-help"; + parser.processUserResponse(validResponse); + expectedOutput += "Attention: Status is not stated. Status set to default: AVAILABLE." + + System.lineSeparator() + "This book is added:" + System.lineSeparator() + + "[B] ID: 2 Title: The Subtle Art of Not Giving a F*ck ISBN: 9780062457714 " + + "Author: Mark Manson Genre: Self-help Status: AVAILABLE Received Date: " + + getCurrentDate() + SEPARATOR_LINEDIVIDER + System.lineSeparator(); + System.setOut(System.out); + output = outputStream.toString(); + assertEquals(expectedOutput, output); + //Test find + validResponse = "find /t The Subtle Art of Not Giving a F*ck"; + parser.processUserResponse(validResponse); + output = outputStream.toString(); + expectedOutput = "Attention: Status is not stated. Status set to default: AVAILABLE." + + System.lineSeparator() + "This book is added:" + LINESEPARATOR + + "[B] ID: 1 Title: Surrounded by Idiots ISBN: 9781250255174 Author: Thomas Erikson Genre:"+ + " Self-help Status: AVAILABLE Received Date: "+getCurrentDate() +LINESEPARATOR + + "____________________________________________________________" +LINESEPARATOR + + "Attention: Status is not stated. Status set to default: AVAILABLE." + + System.lineSeparator() + "This book is added:" + LINESEPARATOR + + "[B] ID: 2 Title: The Subtle Art of Not Giving a F*ck ISBN: 9780062457714 Author: Mark "+ + "Manson Genre: Self-help Status: AVAILABLE Received Date: "+getCurrentDate() +LINESEPARATOR + + "____________________________________________________________" +LINESEPARATOR + + "Here are resources that matched the given filters:" +LINESEPARATOR + + " [BOOKS]" +LINESEPARATOR + + "----------------------------------------------------------------------------------------"+ + "-----------------------------------------------------------" +LINESEPARATOR + + "ID Tag Title ISBN Author "+ + " Genre Link Status Received Date " +LINESEPARATOR + + "----------------------------------------------------------------------------------------"+ + "-----------------------------------------------------------" +LINESEPARATOR + + "2 B The Subtle Art of Not Giving a F*ck 9780062457714 Mark Manson "+ + " Self-help null AVAILABLE "+getCurrentDate()+" " +LINESEPARATOR + + LINESEPARATOR + LINESEPARATOR + + "There are currently 1 resource(s)." +LINESEPARATOR + + "____________________________________________________________" +LINESEPARATOR + LINESEPARATOR ; + assertEquals(expectedOutput, output); + //Negative find test + validResponse = "find /t No Such Book"; + parser.processUserResponse(validResponse); + expectedOutput += "There are no resources found matching the given filters." + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + output = outputStream.toString(); + assertEquals(expectedOutput, output); + //Test edit + validResponse = "edit /id 1 /a Thomas"; + parser.processUserResponse(validResponse); + output = outputStream.toString(); + expectedOutput += "Successfully updated! Your updated resource:" + System.lineSeparator() + + System.lineSeparator() + "[B] ID: 1 Title: Surrounded by Idiots ISBN: 9781250255174 " + + "Author: Thomas Genre: Self-help Status: AVAILABLE" + " Received Date: " + getCurrentDate() + +System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + + validResponse = "edit /id 1 /s lost"; + parser.processUserResponse(validResponse); + output = outputStream.toString(); + expectedOutput += "Successfully updated! Your updated resource:" + System.lineSeparator() + + System.lineSeparator() + "[B] ID: 1 Title: Surrounded by Idiots ISBN: 9781250255174 " + + "Author: Thomas Genre: Self-help Status: LOST" + " Received Date: " + getCurrentDate() + +System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + //Test delete + validResponse = "delete /id 1"; + parser.processUserResponse(validResponse); + output = outputStream.toString(); + expectedOutput += "Looking for ID: 1..." + System.lineSeparator() + + "This resource is removed:" + System.lineSeparator() + + "[B] ID: 1 Title: Surrounded by Idiots ISBN: 9781250255174 " + + "Author: Thomas Genre: Self-help Status: LOST" + " Received Date: " + + getCurrentDate() + System.lineSeparator() + + "____________________________________________________________" + System.lineSeparator(); + assertEquals(expectedOutput, output); + } + +} diff --git a/src/test/java/seedu/util/TestUtil.java b/src/test/java/seedu/util/TestUtil.java new file mode 100644 index 0000000000..ac2e5b7959 --- /dev/null +++ b/src/test/java/seedu/util/TestUtil.java @@ -0,0 +1,168 @@ +package seedu.util; + +import seedu.commands.Command; +import seedu.data.GenericList; +import seedu.data.events.Event; +import seedu.data.resources.Book; +import seedu.data.resources.CD; +import seedu.data.resources.EBook; +import seedu.data.resources.EMagazine; +import seedu.data.resources.ENewspaper; +import seedu.data.resources.Magazine; +import seedu.data.resources.Newspaper; +import seedu.data.resources.Resource; +import seedu.data.Status; +import seedu.exception.SysLibException; +import seedu.parser.Parser; +import seedu.commands.CommandResult; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static seedu.commands.Command.parseInt; +import static seedu.ui.UI.SEPARATOR_LINEDIVIDER; + +public class TestUtil { + + + public String getOutputMessage(Command c, String m, List<Resource> resourceList) throws SysLibException { + Parser parser = new Parser(); + parser.container.setResourcesList(resourceList); + CommandResult commandResult = c.execute(m, parser.container); + return commandResult.feedbackToUser; + } + + public String getSummaryOutputMessage(Command c, String m, GenericList<Resource, Event> container) + throws SysLibException { + CommandResult commandResult = c.execute(m, container); + return commandResult.feedbackToUser; + } + + public static String getCurrentDate(){ + LocalDateTime dateReceived = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); + String formattedDate = dateReceived.format(formatter); + return formattedDate; + } + + public static List<Resource> fillTestResourcesList() { + List<Resource> testResourcesList = new ArrayList<>(); + String[] genres = {"Horror", "Fiction"}; + String[] genresAdventure = {"Adventure"}; + String[] genresNull = {null}; + + + Book testBook = new Book("title2", "TMOBM00000001", "author", genres, 2, + Status.AVAILABLE); + Book testBook2 = new Book("title3", "TMOBM00000001", "author", genresAdventure, 3, + Status.AVAILABLE); + Book testBook3 = new Book("title3", "TMOBM00000001", "author", genresNull, 4, + Status.AVAILABLE); + + EBook testEBook = new EBook("title4", "TMOBM00000001", "author", genresNull, 5, + Status.AVAILABLE, "abc.com"); + + Magazine testMagazine = new Magazine("title5", "TMOBM00000001", "brand", "issue", 6, + Status.AVAILABLE); + EMagazine testEMagazine = new EMagazine("title6", "TMOBM00000001", "brand", "issue", 7, + Status.AVAILABLE, "def.com"); + + CD testCD = new CD("title7", "TMOBM00000001", "creator", "type", 8, + Status.AVAILABLE); + + Newspaper testNewspaper = new Newspaper("title8", "TMOBM00000001", "publisher", + "edition", 9, Status.AVAILABLE); + + ENewspaper testENewspaper = new ENewspaper("title8", "TMOBM00000001", "publisher", + "edition",10,Status.AVAILABLE, "xyz.com"); + + + + testResourcesList.add(testBook); + testResourcesList.add(testBook2); + testResourcesList.add(testBook3); + testResourcesList.add(testEBook); + testResourcesList.add(testMagazine); + testResourcesList.add(testEMagazine); + testResourcesList.add(testCD); + testResourcesList.add(testNewspaper); + testResourcesList.add(testENewspaper); + + return testResourcesList; + } + + public static List<Event> fillTestEventsList() { + List<Event> testEventsList = new ArrayList<>(); + + Event event1 = new Event("New Year 2024", parseDate("1 Jan 2024"), "Happy New Year"); + Event event2 = new Event("April Fools", parseDate("1 Apr 2024"), "Time for mischief"); + Event event3 = new Event("Children's Day", parseDate("1 Oct 2024"), null); + Event event4 = new Event("Meeting", parseDate("23 Oct 2024"), "Board Meeting w CEO"); + Event event5 = new Event("New Year 2025", parseDate("1 Jan 2025"), "Happy New Year"); + + testEventsList.add(event1); + testEventsList.add(event2); + testEventsList.add(event3); + testEventsList.add(event4); + testEventsList.add(event5); + + return testEventsList; + } + + public List<Resource> addDummyResource(List<Resource> resourceList){ + Resource dummyResource = new Resource("title", "TMOBM00000001",1, Status.AVAILABLE); + resourceList.add(dummyResource); + List<Resource> dummyList = resourceList; + return dummyList; + } + + /** + * Parses a date string into a LocalDate object with a specific format. + * + * @param dateStr The date string to be parsed. + * @return The parsed LocalDate object. + * @throws IllegalArgumentException If the date string is in an invalid format. + */ + public static LocalDate parseDate(String dateStr) throws IllegalArgumentException { + DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("dd MMM yyyy") + .toFormatter(Locale.ENGLISH) + .withResolverStyle(ResolverStyle.SMART); + try { + dateStr = checkDate(dateStr); + return LocalDate.parse(dateStr, formatter); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Please enter a valid date in the format 'dd MMM yyyy'" + + SEPARATOR_LINEDIVIDER); + } + } + + /** + * Checks and formats a date string to ensure it is in the correct format. + * + * @param dateStr The date string to be checked. + * @return The formatted date string. + * @throws IllegalArgumentException If the date string is in an invalid format. + */ + public static String checkDate(String dateStr) throws IllegalArgumentException { + String[] temp = dateStr.split(" "); + if (temp.length != 3) { + throw new IllegalArgumentException("Please enter a valid date in the format 'dd MMM yyyy'" + + SEPARATOR_LINEDIVIDER); + } + int first = parseInt(temp[0]); + if (first < 10) { + return "0" + dateStr; + } + return dateStr; + } + +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..e52767c3e7 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,123 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +____________________________________________________________ +Data directory does not exist. Creating now... +Storage file does not exist. Creating now... +Loaded 0 resources and 0 events! +____________________________________________________________ +Syslib v2.1 + ..................... + -##@*+*@*++++++++++#@++## + .@. @-=%= *#-+% + :@ @+- :----------. .=#% + :@ @. *%----------@- =% + :@ @. #* @= =% + :@ @. #* *: :+ + :@ @. *%-----. .=+****+-. + :@ @. :-----.-#*-. .:-*#- + :@ @. .%+. .@*#+.*%. + :@ @: %= %* +@.=% + :@ @*#*. -@ *###***+. @- + :@ .@:.=@... -@ .+*#*#### @- + :@#*++++++++. %=.%+ +# +% + :@. =++++++++-.%*.+%*@. *%. + %+ ........ =#*-:: .-*%= + =*************. .=+****+-. + ____ _ _ _ ____ _ ___ +/ ___| _ _ ___| | (_) |__ / ___| | |_ _| +\___ \| | | / __| | | | '_ \ | | | | | | + ___) | |_| \__ \ |___| | |_) | | |___| |___ | | +|____/ \__, |___/_____|_|_.__/ \____|_____|___| + |___/ + +Hello! What would you like to do? +____________________________________________________________ +> Attention: Status is not stated. Status set to default: AVAILABLE. +This book is added: +[B] ID: 1 Title: Moby Dick ISBN: 1234567890123 Author: Herman Melville Genre: Adventure, Fiction Status: AVAILABLE Received Date: 14 Nov 2023 +____________________________________________________________ +> Attention: Status is not stated. Status set to default: AVAILABLE. +This book is added: +[B] ID: 2 Title: The Subtle Art of Not Giving a F*ck ISBN: 9780062457714 Author: Mark Manson Genre: Self-help Status: AVAILABLE Received Date: 14 Nov 2023 +____________________________________________________________ +> Please enter only 1 tag. +____________________________________________________________ +> Event inserted at: 0 +0: event1 | 18 Dec 2023 | null +____________________________________________________________ +> Event inserted at: 1 +1: event2 | 19 Jan 2024 | null +____________________________________________________________ +> Event inserted at: 0 +0: event3 | 20 Mar 2023 | null +____________________________________________________________ +> Please enter a valid date in the format 'dd MMM yyyy' +____________________________________________________________ +> Please enter a valid ISBN with 13 digits. +____________________________________________________________ +> Commands available: +[add] (Book) Adds a new book. (e.g. add /i ISBN /t TITLE /a AUTHOR /tag TAG [/g GENRE /s STATUS]) +[add] (eBook) Adds a new eBook. (e.g. add /i ISBN /t TITLE /a AUTHOR /tag eb /l LINK [/g GENRE /s STATUS]) +[add] (CD) Adds a new CD. (e.g. add /i ISBN /t TITLE /c CREATOR /ty TYPE /tag cd [/s STATUS]) +[add] (Magazine) Adds a new magazine. (e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag m [/s STATUS]) +[add] (eMagazine) Adds a new eMagazine. (e.g. add /i ISBN /t TITLE /b BRAND /is ISSUE /tag em /l LINK [/s STATUS]) +[add] (Newspaper) Adds a new newspaper. (e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag n [/s STATUS]) +[add] (eNewspaper) Adds a new eNewspaper. (e.g. add /i ISBN /t TITLE /p PUBLISHER /ed EDITION /tag en /l LINK [/s STATUS]) +[delete] deletes the resource with the specified ID from the library inventory. (e.g. delete /id 123456789) +[list] lists all resources OR filter by certain tags, genre, or status. (e.g. list /tag B /g Fiction /s AVAILABLE) +[find] finds a resource by title, author, ISBN or given id. (e.g. find /i 9780763630188 /a AUTHOR) +[edit] edits a listing by entering its id to update its details. (e.g. edit /id 123 /t NEW_TITLE /a NEW_AUTHOR) +[eventadd] adds an event to the database. (e.g. eventadd /t TITLE /date 23 Dec 2023 [/desc DESCRIPTION]) +[eventlist] lists out all events in the database. (e.g. eventlist) +[eventdelete] deletes an event from the database based on the index. (e.g. eventdelete /i INDEX) +[eventedit] edits an event in the event list based on the information given. (e.g. eventedit /i INDEX [/t TITLE /date DATE /desc DESCRIPTION]) +[summary] shows a summary of all resources and the next 3 events. (e.g. summary) +[exit] displays a farewell message and exits the program. (e.g. exit) + +For more information, please refer to our user guide at: https://bit.ly/SyslibUserGuide +____________________________________________________________ +> Summary of Resources: +Total Resources: 2 +Total Books: [▓▓▓▓] 2 +Total E-Books: [] 0 +Total Magazines: [] 0 +Total E-Magazines: [] 0 +Total Newspapers: [] 0 +Total E-Newspapers: [] 0 +Total CDs: [] 0 + +Summary of Events: +Total Events: 3 +Upcoming Events (Next 3): +1. event1 | 18 Dec 2023 | null +2. event2 | 19 Jan 2024 | null +____________________________________________________________ +> Successfully updated! Your updated resource: + +[B] ID: 1 Title: NEWTITLE ISBN: 1234567890123 Author: Herman Melville Genre: Adventure, Fiction Status: AVAILABLE Received Date: 14 Nov 2023 +____________________________________________________________ +> Successfully updated! Your updated resource: + +[B] ID: 1 Title: NEWTITLE ISBN: 1234567890123 Author: Herman Melville Genre: Adventure, Fiction Status: BORROWED Received Date: 14 Nov 2023 +____________________________________________________________ +> Successfully updated! Your updated resource: + +[B] ID: 1 Title: NEWTITLE ISBN: 1234567890123 Author: Herman Melville Genre: Adventure, Fiction Status: LOST Received Date: 14 Nov 2023 +____________________________________________________________ +> Looking for ID: 1... +This resource is removed: +[B] ID: 1 Title: NEWTITLE ISBN: 1234567890123 Author: Herman Melville Genre: Adventure, Fiction Status: LOST Received Date: 14 Nov 2023 +____________________________________________________________ +> Looking for ID: 2... +This resource is removed: +[B] ID: 2 Title: The Subtle Art of Not Giving a F*ck ISBN: 9780062457714 Author: Mark Manson Genre: Self-help Status: AVAILABLE Received Date: 14 Nov 2023 +____________________________________________________________ +> id is missing in the argument! +____________________________________________________________ +> id is missing in the argument! +____________________________________________________________ +> id is missing in the argument! +____________________________________________________________ +> id is missing in the argument! +____________________________________________________________ +> Thanks for using SysLib! We have saved the current resources and events. +See you next time! +____________________________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..aaa8f732c2 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,20 @@ -James Gosling \ No newline at end of file +add /i 1234567890123 /t Moby Dick /a Herman Melville /tag B /g Adventure, Fiction +add /i 9780062457714 /t The Subtle Art of Not Giving a F*ck /a Mark Manson /tag B /g Self-help +add /i 9781250255174 /t Surrounded by Idiots /a Thomas Erikson /tag B /tag B /g Self-help /s lost +eventadd /t event1 /date 18 Dec 2023 +eventadd /t event2 /date 19 Jan 2024 +eventadd /t event3 /date 20 Mar 2023 +eventadd /t event4 /date 24 Deb 2024 /desc description4 +add /t Moby Dick /a Herman Melville /tag B /i TMOBM00000001 /g Adventure, Fiction +help +summary +edit /id 1 /t NEWTITLE +edit /id 1 /s borrowed +edit /id 1 /s lost +delete /id 1 +delete /id 2 +eventdelete /i 3 +eventdelete /i 2 +eventdelete /i 1 +eventdelete /i 0 +exit \ No newline at end of file diff --git a/text-ui-test/logs/eventCommandLogs.log.1 b/text-ui-test/logs/eventCommandLogs.log.1 new file mode 100644 index 0000000000..3a57d3628e --- /dev/null +++ b/text-ui-test/logs/eventCommandLogs.log.1 @@ -0,0 +1,2 @@ +Nov 14, 2023 2:39:47 AM seedu.commands.events.EventDeleteCommand <init> +INFO: EventDelete Command created.