diff --git a/README.md b/README.md index 13f5c77403f..90f0518473d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2122S2-CS2103T-W11-1/tp//workflows/Java%20CI/badge.svg)](https://github.com/AY2122S2-CS2103T-W11-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2122S2-CS2103T-W11-1/tp/branch/master/graph/badge.svg?token=EQL5RQUWFN)](https://codecov.io/gh/AY2122S2-CS2103T-W11-1/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. + +### NUSocials + +* The project simulates an ongoing software project for a desktop application (called _NUSocials_) used for managing contact details. * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +* Example usages: + * Keep track of how and where you met each contact + * Search for contacts + * Plan and view events involving your contacts +* It is named `NUSocials` because we envision an application that helps NUS students maintain a professional contact list to help them stay socially connected. +* For the detailed documentation of this project, see the **[NUSocials Product Website](https://ay2122s2-cs2103t-w11-1.github.io/tp/)**. + +This project is based on the AddressBook-Level3 project, created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index be2d2905dde..4a16751545c 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'NUSocials.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..22292102d61 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,50 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` - ## Project team -### John Doe - - - -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] - -* Role: Project Advisor - -### Jane Doe +### Frederick Tang - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/fredtwt)][[portfolio](team/fredtwt.md)] -* Role: Team Lead -* Responsibilities: UI +* Roles: Team Lead / Developer / Scheduling and tracking +* Responsibilities: + * Oversee the entire project + * Storage, Model, Logic Components -### Johnny Doe +### Ong Kim Lai - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/ongkimlai)][[portfolio](team/ongkimlai.md)] -* Role: Developer -* Responsibilities: Data +* Roles: Deliverables and deadlines / Developer / Documentation +* Responsibilities: + * Handle project deliverables and plan deadlines + * UI, Logic Components -### Jean Doe +### Vikrant - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/viki0526)] [[portfolio](team/viki0526.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +* Roles: Testing / Developer +* Responsibilities: + * Testing of project to ensure everything is proper + * Logic Component -### James Doe +### Manusha - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/manu2002g)] +[[portfolio](team/manu2002g.md)] -* Role: Developer -* Responsibilities: UI +* Roles: Developer +* Responsibilities: + * Ensures project documentation done properly + * Ensures code adheres to coding standards + * Logic Component diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..50695dfbd9e 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -9,7 +9,9 @@ title: Developer Guide ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +1. Referred to [CS2103T textbook](https://nus-cs2103-ay2122s2.github.io/website/se-book-adapted/index.html) for fundamental knowledge on software development. +2. This product is based on the AddressBook-Level3 project created by [SE-EDU initiative](https://se-education.org/) +3. Libraries used: [JavaFx](https://openjfx.io/), [JUnit5](https://github.com/junit-team/junit5), [Jackson](https://github.com/FasterXML/jackson) -------------------------------------------------------------------------------------------------------------------- @@ -19,6 +21,8 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- +
+ ## **Design**
@@ -36,7 +40,7 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called [`Main`](https://github.com/AY2122S2-CS2103T-W11-1/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2122S2-CS2103T-W11-1/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -54,22 +58,26 @@ The rest of the App consists of four components. The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. - + Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. * implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +
+ For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. - + The sections below give more details of each component. +
+ ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2122S2-CS2103T-W11-1/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) @@ -82,11 +90,13 @@ The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* depends on some classes in the `Model` component, as it displays `Person` and `Event` objects residing in the `Model`. + +
### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2122S2-CS2103T-W11-1/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -98,6 +108,8 @@ How the `Logic` component works: 1. The command can communicate with the `Model` when it is executed (e.g. to add a person). 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +
+ The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) @@ -105,6 +117,8 @@ The Sequence Diagram below illustrates the interactions within the `Logic` compo
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+ Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: @@ -113,29 +127,34 @@ How the parsing works: * When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. * All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +
+ ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2122S2-CS2103T-W11-1/tp/blob/master/src/main/java/seedu/address/model/Model.java) The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). +* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object) and all `Event` objects (whihc are contained in a `UniqueEventList` object). * stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the currently 'selected' `Event` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - +
:information_source: **Note:** The alternative (arguably, a more OOP) models are given below for `Person` and `Event` separately. The `Person` model has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+ +
+
+ ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2122S2-CS2103T-W11-1/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) @@ -150,10 +169,272 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa -------------------------------------------------------------------------------------------------------------------- +
+ ## **Implementation** This section describes some noteworthy details on how certain features are implemented. +### Delete multiple persons enhancement + +#### Original Implementation +Originally, the idea was to simply call `deletePerson` on each integer, but this will not work as the indexes of each person +in the contact list might change depending on the order of deletion.
+ +**For example:**
+In a list with only 3 contacts, `delete 1 2 3` will not be allowed as there is no longer an index 3 during the 3rd deletion. + +#### Current Implementation + +The delete command now has to accept multiple indexes as a valid input. The ParserUtil class can easily facilitate this +behaviour by extending the validity checks on the entire string of input. + +The workaround is then to delete each person from the largest to the smallest index. The success message displays the details +of those deleted, so in order to show them in the same order as the input, all the details are first extracted out before deletion.
+ +**For example:**
+Similarly, in a list with only 3 contacts, `delete 1 2 3` will now be allowed. + +**Step 1.** User enters `delete 1 2 3` and `LogicManager` would execute it. + +**Step 2.** `LogicManager` would pass the arguments to `AddressBookParser` to parse the command as a `DeleteCommand`. + +**Step 3.** The arguments `1 2 3` would be passed into `DeleteCommandParser` to detect if the deletion is for multiple persons using `ParserUtil`. + +**Step 4.** Information about Person 1, Person 2 and Person 3 will be extracted according to the last shown list.
+ +**Step 5.** The deletion process starts sequentially. Person 3 gets deleted followed by Person 2, then Person 1. This ensures correctness in the deletion process. + + +The Sequence Diagram below illustrates the interactions within the Logic component for the `execute("delete 1 2 3")` API call. + + + + +
+ +### Tag feature + +#### Current Implementation +The current tagging feature is originally a functionality in the `Add` command. However, it was extracted out and made +into its own command in order to help facilitate a clearer distinction between those features. The `Tag` command would +allow users to tag additional information to existing persons in the address book. + +With this new introduction of the Tag Command, it led to the creation of the abstract class `Tag`. The idea was to only +allow subclasses of `Tag` to be tagged to a person. Currently, there are 4 of such subclasses that extends from `Tag`: +* `Cca` - stores information about the person's cca +* `Education` - stores information about the person's degree course +* `Module` - stores information about the person's module +* `Internship` - stores information about the person's internship + +![Class diagram for Tag](images/TagClassDiagram.png) + +Below is an example usage scenario and how the tagging mechanism behaves at each step: + +**Step 1.** The user enters the following valid `TagCommand`: `tag 1 edu/computer science m/cs2030s m/cs2040s` and `LogicManager` +would execute it. + +**Step 2.** `LogicManger` would pass the argument to `AddressBookParser` to parse the command and identify it as a `TagCommand`. +It will then pass the arguments to `TagCommandParser` to handle the parsing for the identified `TagCommand`. + +**Step 3.** `TagCommandParser` would first parse the index using `ParserUtil#parseIndex()` to identify the person to tag to. +Afterwards, `TagCommandParser` would parse the tag arguments provided using `ParserUtil#parseTags()` to identify the individual +tag types for the arguments provided. + +**Step 4.** After parsing the arguments, the control is handed over to `TagCommand` where it will return a `TagCommand` object. It +will eventually return to `LogicManager` which will call `TagCommand#execute()` to execute the command. + +**Step 5.** Upon execution, the person will be fetched and tagged using `Model#setPerson`. The edited person would then be updated +and stored in the addressbook. `CommandResult` would then generate a success message to inform the user the person has been tagged +successfully. + +The following sequence diagram shows how the tag operation works: + + + +
+ +### Remove Tag feature + +#### Current implementation +The `removetag` command creates and copies the target `Person` into a new `Person` object, except all tags are stored in hashsets instead.
+Hashsets allow the application to perform fast searches and checks, such as checking if all provided tags are existing tags in the target `Person` and to utilize the `removeAll()` function. +`removetag` will not allow user to remove a non-existent tag. + +Given below is an example scenario of how the `removetag` command works. + +**Step 1:** User inputs the following valid `removetag` command: `removetag 2 m/cs2107 m/cs2100`. + +**Step 2:** The second person on the contact list happens to be David, and he has 4 tags. Below is an object diagram of David during the command execution, before any removal of tags. + + + +**Step 3:** After checking that David indeed has the module tags `cs2107` and `cs2100` given by the command input, the command will execute the removal of tags. + +**Step 4:** The removal of tags is successful, and a success message will be generated. + + + +
+ +The following sequence diagrams shows how the `removetag` operation works:
+ + + + +
+ +### Event feature + +#### Current Implementation +The event command allows the user to create an event which will be stored in the address book. Each `Event` has the following +fields: +* `EventName` - the name of the event +* `Information`- additional details about the event +* `Participants` - the persons participating in this event +* `DateTime` - the date and time of the event + +For each `Event`, the user is able to indicate which of the following persons will be tagged to the event being created. Hence, +`Delete` and `Edit` have dependencies on `Event` where changes in the participants' names would have to be changed and reflected +accordingly for the respective events that are affected. + +Below is an example usage scenario and how the event mechanism behaves at each step: + +**Step 1.** The user enters the valid `EventCommand` : `event 1 name/Lunch Appt info/Having lunch at Hai Di Lao d/2023-02-20 t/12:15` and +`LogicManager` would execute it. + +**Step 2.** `LogicManger` would pass the argument to `AddressBookParser` to parse the command and identify it as an `EventCommand`. +It will then pass the arguments to `EventCommandParser` to handle the parsing for the identified `EventCommand`. + +**Step 3.** `EventCommandParser` would first parse the index using `ParserUtil#parseIndex()` to identify the person to tag to the event. +Afterwards, `EventCommandParser` would parse the event arguments provided using `ParserUtil#parseEventName()` to identify the event name, +`ParserUtil#parseInfo()` to identify the event details and `ParserUtil#parseDateTime()` to identify the event date and time. + +**Step 4.** After parsing the arguments, the control is handed over to `EventCommand` where it will return an `EventCommand` object. It +will eventually return to `LogicManager` which will call `EventCommand#execute()` to execute the command. + +**Step 5.** Upon execution, the event will be created and added into the `AddressBook` using `Model#addEvent`. +`CommandResult` would then generate a success message to inform the user the event has been added successfully. + +The following sequence diagram shows how the tag operation works: + + + +
+ +### Cancel Event feature + +#### Current Implementation +The cancel event command would allow the user to cancel and remove an event from the address book. The index specified by the user would +lead to the deletion of the corresponding event in the event list, as long as it is a valid index. The user also has the option of specifying +multiple indexes if multiple deletions are required. + +Below is an example usage scenario and how the tagging mechanism behaves at each step: + +**Step 1.** The user enters the valid `CancelEventCommand` : `cancelevent 1 2` and `LogicManager` would execute it. + +**Step 2.** `LogicManger` would pass the argument to `AddressBookParser` to parse the command and identify it as an `CancelEventCommand`. +It will then pass the arguments to `CancelEventCommandParser` to handle the parsing for the identified `CancelEventCommand`. + +**Step 3.** `CancelEventCommandParser` would first parse the string of indexes using `ParserUtil#parseIndexes()` to identify the events +and ensure the indexes are unique and positive integers. + +**Step 4.** After parsing the arguments, the control is handed over to `CancelEventCommand` where it will return a `CancelEventCommand` object. It +will eventually return to `LogicManager` which will call `CancelEventCommand#execute()` to execute the command. + +**Step 5.** Upon execution, the information of the events will be extracted using `CancelEventCommand#extractDeletedInfo()` that will be use as output for the +notifying the user later. Afterwards, the events will be deleted from the `AddressBook` using `Model#deleteEvent` within `CancelEventCommand#deleteFromList`. +Finally, `CommandResult` would then generate a success message to inform the user the event has been added successfully. + +The following sequence diagram shows how the tag operation works: +![Cancel Event Sequence Diagram](images/CancelEventSequenceDiagram0.png) +![Cancel Event Sequence Diagram](images/CancelEventSequenceDiagram1.png) + +
+ +### Edit Feature + +#### Original Implementation +The `edit` command uses an `EditPersonDescriptor` to store the new information that is to be changed in the person. The +`EditCommandParser` parses the information input and then creates an `EditPersonDescriptor` where the unchanged fields +are copied over from the existing person and the fields to be overwritten are changed. The Find command then takes in +the descriptor and simply changes the persons attribute values to the values stated in the descriptor. + +#### Current Implementation +We initially built the `edit` command to allow overwriting of the tag lists. However later we removed that functionality so the command +can only be used to edit the main fields. This is because we realised that it is easier to use the `tag` and `removetag` commands to +edit the tag lists instead. + +Below is an example usage scenario and how the edit mechanism behaves at each step: + +**Step 1.** The user enters a valid `EditCommand` : `edit 1 n/Alex Ho p/87497763` and `LogicManager` would execute it. + +**Step 2.** `LogicManager` would pass the argument to `AddressBookParser` to parse the command and identify it as a `EditCommand`. +It will then pass the arguments to the `EditCommandParser` to handle the parsing for the identified `EditCommand`. + +**Step 3.** `EditCommandParser` would first parse the index using `ParserUtil#parseIndex()` to identify the person to edit. +Afterwards, it would separately parse the arguments according to prefixes using `ArgumentTokenizer#Tokerize()`. +In this case it would use the parsing functions `ParserUtil#parseName()` and `ParserUtil#parsePhone()` to parse the name and phone number into +`Name` and `Phone` objects. + +**Step 4.** These parsed fields are then stored in a `EditPersonDescriptor` object, which is then passed into `FindCommand`. +Control is handed over to `FindCommand` which will eventually return to `LogicManager`, which will call `FindCommand#Execute()` to +execute the command. + +**Step 5.** Upon execution, the person will be fetched and edited using `Model#setPerson`. The edited person would then be updated +and stored in the addressbook. `CommandResult` would then generate a success message to inform the user the person has been edited +successfully. + +The following sequence diagram shows how the edit operation works: +![EditCommand Sequence Diagram](images/EditSequenceDiagram0.png) + +
+ +### Find feature + +#### Original Implementation +The existing Find feature in AB3 only allowed contacts to be searched for by name. We added additional functionalities +to allow for greater flexibility when filtering large contacts lists according to specific predicates. The `Find` and +`Find -s` command now allow the user to search for specific contact details (name, phone number, email and address) or +specific tags. + +#### Current Implementation +The `Find` command searches for contacts that satisfy any of the given predicates while the `Find -s` command searches +for contacts that satisfy all the given predicates. Do note that the conjunction and disjunction also applies within +each tag field (see User Guide for more details). + +Below is an example scenario of how the finding mechanism behaves at each step: +**Step 1.** The user enters the valid `FindCommand` : `find n/Alex Yeoh edu/computer science` and `LogicManager` would execute it. + +**Step 2.** `LogicManger` would pass the argument to `AddressBookParser` to parse the command and identify it as an `FindCommand`. +It will then pass the arguments to `FindCommandParser` to handle the parsing for the identified `FindCommand`. + +**Step 3.** `FindCommandParser` would separately parse the arguments according to prefixes. So in this case, it would parse the name +as 'Alex Yeoh' and the education tag as 'computer science'. These would be identified as the fields being searched for. The parsing functions +are in ParserUtil (in this case, `ParserUtil#parseNames()` and `ParserUtil#parseTagsForFind()`) + +**Step 4.** After parsing the arguments, a FindOrPredicateParser object containing these arguments (in the form of a `FindPersonDescriptor` object) +is created. FindOrPredicateParser converts the fields searched for into classes that extends `Predicate` (such as +`NameConatainsKeywordPredicateOr` for the name field). It then does the logical 'OR' operation on all the predicates + +**Step 5.** Control is then handed over to FindCommand which takes the predicate as a constructor argument. `FindCommand#execute()` +then calls `ModelManager#updateFilteredPersonList()` with the predicate as the argument. This changes the filteredList that is +rendered by the UI to only show the contacts for which the predicate evaluates to True. Finally, a CommandResult object with a success +message is returned. + +The following class diagram shows important classes for the `find` command and their relationships. Note that there are similar classes +for `find -s` and `find -e` + +![Find Class Diagram](images/FindClassDiagram.png) + +The find command uses several `Predicate` classes in its implementation of the feature. The partial class diagram below describes +the relationship between these classes. The predicates for Email and Address follow the same pattern as the ones for Name and Phone. +The predicates for Internship and Education follow the same pattern as those for Cca and Module. They were left out to simplify the diagram. + +![Class diagram for FindPredicates](images/FindPredicatesClassDiagram.png) + +
+ ### \[Proposed\] Undo/redo feature #### Proposed Implementation @@ -168,15 +449,15 @@ These operations are exposed in the `Model` interface as `Model#commitAddressBoo Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +**Step 1.** The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +**Step 2.** The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +**Step 3.** The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. ![UndoRedoState2](images/UndoRedoState2.png) @@ -184,7 +465,7 @@ Step 3. The user executes `add n/David …​` to add a new person. The `add` co
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +**Step 4.** The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -207,11 +488,11 @@ The `redo` command does the opposite — it calls `Model#redoAddressBook()`,
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +**Step 5.** The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +**Step 6.** The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. ![UndoRedoState5](images/UndoRedoState5.png) @@ -224,20 +505,16 @@ The following activity diagram summarizes what happens when a user executes a ne **Aspect: How undo & redo executes:** * **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. + * Pros: Easy to implement. + * Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. + * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). + * Cons: We must ensure that the implementation of each individual command are correct. _{more aspects and alternatives to be added}_ -### \[Proposed\] Data archiving - -_{Explain here how the data archiving feature will be implemented}_ - -------------------------------------------------------------------------------------------------------------------- @@ -257,71 +534,331 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts +* university students who like to maintain a professional contact list * prefer desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: to facilitate a convenient way for university students to manage their professional networks with fellow acquaintances ### User stories -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +Priorities: +* `* * *` - High (must have) +* `* *` - Medium (nice to have) +* `*` - Low (unlikely to have) + +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|----------------------------------------------------------|----------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | +| `* * *` | user | view all of my contacts | | +| `* * *` | user | add a new contact | +| `* * *` | user | delete a contact | remove entries that I no longer need | +| `* * *` | user | delete multiple contacts at once | delete unwanted entries faster | +| `* * *` | user | edit an existing contact | update the information when needed | +| `* * *` | user | tag additional information to an existing contact | keep a memo of such details for future references | +| `* * ` | user | add an event and tag relevant persons in my contact list | keep a memo of such upcoming events with my contacts for future references | +| `* * ` | user | view all of my upcoming events | | +| `* * ` | user | view all of my past events | | +| `* * ` | user | view all of my past and upcoming events | | +| `* * ` | user | cancel an event | | +| `* * ` | user | cancel multiple events at once | cancel unnecessary events faster | +| `* * ` | user | find a person by name | locate details of persons without having to go through the entire list | +| `* * ` | user | find a person by phone number | locate details of persons without having to go through the entire list | +| `* * ` | user | find a person by email address | locate details of persons without having to go through the entire list | +| `* * ` | user | find a person by module | locate details of persons with identical modules, without having to go through the entire list | +| `* * ` | user | find a person by internship | locate details of persons with identical internship, without having to go through the entire list | +| `* * ` | user | find a person by cca | locate details of persons with identical cca, without having to go through the entire list | +| `* * ` | user | find a person by education | locate details of persons with identical education, without having to go through the entire list | +| `* *` | user with too many irrelevant persons in the contact list | delete all my contacts | reset my contact list | +| `*` | user that tagged a lot of information to the contacts | remove a specific tag of a contact | avoid going through the trouble of re-tagging all the information again | +| `*` | user with many persons in the contact list | sort persons by name in alphabetical order | locate a person easily | + +
-| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +### Use cases -*{More to be added}* +(For all use cases below, the **System** is the `NUSocials` and the **Actor** is the `user`, unless specified otherwise) -### Use cases +### Use case 1: Delete a person + +**MSS** +```` +1. User requests to list persons. +2. NUSocials shows a list of persons. +3. User requests to delete a specific person in the list. +4. NUSocials deletes the person from its addressbook. + + Use case ends. +```` +**Extensions** +```` +2a. The list is empty. + + Use case ends. + +3a. The given index is invalid. + + - 3a1. NUSocials shows an error message. + + Use case resumes at step 2. +```` + +### Use case 2: Add a person + +**MSS** +```` +1. User adds a new person in the given command format (see User Guide). +2. NUSocials adds the new person to the addressbook. + + Use case ends. +```` +**Extensions** +```` +2a. The given add command is invalid. + + - 2a1. NUSocials shows an error message. + + Use case resumes at step 1. +```` + +### Use case 3: Tag a person + +**MSS** +```` +1. User requests to list all persons. +2. NUSocials shows a list of all persons from its addressbook. +3. User inputs command to tag a specific person in the list. +4. NUSocials tags the person. + + Use case ends. +```` +**Extensions** +```` +2a. The list is empty. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) + Use case ends -**Use case: Delete a person** +3a. The given index is invalid. + + - 3a1. NUSocials shows an error message. + + Use case resumes at step 2. + +3b. The given tag command is invalid. + + - 3b1. NUSocials shows an error message. + + Use case resumes at step 2. +```` +### Use case 4: Edit a person **MSS** +```` +1. User requests to list all persons. +2. NUSocials shows a list of all persons from its addressbook. +3. Users requests to overwrite certain fields of the person with updated information. +4. NUSocials edits the person. + + Use case ends. +```` +**Extensions** +```` +2a. The list is empty. + + Use case ends. + +3a. The given index is invalid. + + - 3a1. NUSocials shows an error message. + + Use case resumes at step 2. -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +3b. The given edit command is invalid. + + - 3b1. NUSocials shows an error message. + + Use case resumes at step 2. +```` +### Use case 5: Viewing all persons + +**MSS** +```` +1. User requests to list all persons. +2. NUSocials displays all persons in a list from its addressbook. Use case ends. +```` + +### Use case 6: Viewing events + +**MSS** +```` +1. User requests to list all events. +2. NUSocials displays all events in a list from its addressbook. + Use case ends. +```` **Extensions** +```` +1a. User specifies past or upcoming events. + + Use case resumes at step 2. + +2a. NUSocials displays past or upcoming events instead. + + Use case ends. + +2b. The given find command is invalid. + + - 2b1. NUSocials shows an error message. + + Use case resumes at step 1. +```` -* 2a. The list is empty. +### Use case 7: Finding a person (any field) + +**MSS** +```` +1. User requests to find any person using specific fields. +2. NUSocials shows a list of persons matching any fields from its addressbook. + + Use case ends. +```` +**Extensions** +```` +2a. The given find command is invalid. + + - 2a1. NUSocials shows an error message. + + Use case resumes at step 1. +```` +### Use case 8: Finding a person (all fields) + +```` +Similar to Use case 7, except now: +The user wants to find a person that has every field instead. +```` + +### Use case 9: Removing specific tags + +**MSS** +```` +1. User requests to list all persons. +2. NUSocials shows a list of all persons from its addressbook. +3. User requests to remove certain tags from the person. +4. NUSocials removes the specific tags from the person. + + Use case ends. +```` +**Extensions** +```` +2a. The list is empty. Use case ends. -* 3a. The given index is invalid. +3a. The given index is invalid. - * 3a1. AddressBook shows an error message. + - 3a1. NUSocials shows an error message. Use case resumes at step 2. -*{More to be added}* +3b. The given removetag command is invalid. -### Non-Functional Requirements + - 3b1. NUSocials shows an error message. -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. 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. + Use case resumes at step 2. -*{More to be added}* +3c. The request contains non-existent tags to be removed. + + - 3c1. NUSocials shows an error message. + + Use case resumes at step 2. +```` + +### Use case 10: Delete multiple persons + +**MSS** +```` +Similar to Use case 1, except now: +The user wants to delete multiple persons instead. +```` +**Extensions** +```` +2a. The list is empty. + + Use case ends. + +3a. One or more of given indexes are invalid. + + - 3a1. NUSocials shows an error message. + + Use case resumes at step 2. +```` + +### Use case 11: Adding an event + +**MSS** +```` +1. User adds a new event in the given command format (see User Guide). +2. NUSocials adds the new event to the addressbook. + + Use case ends. +```` +**Extensions** +```` +2a. The given event command is invalid. + + - 2a1. NUSocials shows an error message. + + Use case resumes at step 1. +```` + +### Use case 12: Cancelling events + +**MSS** +```` +Similar to Use case 10, except now: +The user wants to delete event(s) instead. +```` +**Extensions** +```` +2a. The list is empty. + + Use case ends. + +3a. One or more of given indexes are invalid. + + - 3a1. NUSocials shows an error message. + + Use case resumes at step 2. +```` + +### Non-Functional Requirements + +1. Should work on _mainstream OS_ as long as it has Java `11` or above installed. +2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +3. 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. +4. The system should work on both 32-bit and 64-bit environments. +5. The system should respond within 3 seconds. +6. The product is strictly an offline application and data is stored locally. +7. The product is not required to handle the feature of finding past events based on the date and time. +(i.e using a past date & time for the "find -e" command would be invalid) +8. The product is not required to handle multiple whitespaces in between words for all data field inputs. +(i.e "Alison Baker" will not be identified the same as "Alison    Becker") +9. The product is not required to handle the multiple events occurring at the same time. +(i.e Multiple events sharing the same date and time would be recognized as separate unique events respectively) +10. The product is not required to handle the visibility of long addresses in the person's contact card. +11. The system should be usable by a novice who has used CLI commands before. ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +* **Mainstream OS**: Windows, macOS +* **CLI**: Command Line Interface + +
-------------------------------------------------------------------------------------------------------------------- @@ -338,40 +875,179 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Double-click the jar file
+ Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. -1. Saving window preferences +2. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + 1. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location are retained. -1. _{ more test cases …​ }_ +3. Subsequent launch + 1. Make some changes to NUSocials and close the application. -### Deleting a person + 1. Reopen the application by double-clicking the jar file
+ Expected: Shows the GUI and loads contacts from NUSocials. Should reflect the changes made previously. + +### Deleting person 1. Deleting a person while all persons are being shown + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 1. Test case: `delete 1`
+ Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. + + 1. Test case: `delete 0`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + + 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +1. Deleting multiple persons while all persons are being shown + + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 1. Test case: `delete 1 2 3`
+ Expected: First, second and third contacts are deleted from the list. Details of the deleted contacts shown in the status message. + + 1. Test case: `delete 1 0 2 3` , `delete 0 1 2 3`
+ Expected: No persons are deleted. Error details shown in the status message. Status bar remains the same. + + 1. Other incorrect delete commands to try: `delete 1 x 2 3`, `delete 1 2 3 x` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Adding an event + +1. Adding an event + + 1. Test case: `event name/lunch info/at HDL d/2023-11-10 t/12:12`
+ Expected: The new event is added into the event list and displayed on the interface. Details of the added event shown in the status message. + + 2. Test case: `event name/ info/at HDL d/2023-11-10 t/12:12`
+ Expected: No new event is added into the event list. Error details shown in the status message. Status bar remains the same + + 3. Other incorrect event commands to try: `event name/lunch info/ d/2023-11-10 t/12:12`, `event name/lunch info/at HDL d/2019-11-10 t/12:12`, `event name/lunch info/at HDL d/2023-11-10 t/28:12`
+ Expected: Similar to previous. + +### Cancelling event + +1. Cancelling an event while all events are being shown + + 1. Prerequisites: List all events using the `showevents` command. Multiple events in the list. + If event list is empty, add new events to the list first. (At least 1 event is added) + + 2. Test case: `cancelevent 1`
+ Expected: First event is deleted from the list. Details of the deleted event shown in the status message. + + 3. Test case: `cancelevent 0`
+ Expected: No event is deleted. Error details shown in the status message. Status bar remains the same. + + 4. Other incorrect cancel event commands to try: `cancelevent`, `cancelevent x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +1. Cancelling multiple events while all events are being shown + + 1. Prerequisites: List all events using the `showevents` command. Multiple events in the list. + If event list is empty, add new events to the list first. (At least 2 events are added) + + 3. Test case: `cancelevent 1 2`
+ Expected: First and second events are deleted from the list. Details of the deleted events shown in the status message. + + 4. Test case: `cancelevent 1 0 2` , `cancelevent 0 1 2`
+ Expected: No events are deleted. Error details shown in the status message. Status bar remains the same. + + 5. Other incorrect cancel event commands to try: `cancelevent 1 1 2`, `cancelevent 1 2 x` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Tagging information to an existing person + +1. Tagging cca information to a person + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + If person list is empty, add new persons to the list first. (At least 1 person is added) - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 2. Test case: `tag 1 c/bouldering` + Expected: First person is tagged with the cca information and the information is displayed on the first person's contact card. + Details of the updated tag information of the person is shown in the status message. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 3. Test case: `tag 1 c/$$` + Expected: First person is not tagged with the cca information. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 4. Other incorrect tag commands to try: `tag 1 c/`, `tag 1 c/ ` Expected: Similar to previous. -1. _{ more test cases …​ }_ +2. Tagging education information to a person + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + If person list is empty, add new persons to the list first. (At least 1 person is added) + + 2. Test case: `tag 1 edu/computer science` + Expected: First person is tagged with the education information and the information is displayed on the first person's contact card. + Details of the updated tag information of the person is shown in the status message. + + 3. Test case: `tag 1 edu/$$` + Expected: First person is not tagged with the education information. Error details shown in the status message. Status bar remains the same. + + 4. Other incorrect tag commands to try: `tag 1 edu/`, `tag 1 edu/ ` + Expected: Similar to previous. + +3. Tagging internship information to a person + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + If person list is empty, add new persons to the list first. (At least 1 person is added) + + 2. Test case: `tag 1 i/shopee` + Expected: First person is tagged with the internship information and the information is displayed on the first person's contact card. + Details of the updated tag information of the person is shown in the status message. + + 3. Test case: `tag 1 i/$$` + Expected: First person is not tagged with the internship information. Error details shown in the status message. Status bar remains the same. + 4. Other incorrect tag commands to try: `tag 1 i/`, `tag 1 i/ ` + Expected: Similar to previous. + +4. Tagging module information to a person + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + If person list is empty, add new persons to the list first. (At least 1 person is added) + + 2. Test case: `tag 1 m/cs2040s` + Expected: First person is tagged with the module information and the information is displayed on the first person's contact card. + Details of the updated tag information of the person is shown in the status message. + + 3. Test case: `tag 1 m/$$` + Expected: First person is not tagged with the module information. Error details shown in the status message. Status bar remains the same. + + 4. Other incorrect tag commands to try: `tag 1 m/`, `tag 1 m/ ` + Expected: Similar to previous. + +### Removing tags from an existing person + +1. Removing tags + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + Person selected by the index has to contain tags that are exact matches to the input. + + 2. Test case: `removetag 1 m/cs2040s` (First person has a module tag "cs2040s") + Expected: First person's "cs2040s" module tag removed. Details of the updated tag information of the person is shown + in the status message. + + 3. Test case: `removetag 1 m/cs3230` (First person does not have a module tag "cs3230") + Expected: No tags removed. Error details shown in the status message. Status bar remains the same. + + 4. Other incorrect `removetag` commands to try: `removetag`, `removetag 1 edu/`, `removetag x edu/` (where x is larger than the list size) + Expected: Similar to previous. + ### Saving data 1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ + 1. To simulate a missing or corrupted file, navigate to the `data` folder and delete `addressbook.json` or modify some data to be invalid (such as changing an event's date to 31st February or making someone's phone number exceed 10 digits). + 1. Open the application, you will be greeted with an empty addressbook. + 1. To recover, simply edit the `addressbook.json` again and reverse the changes made. + 1. **Caution:** Deleting the whole `addressbook.json` fixes the issue as well. However, all previous data will be lost, and a sample addressbook will be loaded. + +2. How to save -1. _{ more test cases …​ }_ + 1. NUSocials saves to any changes right away, there is no need for a manual save. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..5c50ea05387 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,7 +3,8 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +NUSocials is a **desktop app for university students to maintain a professional contact list, where users can keep track of information about friends/acquaintances easily in one single platform. +It is optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). The value of the app is to facilitate a convenient way for university students to manage their professional networks with fellow acquaintances. * Table of Contents {:toc} @@ -14,27 +15,31 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `NUSocials.jar` from [here](https://github.com/AY2122S2-CS2103T-W11-1/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for NUSocials. -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +4. To start the app: + - Windows: Double-click on `NUSocials.jar`. + - MacOS: On terminal, navigate to the directory containing `NUSocials.jar` and run `java -jar NUSocials.jar`. -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+5. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+ ![img.png](images/UiSampleAddressBook.png) + +6. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: - * **`list`** : Lists all contacts. + * **`list`**
Lists all contacts. - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * **`add`**`n/fred p/99998888 e/fred@example.com a/fred street, block 123, #01-01`
Adds a contact named `fred` to NUSocials. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. + * **`tag`** `2 edu/computer science m/CS2040S`
Tags the 2nd contact shown in the current list with a Computer Science degree and CS2040S module. - * **`clear`** : Deletes all contacts. + * **`event`** `1 name/Lunch appointment info/at Hai Di Lao d/2023-05-15 t/13:00`
Creates an event named `Lunch appointment` and associate it with the 1st contact. - * **`exit`** : Exits the app. + * **`find`**`n/fred`
Find any person whose name contains 'fred'. -1. Refer to the [Features](#features) below for details of each command. +7. Refer to the [Features](#features) below for details of each command. -------------------------------------------------------------------------------------------------------------------- @@ -44,107 +49,441 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo **:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +* Words in `UPPER_CASE` are mandatory parameters to be supplied by the user.
+ e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/Kim Lai`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g `n/NAME [edu/EDUCATION]` can be used as `n/Kim Lai edu/computer science` or as `n/Kim Lai`. + +* Items with `…`​ after them can be used zero or more times.
+ e.g. `[m/MODULE]…​` can be used as ` ` (i.e. 0 times), `m/CS2040S`, `m/CS2030S m/CS2100` etc. + +* Extraneous parameters for commands that do not take in parameters (such as `list`, `clear`, `help` and `exit`) will be ignored.
+ e.g. if the command specified is `list 123`, it will be interpreted as `list`. -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* If a parameter is expected only once in the command but you specified it multiple times, they will all be rejected.
+ e.g. if you specify `p/12345678 p/87654321`, both will be rejected. * Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +* If an `INDEX` is used, it **must be a positive integer** (i.e. 1, 2, 3…​). -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +* All commands are case-sensitive. ### Viewing help : `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the help page. -![help message](images/helpMessage.png) +![help message](images/help_message.png) Format: `help` +### Person Commands: +#### Listing all persons: `list` +Shows a list of all persons in NUSocials. + +Format: `list` + +* All existing persons and upcoming events are automatically rendered when the application is launched. +* The different tags are listed as follows: + * Yellow for education + * Teal for modules + * Orange for CCAs + * Red for internships +* Personal details are listed in the following order: + * Phone number + * Address + * Email + +![Sample Person Card.png](images/screenshots/samplePersonCard.png) + +#### Adding a person: `add` +Adds a person to NUSocials and lists all upcoming events. + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS` -### Adding a person: `add` +Constraints: +* Only 1 prefix for each field is allowed. +* `NAME` has to be alphanumeric not blank. +* `PHONE_NUMBER` has to be between 3 and 10 digits long and not blank. +* `EMAIL` has to be in a valid email format and not blank. + * The local part of the `EMAIL` should be alphnumeric and these special characters, excluding the parentheses, (+-._) + * The local part should not start and end with any of the mentioned special characters + * "@" comes right after the local part, followed by a domain name + * The domain name must: + * end with a domain label at least 2 characters long\n" + * have each domain label start and end with alphanumeric characters\n" + * have each domain label consist of alphanumeric characters, separated only by hyphens, if any." +* `ADDRESS` has to not be blank. +* All fields must be used. -Adds a person to the address book. +Example: +* `add n/Alisson Becker p/12345678 e/alisson111@example.com a/VVD street, block 123, #01-01` + Adds a person with the following fields: + - Name: Alisson Becker + - Phone Number: 12345678 + - Email: alisson111@example.com + - Address: VVD street, block 123, #01-01 -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +
-
:bulb: **Tip:** -A person can have any number of tags (including 0) +| Before | After | + :---:|:---: +| ![before command execution.png](images/screenshots/beforeCommand.png) | ![result for 'add n/Alisson Becker p/12345678 e/alisson111@example.com a/VVD street, block 123, #01-01'](images/screenshots/addAlissonBecker.png) | + +
+ +**:x: Invalid Examples:**
+ +* `add n/Kim Lai n/Fred Tang p/12345678 e/kimlai222@example.com a/KL street, block 190, #01-23`
+ +Not allowed as `n/` prefix is used more than once. + +* `add n/Kim Lai p/ e/kimlai222@example.com a/KL street, block 190, #01-23`
+ +Not allowed as `PHONE_NUMBER` is blank.
+ +#### Deleting a person : `delete` + +Deletes the specified person from NUSocials. + +Format: `delete INDEX` + +* Deletes the person at the specified `INDEX`. +* The index refers to the index number shown in the displayed person list. + +Constraints: +* `INDEX` provided has to be on the currently shown contact list. + Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `list` followed by `delete 2` deletes the 2nd person in the currently shown contact list. +* `find n/Betsy` followed by `delete 1` deletes the 1st person from the resulting list of the `find` command. -### Listing all persons : `list` +#### Deleting multiple persons : `delete` -Shows a list of all persons in the address book. +Deletes all the specified persons from NUSocials. -Format: `list` +Format: `delete INDEX…​` + +* Deletes multiple persons at the specified `INDEX` numbers. +* The index refers to the index number shown in the displayed person list. +* Each index **must be separated by a whitespace** and **must be unique**. + +Constraints: +* All `INDEX` numbers provided has to be on the currently shown contact list. + +Example: +* `list` followed by `delete 2 5 7` deletes the 2nd, 5th and 7th person in the currently shown list. + +#### Editing a person : `edit` + +Edits an existing person's details in NUSocials. + +Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]` + +* Edits the person at the specified `INDEX`. +* The index refers to the index number shown in the displayed person list. +* At least one of the optional fields must be provided. + +Constraints: +* Only 1 prefix for each field allowed. +* `NAME` has to be alphanumeric not blank. +* `PHONE_NUMBER` has to be between 3 and 10 digits long and not blank. +* `EMAIL` has to be in a valid email format and not blank. +* `ADDRESS` has to not be blank. +* `INDEX` provided has to be on the currently shown contact list. + +Example: +* `edit 1 p/91234567 e/KL123@example.com`
+ Edits the phone number and email address of the 1st person to `91234567` and `KL123@example.com` respectively. + +
+ +**:exclamation: Caution:** +Existing values will be overwritten and updated to the new input values! +
+ +#### Tagging a person: `tag` +Tags additional information to an existing contact. + +Format: `tag INDEX [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​` + +* Tags the relevant information to the person at the specified `INDEX`. +* The index refers to the index number shown in the displayed person list. +* Alphabets in the input tag values will be converted to lowercase. +* Input tag values will be added to the existing tags in their respective fields. +* If the new input tag values are the same as existing tags, then nothing will be added. + +Constraints: +* At least one of the prefixes must be provided. +* If a prefix is used, the input after must not be blank and must be alphanumeric. +* `INDEX` provided has to be on the currently shown contact list. + +Example: +* `tag 1 i/abc company m/CS2100 m/CS2105`(as shown below)
+ Tags the internship company and 2 modules to the 1st person in the currently shown contact list. + +| Before | After | + :---:|:---: +| ![before command execution.png](images/screenshots/beforeCommand.png) | ![result for 'tag 1 i/abc company m/CS2100 m/CS2105'](images/screenshots/tagInternshipModuleModule.png) | + +
+ +**:x: Invalid Examples:**
+ +* `tag 1`
+ +Not allowed as no prefix provided. + +* `tag 1 i/ m/`
+ +Not allowed as there is no input given after a prefix is used. + +* `tag 0 i/xyz company m/CS2103T`
+ +Not allowed as there `INDEX` 0 does not exist in the contact list. +
+ +#### Removing specific tags from person: `removetag` +Removes the specific tags of an existing contact. + +Format: `removetag INDEX [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​` + +* Removes the tags from the person at the specified `INDEX`. +* The index refers to the index number shown in the displayed person list. + +Constraints: +* At least one of the prefixes must be provided. +* If a prefix is used, the input after must not be blank. +* All inputs for tags provided must be an exact match to existing tags. +* `INDEX` provided has to be on the currently shown contact list. + +Example: +* `removetag 1 i/abc company m/CS2100 m/CS2030S`
+Removes the internship company tag and the 2 modules tags from the 1st person in the currently shown contact list. + +
+ +**:x: Invalid Examples:**
+ +* `removetag 1 i/ m/`
+ +Not allowed as there is no input given after a prefix is used. + +* `removetag 1 edu/computer` while person 1 has an education tag with `computer science`
+ +Not allowed as it is not an exact match. +
-### Editing a person : `edit` +#### Finding persons: `find` -Edits an existing person in the address book. +Finds persons that match any of the given fields and tags. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `find [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​` -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ +* The search is case-insensitive. e.g `hans` will match `Hans` +* The matching is done by character sequence. e.g. `ha` or `ns` will match `Hans` +* Persons matching at least one of the fields or tags will be returned. + +Constraints: * At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* Only 1 prefix for each basic particulars field is allowed. Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `find m/cs2030s m/cs2040s`
+Returns anyone tagged with either `cs2030s` or `cs2040s` or both +* `find n/Hans m/cs2100`
+Returns `Hans` and `Bo Yang` (i.e. Bo Yang is tagged with cs2100) +* `find i/Shopee m/cs2040s m/cs2030s`
+Returns `Alex Yeoh` and `Bernice Yu` (as shown below) + +| Before | After | +:---:|:---: +| ![before command execution.png](images/screenshots/beforeCommand.png) | ![result for 'find i/Shopee m/cs2040s cs2030s'](images/screenshots/findShopeeCS2040sCS2030sResult.png) | + +
+ +**:x: Invalid Examples:**
+ +* `find n/ m/`
+ +Not allowed as there is no input given after a prefix is used. + +* `find n/Hans n/Chewbacca`
+ +Not allowed as the `n/` prefix is used more than once. +
-### Locating persons by name: `find` +#### Finding specific persons: `find -s` -Finds persons whose names contain any of the given keywords. +Finds persons that match all given fields and tags. -Format: `find KEYWORD [MORE_KEYWORDS]` +Format: `find -s [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​` * The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* The matching is done by character sequence. e.g. `ha` or `ns` will match `Hans` +* Only persons matching all fields and tags will be returned. + +Constraints: +* At least one of the optional fields must be provided. +* Only 1 prefix for each basic particulars field is allowed. Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `find -s n/Bo Yang m/cs2040s`
+ Returns `Bo Yang` (i.e. Bo Yang is tagged with cs2040s) -### Deleting a person : `delete` +* `find -s i/Shopee m/cs2040s m/cs2030s`
+ Returns `Alex Yeoh` (as shown below) -Deletes the specified person from the address book. +| Before | After | +:---:|:---: +| ![before command execution.png](images/screenshots/beforeCommand.png) | ![result for 'find -s i/Shopee m/cs2040s cs2030s'](images/screenshots/find-sShopeeCS2040sCS2030s.png) | -Format: `delete INDEX` +
-* Deletes the person at the specified `INDEX`. +**:x: Invalid Examples:**
+ +* `find -s n/ m/`
+ +Not allowed as there is no input given after a prefix is used.
+ +* `find -s n/Hans n/Solo`
+ +Not allowed as the `n/` prefix is used more than once. +
+ +### Event Commands: +#### Showing events: `showevents` +Shows a list of all events in NUSocials. + +Format: `showevents` + +* Events shown are automatically sorted in chronological order. + +
+ +**:information_source: Tip :**
+Use the `-upcoming` or `-past` flags to filter the event list. + +Alternate formats: +1. `showevents -upcoming` +* Shows a list of all upcoming events instead + +2. `showevents -past` +* Shows a list of all past events instead +
+ +#### Adding an event: `event` +Adds an event into NUSocials. + +Format: `event INDEX…​ name/EVENT NAME info/EVENT DETAILS d/DATE t/TIME` + +* Tags the participating persons to the events based on the specified `INDEX…​`. * The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +* Each index **must be separated by a whitespace** and **must be unique**. + +Constraints: +* All fields must be provided. +* Only 1 prefix for each field is allowed. +* `DATE` has to be in the format of `yyyy-MM-dd`. +* `TIME` has to be in the format of `HH:mm`. +* `DATE` and `TIME` has to be valid (i.e Date and Time specified must be after the current date and time) +* `EVENT NAME` has a limit of 100 characters. +* `EVENT DETAILS` has a limit of 300 characters. + +Example: +* `event 2 3 name/Movie marathon info/Harry Potter movies d/2022-08-15 t/14:00`
+Creates the Event and adds into NUSocials. (as shown below) + +| Before | After | +:---:|:---: +| ![before command execution.png](images/screenshots/beforeCommand.png) | ![result for 'event 2 3 name/Movie marathon info/Harry Potter movies d/2022-08-15 t/14:00'](images/screenshots/eventMovieMarathon.png) | + +
+ +**:x: Invalid Examples:**
+ +* `event 1 2 name/ info/At Michael's d/2022-08-22 t/19:00`
+ +Not allowed as there is no input after a prefix is used. + +* `event 1 2 name/Dinner appointment name/Game night info/At Michael's d/2022-08-22 t/19:00`
+ +Not allowed as the `name/` prefix is more than once. +
+ +#### Cancelling an event : `cancelevent` + +Deletes the specified event from NUSocials. + +Format: `cancelevent INDEX` + +* Deletes the event at the specified `INDEX`. +* The index refers to the index number shown in the displayed event list. + +Constraints: +* `INDEX` number provided has to be on the currently shown contact list. + +Examples: +* `cancelevent 2` deletes the 2nd event in the currently shown event list. + +#### Cancelling multiple events: `cancelevent` + +Deletes all the specified events from NUSocials. + +Format: `cancelevent INDEX…​` + +* Deletes multiple events at the specified `INDEX` numbers. +* The index refers to the index number shown in the displayed event list. +* Each index **must be separated by a whitespace** and **must be unique**. + +Constraints: +* All `INDEX` numbers provided has to be on the currently shown contact list. Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +* `cancelevent 2 5 7` deletes the 2nd, 5th and 7th events in the currently shown event list. + +#### Finding an event: `find -e` + +Finds an event that matches any of the given details + +Format: `find -e [name/EVENT NAME]…​ [info/INFORMATION]…​ [part/PARTICPANT]…​ [dt/DATE AND TIME]…​` + +* The search is case-insensitive. e.g `lunch` will match `Lunch` +* The matching is done by character sequence. e.g. `lun` will match `lunch` +* Events matching at least one of the fields will be returned. + +Constraints: +* At least one of the optional fields must be provided. +* Only 1 prefix for each basic particulars field is allowed. +* `DATE` has to be in the format of `yyyy-MM-dd`. +* `TIME` has to be in the format of `HH:mm`. +* `DATE` and `TIME` has to be valid (i.e Date and Time specified must be after the current date and time) + +Example: +* `find -e name/lunch part/Alex Yeoh` returns all events containing `lunch` in its name and all events involving Alex Yeoh
+ +
+ +**:x: Invalid Examples:**
+ +* `find -e name/ info/`
+ +Not allowed as there is no input given after a prefix is used. + +* `find -e name/Dinner name/Lunch`
+ +Not allowed as the `name/` prefix is used more than once. +
### Clearing all entries : `clear` -Clears all entries from the address book. +Clears all entries from NUSocials. Format: `clear` @@ -156,26 +495,26 @@ Format: `exit` ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +NUSocials data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. ### Editing the data file -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +NUSocials data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. -
+
-### Archiving data files `[coming in v2.0]` +**:exclamation: Caution:** +If your changes to the data file makes its format invalid, NUSocials will discard all data and start with an empty data file at the next run. +
-_Details coming soon ..._ +
-------------------------------------------------------------------------------------------------------------------- ## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous NUSocials home folder. -------------------------------------------------------------------------------------------------------------------- @@ -183,10 +522,17 @@ _Details coming soon ..._ Action | Format, Examples --------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` +**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS`
e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665` +**Tag** | `tag INDEX [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​`
e.g. `tag 1 m/CS2105 m/CS2106` +**Remove Tag** | `removetag INDEX [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​`
e.g. `removetag 1 c/Bouldering m/CS2105 m/CS2106` +**Event** | `event INDEX…​ name/EVENT NAME info/INFORMATION d/DATE t/TIME`
e.g. `event 1 name/Dinner Date info/Having Dinner at Bread Street Kitchen by Gordon Ramsay d/2022-12-20 t/20:15` +**Cancel Event** | `cancelevent INDEX…​`
e.g. `cancelevent 1 2 3` **Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` +**Delete** | `delete INDEX`
e.g. `delete 3`
`delete INDEX…​`
e.g. `delete 1 3 5` +**Edit** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]`
e.g. `edit 2 n/Fred e/fred111@example.com` +**Find** | `find [n/NAME]…​ [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​`
e.g. `find n/john edu/computer science` +**Find specific match** | `find -s [n/NAME]…​ [i/INTERNSHIP]…​ [m/MODULE]…​ [c/CCA]…​ [edu/EDUCATION]…​`
e.g. `find -s n/john i/bytedance edu/computer science` +**Find Event** | `find -e [name/EVENT NAME]…​ [info/INFORMATION]…​ [part/PARTICIPANT]…​ [dt/DATE AND TIME]…​`
e.g. `find -e name/Dinner info/Candice's birthday dt/2022-05-12 19:30` **List** | `list` +**Show Events** | `showevents` `showevents -upcoming` `showevents -past` **Help** | `help` diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..35d32960627 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "NUSocials" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S2-CS2103T-W11-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..2893c4479ee 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "NUSocials"; font-size: 32px; } } diff --git a/docs/diagrams/CancelEventSequenceDiagram.puml b/docs/diagrams/CancelEventSequenceDiagram.puml new file mode 100644 index 00000000000..f36f6d2abaf --- /dev/null +++ b/docs/diagrams/CancelEventSequenceDiagram.puml @@ -0,0 +1,86 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":CancelEventCommandParser" as CancelEventCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "c:CancelEventCommand" as CancelEventCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("cancelevent 1 2 ") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("cancelevent 1 2") +activate AddressBookParser + +create CancelEventCommandParser +AddressBookParser -> CancelEventCommandParser +activate CancelEventCommandParser + +CancelEventCommandParser --> AddressBookParser +deactivate CancelEventCommandParser + +AddressBookParser -> CancelEventCommandParser : parse("1 2") +activate CancelEventCommandParser + +CancelEventCommandParser -> ParserUtil : parseIndexes("1 2") +activate ParserUtil +create CancelEventCommand +ParserUtil -> CancelEventCommand +activate CancelEventCommand + +CancelEventCommand --> ParserUtil : c +deactivate CancelEventCommand +ParserUtil --> CancelEventCommandParser : c +deactivate ParserUtil + +CancelEventCommandParser --> AddressBookParser : c +deactivate CancelEventCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CancelEventCommandParser -[hidden]-> AddressBookParser +destroy CancelEventCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> CancelEventCommand : execute() +activate CancelEventCommand +CancelEventCommand -> CancelEventCommand : extractDeletedInfo() +activate CancelEventCommand +CancelEventCommand --> CancelEventCommand +deactivate CancelEventCommand +CancelEventCommand -> CancelEventCommand : deleteFromList() +activate CancelEventCommand +CancelEventCommand -> Model : deleteEvent(Event2) +activate Model +Model --> CancelEventCommand +deactivate Model + +CancelEventCommand -> Model : deleteEvent(Event1) +activate Model +Model --> CancelEventCommand +deactivate Model + +CancelEventCommand --> CancelEventCommand +deactivate CancelEventCommand + +create CommandResult +CancelEventCommand -> CommandResult +activate CommandResult + +CommandResult --> CancelEventCommand +deactivate CommandResult + +CancelEventCommand --> LogicManager : result +deactivate CancelEventCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/CancelEventSequenceDiagram0.puml b/docs/diagrams/CancelEventSequenceDiagram0.puml new file mode 100644 index 00000000000..18251cbb740 --- /dev/null +++ b/docs/diagrams/CancelEventSequenceDiagram0.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":CancelEventCommandParser" as CancelEventCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "c:CancelEventCommand" as CancelEventCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("cancelevent 1 2 ") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("cancelevent 1 2") +activate AddressBookParser + +create CancelEventCommandParser +AddressBookParser -> CancelEventCommandParser +activate CancelEventCommandParser + +CancelEventCommandParser --> AddressBookParser +deactivate CancelEventCommandParser + +AddressBookParser -> CancelEventCommandParser : parse("1 2") +activate CancelEventCommandParser + +CancelEventCommandParser -> ParserUtil : parseIndexes("1 2") +activate ParserUtil +create CancelEventCommand +ParserUtil -> CancelEventCommand +activate CancelEventCommand + +CancelEventCommand --> ParserUtil : c +deactivate CancelEventCommand +ParserUtil --> CancelEventCommandParser : c +deactivate ParserUtil + +CancelEventCommandParser --> AddressBookParser : c +deactivate CancelEventCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CancelEventCommandParser -[hidden]-> AddressBookParser +destroy CancelEventCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> CancelEventCommand : execute() +activate CancelEventCommand + +ref over CancelEventCommand, Model +Cancelling the event +end ref + +CancelEventCommand --> LogicManager : result +deactivate CancelEventCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/CancelEventSequenceDiagram1.puml b/docs/diagrams/CancelEventSequenceDiagram1.puml new file mode 100644 index 00000000000..aeccd7d72f6 --- /dev/null +++ b/docs/diagrams/CancelEventSequenceDiagram1.puml @@ -0,0 +1,42 @@ +@startuml +!include style.puml + +mainframe **sd** Cancelling the event + +box Logic LOGIC_COLOR_T1 +participant "c:CancelEventCommand" as CancelEventCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +activate CancelEventCommand +CancelEventCommand -> CancelEventCommand : extractDeletedInfo() +activate CancelEventCommand +CancelEventCommand --> CancelEventCommand +deactivate CancelEventCommand +CancelEventCommand -> CancelEventCommand : deleteFromList() +activate CancelEventCommand +CancelEventCommand -> Model : deleteEvent(Event2) +activate Model +Model --> CancelEventCommand +deactivate Model + +CancelEventCommand -> Model : deleteEvent(Event1) +activate Model +Model --> CancelEventCommand +deactivate Model + +CancelEventCommand --> CancelEventCommand +deactivate CancelEventCommand + +create CommandResult +CancelEventCommand -> CommandResult +activate CommandResult + +CommandResult --> CancelEventCommand +deactivate CommandResult + +@enduml diff --git a/docs/diagrams/DeleteMultipleSequenceDiagram.puml b/docs/diagrams/DeleteMultipleSequenceDiagram.puml new file mode 100644 index 00000000000..eaed6e1abb0 --- /dev/null +++ b/docs/diagrams/DeleteMultipleSequenceDiagram.puml @@ -0,0 +1,89 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("delete 1 2 3") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete 1 2 3") +activate AddressBookParser + +create DeleteCommandParser +AddressBookParser -> DeleteCommandParser +activate DeleteCommandParser + +DeleteCommandParser --> AddressBookParser +deactivate DeleteCommandParser + +AddressBookParser -> DeleteCommandParser : parse("1 2 3") +activate DeleteCommandParser + +DeleteCommandParser -> ParserUtil : parseIndexes("1 2 3") +activate ParserUtil +create DeleteCommand +ParserUtil -> DeleteCommand +activate DeleteCommand + +DeleteCommand --> ParserUtil : d +deactivate DeleteCommand +ParserUtil --> DeleteCommandParser : d +deactivate ParserUtil + +DeleteCommandParser --> AddressBookParser : d +deactivate DeleteCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteCommandParser -[hidden]-> AddressBookParser +destroy DeleteCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteCommand : execute() +activate DeleteCommand + +DeleteCommand -> DeleteCommand : deleteFromList() +activate DeleteCommand + +DeleteCommand -> Model : deletePerson(Person3) +activate Model +Model --> DeleteCommand +deactivate Model + +DeleteCommand -> Model : deletePerson(Person2) +activate Model +Model --> DeleteCommand +deactivate Model + +DeleteCommand -> Model : deletePerson(Person1) +activate Model +Model --> DeleteCommand +deactivate Model + +DeleteCommand --> DeleteCommand +deactivate DeleteCommand + +create CommandResult +DeleteCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteCommand +deactivate CommandResult + +DeleteCommand --> LogicManager : result +deactivate DeleteCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteMultipleSequenceDiagram0.puml b/docs/diagrams/DeleteMultipleSequenceDiagram0.puml new file mode 100644 index 00000000000..392f3ed1291 --- /dev/null +++ b/docs/diagrams/DeleteMultipleSequenceDiagram0.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("delete 1 2 3") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete 1 2 3") +activate AddressBookParser + +create DeleteCommandParser +AddressBookParser -> DeleteCommandParser +activate DeleteCommandParser + +DeleteCommandParser --> AddressBookParser +deactivate DeleteCommandParser + +AddressBookParser -> DeleteCommandParser : parse("1 2 3") +activate DeleteCommandParser + +DeleteCommandParser -> ParserUtil : parseIndexes("1 2 3") +activate ParserUtil +create DeleteCommand +ParserUtil -> DeleteCommand +activate DeleteCommand + +DeleteCommand --> ParserUtil : d +deactivate DeleteCommand +ParserUtil --> DeleteCommandParser : d +deactivate ParserUtil + +DeleteCommandParser --> AddressBookParser : d +deactivate DeleteCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteCommandParser -[hidden]-> AddressBookParser +destroy DeleteCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteCommand : execute() +activate DeleteCommand + +ref over DeleteCommand, Model +Deletion of multiple contacts +end ref + +DeleteCommand --> LogicManager : result +deactivate DeleteCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/DeleteMultipleSequenceDiagram1.puml b/docs/diagrams/DeleteMultipleSequenceDiagram1.puml new file mode 100644 index 00000000000..de90686336f --- /dev/null +++ b/docs/diagrams/DeleteMultipleSequenceDiagram1.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml + +mainframe **sd** Deletion of multiple contacts + +box Logic LOGIC_COLOR_T1 +participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +activate DeleteCommand + +DeleteCommand -> DeleteCommand : deleteFromList() +activate DeleteCommand + +DeleteCommand -> Model : deletePerson(Person3) +activate Model +Model --> DeleteCommand +deactivate Model + +DeleteCommand -> Model : deletePerson(Person2) +activate Model +Model --> DeleteCommand +deactivate Model + +DeleteCommand -> Model : deletePerson(Person1) +activate Model +Model --> DeleteCommand +deactivate Model + +DeleteCommand --> DeleteCommand +deactivate DeleteCommand + +create CommandResult +DeleteCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteCommand +deactivate CommandResult + +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..47284d0d25b 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -5,6 +5,7 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -29,12 +30,16 @@ deactivate DeleteCommandParser AddressBookParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser +DeleteCommandParser -> ParserUtil : parseIndexes("1") +activate ParserUtil create DeleteCommand -DeleteCommandParser -> DeleteCommand +ParserUtil -> DeleteCommand activate DeleteCommand -DeleteCommand --> DeleteCommandParser : d +DeleteCommand --> ParserUtil : d deactivate DeleteCommand +ParserUtil --> DeleteCommandParser : d +deactivate ParserUtil DeleteCommandParser --> AddressBookParser : d deactivate DeleteCommandParser @@ -48,12 +53,17 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) -activate Model +DeleteCommand -> DeleteCommand : deleteFromList() +activate DeleteCommand +DeleteCommand -> Model : deletePerson(Person1) +activate Model Model --> DeleteCommand deactivate Model +DeleteCommand --> DeleteCommand +deactivate DeleteCommand + create CommandResult DeleteCommand -> CommandResult activate CommandResult diff --git a/docs/diagrams/EditSequenceDiagram0.puml b/docs/diagrams/EditSequenceDiagram0.puml new file mode 100644 index 00000000000..20abddaf6df --- /dev/null +++ b/docs/diagrams/EditSequenceDiagram0.puml @@ -0,0 +1,85 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditCommandParser" as EditCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "e:EditCommand" as EditCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "p2:Person" as Person MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("edit 2 \nn/John Doe ") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("edit 2 \nn/John Doe ") +activate AddressBookParser + +create EditCommandParser +AddressBookParser -> EditCommandParser +activate EditCommandParser + +EditCommandParser --> AddressBookParser +deactivate EditCommandParser + +AddressBookParser -> EditCommandParser : parse("2") +activate EditCommandParser + +EditCommandParser -> ParserUtil : parseName("John Doe") +activate ParserUtil +create EditCommand +ParserUtil -> EditCommand +activate EditCommand + +EditCommand --> ParserUtil : e +deactivate EditCommand +ParserUtil --> EditCommandParser : e +deactivate ParserUtil + +EditCommandParser --> AddressBookParser : t +deactivate EditCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditCommandParser -[hidden]-> AddressBookParser +destroy EditCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> EditCommand : execute() +activate EditCommand + +create Person +EditCommand -> Person +activate Person +Person --> EditCommand : p2 +deactivate Person + +EditCommand -> EditCommand : edit name field of p2 +activate EditCommand +EditCommand --> EditCommand : new_p2 +deactivate EditCommand + +EditCommand -> Model : setPerson(p2, new_p2) +activate Model +Model --> EditCommand +deactivate Model + +create CommandResult +EditCommand -> CommandResult +activate CommandResult + +CommandResult --> EditCommand +deactivate CommandResult + +EditCommand --> LogicManager : result +deactivate EditCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/EventClassDiagram.puml b/docs/diagrams/EventClassDiagram.puml new file mode 100644 index 00000000000..3a67299a7fa --- /dev/null +++ b/docs/diagrams/EventClassDiagram.puml @@ -0,0 +1,14 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +AddressBook *-right-> "1" UniqueEventList +UniqueEventList -right-> Event + +Event *-up-> "1" EventName +Event *-up-> "1" Information +Event *-up-> "1" Participants +Event *-up-> "1" DateTime +@enduml diff --git a/docs/diagrams/EventSequenceDiagram.puml b/docs/diagrams/EventSequenceDiagram.puml new file mode 100644 index 00000000000..ae3fc88a8e2 --- /dev/null +++ b/docs/diagrams/EventSequenceDiagram.puml @@ -0,0 +1,83 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EventCommandParser" as EventCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "e:EventCommand" as EventCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "e1:Event" as Event MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("event 1 \nname/Lunch Appt \ninfo/Having lunch at Hai Di Lao \nd/2023-02-20 t/12:15") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("event 1 \nname/Lunch Appt \ninfo/Having lunch at Hai Di Lao \nd/2023-02-20 t/12:15") +activate AddressBookParser + +create EventCommandParser +AddressBookParser -> EventCommandParser +activate EventCommandParser + +EventCommandParser --> AddressBookParser +deactivate EventCommandParser + +AddressBookParser -> EventCommandParser : parse("1") +activate EventCommandParser + +EventCommandParser -> ParserUtil : parseEventName("Lunch Appt") +activate ParserUtil +EventCommandParser -> ParserUtil : parseInfo("Having lunch at Hai Di Lao") +EventCommandParser -> ParserUtil : parseDateTime("2023-02-20", "12:15") +create EventCommand +ParserUtil -> EventCommand +activate EventCommand + +EventCommand --> ParserUtil : e +deactivate EventCommand +ParserUtil --> EventCommandParser : e +deactivate ParserUtil + +EventCommandParser --> AddressBookParser : e +deactivate EventCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EventCommandParser -[hidden]-> AddressBookParser +destroy EventCommandParser + +AddressBookParser --> LogicManager : e +deactivate AddressBookParser + +LogicManager -> EventCommand : execute() +activate EventCommand + +create Event +EventCommand -> Event : create event e1 +activate Event +Event --> EventCommand : e1 +deactivate Event + + +EventCommand -> Model : addEvent(e1) +activate Model +Model --> EventCommand +deactivate Model + +create CommandResult +EventCommand -> CommandResult +activate CommandResult + +CommandResult --> EventCommand +deactivate CommandResult + +EventCommand --> LogicManager : result +deactivate EventCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/EventSequenceDiagram0.puml b/docs/diagrams/EventSequenceDiagram0.puml new file mode 100644 index 00000000000..0eafc4244ea --- /dev/null +++ b/docs/diagrams/EventSequenceDiagram0.puml @@ -0,0 +1,67 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EventCommandParser" as EventCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "e:EventCommand" as EventCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("event 1 \nname/Lunch Appt \ninfo/Having lunch at Hai Di Lao \nd/2023-02-20 t/12:15") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("event 1 \nname/Lunch Appt \ninfo/Having lunch at Hai Di Lao \nd/2023-02-20 t/12:15") +activate AddressBookParser + +create EventCommandParser +AddressBookParser -> EventCommandParser +activate EventCommandParser + +EventCommandParser --> AddressBookParser +deactivate EventCommandParser + +AddressBookParser -> EventCommandParser : parse("1") +activate EventCommandParser + +EventCommandParser -> ParserUtil : parseEventName("Lunch Appt") +activate ParserUtil +EventCommandParser -> ParserUtil : parseInfo("Having lunch at Hai Di Lao") +EventCommandParser -> ParserUtil : parseDateTime("2023-02-20", "12:15") +create EventCommand +ParserUtil -> EventCommand +activate EventCommand + +EventCommand --> ParserUtil : e +deactivate EventCommand +ParserUtil --> EventCommandParser : e +deactivate ParserUtil + +EventCommandParser --> AddressBookParser : e +deactivate EventCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EventCommandParser -[hidden]-> AddressBookParser +destroy EventCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> EventCommand : execute() +activate EventCommand + +ref over EventCommand, Model +Adding the event +end ref + +EventCommand --> LogicManager : result +deactivate EventCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/EventSequenceDiagram1.puml b/docs/diagrams/EventSequenceDiagram1.puml new file mode 100644 index 00000000000..87d72cea89d --- /dev/null +++ b/docs/diagrams/EventSequenceDiagram1.puml @@ -0,0 +1,37 @@ +@startuml +!include style.puml + +mainframe **sd** Adding the event + +box Logic LOGIC_COLOR_T1 +participant "e:EventCommand" as EventCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "e1:Event" as Event MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +activate EventCommand + +create Event +EventCommand -> Event : create event e1 +activate Event +Event --> EventCommand : e1 +deactivate Event + + +EventCommand -> Model : addEvent(e1) +activate Model +Model --> EventCommand +deactivate Model + +create CommandResult +EventCommand -> CommandResult +activate CommandResult + +CommandResult --> EventCommand +deactivate CommandResult + +@enduml diff --git a/docs/diagrams/FindClassDiagram.puml b/docs/diagrams/FindClassDiagram.puml new file mode 100644 index 00000000000..33b22968c85 --- /dev/null +++ b/docs/diagrams/FindClassDiagram.puml @@ -0,0 +1,22 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class "FindCommand" as FindCommand +Command <|-- FindCommand +FindCommand o--right "1" Predicate : contains +FindPersonDescriptor .up. FindCommand + +Name "*" -up-* FindPersonDescriptor +Phone "*" -up-* FindPersonDescriptor +Address "*" -up-* FindPersonDescriptor +Tag "*" -up-* FindPersonDescriptor + +FindOrPredicateParser .right. FindCommand +FindOrPredicateParser .right. FindPersonDescriptor + +FindAndPredicateParser .up. FindCommand +FindAndPredicateParser .up. FindPersonDescriptor +@enduml diff --git a/docs/diagrams/FindPredicatesClassDiagram.puml b/docs/diagrams/FindPredicatesClassDiagram.puml new file mode 100644 index 00000000000..d13761e1dca --- /dev/null +++ b/docs/diagrams/FindPredicatesClassDiagram.puml @@ -0,0 +1,34 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +class "<>\nPredicate" as Predicate +class "{abstract}\nContainsKeywordPredicate" as ckp +Predicate <|-- ckp +class "{abstract}\nFieldContainsKeywordPredicateAnd" as fckpAnd +class "{abstract}\nFieldContainsKeywordPredicateOr" as fckpOr +ckp <|-- fckpAnd +ckp <|--- fckpOr +class "{abstract}\nTagsContainsKeywordPredicateAnd" as tckpAnd +class "{abstract}\nTagsContainsKeywordPredicateOr" as tckpOr +ckp <|---- tckpAnd +ckp <|----- tckpOr +class "{abstract}\nNameContainsKeywordPredicateAnd" as nckpAnd +class "{abstract}\nNameContainsKeywordPredicateOr" as nckpOr +class "{abstract}\nPhoneContainsKeywordPredicateAnd" as pckpAnd +class "{abstract}\nPhoneContainsKeywordPredicateOr" as pckpOr +fckpAnd <|-- nckpAnd +fckpOr <|-- nckpOr +fckpAnd <|-- pckpAnd +fckpOr <|-- pckpOr +class "{abstract}\nCcaContainsKeywordPredicateAnd" as cckpAnd +class "{abstract}\nCcaContainsKeywordPredicateOr" as cckpOr +class "{abstract}\nModuleContainsKeywordPredicateAnd" as mckpAnd +class "{abstract}\nModuleContainsKeywordPredicateOr" as mckpOr +tckpAnd <|-- cckpAnd +tckpOr <|-- cckpOr +tckpAnd <|-- mckpAnd +tckpOr <|-- mckpOr +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..4eb74be1e44 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -13,13 +13,9 @@ Class ModelManager Class UserPrefs Class UniquePersonList +Class UniqueEventList Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag - +Class Event } Class HiddenOutside #FFFFFF @@ -35,16 +31,10 @@ ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList +AddressBook *--> "1" UniqueEventList UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag - -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +UniqueEventList --> "~* all" Event ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Event @enduml diff --git a/docs/diagrams/RemoveTagSequenceDiagram.puml b/docs/diagrams/RemoveTagSequenceDiagram.puml new file mode 100644 index 00000000000..08586360ec1 --- /dev/null +++ b/docs/diagrams/RemoveTagSequenceDiagram.puml @@ -0,0 +1,85 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":RemoveTagCommandParser" as RemoveTagCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "t:RemoveTagCommand" as RemoveTagCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "p2:Person" as Person MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("removetag 2 \nm/cs2100 m/cs2107") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("removetag 2 \nm/cs2100 m/cs2107") +activate AddressBookParser + +create RemoveTagCommandParser +AddressBookParser -> RemoveTagCommandParser +activate RemoveTagCommandParser + +RemoveTagCommandParser --> AddressBookParser +deactivate RemoveTagCommandParser + +AddressBookParser -> RemoveTagCommandParser : parse("2") +activate RemoveTagCommandParser + +RemoveTagCommandParser -> ParserUtil : parseTags("cs2100, cs2107") +activate ParserUtil +create RemoveTagCommand +ParserUtil -> RemoveTagCommand +activate RemoveTagCommand + +RemoveTagCommand --> ParserUtil : t +deactivate RemoveTagCommand +ParserUtil --> RemoveTagCommandParser : t +deactivate ParserUtil + +RemoveTagCommandParser --> AddressBookParser : t +deactivate RemoveTagCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +RemoveTagCommandParser -[hidden]-> AddressBookParser +destroy RemoveTagCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> RemoveTagCommand : execute() +activate RemoveTagCommand + +create Person +RemoveTagCommand -> Person +activate Person +Person --> RemoveTagCommand : p2 +deactivate Person + +RemoveTagCommand -> RemoveTagCommand : remove matching tags from p2 +activate RemoveTagCommand +RemoveTagCommand --> RemoveTagCommand : new_p2 +deactivate RemoveTagCommand + +RemoveTagCommand -> Model : setPerson(p2, new_p2) +activate Model +Model --> RemoveTagCommand +deactivate Model + +create CommandResult +RemoveTagCommand -> CommandResult +activate CommandResult + +CommandResult --> RemoveTagCommand +deactivate CommandResult + +RemoveTagCommand --> LogicManager : result +deactivate RemoveTagCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/RemoveTagSequenceDiagram0.puml b/docs/diagrams/RemoveTagSequenceDiagram0.puml new file mode 100644 index 00000000000..a74ff6b7071 --- /dev/null +++ b/docs/diagrams/RemoveTagSequenceDiagram0.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":RemoveTagCommandParser" as RemoveTagCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "t:RemoveTagCommand" as RemoveTagCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("removetag 2 \nm/cs2100 m/cs2107") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("removetag 2 \nm/cs2100 m/cs2107") +activate AddressBookParser + +create RemoveTagCommandParser +AddressBookParser -> RemoveTagCommandParser +activate RemoveTagCommandParser + +RemoveTagCommandParser --> AddressBookParser +deactivate RemoveTagCommandParser + +AddressBookParser -> RemoveTagCommandParser : parse("2") +activate RemoveTagCommandParser + +RemoveTagCommandParser -> ParserUtil : parseTags("cs2100, cs2107") +activate ParserUtil +create RemoveTagCommand +ParserUtil -> RemoveTagCommand +activate RemoveTagCommand + +RemoveTagCommand --> ParserUtil : t +deactivate RemoveTagCommand +ParserUtil --> RemoveTagCommandParser : t +deactivate ParserUtil + +RemoveTagCommandParser --> AddressBookParser : t +deactivate RemoveTagCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +RemoveTagCommandParser -[hidden]-> AddressBookParser +destroy RemoveTagCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> RemoveTagCommand : execute() +activate RemoveTagCommand + +ref over RemoveTagCommand, Model +Removal of matching tags +end ref + +RemoveTagCommand --> LogicManager : result +deactivate RemoveTagCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/RemoveTagSequenceDiagram1.puml b/docs/diagrams/RemoveTagSequenceDiagram1.puml new file mode 100644 index 00000000000..0c29abbd048 --- /dev/null +++ b/docs/diagrams/RemoveTagSequenceDiagram1.puml @@ -0,0 +1,41 @@ +@startuml +!include style.puml + +mainframe **sd** Removal of matching tags + +box Logic LOGIC_COLOR_T1 +participant "t:RemoveTagCommand" as RemoveTagCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "p2:Person" as Person MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +activate RemoveTagCommand + +create Person +RemoveTagCommand -> Person +activate Person +Person --> RemoveTagCommand : p2 +deactivate Person + +RemoveTagCommand -> RemoveTagCommand : remove matching tags from p2 +activate RemoveTagCommand +RemoveTagCommand --> RemoveTagCommand : new_p2 +deactivate RemoveTagCommand + +RemoveTagCommand -> Model : setPerson(p2, new_p2) +activate Model +Model --> RemoveTagCommand +deactivate Model + +create CommandResult +RemoveTagCommand -> CommandResult +activate CommandResult + +CommandResult --> RemoveTagCommand +deactivate CommandResult + +@enduml diff --git a/docs/diagrams/RemoveTagState0.puml b/docs/diagrams/RemoveTagState0.puml new file mode 100644 index 00000000000..093559e1e72 --- /dev/null +++ b/docs/diagrams/RemoveTagState0.puml @@ -0,0 +1,29 @@ +@startuml + +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Before removetag() + +object "__David:Person__" as Person { + name = "David Choo" + address = "Example Street" + phone = 81234567 + email = "david@example.com" +} + +object "__nus:Education__" as nusTag +object "__dso:Internship__" as dsoTag +object "__cs2100:Module__" as cs2100Tag +object "__cs2107:Module__" as cs2107Tag + +Person -down-> nusTag +Person -down-> dsoTag +Person -down-> cs2100Tag +Person -down-> cs2107Tag + +nusTag -[hidden]right- dsoTag +dsoTag -[hidden]right- cs2100Tag +cs2100Tag -[hidden]right- cs2107Tag + +@enduml diff --git a/docs/diagrams/RemoveTagState1.puml b/docs/diagrams/RemoveTagState1.puml new file mode 100644 index 00000000000..111c2985991 --- /dev/null +++ b/docs/diagrams/RemoveTagState1.puml @@ -0,0 +1,28 @@ +@startuml + +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title After removetag() + +object "__David:Person__" as Person { + name = "David Choo" + address = "Example Street" + phone = 81234567 + email = "david@example.com" +} + +object "__cs2100:Module__" as cs2100Tag +object "__nus:Education__" as nusTag +object "__dso:Internship__" as dsoTag +object "__cs2107:Module__" as cs2107Tag + +Person -down-> nusTag +Person -down-> dsoTag +Person -down-> cs2100Tag +Person -down-> cs2107Tag + +hide cs2100Tag +hide cs2107Tag + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..c44def2e5f2 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -20,6 +20,8 @@ Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson Class JsonAdaptedTag +Class JsonAdaptedEvent +Class JsonAdaptedName } } @@ -38,6 +40,8 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson +JsonSerializableAddressBook --> "*" JsonAdaptedEvent +JsonAdaptedEvent --> "*" JsonAdaptedName JsonAdaptedPerson --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/TagClassDiagram.puml b/docs/diagrams/TagClassDiagram.puml new file mode 100644 index 00000000000..c2b7f513ae6 --- /dev/null +++ b/docs/diagrams/TagClassDiagram.puml @@ -0,0 +1,22 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class "{abstract}\nTag" as Tag + +AddressBook *-right-> "1" UniquePersonList +UniquePersonList -right-> Person + +Person -down-> "*" Tag +Cca -up-|> Tag +Education -up-|> Tag +Internship -up-|> Tag +Module -up-|> Tag + +Person *-up-> "1" Name +Person *-up-> "1" Phone +Person *-up-> "1" Email +Person *-up-> "1 "Address +@enduml diff --git a/docs/diagrams/TagSequenceDiagram.puml b/docs/diagrams/TagSequenceDiagram.puml new file mode 100644 index 00000000000..227f61e7e74 --- /dev/null +++ b/docs/diagrams/TagSequenceDiagram.puml @@ -0,0 +1,86 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TagCommandParser" as TagCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "t:TagCommand" as TagCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "p1:Person" as Person MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("tag 1 \nedu/computer science \nm/cs2030s m/cs2040s") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("tag 1 \nedu/computer science \nm/cs2030s m/cs2040s") +activate AddressBookParser + +create TagCommandParser +AddressBookParser -> TagCommandParser +activate TagCommandParser + +TagCommandParser --> AddressBookParser +deactivate TagCommandParser + +AddressBookParser -> TagCommandParser : parse("1") +activate TagCommandParser + +TagCommandParser -> ParserUtil : parseTags("computer science") +activate ParserUtil +TagCommandParser -> ParserUtil : parseTags("cs2030s, cs2040s") +create TagCommand +ParserUtil -> TagCommand +activate TagCommand + +TagCommand --> ParserUtil : t +deactivate TagCommand +ParserUtil --> TagCommandParser : t +deactivate ParserUtil + +TagCommandParser --> AddressBookParser : t +deactivate TagCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TagCommandParser -[hidden]-> AddressBookParser +destroy TagCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> TagCommand : execute() +activate TagCommand + +create Person +TagCommand -> Person +activate Person +Person --> TagCommand : p1 +deactivate Person + +TagCommand -> TagCommand : add tags to p1 +activate TagCommand +TagCommand --> TagCommand : new_p1 +deactivate TagCommand + +TagCommand -> Model : setPerson(p1, new_p1) +activate Model +Model --> TagCommand +deactivate Model + +create CommandResult +TagCommand -> CommandResult +activate CommandResult + +CommandResult --> TagCommand +deactivate CommandResult + +TagCommand --> LogicManager : result +deactivate TagCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TagSequenceDiagram0.puml b/docs/diagrams/TagSequenceDiagram0.puml new file mode 100644 index 00000000000..92b8898fa2f --- /dev/null +++ b/docs/diagrams/TagSequenceDiagram0.puml @@ -0,0 +1,66 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TagCommandParser" as TagCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant "t:TagCommand" as TagCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("tag 1 \nedu/computer science \nm/cs2030s m/cs2040s") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("tag 1 \nedu/computer science \nm/cs2030s m/cs2040s") +activate AddressBookParser + +create TagCommandParser +AddressBookParser -> TagCommandParser +activate TagCommandParser + +TagCommandParser --> AddressBookParser +deactivate TagCommandParser + +AddressBookParser -> TagCommandParser : parse("1") +activate TagCommandParser + +TagCommandParser -> ParserUtil : parseTags("computer science") +activate ParserUtil +TagCommandParser -> ParserUtil : parseTags("cs2030s, cs2040s") +create TagCommand +ParserUtil -> TagCommand +activate TagCommand + +TagCommand --> ParserUtil : t +deactivate TagCommand +ParserUtil --> TagCommandParser : t +deactivate ParserUtil + +TagCommandParser --> AddressBookParser : t +deactivate TagCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TagCommandParser -[hidden]-> AddressBookParser +destroy TagCommandParser + +AddressBookParser --> LogicManager : t +deactivate AddressBookParser + +LogicManager -> TagCommand : execute() +activate TagCommand + +ref over TagCommand, Model +Adding tags to the person +end ref + +TagCommand --> LogicManager : result +deactivate TagCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/TagSequenceDiagram1.puml b/docs/diagrams/TagSequenceDiagram1.puml new file mode 100644 index 00000000000..4f2861646fc --- /dev/null +++ b/docs/diagrams/TagSequenceDiagram1.puml @@ -0,0 +1,41 @@ +@startuml +!include style.puml + +mainframe **sd** Adding tags to the person + +box Logic LOGIC_COLOR_T1 +participant "t:TagCommand" as TagCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "p1:Person" as Person MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +activate TagCommand + +create Person +TagCommand -> Person +activate Person +Person --> TagCommand : p1 +deactivate Person + +TagCommand -> TagCommand : add tags to p1 +activate TagCommand +TagCommand --> TagCommand : new_p1 +deactivate TagCommand + +TagCommand -> Model : setPerson(p1, new_p1) +activate Model +Model --> TagCommand +deactivate Model + +create CommandResult +TagCommand -> CommandResult +activate CommandResult + +CommandResult --> TagCommand +deactivate CommandResult + +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..b4432ff88f8 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -12,7 +12,9 @@ Class MainWindow Class HelpWindow Class ResultDisplay Class PersonListPanel +Class EventListPanel Class PersonCard +Class EventCard Class StatusBarFooter Class CommandBox } @@ -33,10 +35,12 @@ UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" EventListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +EventListPanel -down-> "*" EventCard MainWindow -left-|> UiPart @@ -44,17 +48,22 @@ ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart +EventListPanel --|> UiPart +EventCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart PersonCard ..> Model +EventCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic PersonListPanel -[hidden]left- HelpWindow +EventListPanel -[hidden]left- PersonListPanel HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter +EventCard -[hidden]down- PersonCard MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..34885420931 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -15,6 +15,6 @@ State2 -[hidden]right-> State3 hide State2 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State1 @end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..0e2c8c72d33 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -16,7 +16,7 @@ State2 -[hidden]right-> State3 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..0ce7073e187 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..50bf43b3f34 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..83cbe4c740c 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..fc89dd99d2d 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 note right on link: State ab2 deleted. diff --git a/docs/images/CancelEventSequenceDiagram.png b/docs/images/CancelEventSequenceDiagram.png new file mode 100644 index 00000000000..7e38b361f48 Binary files /dev/null and b/docs/images/CancelEventSequenceDiagram.png differ diff --git a/docs/images/CancelEventSequenceDiagram0.png b/docs/images/CancelEventSequenceDiagram0.png new file mode 100644 index 00000000000..3b7ff960d87 Binary files /dev/null and b/docs/images/CancelEventSequenceDiagram0.png differ diff --git a/docs/images/CancelEventSequenceDiagram1.png b/docs/images/CancelEventSequenceDiagram1.png new file mode 100644 index 00000000000..f740950dc63 Binary files /dev/null and b/docs/images/CancelEventSequenceDiagram1.png differ diff --git a/docs/images/DeleteMultipleSequenceDiagram.png b/docs/images/DeleteMultipleSequenceDiagram.png new file mode 100644 index 00000000000..096fce528f7 Binary files /dev/null and b/docs/images/DeleteMultipleSequenceDiagram.png differ diff --git a/docs/images/DeleteMultipleSequenceDiagram0.png b/docs/images/DeleteMultipleSequenceDiagram0.png new file mode 100644 index 00000000000..9ac6c81a9b1 Binary files /dev/null and b/docs/images/DeleteMultipleSequenceDiagram0.png differ diff --git a/docs/images/DeleteMultipleSequenceDiagram1.png b/docs/images/DeleteMultipleSequenceDiagram1.png new file mode 100644 index 00000000000..d1589ae4003 Binary files /dev/null and b/docs/images/DeleteMultipleSequenceDiagram1.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..0ee62b04f0f 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditSequenceDiagram0.png b/docs/images/EditSequenceDiagram0.png new file mode 100644 index 00000000000..7558831164f Binary files /dev/null and b/docs/images/EditSequenceDiagram0.png differ diff --git a/docs/images/EventClassDiagram.png b/docs/images/EventClassDiagram.png new file mode 100644 index 00000000000..b68c0bc1d28 Binary files /dev/null and b/docs/images/EventClassDiagram.png differ diff --git a/docs/images/EventSequenceDiagram.png b/docs/images/EventSequenceDiagram.png new file mode 100644 index 00000000000..42213fe6e90 Binary files /dev/null and b/docs/images/EventSequenceDiagram.png differ diff --git a/docs/images/EventSequenceDiagram0.png b/docs/images/EventSequenceDiagram0.png new file mode 100644 index 00000000000..6fd2cc27e1d Binary files /dev/null and b/docs/images/EventSequenceDiagram0.png differ diff --git a/docs/images/EventSequenceDiagram1.png b/docs/images/EventSequenceDiagram1.png new file mode 100644 index 00000000000..125d064880a Binary files /dev/null and b/docs/images/EventSequenceDiagram1.png differ diff --git a/docs/images/FindClassDiagram.png b/docs/images/FindClassDiagram.png new file mode 100644 index 00000000000..55f19e5e31d Binary files /dev/null and b/docs/images/FindClassDiagram.png differ diff --git a/docs/images/FindPredicatesClassDiagram.png b/docs/images/FindPredicatesClassDiagram.png new file mode 100644 index 00000000000..5e5181be7ad Binary files /dev/null and b/docs/images/FindPredicatesClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..0bd5fd3449e 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/RemoveTagSequenceDiagram.png b/docs/images/RemoveTagSequenceDiagram.png new file mode 100644 index 00000000000..5b4e0a4e092 Binary files /dev/null and b/docs/images/RemoveTagSequenceDiagram.png differ diff --git a/docs/images/RemoveTagSequenceDiagram0.png b/docs/images/RemoveTagSequenceDiagram0.png new file mode 100644 index 00000000000..5ee0e12ef5e Binary files /dev/null and b/docs/images/RemoveTagSequenceDiagram0.png differ diff --git a/docs/images/RemoveTagSequenceDiagram1.png b/docs/images/RemoveTagSequenceDiagram1.png new file mode 100644 index 00000000000..f4c310aff08 Binary files /dev/null and b/docs/images/RemoveTagSequenceDiagram1.png differ diff --git a/docs/images/RemoveTagState0.png b/docs/images/RemoveTagState0.png new file mode 100644 index 00000000000..be970537dc2 Binary files /dev/null and b/docs/images/RemoveTagState0.png differ diff --git a/docs/images/RemoveTagState1.png b/docs/images/RemoveTagState1.png new file mode 100644 index 00000000000..1e368da21e2 Binary files /dev/null and b/docs/images/RemoveTagState1.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..0527aa9c569 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/TagClassDiagram.png b/docs/images/TagClassDiagram.png new file mode 100644 index 00000000000..93c2991c9b6 Binary files /dev/null and b/docs/images/TagClassDiagram.png differ diff --git a/docs/images/TagSequenceDiagram.png b/docs/images/TagSequenceDiagram.png new file mode 100644 index 00000000000..530c045e872 Binary files /dev/null and b/docs/images/TagSequenceDiagram.png differ diff --git a/docs/images/TagSequenceDiagram0.png b/docs/images/TagSequenceDiagram0.png new file mode 100644 index 00000000000..6c0a2290aed Binary files /dev/null and b/docs/images/TagSequenceDiagram0.png differ diff --git a/docs/images/TagSequenceDiagram1.png b/docs/images/TagSequenceDiagram1.png new file mode 100644 index 00000000000..a50889ca3e0 Binary files /dev/null and b/docs/images/TagSequenceDiagram1.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..5a57521d350 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 785e04dbab4..aace26f1b04 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiSampleAddressBook.png b/docs/images/UiSampleAddressBook.png new file mode 100644 index 00000000000..e6003998cc7 Binary files /dev/null and b/docs/images/UiSampleAddressBook.png differ diff --git a/docs/images/fredtwt.png b/docs/images/fredtwt.png new file mode 100644 index 00000000000..63db18fafef Binary files /dev/null and b/docs/images/fredtwt.png differ diff --git a/docs/images/help_message.png b/docs/images/help_message.png new file mode 100644 index 00000000000..b0312be5029 Binary files /dev/null and b/docs/images/help_message.png differ diff --git a/docs/images/manu2002g.png b/docs/images/manu2002g.png new file mode 100644 index 00000000000..f429d6a32d7 Binary files /dev/null and b/docs/images/manu2002g.png differ diff --git a/docs/images/ongkimlai.png b/docs/images/ongkimlai.png new file mode 100644 index 00000000000..e96b66ff3a7 Binary files /dev/null and b/docs/images/ongkimlai.png differ diff --git a/docs/images/screenshots/addAlissonBecker.png b/docs/images/screenshots/addAlissonBecker.png new file mode 100644 index 00000000000..537d824b04e Binary files /dev/null and b/docs/images/screenshots/addAlissonBecker.png differ diff --git a/docs/images/screenshots/beforeCommand.png b/docs/images/screenshots/beforeCommand.png new file mode 100644 index 00000000000..0a05f83f8cb Binary files /dev/null and b/docs/images/screenshots/beforeCommand.png differ diff --git a/docs/images/screenshots/eventMovieMarathon.png b/docs/images/screenshots/eventMovieMarathon.png new file mode 100644 index 00000000000..0eb40511a64 Binary files /dev/null and b/docs/images/screenshots/eventMovieMarathon.png differ diff --git a/docs/images/screenshots/find-sShopeeCS2040sCS2030s.png b/docs/images/screenshots/find-sShopeeCS2040sCS2030s.png new file mode 100644 index 00000000000..cdce02e4344 Binary files /dev/null and b/docs/images/screenshots/find-sShopeeCS2040sCS2030s.png differ diff --git a/docs/images/findAlexDavidResult.png b/docs/images/screenshots/findAlexDavidResult.png similarity index 100% rename from docs/images/findAlexDavidResult.png rename to docs/images/screenshots/findAlexDavidResult.png diff --git a/docs/images/screenshots/findShopeeCS2040sCS2030sResult.png b/docs/images/screenshots/findShopeeCS2040sCS2030sResult.png new file mode 100644 index 00000000000..4ae04166350 Binary files /dev/null and b/docs/images/screenshots/findShopeeCS2040sCS2030sResult.png differ diff --git a/docs/images/screenshots/samplePersonCard.png b/docs/images/screenshots/samplePersonCard.png new file mode 100644 index 00000000000..c67599a19fc Binary files /dev/null and b/docs/images/screenshots/samplePersonCard.png differ diff --git a/docs/images/screenshots/tagInternshipModuleModule.png b/docs/images/screenshots/tagInternshipModuleModule.png new file mode 100644 index 00000000000..54a4610c328 Binary files /dev/null and b/docs/images/screenshots/tagInternshipModuleModule.png differ diff --git a/docs/images/viki0526.png b/docs/images/viki0526.png new file mode 100644 index 00000000000..1ce7ce16dc8 Binary files /dev/null and b/docs/images/viki0526.png differ diff --git a/docs/img.png b/docs/img.png new file mode 100644 index 00000000000..c67599a19fc Binary files /dev/null and b/docs/img.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..f7ac387fc87 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: NUSocials --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/AY2122S2-CS2103T-W11-1/tp//workflows/Java%20CI/badge.svg)](https://github.com/AY2122S2-CS2103T-W11-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2122S2-CS2103T-W11-1/tp/branch/master/graph/badge.svg?token=EQL5RQUWFN)](https://codecov.io/gh/AY2122S2-CS2103T-W11-1/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**NUSocials is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using NUSocials, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing NUSocials, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/fredtwt.md b/docs/team/fredtwt.md new file mode 100644 index 00000000000..6db536ad3d9 --- /dev/null +++ b/docs/team/fredtwt.md @@ -0,0 +1,58 @@ +--- +layout: page +title: Frederick Tang's Project Portfolio Page +--- + +### Project: NUSocials + +NUSocials is a desktop address book application for university students who like to maintain a professional contact list. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to tag additional information to an existing contact entry. [(#35)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/35) + * What it does: allows the user to tag additional information to an existing contact. + * Justification: This feature improves the product significantly because a user can tag important information to their own contacts for future references. + * Highlights: This enhancement affects the existing UI layout. It required an in-depth analysis of how the tagged information should be displayed alongside with their respective contacts. + +* **New Feature**: Added the ability to add events to the address book. [(#64, ](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/64) [#73, ](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/73) [#82)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/82) + * What it does: allows the user to add events and tag them to contacts that are participating. + * Justification: This feature improves the product significantly because a user can keep track of upcoming events they have with their contacts. + * Highlights: This enhancement affects the existing UI layout. It required an in-depth analysis of how the event information should be displayed alongside the contact list in the application. + +* **New Feature**: Added the ability to cancel events and delete them from the address book. [(#76)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/76) + * What it does: allows the user to cancel events and delete them from the address book. + * Justification: This feature improves the product significantly because a user can remove unwanted events and reduce clutter in their event list. + * Highlights: This enhancement requires an in-depth analysis of how the UI layout of the event list should be altered after an event has been deleted. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=fredtwt&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&tabAuthor=fredtwt&tabRepo=AY2122S2-CS2103T-W11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +* **Enhancements to existing features:** + * Removed the tag functionality from add command [(#33)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/33) + * Created a tag command that can tag specific fields to existing contact entries + * Enhanced the cancel event command to handle multiple cancellations at once + * Contributed to the logic for `find` command to handle the following cases: [(#75, ](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/75) [#95)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/95) + * basic particulars' prefixes can only be used once for each field + * search inputs can take in multiple strings now (i.e edu/computer science) + +* **Test cases implemented:** + * EventCommand [(#157)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/157) + * EventCommandParser [(#161)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/161) + * CancelEventCommand [(#158)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/158) + * CancelEventCommandParser [(#161)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/161) + * All the event field types [(#158)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/158) + * JsonAdaptedEvent [(#161)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/161) + +* **Documentation**: + * User Guide: + * Added documentation for the features `event`, `tag`, `cancelevent`. + * Did cosmetic tweaks on the documentation for existing features `list`, `add`, `delete`. + * Did cosmetic tweaks on the command summary to reflect NUSocials command format. + + * Developer Guide: + * Added user stories for the features `add`, `tag`, `event`, `cancelevent`. [(#87)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/87) + * Added use cases for the features `add`, `tag`, `event`, `cancelevent`. [(#87)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/87) + * Modified the `ModelClassDiagram` and `StorageClassDiagram`. [(#87)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/87) + * Added `TagSequenceDiagram`, `CancelEventSequenceDiagram`, `EventSequenceDiagram`. [(#167)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/167) + * Added the implementation design details for `tag`, `event`, `cancelevent`. [(#67, ](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/67) [#88, ](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/88) [#90)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/90) + * Added manual testing instructions for `tag`, `event`, `cancelevent`. [(#171)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/171) + * Contributed to the NFRs and glossary. diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/manu2002g.md b/docs/team/manu2002g.md new file mode 100644 index 00000000000..50a424aef73 --- /dev/null +++ b/docs/team/manu2002g.md @@ -0,0 +1,42 @@ +--- +layout: page +title: Manusha Galappaththi's Project Portfolio Page +--- + +### Project: NUSocials + +NUSocials is a desktop address book application for university students who like to maintain a professional contact list. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to edit previously added entries + * What it does: Allows the user to change the details of a previously added contact. This feature initially supported editing tags as well but was later removed. + * Justification: Users may make mistakes when entering contact details or tags. We should allow them to rectify this using an edit command + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* **New Feature**: Added the ability to filter the events list + * What it does: Allows the user to view upcoming, past or all events in the database. + * Justification: Being able to filter and view events by upcoming/past greatly improved the usefulness of the events feature. + +* **New Feature**: Added the ability to find contact entries + * What it does: Allows the user search for a contact entry in the list according to basic particulars. Functionality for tags was added by another teammate. + * Justification: Users may have very large lists of contacts and may want to filter them by a certain tag or detail. We should allow them to do this using a find command. + * Highlights: This enhancement offers both 'AND' and 'OR' search for the fields searched for. The implementation was challenging as it required good knowledge of java predicates and functional programming. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&tabAuthor=manu2002g&tabRepo=AY2122S2-CS2103T-W11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=&authorshipIsBinaryFileTypeChecked=false) + +* **Documentation**: + * User Guide: + * Added documentation for `edit` and `showevent` features. + * Developer Guide: + * Added implementation for `edit`including the sequence diagram + * Added class diagram describing the predicates used in `find` and `find -s` + * Contributed to user stories, use cases, non-functional requirements and glossary + +* **Test Case Implementation**: + * ShowEventsCommand + * ShowEventsCommandParser + * FindCommand + * EditCommand + + diff --git a/docs/team/ongkimlai.md b/docs/team/ongkimlai.md new file mode 100644 index 00000000000..259280b7a6c --- /dev/null +++ b/docs/team/ongkimlai.md @@ -0,0 +1,61 @@ +--- +layout: page +title: Ong Kim Lai's Project Portfolio Page +--- + +### Project: NUSocials + +NUSocials is a desktop address book application for university students who like to maintain a professional contact list. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **Feature Enhancement**: Added the ability to delete multiple contacts. [(#34)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/34) + * What it does: allows the user to delete multiple contacts in a single command. + * Justification: This feature improves the product as a user can efficiently delete many contacts at once instead of inputting a delete command for each contact that he or she wants to delete. + * Highlights: This enhancement affects the existing UI layout. It required an in-depth analysis of how the contacts would be displayed during the deletion process. + +* **New Feature**: Added the ability to remove tags from existing contacts. [(#52)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/52) + * What it does: allows the user to remove tags from an existing contact. + * Justification: This feature improves the product significantly because it allows the user to simply remove a tag, otherwise users have to recreate another contact. + +* Updated the UI to fit requirements for new features [(#70)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/70) + * Added a split-pane to fit 2 panels. (contacts panel and events panel) + * Designed the events panel. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&tabAuthor=ongkimlai&tabRepo=AY2122S2-CS2103T-W11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs&authorshipIsBinaryFileTypeChecked=false&zA=ongkimlai&zR=AY2122S2-CS2103T-W11-1%2Ftp%5Bmaster%5D&zACS=81.23391812865498&zS=2022-02-18&zFS=&zU=2022-02-26&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false) + +* **Documentation**: + * User Guide: + * Added documentation for `removetag` and `delete` enhancement + * Updated documentation for exact tag names for `find` and `tag` + * Added all screenshots found in User Guide + * Added constraints section for all features + * Developer Guide: + * Added user stories for `removetag`, `delete` multiple contacts, viewing events + * Added use cases for `removetag`, `find`, `delete` multiple contacts, viewing events + * Added implementation for `delete` multiple contacts and its sequence diagrams + * Added implementation for `removetag` and its sequence and object diagrams + * Updated UI component class diagram + * Cosmetic tweaks to the formatting of use cases + * Contributed to the NFRs + +* **Test Case Implementation**: + * DeleteCommand [(#34, ](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/34) [#41)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/41) + * DeleteCommandParser [(#34)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/34) + * TagCommand [(#41)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/41) + * TagCommandParser [(#156)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/156) + * RemoveTagCommand [(#52)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/52) + * RemoveTagCommandParser [(#156)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/156) + * JsonAdaptedName [(#159)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/159) + * EditCommand (fixed bugs) [(#170)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/170) + +* **Review/mentoring contributions**: + * Supported a teammate with the steps to implement `showevents` + * Suggested fixes for a few test cases in `EditCommand` [(#92)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/92) + +* **Tools**: + * Integrated Codecov into team repo + * Change the header to NUSocials [(#5)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/55) + * Update the links to the Java-CI and CodeCov status-banners + * Update the link to the project’s GitHub repository + * Update site-wide settings [(#18)](https://github.com/AY2122S2-CS2103T-W11-1/tp/pull/18) diff --git a/docs/team/viki0526.md b/docs/team/viki0526.md new file mode 100644 index 00000000000..23df9e38195 --- /dev/null +++ b/docs/team/viki0526.md @@ -0,0 +1,39 @@ +--- +layout: page +title: Vikrant Prakash's Project Portfolio Page +--- + +### Project: NUSocials + +NUSocials is a desktop address book application for university students who like to maintain a professional contact list. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* Updated the UI to display the tags added + * Used JavaFX Hboxes to create rudimentary tags for testing purposes before the final tag UI was designed + +* **New Feature**: Added the ability to view all contact entries. + * What it does: allows the user to view a list of all contacts in the stored in the database. + * Justification: This feature improves the product significantly because a user can view all their contacts and choose which ones to tag or delete. + * Highlights: This enhancement required an analysis of the UI design layout to find the best possible way to display the result. + +* **Feature Enhancement**: Added the ability to find contact entries by tags + * What it does: allows the user search for a contact entry in the list according to tags. + * Justification: Users may have very large lists of contacts and may want to filter them by a certain tag. We should allow them to do this using by extending the find command for tags + * Highlights: This enhancement offers both 'AND' and 'OR' search for the fields searched for. The implementation was challenging as it required good knowledge of java predicates and functional programming. + +* **New Feature**: Added the ability to find events + * What it does: allows the user search for an event in the list according to the event details + * Justification: Users may have very large lists of upcoming events and may want to look for a specific one. We should allow them to do this using a find event command + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=viki0526&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=viki0526&tabRepo=AY2122S2-CS2103T-W11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +* **Documentation**: + * User Guide: + * Added documentation for `find`, `find -s`, `find -e` + * Added documentation for the `list` feature + * Developer Guide: + * Contributed to user stories, usecases, non-functional requirements and glossary + * Added implementation for find contacts including its class diagram + * Added implementation for find events diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..0396c6a89af 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -8,6 +8,8 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid"; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d PERSON(S) LISTED!"; + public static final String MESSAGE_EVENTS_LISTED_OVERVIEW = "%1$d EVENT(S) LISTED!"; } diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c09..813673c51f7 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -8,8 +8,9 @@ * base the other component is using for its index. However, after receiving the {@code Index}, that component can * convert it back to an int if the index will not be passed to a different component again. */ -public class Index { +public class Index implements Comparable { private int zeroBasedIndex; + private final int originalZeroBasedIndex; /** * Index can only be created by calling {@link Index#fromZeroBased(int)} or @@ -21,6 +22,7 @@ private Index(int zeroBasedIndex) { } this.zeroBasedIndex = zeroBasedIndex; + this.originalZeroBasedIndex = zeroBasedIndex; } public int getZeroBased() { @@ -31,6 +33,25 @@ public int getOneBased() { return zeroBasedIndex + 1; } + public int getOriginalZeroBased() { + return zeroBasedIndex; + } + + public Index getOriginalZeroBasedAsIndex() { + return new Index(getOriginalZeroBased()); + } + + public int getOrignalOneBased() { + return zeroBasedIndex + 1; + } + + /** + * Decreases the index by 1 + */ + public void decreaseIndex() { + zeroBasedIndex--; + } + /** * Creates a new {@code Index} using a zero-based index. */ @@ -49,6 +70,13 @@ public static Index fromOneBased(int oneBasedIndex) { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Index // instanceof handles nulls - && zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check + //&& zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check + && originalZeroBasedIndex == ((Index) other).originalZeroBasedIndex); // state check + + } + + @Override + public int compareTo(Index other) { + return other.originalZeroBasedIndex - originalZeroBasedIndex; } } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..373c9514293 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -6,6 +6,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; +import java.util.HashSet; /** * Helper functions for handling strings. @@ -38,6 +39,23 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code string} contains the {@code substring}. + * Ignores case, a full substring match is required. + * + * @param string cannot be null + * @param substring cannot be null, cannot be empty + */ + public static boolean containsSubstringIgnoreCase(String string, String substring) { + requireNonNull(string); + requireNonNull(substring); + + String preppedSubstring = substring.trim(); + checkArgument(!preppedSubstring.isEmpty(), "Substring parameter cannot be empty"); + + return string.contains(preppedSubstring); + } + /** * Returns a detailed message of the t, including the stack trace. */ @@ -65,4 +83,64 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + /** + * Returns true if {@code s} contains multiple entries, does not check for validness. + * Returns false if {@code s} is only a single entry. + * e.g. "1", "2" returns false, "1 2 3", "5 10" (multiple whitespaces) returns true + * + * @param s trimmed string of arguments + * @throws NullPointerException if {@code s} is null. + */ + public static boolean containsMultipleIndex(String s) { + requireNonNull(s); + String[] indexes = s.split(" "); + + return indexes.length != 1; + } + + /** + * Returns true if {@code s} contains multiple all unique entries (integers), does not check for validness. + * Returns false if {@code s} contains duplicate entries (integers). + * e.g.: + * "1 1", "1 2 1 3" returns false + * "1 2 3", "5 10 2 3" (multiple whitespaces) returns true + * + * @param s trimmed string of arguments + * @throws NullPointerException if {@code s} is null. + */ + public static boolean isAllUniqueIntegers(String s) { + requireNonNull(s); + String[] indexes = s.split(" "); + HashSet hashSet = new HashSet<>(); + for (String t : indexes) { + int n = Integer.parseInt(t); + if (!hashSet.contains(n)) { + hashSet.add(n); + } else { + return false; + } + } + return true; + } + + /** + * Returns true if every entry in {@code s} represents a non-zero unsigned integer + * e.g. "5 6 7 8" + * Will return false for any other non-null string input + * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "1 a" (contains letters) + * "1, 2 3" (contains comma), "1 2 3" (multiple whitespaces between adjacent integers) + * + * @throws NullPointerException if {@code s} is null. + */ + public static boolean isAllNonZeroUnsignedInteger(String s) { + requireNonNull(s); + String[] indexes = s.split(" "); + boolean result = true; + + for (String t : indexes) { + result = result && isNonZeroUnsignedInteger(t); + } + return result; + } } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..51acb7789f7 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,6 +8,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -33,6 +34,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of events */ + ObservableList getFilteredEventList(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..5772c169e76 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -14,6 +14,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; import seedu.address.storage.Storage; @@ -40,7 +41,6 @@ public LogicManager(Model model, Storage storage) { @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); - CommandResult commandResult; Command command = addressBookParser.parseCommand(commandText); commandResult = command.execute(model); @@ -64,6 +64,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredEventList() { + return model.getFilteredEventList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..bea8e3069ff 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -5,7 +5,6 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -18,22 +17,20 @@ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book.\n" + + "Only 1 prefix for basic particular (n/, p/, e/, a/) can be provided\n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + PREFIX_ADDRESS + "ADDRESS \n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; + public static final String MESSAGE_SUCCESS = "NEW PERSON ADDED: %n%1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/CancelEventCommand.java b/src/main/java/seedu/address/logic/commands/CancelEventCommand.java new file mode 100644 index 00000000000..aa300b66e69 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CancelEventCommand.java @@ -0,0 +1,129 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; + +/** + * Deletes an event identified using it's displayed index from the address book + */ +public class CancelEventCommand extends Command { + + public static final String COMMAND_WORD = "cancelevent"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the event identified by the index number(s) used in the displayed event\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + "1\n" + + "OR\n" + + "Parameters: INDEX... (all indexes must be unique and positive integers)" + + "Example: " + COMMAND_WORD + "1 3 5"; + + public static final String MESSAGE_DELETE_EVENT_SUCCESS = "CANCELLED EVENT:%n%1$s"; + public static final String MESSAGE_DELETE_EVENTS_SUCCESS = "CANCELLED EVENTS:%n%1$s"; + + private final Index[] targetIndexArr; + private final Index targetIndex; + + /** + * Constructor for CancelEventCommand, for singular deletion + * + * @param targetIndex the event to be deleted + */ + public CancelEventCommand(Index targetIndex) { + this.targetIndex = targetIndex; + this.targetIndexArr = new Index[]{targetIndex}; + } + + /** + * Constructor for CancelEventCommand, for multiple deletions + * + * @param targetIndexArr the events to be deleted + */ + public CancelEventCommand(Index[] targetIndexArr) { + this.targetIndexArr = targetIndexArr; + this.targetIndex = targetIndexArr[0]; + } + + /** + * In the multiple deletion case, ensures all user input indexes are within range of the list. + * + * @param listSize the size of the last displayed list. + * @return true if every index is within the range of the list. + */ + private boolean checkIndexRange(int listSize) { + boolean result = true; + for (Index target : targetIndexArr) { + result = result && (target.getZeroBased() < listSize); + } + return result; + } + + /** + * Extracts the information before deletion for the success message. + * + * @param lastShownList the last displayed list. + * @return a string containing all the information of the event(s) to be deleted. + */ + private String extractDeletedInfo(List lastShownList) { + final StringBuilder deletedEventOrEvents = new StringBuilder(); + for (int i = 0; i < targetIndexArr.length; i++) { + Index target = targetIndexArr[i]; + Event eventToDelete = lastShownList.get(target.getZeroBased()); + if (i > 0) { + deletedEventOrEvents.append(System.lineSeparator()); + } + deletedEventOrEvents.append(eventToDelete); + } + return deletedEventOrEvents.toString(); + } + + /** + * Deletes the events specified in the targetIndexArr. + * A copy of targetIndexArr is created for defensive programming. + * targetIndexArrClone is sorted in descending order so that the deletion process will not delete the wrong event. + * Example: If index 1 is deleted first, the original index 2 becomes index 1. + * + * @param model the addressbook model + * @param lastShownList the last displayed person list + */ + private void deleteFromList(Model model, List lastShownList) { + Index[] targetIndexArrClone = targetIndexArr.clone(); + Arrays.sort(targetIndexArrClone); + for (Index target : targetIndexArrClone) { + Event eventToDelete = lastShownList.get(target.getZeroBased()); + model.deleteEvent(eventToDelete); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredEventList(); + int lastShownListSize = lastShownList.size(); + + if (!checkIndexRange(lastShownListSize)) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + String deletedEventOrEvents = extractDeletedInfo(lastShownList); + deleteFromList(model, lastShownList); + + return targetIndexArr.length == 1 + ? new CommandResult(String.format(MESSAGE_DELETE_EVENT_SUCCESS, deletedEventOrEvents)) + : new CommandResult(String.format(MESSAGE_DELETE_EVENTS_SUCCESS, deletedEventOrEvents)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CancelEventCommand // instanceof handles nulls + && targetIndex.equals(((CancelEventCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..1c7992d8198 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,7 +11,7 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "NUSocials has been cleared!"; @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..385d0865709 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -2,12 +2,15 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.List; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.event.Event; +import seedu.address.model.person.Name; import seedu.address.model.person.Person; /** @@ -18,30 +21,126 @@ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the person identified by the index number(s) used in the displayed person list.\n" + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + "Example: " + COMMAND_WORD + " 1\n" + + "OR\n" + + "Parameters: INDEX... (all indexes must be unique and positive integers)\n" + + "Example: " + COMMAND_WORD + " 1 3 5"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "DELETED PERSON: %n%1$s"; + public static final String MESSAGE_DELETE_MULTIPLE_PERSON_SUCCESS = "DELETED PERSONS: %n%1$s"; + + private final Index[] targetIndexArr; private final Index targetIndex; + /** + * Constructor for DeleteCommand, for singular deletion + * + * @param targetIndex the person to be deleted + */ public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; + this.targetIndexArr = new Index[]{targetIndex}; + } + + /** + * Constructor for DeleteCommand, for multiple deletions + * + * @param targetIndexArr the persons to be deleted + */ + public DeleteCommand(Index[] targetIndexArr) { + this.targetIndexArr = targetIndexArr; + this.targetIndex = targetIndexArr[0]; + } + + /** + * In the multiple deletion case, ensures all user input indexes are within range of the list. + * + * @param listSize the size of the last displayed list. + * @return true if every index is within the range of the list. + */ + private boolean checkIndexRange(int listSize) { + boolean result = true; + for (Index target : targetIndexArr) { + result = result && (target.getZeroBased() < listSize); + } + return result; + } + + /** + * Extracts the information before deletion for the success message. + * + * @param lastShownList the last displayed list. + * @return a string containing all the information of the person(s) to be deleted. + */ + private String extractDeletedInfo(List lastShownList) { + final StringBuilder deletedPersonOrPersons = new StringBuilder(); + for (int i = 0; i < targetIndexArr.length; i++) { + Index target = targetIndexArr[i]; + Person personToDelete = lastShownList.get(target.getZeroBased()); + if (i > 0) { + deletedPersonOrPersons.append(System.lineSeparator()); + } + deletedPersonOrPersons.append(personToDelete); + } + return deletedPersonOrPersons.toString(); + } + + /** + * Deletes the persons specified in the targetIndexArr. + * A copy of targetIndexArr is created for defensive programming. + * targetIndexArrClone is sorted in descending order so that the deletion process will not delete the wrong person. + * Example: If index 1 is deleted first, the original index 2 becomes index 1. + * + * @param model the addressbook model + * @param lastShownList the last displayed person list + * @param lastEventList the last display event list + */ + private void deleteFromList(Model model, List lastShownList, List lastEventList) { + Index[] targetIndexArrClone = targetIndexArr.clone(); + Arrays.sort(targetIndexArrClone); + for (Index target : targetIndexArrClone) { + Person personToDelete = lastShownList.get(target.getZeroBased()); + updateEvents(model, personToDelete, lastEventList); + model.deletePerson(personToDelete); + } + } + + private void updateEvents(Model model, Person person, List lastEventList) { + Name name = person.getName(); + + for (int i = 0; i < lastEventList.size(); i++) { + Event currEvent = lastEventList.get(i); + List editedParticipants = currEvent.getParticipants(); + if (editedParticipants.contains(name) && editedParticipants.size() == 1) { + model.deleteEvent(currEvent); + } else if (editedParticipants.contains(name) && editedParticipants.size() > 1) { + editedParticipants.remove(name); + Event editedEvent = new Event(currEvent.getEventName(), currEvent.getEventInfo(), editedParticipants, + currEvent.getDateTime()); + model.setEvent(currEvent, editedEvent); + } + } } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); List lastShownList = model.getFilteredPersonList(); + List lastEventList = model.getFilteredEventList(); + int lastShownListSize = lastShownList.size(); - if (targetIndex.getZeroBased() >= lastShownList.size()) { + if (!checkIndexRange(lastShownListSize)) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } + String deletedPersonOrPersons = extractDeletedInfo(lastShownList); + deleteFromList(model, lastShownList, lastEventList); - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + return targetIndexArr.length == 1 + ? new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, deletedPersonOrPersons)) + : new CommandResult(String.format(MESSAGE_DELETE_MULTIPLE_PERSON_SUCCESS, deletedPersonOrPersons)); } @Override diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..7c4eb1af51b 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -5,20 +5,18 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import java.util.Collections; -import java.util.HashSet; +import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.Set; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.event.Event; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -34,20 +32,20 @@ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " + + "by the index number used in the displayed person list.\n" + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_ADDRESS + "ADDRESS]\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "EDITED PERSON:%n%1$s"; + public static final String MESSAGE_NOT_EDITED_OR_INVALID = "At least one field to edit must be provided." + + "The edit command does not accept tags."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; private final Index index; @@ -69,6 +67,7 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); List lastShownList = model.getFilteredPersonList(); + List lastEventList = model.getFilteredEventList(); if (index.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); @@ -82,24 +81,59 @@ public CommandResult execute(Model model) throws CommandException { } model.setPerson(personToEdit, editedPerson); + updateEvent(model, personToEdit, editedPerson, lastEventList); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } + /** + * Updates the event to reflect the new participants + * @param model the current model + * @param personToEdit the person that is being edited + * @param editedPerson the person that has been edited + * @param lastEventList the event list + */ + public void updateEvent(Model model, Person personToEdit, Person editedPerson, List lastEventList) { + Name oldName = personToEdit.getName(); + Name newName = editedPerson.getName(); + + for (int i = 0; i < lastEventList.size(); i++) { + Event currEvent = lastEventList.get(i); + List participants = currEvent.getParticipants(); + if (participants.remove(oldName)) { + participants.add(newName); + } + model.setEvent(currEvent, new Event(currEvent.getEventName(), currEvent.getEventInfo(), participants, + currEvent.getDateTime())); + } + } + /** * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. */ private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { assert personToEdit != null; + List updatedEducations = editPersonDescriptor.getEducations().isEmpty() + ? personToEdit.getEducations() + : editPersonDescriptor.getEducations(); + List updatedCcas = editPersonDescriptor.getCcas().isEmpty() + ? personToEdit.getCcas() + : editPersonDescriptor.getCcas(); + List updatedInternships = editPersonDescriptor.getInternships().isEmpty() + ? personToEdit.getInternships() + : editPersonDescriptor.getInternships(); + List updatedModules = editPersonDescriptor.getModules().isEmpty() + ? personToEdit.getModules() + : editPersonDescriptor.getModules(); Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, + updatedEducations, updatedInternships, updatedModules, updatedCcas); } @Override @@ -129,7 +163,10 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; - private Set tags; + private List educations = new ArrayList<>(); + private List internships = new ArrayList<>(); + private List modules = new ArrayList<>(); + private List ccas = new ArrayList<>(); public EditPersonDescriptor() {} @@ -142,14 +179,21 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); - setTags(toCopy.tags); + setEducations(toCopy.educations); + setInternships(toCopy.internships); + setModules(toCopy.modules); + setCcas(toCopy.ccas); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, address) + && educations.isEmpty() + && internships.isEmpty() + && modules.isEmpty() + && ccas.isEmpty(); } public void setName(Name name) { @@ -184,21 +228,36 @@ public Optional
getAddress() { return Optional.ofNullable(address); } - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setEducations(List tags) { + this.educations = tags; } - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public List getEducations() { + return educations; + } + + public void setInternships(List tags) { + this.internships = tags; + } + + public List getInternships() { + return internships; + } + + public void setModules(List tags) { + this.modules = tags; + } + + public List getModules() { + return modules; + } + + public void setCcas(List tags) { + this.ccas = tags; + } + + public List getCcas() { + return ccas; } @Override @@ -220,7 +279,10 @@ public boolean equals(Object other) { && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getEducations().equals(e.getEducations()) + && getInternships().equals(e.getInternships()) + && getModules().equals(e.getModules()) + && getCcas().equals(e.getCcas()); } } } diff --git a/src/main/java/seedu/address/logic/commands/EventCommand.java b/src/main/java/seedu/address/logic/commands/EventCommand.java new file mode 100644 index 00000000000..8ac2f32d8ab --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EventCommand.java @@ -0,0 +1,124 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; + +public class EventCommand extends Command { + public static final String COMMAND_WORD = "event"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Tags an event to the person identified by the index " + + "number. \nAll prefix must be used and each prefix must be used only once!" + + "\nParameters: INDEX (must be a positive integer) " + PREFIX_EVENT_NAME + "EVENT NAME " + PREFIX_INFO + + "EVENT DETAILS " + PREFIX_DATE + "yyyy-MM-dd " + PREFIX_TIME + "HH:mm"; + public static final String MESSAGE_ARGUMENTS = "TAGGED THE FOLLOWING EVENT TO %1$s:" + "\n%2$s"; + public static final String MESSAGE_DUPLICATE_EVENT = "This event already exists in the address book"; + public static final String MESSAGE_TOO_MANY_PREFIXES = "At most one prefix for basic particulars can be provided"; + + + private final Index[] indexes; + private final EventName name; + private final Information information; + private final DateTime dateTime; + private final Set names; + + /** + * Constructor for EventCommand. + * @param indexes the indexes of the persons tagged to this event + * @param name the name of the event + * @param information the details of the event + * @param dateTime the date and time of the event + */ + public EventCommand(Index[] indexes, EventName name, Information information, DateTime dateTime) { + requireAllNonNull(indexes, name, information, dateTime); + + this.indexes = indexes; + this.name = name; + this.information = information; + this.dateTime = dateTime; + this.names = new HashSet<>(); + } + + /** + * In the multiple deletion case, ensures all user input indexes are within range of the list. + * + * @param listSize the size of the last displayed list. + * @return true if every index is within the range of the list. + */ + private boolean checkIndexRange(int listSize) { + boolean result = true; + for (Index target : indexes) { + result = result && (target.getZeroBased() < listSize); + } + return result; + } + + private Event createEvent(List lst, Index[] indexes) { + Set tempList = new HashSet<>(); + for (Index currIndex : indexes) { + tempList.add(lst.get(currIndex.getZeroBased()).getName()); + } + return new Event(this.name, this.information, new ArrayList<>(tempList), this.dateTime); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + int listSize = lastShownList.size(); + + if (!checkIndexRange(listSize)) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + // Creating the event with the list of participants provided + Event currEvent = createEvent(lastShownList, indexes); + + // Tagging the event to each participant + for (Index currIndex : indexes) { + Person currPerson = lastShownList.get(currIndex.getZeroBased()); + names.add(currPerson.getName().toString()); + } + if (model.hasEvent(currEvent)) { + throw new CommandException(MESSAGE_DUPLICATE_EVENT); + } + + model.addEvent(currEvent); + model.updateFilteredEventList(Model.PREDICATE_SHOW_UPCOMING_EVENTS); + return new CommandResult(String.format(MESSAGE_ARGUMENTS, names, currEvent)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof EventCommand)) { + return false; + } + + EventCommand e = (EventCommand) other; + return name.equals(e.name) && information.equals(e.information) + && Arrays.equals(indexes, e.indexes) && dateTime.equals(e.dateTime); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..629e3cdf305 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,42 +1,253 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; import seedu.address.commons.core.Messages; +import seedu.address.commons.util.CollectionUtil; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Address; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; + /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Finds and lists all persons or events in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose fields contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Use find -s to find all person where fields must contain all the keywords provided.\n" + + "Only 1 prefix for basic particular (n/, p/, e/, a/) can be provided\n" + + "Parameters: " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_EDUCATION + "EDUCATION]" + + "[" + PREFIX_INTERNSHIP + "INTERNSHIP]" + + "[" + PREFIX_MODULE + "MODULE]" + + "[" + PREFIX_CCA + "CCA]\n" + + "Example: " + COMMAND_WORD + " n/alice e/gmail.com i/Facebook"; + + public static final String MESSAGE_NO_PARAMETERS = "At least one field must be provided."; + public static final String MESSAGE_TOO_MANY_PREFIXES = "At most one prefix for basic particulars can be provided"; - private final NameContainsKeywordsPredicate predicate; + private final Predicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(Predicate predicate) { this.predicate = predicate; } @Override public CommandResult execute(Model model) { requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); } @Override public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check + if (other == this) { + return true; + } + + if (!(other instanceof FindCommand)) { + return false; + } + + FindCommand e = (FindCommand) other; + System.out.println(predicate.equals(e.predicate)); + return predicate.equals(e.predicate); + } + + /** + * Stores the details to find the person with. Each non-empty field value will + * be added to the predicate to filter the contact list + */ + public static class FindPersonDescriptor { + private List names; + private List phones; + private List emails; //This is String to facilitate the use of partial emails for searching. + private List
addresses; + private List educations; + private List internships; + private List modules; + private List ccas; + + + public FindPersonDescriptor() { + } + + /** + * Copy constructor. + */ + public FindPersonDescriptor(FindPersonDescriptor toCopy) { + setNames(toCopy.names); + setPhones(toCopy.phones); + setEmails(toCopy.emails); + setAddresses(toCopy.addresses); + setEducations(toCopy.educations); + setInternships(toCopy.internships); + setModules(toCopy.modules); + setCcas(toCopy.ccas); + } + + /** + * Returns true if at least one field is searched for. + */ + public boolean isAnyFieldPresent() { + return CollectionUtil.isAnyNonNull(names, phones, emails, addresses, + educations, internships, modules, ccas); + } + + public void setNames(List names) { + this.names = names; + } + + public Optional> getNames() { + return Optional.ofNullable(names); + } + + public Optional> getStringNames() { + return getNames().map(names -> + names.stream().map(name -> name.fullName).collect(Collectors.toList())); + } + + public void setPhones(List phones) { + this.phones = phones; + } + + public Optional> getPhones() { + return Optional.ofNullable(phones); + } + + public Optional> getStringPhones() { + return getPhones().map(list -> + list.stream().map(name -> name.value).collect(Collectors.toList())); + } + + public void setEmails(List emails) { + this.emails = emails; + } + + public Optional> getEmails() { + return Optional.ofNullable(emails); + } + + public Optional> getStringEmails() { + return getEmails(); + } + + public void setAddresses(List
addresses) { + this.addresses = addresses; + } + + public Optional> getAddresses() { + return Optional.ofNullable(addresses); + } + + public Optional> getStringAddresses() { + return getAddresses().map(list -> + list.stream().map(name -> name.value).collect(Collectors.toList())); + } + + //Methods for getting and setting tag lists + + public void setEducations(List tags) { + this.educations = tags; + } + + public Optional> getEducations() { + return Optional.ofNullable(educations); + } + + public Optional> getStringEducations() { + return getEducations().map(list -> + list.stream().map(Tag::getTagString).collect(Collectors.toList())); + } + + public void setInternships(List tags) { + this.internships = tags; + } + + public Optional> getInternships() { + return Optional.ofNullable(internships); + } + + public Optional> getStringInternships() { + return getInternships().map(list -> + list.stream().map(Tag::getTagString).collect(Collectors.toList())); + } + + public void setModules(List tags) { + this.modules = tags; + } + + public Optional> getModules() { + return Optional.ofNullable(modules); + } + + public Optional> getStringModules() { + return getModules().map(list -> + list.stream().map(Tag::getTagString).collect(Collectors.toList())); + } + + public void setCcas(List tags) { + this.ccas = tags; + } + + public Optional> getCcas() { + return Optional.ofNullable(ccas); + } + + public Optional> getStringCcas() { + return getCcas().map(list -> + list.stream().map(Tag::getTagString).collect(Collectors.toList())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindCommand.FindPersonDescriptor)) { + return false; + } + + // state check + FindPersonDescriptor e = (FindPersonDescriptor) other; + + return getNames().equals(e.getNames()) + && getPhones().equals(e.getPhones()) + && getEmails().equals(e.getEmails()) + && getAddresses().equals(e.getAddresses()); + } } } + + diff --git a/src/main/java/seedu/address/logic/commands/FindEventCommand.java b/src/main/java/seedu/address/logic/commands/FindEventCommand.java new file mode 100644 index 00000000000..f12fa8351fe --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindEventCommand.java @@ -0,0 +1,147 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.model.Model; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; +import seedu.address.model.person.Name; + +public class FindEventCommand extends Command { + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all events whose details contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " name/lunch appointment"; + + private final Predicate predicate; + + public FindEventCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + model.updateFilteredEventList(predicate); + + return new CommandResult( + String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindEventCommand // instanceof handles nulls + && predicate.equals(((FindEventCommand) other).predicate)); // state check + } + + /** + * Stores the details to find an event with. Each non-empty field value will + * be added to the predicate to filter the contact list + */ + public static class FindEventDescriptor { + private List names; + private List informations; + private List participants; + private List dateTimes; + + public FindEventDescriptor() { + } + + /** + * Copy constructor. + */ + public FindEventDescriptor(FindEventCommand.FindEventDescriptor toCopy) { + setNames(toCopy.names); + setInformations(toCopy.informations); + setParticipants(toCopy.participants); + setDateTimes(toCopy.dateTimes); + } + + /** + * Returns true if at least one field is searched for. + */ + public boolean isAnyFieldPresent() { + return CollectionUtil.isAnyNonNull(names, informations, participants, dateTimes); + } + + public void setNames(List names) { + this.names = names; + } + + public Optional> getNames() { + return Optional.ofNullable(names); + } + + public Optional> getStringNames() { + return getNames().map(names -> + names.stream().map(name -> name.value).collect(Collectors.toList())); + } + + public void setInformations(List informations) { + this.informations = informations; + } + + public Optional> getInformations() { + return Optional.ofNullable(informations); + } + + public Optional> getStringInformations() { + return getInformations().map(list -> + list.stream().map(info -> info.value).collect(Collectors.toList())); + } + + public void setParticipants(List participants) { + this.participants = participants; + } + + public Optional> getParticipants() { + return Optional.ofNullable(participants); + } + + public Optional> getStringParticipants() { + return getParticipants().map(list -> + list.stream().map(participants -> participants.fullName).collect(Collectors.toList())); + } + + public void setDateTimes(List dateTimes) { + this.dateTimes = dateTimes; + } + + public Optional> getDateTimes() { + return Optional.ofNullable(dateTimes); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindEventCommand.FindEventDescriptor)) { + return false; + } + + // state check + FindEventCommand.FindEventDescriptor e = (FindEventCommand.FindEventDescriptor) other; + + return getNames().equals(e.getNames()) + && getNames().equals(e.getInformations()) + && getInformations().equals(e.getParticipants()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..59b1684a3ca 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -12,13 +12,14 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "LISTED ALL PERSONS"; @Override public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + + return new CommandResult(MESSAGE_SUCCESS, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java b/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java new file mode 100644 index 00000000000..7f4067d9661 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveTagCommand.java @@ -0,0 +1,144 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +public class RemoveTagCommand extends Command { + + public static final String COMMAND_WORD = "removetag"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes the specified tags from the person identified " + + "by the index number used in the last person listing. " + + "If there are non-existing tags, the command will not work.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_EDUCATION + "[EDUCATION]... " + + PREFIX_CCA + "[CCA]... " + + PREFIX_INTERNSHIP + "[INTERNSHIP]... " + + PREFIX_MODULE + "[MODULE]..." + + "\nExample: " + COMMAND_WORD + " 1 " + + PREFIX_EDUCATION + "Computer Science " + + PREFIX_CCA + "Tennis " + + PREFIX_INTERNSHIP + "Grab " + + PREFIX_MODULE + "CS2040S"; + public static final String MESSAGE_NO_PARAMETERS = "At least 1 field must be used and not blank."; + public static final String MESSAGE_NO_TAG_FOUND = "Deleting a non-existent tag cannot be done. " + + "All specified tags must be present in %1$s"; + public static final String MESSAGE_ARGUMENTS = "TAGS REMOVED FROM %1$s \n-Current Tags- \nEducation: %2$s " + + "\nInternship: %3$s " + "\nModule: %4$s " + "\nCCA: %5$s"; + + private final Index index; + private final List educations; + private final List internships; + private final List modules; + private final List ccas; + + /** + * @param index of the person in the filtered person list to tag + * @param education of the person to be updated to + * @param internship of the person to be updated to + * @param module of the person to be updated to + * @param cca of the person to be updated to + */ + public RemoveTagCommand(Index index, List education, List internship, List module, List cca) { + requireAllNonNull(index, education, internship, module, cca); + + this.index = index; + this.educations = education; + this.internships = internship; + this.modules = module; + this.ccas = cca; + } + + private boolean isAllEmpty() { + return educations.isEmpty() && internships.isEmpty() && modules.isEmpty() && ccas.isEmpty(); + } + + /** + * Generates a command execution success message based on whether + * the tag is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + return String.format(MESSAGE_ARGUMENTS, + personToEdit.getName(), + personToEdit.getEducations(), + personToEdit.getInternships(), + personToEdit.getModules(), + personToEdit.getCcas()); + } + + private boolean isAllTagsPresent(Set eduSet, Set internSet, Set moduleSet, Set ccaSet) { + return eduSet.containsAll(educations) && internSet.containsAll(internships) + && moduleSet.containsAll(modules) && moduleSet.containsAll(modules) && ccaSet.containsAll(ccas); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + if (isAllEmpty()) { + throw new CommandException(MESSAGE_NO_PARAMETERS); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + Set currEducations = new HashSet<>(personToEdit.getEducations()); + Set currInternships = new HashSet<>(personToEdit.getInternships()); + Set currModules = new HashSet<>(personToEdit.getModules()); + Set currCcas = new HashSet<>(personToEdit.getCcas()); + + if (!isAllTagsPresent(currEducations, currInternships, currModules, currCcas)) { + throw new CommandException(String.format(MESSAGE_NO_TAG_FOUND, personToEdit.getName())); + } + currEducations.removeAll(educations); + currInternships.removeAll(internships); + currModules.removeAll(modules); + currCcas.removeAll(ccas); + + + Person editedPerson = new Person( + personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), personToEdit.getAddress(), + new ArrayList<>(currEducations), new ArrayList<>(currInternships), + new ArrayList<>(currModules), new ArrayList<>(currCcas)); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(generateSuccessMessage(editedPerson)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemoveTagCommand)) { + return false; + } + + // state check + RemoveTagCommand e = (RemoveTagCommand) other; + return index.equals(e.index) && educations.equals(e.educations) && internships.equals(e.internships) + && modules.equals(e.modules) && ccas.equals(e.ccas); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ShowEventsCommand.java b/src/main/java/seedu/address/logic/commands/ShowEventsCommand.java new file mode 100644 index 00000000000..d19dd718d61 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowEventsCommand.java @@ -0,0 +1,52 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.event.Event; + +public class ShowEventsCommand extends Command { + + public static final String COMMAND_WORD = "showevents"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Filters the events list to show either upcoming, past or all events. \n" + + "By defualt shows all events, unless one of the parameter flags is specified.\n" + + "Parameter flags: " + + "[-upcoming] [-past]\n" + + "Example: " + COMMAND_WORD + "-upcoming"; + + private final Predicate predicate; + + public ShowEventsCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + + model.updateFilteredEventList(predicate); + + return new CommandResult( + String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ShowEventsCommand)) { + return false; + } + + ShowEventsCommand e = (ShowEventsCommand) other; + return predicate.equals(e.predicate); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/TagCommand.java b/src/main/java/seedu/address/logic/commands/TagCommand.java new file mode 100644 index 00000000000..0b981a9ac7b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TagCommand.java @@ -0,0 +1,136 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * Tags additional information of an existing person in the address book. + */ +public class TagCommand extends Command { + public static final String COMMAND_WORD = "tag"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Tags additional information to the person identified " + + "by the index number used in the last person listing. " + + "If there are existing tags, the new tags will be appended to them.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_EDUCATION + "[EDUCATION]... " + + PREFIX_CCA + "[CCA]... " + + PREFIX_INTERNSHIP + "[INTERNSHIP]... " + + PREFIX_MODULE + "[MODULE]..." + + "\nExample: " + COMMAND_WORD + " 1 " + + PREFIX_EDUCATION + "Computer Science " + + PREFIX_CCA + "Bouldering " + + PREFIX_INTERNSHIP + "GIC " + + PREFIX_MODULE + "CS2040S"; + public static final String MESSAGE_NO_PARAMETERS = "At least 1 field must be used and not blank."; + public static final String MESSAGE_ARGUMENTS = "TAGGED TO %1$s \n-Current Tags- \nEducation: %2$s" + + "\nInternship: %3$s " + "\nModule: %4$s " + "\nCCA: %5$s"; + + private final Index index; + private final List educations; + private final List internships; + private final List modules; + private final List ccas; + + /** + * @param index of the person in the filtered person list to tag + * @param education of the person to be updated to + * @param internship of the person to be updated to + * @param module of the person to be updated to + * @param cca of the person to be updated to + */ + public TagCommand(Index index, List education, List internship, List module, List cca) { + requireAllNonNull(index, education, internship, module, cca); + + this.index = index; + this.educations = education; + this.internships = internship; + this.modules = module; + this.ccas = cca; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + if (isAllEmpty()) { + throw new CommandException(MESSAGE_NO_PARAMETERS); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + Set currEducations = new HashSet<>(personToEdit.getEducations()); + Set currInternships = new HashSet<>(personToEdit.getInternships()); + Set currModules = new HashSet<>(personToEdit.getModules()); + Set currCcas = new HashSet<>(personToEdit.getCcas()); + + currEducations.addAll(educations); + currInternships.addAll(internships); + currModules.addAll(modules); + currCcas.addAll(ccas); + + Person editedPerson = new Person( + personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), personToEdit.getAddress(), + new ArrayList<>(currEducations), new ArrayList<>(currInternships), + new ArrayList<>(currModules), new ArrayList<>(currCcas)); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); + + + return new CommandResult(generateSuccessMessage(editedPerson)); + } + + private boolean isAllEmpty() { + return educations.isEmpty() && internships.isEmpty() && modules.isEmpty() && ccas.isEmpty(); + } + + /** + * Generates a command execution success message based on whether + * the tag is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + return String.format(MESSAGE_ARGUMENTS, + personToEdit.getName(), + personToEdit.getEducations(), + personToEdit.getInternships(), + personToEdit.getModules(), + personToEdit.getCcas()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TagCommand)) { + return false; + } + + // state check + TagCommand e = (TagCommand) other; + return index.equals(e.index) && educations.equals(e.educations) && internships.equals(e.internships) + && modules.equals(e.modules) && ccas.equals(e.ccas); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..29993d526f6 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -5,9 +5,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; @@ -17,7 +15,6 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new AddCommand object @@ -31,7 +28,7 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { @@ -42,9 +39,8 @@ public AddCommand parse(String args) throws ParseException { Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, address); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..52baba4794e 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,14 +7,19 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.CancelEventCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EventCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.RemoveTagCommand; +import seedu.address.logic.commands.ShowEventsCommand; +import seedu.address.logic.commands.TagCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -53,6 +58,9 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case TagCommand.COMMAND_WORD: + return new TagCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); @@ -62,6 +70,18 @@ public Command parseCommand(String userInput) throws ParseException { case ListCommand.COMMAND_WORD: return new ListCommand(); + case ShowEventsCommand.COMMAND_WORD: + return new ShowEventsCommandParser().parse(arguments); + + case RemoveTagCommand.COMMAND_WORD: + return new RemoveTagCommandParser().parse(arguments); + + case EventCommand.COMMAND_WORD: + return new EventCommandParser().parse(arguments); + + case CancelEventCommand.COMMAND_WORD: + return new CancelEventCommandParser().parse(arguments); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..b14e0745439 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -1,10 +1,23 @@ package seedu.address.logic.parser; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import seedu.address.logic.commands.EventCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.parser.exceptions.ParseException; + /** * Tokenizes arguments string of the form: {@code preamble value value ...}
* e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
@@ -37,14 +50,21 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { */ private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { return Arrays.stream(prefixes) - .flatMap(prefix -> findPrefixPositions(argsString, prefix).stream()) + .flatMap(prefix -> { + try { + return findPrefixPositions(argsString, prefix).stream(); + } catch (ParseException e) { + e.initCause(new ParseException(FindCommand.MESSAGE_TOO_MANY_PREFIXES)); + } + return null; + }) .collect(Collectors.toList()); } /** * {@see findAllPrefixPositions} */ - private static List findPrefixPositions(String argsString, Prefix prefix) { + private static List findPrefixPositions(String argsString, Prefix prefix) throws ParseException { List positions = new ArrayList<>(); int prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), 0); @@ -54,6 +74,16 @@ private static List findPrefixPositions(String argsString, Prefi prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), prefixPosition); } + if ((prefix.equals(PREFIX_NAME) || prefix.equals(PREFIX_ADDRESS) + || prefix.equals(PREFIX_EMAIL) || prefix.equals(PREFIX_PHONE)) + && positions.size() > 1) { + throw new ParseException(FindCommand.MESSAGE_TOO_MANY_PREFIXES); + } + + if ((prefix.equals(PREFIX_EVENT_NAME) || prefix.equals(PREFIX_INFO) || prefix.equals(PREFIX_DATE) + || prefix.equals(PREFIX_TIME)) && positions.size() > 1) { + throw new ParseException(EventCommand.MESSAGE_TOO_MANY_PREFIXES); + } return positions; } diff --git a/src/main/java/seedu/address/logic/parser/CancelEventCommandParser.java b/src/main/java/seedu/address/logic/parser/CancelEventCommandParser.java new file mode 100644 index 00000000000..c66f391d784 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CancelEventCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CancelEventCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new CancelEventCommand object + */ +public class CancelEventCommandParser implements Parser { + + /** + * Parses the give {@code String} of argument sin the context of the CancelEventCommand + * and returns a CancelEventCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CancelEventCommand parse(String args) throws ParseException { + try { + Index[] indexes = ParserUtil.parseIndexes(args); + return new CancelEventCommand(indexes); + } catch (ParseException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CancelEventCommand.MESSAGE_USAGE), e); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..eb28fc83742 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,6 +10,16 @@ public class CliSyntax { public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_INTERNSHIP = new Prefix("i/"); + public static final Prefix PREFIX_MODULE = new Prefix("m/"); + public static final Prefix PREFIX_CCA = new Prefix("c/"); + public static final Prefix PREFIX_EDUCATION = new Prefix("edu/"); + public static final Prefix PREFIX_EVENT_NAME = new Prefix("name/"); + public static final Prefix PREFIX_INFO = new Prefix("info/"); + public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_TIME = new Prefix("t/"); + public static final Prefix PREFIX_PARTICIPANTS = new Prefix("part/"); + public static final Prefix PREFIX_DATETIME = new Prefix("dt/"); + } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 522b93081cc..c0704ae5256 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -18,8 +18,8 @@ public class DeleteCommandParser implements Parser { */ public DeleteCommand parse(String args) throws ParseException { try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + Index[] indexes = ParserUtil.parseIndexes(args); + return new DeleteCommand(indexes); } catch (ParseException pe) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..24944d6f718 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,15 +3,15 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; +import java.util.List; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditCommand; @@ -32,7 +32,8 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_CCA, PREFIX_EDUCATION, PREFIX_MODULE, PREFIX_INTERNSHIP); Index index; @@ -55,20 +56,38 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + if (argMultimap.getValue(PREFIX_CCA).isPresent()) { + List cca = ParserUtil.parseTagsForEdit(argMultimap.getAllValues(PREFIX_CCA), Tag.CCA); + editPersonDescriptor.setCcas(cca); + } + if (argMultimap.getValue(PREFIX_EDUCATION).isPresent()) { + List education = ParserUtil.parseTagsForEdit(argMultimap.getAllValues(PREFIX_EDUCATION), + Tag.EDUCATION); + editPersonDescriptor.setEducations(education); + } + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + List module = ParserUtil.parseTagsForEdit(argMultimap.getAllValues(PREFIX_MODULE), Tag.MODULE); + editPersonDescriptor.setModules(module); + } + if (argMultimap.getValue(PREFIX_INTERNSHIP).isPresent()) { + List internship = ParserUtil.parseTagsForEdit(argMultimap.getAllValues(PREFIX_INTERNSHIP), + Tag.INTERNSHIP); + editPersonDescriptor.setInternships(internship); + } if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED_OR_INVALID); } return new EditCommand(index, editPersonDescriptor); } - /** + /* * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. * If {@code tags} contain only one element which is an empty string, it will be parsed into a * {@code Set} containing zero tags. */ + /* private Optional> parseTagsForEdit(Collection tags) throws ParseException { assert tags != null; @@ -78,5 +97,5 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; return Optional.of(ParserUtil.parseTags(tagSet)); } - + */ } diff --git a/src/main/java/seedu/address/logic/parser/EventCommandParser.java b/src/main/java/seedu/address/logic/parser/EventCommandParser.java new file mode 100644 index 00000000000..5b52a631064 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EventCommandParser.java @@ -0,0 +1,62 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EventCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; + +/** + * Parses input arguments and creates a new {@Code EventCommand} object + */ +public class EventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EventCommand + * and returns an EventCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EventCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_EVENT_NAME, PREFIX_INFO, + PREFIX_TIME, PREFIX_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_EVENT_NAME, PREFIX_INFO, PREFIX_TIME, PREFIX_DATE)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EventCommand.MESSAGE_USAGE)); + } + + Index[] indexes; + try { + indexes = ParserUtil.parseIndexes(argMultimap.getPreamble()); + } catch (ParseException e) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EventCommand.MESSAGE_USAGE), e); + } + + EventName eventName = ParserUtil.parseEventName(argMultimap.getValue(PREFIX_EVENT_NAME).get()); + Information info = ParserUtil.parseInfo(argMultimap.getValue(PREFIX_INFO).get()); + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE).get(), + argMultimap.getValue(PREFIX_TIME).get()); + + return new EventCommand(indexes, eventName, info, dateTime); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/FindAndPredicateParser.java b/src/main/java/seedu/address/logic/parser/FindAndPredicateParser.java new file mode 100644 index 00000000000..dc33857f628 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindAndPredicateParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.FindCommand; +import seedu.address.model.person.AddressContainsKeywordsPredicateAnd; +import seedu.address.model.person.CcaContainsKeywordsPredicateAnd; +import seedu.address.model.person.EducationContainsKeywordsPredicateAnd; +import seedu.address.model.person.EmailContainsKeywordsPredicateAnd; +import seedu.address.model.person.InternshipContainsKeywordsPredicateAnd; +import seedu.address.model.person.ModuleContainsKeywordsPredicateAnd; +import seedu.address.model.person.NameContainsKeywordsPredicateAnd; +import seedu.address.model.person.Person; +import seedu.address.model.person.PhoneContainsKeywordsPredicateAnd; + +public class FindAndPredicateParser { + + /** + * Parses a FindPersonDescriptor into a single predicate that is true if any of the predicates in each field + * of the descriptor are satisfied. A field is satisfied if any item from the list appears. The combined predicate + * is satisfied if all the fields are satisfied. + * + * @param personDescriptor an object describing the predicate list for each field. + * @return a FindCommand to be executed. + */ + public FindCommand parse(FindCommand.FindPersonDescriptor personDescriptor) { + List> predicateList = new ArrayList<>(); + + personDescriptor.getStringNames().ifPresent(names -> + predicateList.add(new NameContainsKeywordsPredicateAnd(names))); + personDescriptor.getStringPhones().ifPresent(phones -> + predicateList.add(new PhoneContainsKeywordsPredicateAnd(phones))); + personDescriptor.getStringEmails().ifPresent(emails -> + predicateList.add(new EmailContainsKeywordsPredicateAnd(emails))); + personDescriptor.getStringAddresses().ifPresent(list -> + predicateList.add(new AddressContainsKeywordsPredicateAnd(list))); + + personDescriptor.getStringEducations().ifPresent(list -> + predicateList.add(new EducationContainsKeywordsPredicateAnd(list))); + personDescriptor.getStringInternships().ifPresent(list -> + predicateList.add(new InternshipContainsKeywordsPredicateAnd(list))); + personDescriptor.getStringModules().ifPresent(list -> + predicateList.add(new ModuleContainsKeywordsPredicateAnd(list))); + personDescriptor.getStringCcas().ifPresent(list -> + predicateList.add(new CcaContainsKeywordsPredicateAnd(list))); + + Predicate predicate = predicateList.stream().reduce(x->true, Predicate::and); + + return new FindCommand(predicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..65cd22c3654 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,33 +1,133 @@ package seedu.address.logic.parser; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INFO; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PARTICIPANTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import java.util.Arrays; +import java.util.List; +import seedu.address.logic.commands.Command; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindCommand.FindPersonDescriptor; +import seedu.address.logic.commands.FindEventCommand; +import seedu.address.logic.commands.FindEventCommand.FindEventDescriptor; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.tag.Tag; + + /** * Parses input arguments and creates a new FindCommand object */ -public class FindCommandParser implements Parser { +public class FindCommandParser implements Parser { + + public static final String DATE_TIME_FORMAT = "Please provide the date followed by time\n" + + "Eg: find -e dt/2022-08-08 03:00"; + + public static final String EMPTY_EMAIL = "Please provide a valid string for email."; /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format + * + * @return a FindCommand object that will execute the search. + * @throws ParseException if the user input does not conform the expected format. */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + public Command parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_CCA, PREFIX_EDUCATION, PREFIX_MODULE, PREFIX_INTERNSHIP, PREFIX_EVENT_NAME, + PREFIX_INFO, PREFIX_PARTICIPANTS, PREFIX_DATETIME); + + boolean isAndSearch = false; + boolean isEventSearch = false; + + if (argMultimap.getPreamble().equals("-s")) { + isAndSearch = true; + } else if (argMultimap.getPreamble().equals("-e")) { + isEventSearch = true; + } else if (!argMultimap.getPreamble().equals("")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + if (!isEventSearch) { + FindPersonDescriptor personDescriptor = new FindCommand.FindPersonDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + personDescriptor.setNames(ParserUtil.parseNames(argMultimap.getAllValues(PREFIX_NAME))); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + personDescriptor.setPhones(ParserUtil.parsePhones(argMultimap.getAllValues(PREFIX_PHONE))); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + List emails = argMultimap.getAllValues(PREFIX_EMAIL); + if (emails.contains("")) { + throw new ParseException(EMPTY_EMAIL); + } + personDescriptor.setEmails(emails); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + personDescriptor.setAddresses(ParserUtil.parseAddresses(argMultimap.getAllValues(PREFIX_ADDRESS))); + } + if (argMultimap.getValue(PREFIX_CCA).isPresent()) { + List cca = ParserUtil.parseTagsForFind(argMultimap.getAllValues(PREFIX_CCA), Tag.CCA); + personDescriptor.setCcas(cca); + } + if (argMultimap.getValue(PREFIX_EDUCATION).isPresent()) { + List education = ParserUtil.parseTagsForFind(argMultimap.getAllValues(PREFIX_EDUCATION), + Tag.EDUCATION); + personDescriptor.setEducations(education); + } + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + List module = ParserUtil.parseTagsForFind(argMultimap.getAllValues(PREFIX_MODULE), Tag.MODULE); + personDescriptor.setModules(module); + } + if (argMultimap.getValue(PREFIX_INTERNSHIP).isPresent()) { + List internship = ParserUtil.parseTagsForFind(argMultimap.getAllValues(PREFIX_INTERNSHIP), + Tag.INTERNSHIP); + personDescriptor.setInternships(internship); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } + if (!personDescriptor.isAnyFieldPresent()) { + throw new ParseException(FindCommand.MESSAGE_NO_PARAMETERS); + } + + if (isAndSearch) { + return new FindAndPredicateParser().parse(personDescriptor); + } else { + return new FindOrPredicateParser().parse(personDescriptor); + } + } else { + FindEventDescriptor eventDescriptor = new FindEventCommand.FindEventDescriptor(); + if (argMultimap.getValue(PREFIX_EVENT_NAME).isPresent()) { + eventDescriptor.setNames(ParserUtil.parseEventNames(argMultimap.getAllValues(PREFIX_EVENT_NAME))); + } + if (argMultimap.getValue(PREFIX_INFO).isPresent()) { + eventDescriptor.setInformations(ParserUtil.parseInfos(argMultimap.getAllValues(PREFIX_INFO))); + } + if (argMultimap.getValue(PREFIX_PARTICIPANTS).isPresent()) { + eventDescriptor.setParticipants(ParserUtil.parseNames(argMultimap.getAllValues(PREFIX_PARTICIPANTS))); + } + if (argMultimap.getValue(PREFIX_DATETIME).isPresent()) { + eventDescriptor.setDateTimes(ParserUtil.parseDateTimes(argMultimap.getAllValues(PREFIX_DATETIME))); + } + + if (!eventDescriptor.isAnyFieldPresent()) { + throw new ParseException(FindCommand.MESSAGE_NO_PARAMETERS); + } + return new FindEventPredicateParser().parse(eventDescriptor); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/FindEventPredicateParser.java b/src/main/java/seedu/address/logic/parser/FindEventPredicateParser.java new file mode 100644 index 00000000000..b760d814c7b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindEventPredicateParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.FindEventCommand; +import seedu.address.model.event.DateTimePredicate; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventInfoContainsKeywordsPredicate; +import seedu.address.model.event.EventNameContainsKeywordsPredicate; +import seedu.address.model.event.EventParticipantsContainsKeywordsPredicate; + +public class FindEventPredicateParser { + /** + * Parses a FindEventDescriptor into a single predicate that is true if any of the predicates in each field + * of the descriptor are satisfied. + * + * @param eventDescriptor an object describing the predicate list for each field. + * @return a FindCommand to be executed. + */ + public FindEventCommand parse(FindEventCommand.FindEventDescriptor eventDescriptor) { + List> predicateList = new ArrayList<>(); + + eventDescriptor.getStringNames().ifPresent(names -> + predicateList.add(new EventNameContainsKeywordsPredicate(names))); + eventDescriptor.getStringInformations().ifPresent(infos -> + predicateList.add(new EventInfoContainsKeywordsPredicate(infos))); + eventDescriptor.getStringParticipants().ifPresent(parts -> + predicateList.add(new EventParticipantsContainsKeywordsPredicate(parts))); + eventDescriptor.getDateTimes().ifPresent(datetime -> + predicateList.add(new DateTimePredicate(datetime))); + + Predicate predicate = predicateList.stream().reduce(x->false, Predicate::or); + + return new FindEventCommand(predicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindOrPredicateParser.java b/src/main/java/seedu/address/logic/parser/FindOrPredicateParser.java new file mode 100644 index 00000000000..fa28c7ed633 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindOrPredicateParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindCommand.FindPersonDescriptor; +import seedu.address.model.person.AddressContainsKeywordsPredicateOr; +import seedu.address.model.person.CcaContainsKeywordsPredicateOr; +import seedu.address.model.person.EducationContainsKeywordsPredicateOr; +import seedu.address.model.person.EmailContainsKeywordsPredicateOr; +import seedu.address.model.person.InternshipContainsKeywordsPredicateOr; +import seedu.address.model.person.ModuleContainsKeywordsPredicateOr; +import seedu.address.model.person.NameContainsKeywordsPredicateOr; +import seedu.address.model.person.Person; +import seedu.address.model.person.PhoneContainsKeywordsPredicateOr; + +public class FindOrPredicateParser { + + + /** + * Parses a FindPersonDescriptor into a single predicate that is true if any of the predicates in each field + * of the descriptor are satisfied. A field is satisfied if any item from the list appears. The combined predicate + * is satisfied if any of the fields are satisfied. + * + * @param personDescriptor an object describing the predicte list for each field. + * @return a FindCommand to be executed. + */ + public FindCommand parse(FindPersonDescriptor personDescriptor) { + List> predicateList = new ArrayList<>(); + + personDescriptor.getStringNames().ifPresent(names -> + predicateList.add(new NameContainsKeywordsPredicateOr(names))); + personDescriptor.getStringPhones().ifPresent(phones -> + predicateList.add(new PhoneContainsKeywordsPredicateOr(phones))); + personDescriptor.getStringEmails().ifPresent(emails -> + predicateList.add(new EmailContainsKeywordsPredicateOr(emails))); + personDescriptor.getStringAddresses().ifPresent(list -> + predicateList.add(new AddressContainsKeywordsPredicateOr(list))); + + personDescriptor.getStringEducations().ifPresent(list -> + predicateList.add(new EducationContainsKeywordsPredicateOr(list))); + personDescriptor.getStringInternships().ifPresent(list -> + predicateList.add(new InternshipContainsKeywordsPredicateOr(list))); + personDescriptor.getStringModules().ifPresent(list -> + predicateList.add(new ModuleContainsKeywordsPredicateOr(list))); + personDescriptor.getStringCcas().ifPresent(list -> + predicateList.add(new CcaContainsKeywordsPredicateOr(list))); + + Predicate predicate = predicateList.stream().reduce(x->false, Predicate::or); + + return new FindCommand(predicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..aa955cf4965 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,15 +2,24 @@ import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -21,6 +30,9 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_INDEX_MULTIPLE = "All indexes must be unique" + + " and a non-zero unsigned integer."; + public static final String INVALID_TAGTYPE = "The tag type is invalid!"; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -35,6 +47,38 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws ParseException if the specified any of the indexes are invalid (not non-zero unsigned integer). + */ + public static Index[] parseIndexes(String oneBasedIndex) throws ParseException { + String trimmedIndex = oneBasedIndex.trim(); + boolean isMultipleIndex = StringUtil.containsMultipleIndex(trimmedIndex); + boolean isAllValidIntegers = isMultipleIndex && StringUtil.isAllNonZeroUnsignedInteger(trimmedIndex); + boolean isValidMultipleIndex = isAllValidIntegers && StringUtil.isAllUniqueIntegers(trimmedIndex); + + if (!isMultipleIndex && !StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } else if (isMultipleIndex && !isValidMultipleIndex) { + throw new ParseException(MESSAGE_INVALID_INDEX_MULTIPLE); + } + + return getIndexes(trimmedIndex); + } + + /** + * Transforms a string of valid one-based indexes into an array of {@code Index}. + */ + private static Index[] getIndexes(String trimmedIndex) { + String[] oneBasedArr = trimmedIndex.split(" "); + Index[] indexArr = new Index[oneBasedArr.length]; + for (int i = 0; i < oneBasedArr.length; i++) { + indexArr[i] = Index.fromOneBased(Integer.parseInt(oneBasedArr[i])); + } + return indexArr; + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -50,6 +94,21 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses a {@code List } into a {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException through parseName. + */ + public static List parseNames(List list) throws ParseException { + requireNonNull(list); + final Set set = new HashSet<>(); + for (String value : list) { + set.add(parseName(value.trim())); + } + return new ArrayList<>(set); + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. @@ -65,6 +124,21 @@ public static Phone parsePhone(String phone) throws ParseException { return new Phone(trimmedPhone); } + /** + * Parses a {@code List } into a {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException through parsePhone. + */ + public static List parsePhones(List list) throws ParseException { + requireNonNull(list); + final Set set = new HashSet<>(); + for (String value : list) { + set.add(parsePhone(value.trim())); + } + return new ArrayList<>(set); + } + /** * Parses a {@code String address} into an {@code Address}. * Leading and trailing whitespaces will be trimmed. @@ -80,6 +154,21 @@ public static Address parseAddress(String address) throws ParseException { return new Address(trimmedAddress); } + /** + * Parses a {@code List } into a {@code List
}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException through parseAddress. + */ + public static List
parseAddresses(List list) throws ParseException { + requireNonNull(list); + final Set
set = new HashSet<>(); + for (String value : list) { + set.add(parseAddress(value.trim())); + } + return new ArrayList<>(set); + } + /** * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. @@ -95,30 +184,215 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code List } into a {@code List}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { + public static List parseEmails(List list) throws ParseException { + requireNonNull(list); + final Set set = new HashSet<>(); + for (String value : list) { + for (String s: value.split(" ")) { + set.add(parseEmail(s.trim())); + } + } + return new ArrayList<>(set); + } + + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag, String type) throws ParseException { requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + String trimmedTag = tag.trim().toLowerCase(); + + switch (type) { + case Tag.EDUCATION: + if (!Education.isValidTagName(trimmedTag)) { + throw new ParseException(Education.MESSAGE_CONSTRAINTS); + } + return new Education(trimmedTag); + case Tag.INTERNSHIP: + if (!Internship.isValidTagName(trimmedTag)) { + throw new ParseException(Internship.MESSAGE_CONSTRAINTS); + } + return new Internship(trimmedTag); + case Tag.MODULE: + if (!Module.isValidTagName(trimmedTag)) { + throw new ParseException(Module.MESSAGE_CONSTRAINTS); + } + return new Module(trimmedTag); + case Tag.CCA: + if (!Cca.isValidTagName(trimmedTag)) { + throw new ParseException(Cca.MESSAGE_CONSTRAINTS); + } + return new Cca(trimmedTag); + default: + throw new ParseException(INVALID_TAGTYPE); + } + } + + /** + * Parses {@code Collection tags} into a {@code List}. + */ + public static List parseTags(Collection tags, String type) throws ParseException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName, type)); } - return new Tag(trimmedTag); + return new ArrayList<>(tagSet); } /** * Parses {@code Collection tags} into a {@code Set}. + * Returns an empty ArrayList if the tags list is [""], this is the case that the tag list is to be cleared. */ - public static Set parseTags(Collection tags) throws ParseException { + public static List parseTagsForEdit(Collection tags, String type) throws ParseException { requireNonNull(tags); final Set tagSet = new HashSet<>(); + // This is the case that the tag list is meant to be cleared + if (tags.size() == 1 && tags.contains("")) { + return new ArrayList<>(); + } for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + tagSet.add(parseTag(tagName, type)); + } + return new ArrayList<>(tagSet); + } + + /** + * Parses {@code Collection tags} into a {@code Set}. + * Returns a list with all tagNames split using whitespace and trimmed. + */ + public static List parseTagsForFind(Collection tags, String type) throws ParseException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName.trim(), type)); + } + return new ArrayList<>(tagSet); + } + + /** + * Parses {@code String name} into an {@code EventName}. + * Leading and trailing whitespaces will be trimmed. + */ + public static EventName parseEventName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!EventName.isValidEventName(trimmedName)) { + throw new ParseException(EventName.MESSAGE_CONSTRAINTS); + } + return new EventName(trimmedName); + } + + /** + * Parses a {@code List } into a {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if any single list item has more than one word. + */ + public static List parseEventNames(List list) throws ParseException { + requireNonNull(list); + final Set set = new HashSet<>(); + for (String value : list) { + set.add(parseEventName(value.trim())); + } + return new ArrayList<>(set); + } + + /** + * Parses {@code String info} into an {@code Information}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Information parseInfo(String info) throws ParseException { + requireNonNull(info); + String trimmedInfo = info.trim(); + if (!Information.isValidInformation(trimmedInfo)) { + throw new ParseException(Information.MESSAGE_CONSTRAINTS); + } + return new Information(trimmedInfo); + } + + /** + * Parses a {@code List } into a {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if any single list item has more than one word. + */ + public static List parseInfos(List list) throws ParseException { + requireNonNull(list); + final Set set = new HashSet<>(); + for (String value : list) { + set.add(parseInfo(value.trim())); + } + return new ArrayList<>(set); + } + + /** + * Parses {@code String date} and {@code String time} into a {@code DateTime}. + * Returns a DateTime object that contains formatted date and time of the event. + */ + public static DateTime parseDateTime(String date, String time) throws ParseException { + requireNonNull(date); + requireNonNull(time); + String trimmedDate = date.trim(); + String trimmedTime = time.trim(); + + if (!DateTime.isValidTime(trimmedTime)) { + throw new ParseException(DateTime.TIME_MESSAGE_CONSTRAINTS); + } else if (!DateTime.isValidDate(trimmedDate)) { + throw new ParseException(DateTime.DATE_MESSAGE_CONSTRAINTS); + } + + String[] tempDate = trimmedDate.split("-"); + String[] tempTime = trimmedTime.split(":"); + int year = Integer.parseInt(tempDate[0]); + int month = Integer.parseInt(tempDate[1]); + int day = Integer.parseInt(tempDate[2]); + int hour = Integer.parseInt(tempTime[0]); + int min = Integer.parseInt(tempTime[1]); + + + if (!DateTime.isValidDateTime(year, month, day, hour, min)) { + throw new ParseException(DateTime.DATETIME_MESSAGE_CONSTRAINTS); + } + + return new DateTime(year, month, day, hour, min); + } + + /** + * Parses string find prefix for date and time into a DateTime object + * @param dateTime + * @throws ParseException + */ + public static DateTime parseDateTimeForFind(String dateTime) throws ParseException { + String trimmedDateTime = dateTime.trim(); + if (trimmedDateTime.split(" ").length != 2) { + throw new ParseException(FindCommandParser.DATE_TIME_FORMAT); + } + return parseDateTime(trimmedDateTime.split(" ")[0], trimmedDateTime.split(" ")[1]); + } + + /** + * Parses a list of string DateTimes into a list of DateTime objects + * + * @param stringDateTimes + * @throws ParseException + */ + public static List parseDateTimes(List stringDateTimes) throws ParseException { + requireNonNull(stringDateTimes); + final Set set = new HashSet<>(); + for (String value : stringDateTimes) { + set.add(parseDateTimeForFind(value.trim())); } - return tagSet; + return new ArrayList<>(set); } } diff --git a/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java new file mode 100644 index 00000000000..cec21f1a4d5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveTagCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RemoveTagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new RemoveTagCommand object + */ +public class RemoveTagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveTagCommand + * and returns an RemoveTagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveTagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CCA, PREFIX_EDUCATION, + PREFIX_MODULE, PREFIX_INTERNSHIP); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveTagCommand.MESSAGE_USAGE), ive); + } + + List education = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_EDUCATION), Tag.EDUCATION); + List internship = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_INTERNSHIP), Tag.INTERNSHIP); + List module = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_MODULE), Tag.MODULE); + List cca = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_CCA), Tag.CCA); + + return new RemoveTagCommand(index, education, internship, module, cca); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ShowEventsCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowEventsCommandParser.java new file mode 100644 index 00000000000..41a6fe68ed1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ShowEventsCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_PAST_EVENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_UPCOMING_EVENTS; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.ShowEventsCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Event; + +public class ShowEventsCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ShowEventCommand + * and returns a ShowEventCommand object for execution. + * + * @return a ShowEventCommand object that will filter the events list. + * @throws ParseException if the user input does not conform the expected format, such as having an invalid flag. + */ + public ShowEventsCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args); + + Predicate predicate; + + if (argMultimap.getPreamble().equals("-upcoming")) { + predicate = PREDICATE_SHOW_UPCOMING_EVENTS; + } else if (argMultimap.getPreamble().equals("-past")) { + predicate = PREDICATE_SHOW_PAST_EVENTS; + } else if (argMultimap.getPreamble().equals("")) { + predicate = PREDICATE_SHOW_ALL_EVENTS; + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ShowEventsCommand.MESSAGE_USAGE)); + } + + return new ShowEventsCommand(predicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/TagCommandParser.java b/src/main/java/seedu/address/logic/parser/TagCommandParser.java new file mode 100644 index 00000000000..f2747d7169b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TagCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.TagCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + + +/** + * Parses input arguments and creates a new TagCommand object + */ +public class TagCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TagCommand + * and returns an TagCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TagCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CCA, PREFIX_EDUCATION, + PREFIX_MODULE, PREFIX_INTERNSHIP); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE), ive); + } + + List education = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_EDUCATION), Tag.EDUCATION); + List internship = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_INTERNSHIP), Tag.INTERNSHIP); + List module = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_MODULE), Tag.MODULE); + List cca = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_CCA), Tag.CCA); + + return new TagCommand(index, education, internship, module, cca); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..6c9209a129b 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -5,6 +5,8 @@ import java.util.List; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; +import seedu.address.model.event.UniqueEventList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -15,6 +17,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueEventList events; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -25,6 +28,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + events = new UniqueEventList(); } public AddressBook() {} @@ -54,6 +58,7 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setEvents(newData.getEventList()); } //// person-level operations @@ -74,6 +79,7 @@ public void addPerson(Person p) { persons.add(p); } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. @@ -106,15 +112,68 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + /** + * Replaces the contents of the event list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + this.events.setEvents(events); + } + + //// person-level operations + + /** + * Returns true if an event with the same identity as {@code event} exists in the address book. + */ + public boolean hasEvent(Event event) { + requireNonNull(event); + return events.contains(event); + } + + /** + * Adds an event to the address book. + * The event must not already exist in the address book. + */ + public void addEvent(Event e) { + events.add(e); + } + + /** + * Replaces the given event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the address book. + * The event identity of {@code editedEvent} must not be the same as another existing event in the address book. + */ + public void setEvent(Event target, Event editedEvent) { + requireNonNull(editedEvent); + + events.setEvent(target, editedEvent); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeEvent(Event key) { + events.remove(key); + } + + //// util methods + + @Override + public ObservableList getEventList() { + return events.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && persons.equals(((AddressBook) other).persons) + && events.equals(((AddressBook) other).events)); } @Override public int hashCode() { - return persons.hashCode(); + return persons.hashCode() + events.hashCode(); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..f207057e84c 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,10 +1,12 @@ package seedu.address.model; import java.nio.file.Path; +import java.time.LocalDateTime; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -14,6 +16,19 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_EVENTS = unused -> true; + + Predicate PREDICATE_SHOW_UPCOMING_EVENTS = event -> { + LocalDateTime now = LocalDateTime.now(); + return event.getDateTime().value.isAfter(now); + }; + + Predicate PREDICATE_SHOW_PAST_EVENTS = event -> { + LocalDateTime now = LocalDateTime.now(); + return event.getDateTime().value.isBefore(now); + }; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -76,12 +91,45 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + /** + * Returns true if an event with the same identity as {@code event} exists in the address book. + */ + boolean hasEvent(Event event); + + /** + * Deletes the given event. + * The event must exist in the address book. + */ + void deleteEvent(Event target); + + /** + * Adds the given event. + * {@code event} must not already exist in the address book. + */ + void addEvent(Event event); + + /** + * Replaces the given event {@code target} with {@code editedEvent}. + * {@code target} must exist in the address book. + * The event identity of {@code editedEvent} must not be the same as another existing event in the address book. + */ + void setEvent(Event target, Event editedEvent); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered event list */ + ObservableList getFilteredEventList(); + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Updates the filter of the filtered event list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredEventList(Predicate predicate); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..6d7771bb361 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,6 +11,7 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -22,6 +23,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredEvents; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,6 +36,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredEvents = new FilteredList<>(this.addressBook.getEventList()); } public ModelManager() { @@ -147,4 +150,40 @@ public boolean equals(Object obj) { && filteredPersons.equals(other.filteredPersons); } + //=========== EventBook ================================================================================= + + @Override + public boolean hasEvent(Event event) { + requireNonNull(event); + return addressBook.hasEvent(event); + } + + @Override + public void deleteEvent(Event target) { + addressBook.removeEvent(target); + } + + @Override + public void addEvent(Event event) { + addressBook.addEvent(event); + } + + @Override + public void setEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + addressBook.setEvent(target, editedEvent); + } + + @Override + public ObservableList getFilteredEventList() { + return filteredEvents; + } + + @Override + public void updateFilteredEventList(Predicate predicate) { + requireNonNull(predicate); + filteredEvents.setPredicate(predicate); + } + } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..05d681dd157 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -14,4 +15,10 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the events list. + * This list will not contain any duplicate events. + */ + ObservableList getEventList(); + } diff --git a/src/main/java/seedu/address/model/event/DateTime.java b/src/main/java/seedu/address/model/event/DateTime.java new file mode 100644 index 00000000000..9a0062e392a --- /dev/null +++ b/src/main/java/seedu/address/model/event/DateTime.java @@ -0,0 +1,76 @@ +package seedu.address.model.event; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class DateTime implements Comparable { + public static final String DATETIME_MESSAGE_CONSTRAINTS = "Date and Time has to be valid!"; + public static final String DATE_MESSAGE_CONSTRAINTS = "Date has to be in the format of yyyy-MM-DD!"; + public static final String TIME_MESSAGE_CONSTRAINTS = "Time has to be in the format of HH:mm!"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static final DateTimeFormatter DISPLAY_FORMATTER = DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm"); + + public static final String DATE_VALIDATION_REGEX = "^(\\d{4})-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$"; + public static final String TIME_VALIDATION_REGEX = "([01]?[0-9]|2[0-3]):[0-5][0-9]"; + + public final LocalDateTime value; + + /** + * Constructs a {@Code DateTime} + * @param year the event year + * @param month the event month + * @param day the event day + * @param hour the event hour + * @param min the event min + */ + public DateTime(int year, int month, int day, int hour, int min) { + value = LocalDateTime.of(year, month, day, hour, min); + } + + /** + * Returns true if a given date and time is valid. + */ + public static boolean isValidDateTime(int year, int month, int day, int hour, int min) { + LocalDateTime now = LocalDateTime.now(); + try { + return LocalDateTime.of(year, month, day, hour, min).isAfter(now); + } catch (DateTimeException e) { + return false; + } + } + + public static boolean isValidDate(String test) { + return test.matches(DATE_VALIDATION_REGEX); + } + + public static boolean isValidTime(String test) { + return test.matches(TIME_VALIDATION_REGEX); + } + + public String displayDateTime() { + return value.format(DISPLAY_FORMATTER); + } + + @Override + public String toString() { + return value.format(DATE_TIME_FORMATTER); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof DateTime + && value.isEqual(((DateTime) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(DateTime other) { + return value.compareTo(other.value); + } +} diff --git a/src/main/java/seedu/address/model/event/DateTimePredicate.java b/src/main/java/seedu/address/model/event/DateTimePredicate.java new file mode 100644 index 00000000000..2186b726736 --- /dev/null +++ b/src/main/java/seedu/address/model/event/DateTimePredicate.java @@ -0,0 +1,32 @@ +package seedu.address.model.event; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public class DateTimePredicate implements Predicate { + private final List keywords; + private final Function field; + + /** + * Constructor for DateTimePredicate + * @param keywords + */ + public DateTimePredicate(List keywords) { + this.keywords = keywords; + this.field = event -> event.getDateTime(); + } + + @Override + public boolean test(Event event) { + return keywords.stream() + .anyMatch(keyword -> keyword.equals(field.apply(event))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTimePredicate // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/Event.java b/src/main/java/seedu/address/model/event/Event.java new file mode 100644 index 00000000000..020dc878c0b --- /dev/null +++ b/src/main/java/seedu/address/model/event/Event.java @@ -0,0 +1,101 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import seedu.address.model.person.Name; + +/** + * Represents an Event in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Event implements Comparable { + + // Identity fields + private final EventName name; + private final Information info; + private final List participants; + private final DateTime dateTime; + + /** + * Every field must be present and not null. + */ + public Event(EventName name, Information info, List participants, DateTime dateTime) { + requireAllNonNull(name, info, participants, dateTime); + this.name = name; + this.info = info; + this.participants = participants; + this.dateTime = dateTime; + } + + public EventName getEventName() { + return name; + } + + public Information getEventInfo() { + return info; + } + + public List getParticipants() { + return participants; + } + + public List getParticipantStrings() { + return getParticipants().stream().map(participant -> participant.fullName) + .collect(Collectors.toList()); + } + + public DateTime getDateTime() { + return dateTime; + } + + public boolean isParticipantsEmpty() { + return participants.isEmpty(); + } + + /** + * Returns true if both events have the same identity fields. + */ + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Event)) { + return false; + } + + Event otherEvent = (Event) other; + return otherEvent.getEventName().equals(getEventName()) + && otherEvent.getEventInfo().equals(getEventInfo()) + && otherEvent.getParticipants().equals(getParticipants()) + && otherEvent.getDateTime().equals(getDateTime()); + } + + @Override + public int hashCode() { + return Objects.hash(name, info, participants, dateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Event Name: ") + .append(getEventName()) + .append("\nDetails: ") + .append(getEventInfo()) + .append("\nDate & Time: ") + .append(getDateTime()) + .append("\nParticipants: ") + .append(getParticipants()); + return builder.toString(); + } + + @Override + public int compareTo(Event other) { + return dateTime.compareTo(other.dateTime); + } +} diff --git a/src/main/java/seedu/address/model/event/EventFieldContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/event/EventFieldContainsKeywordsPredicate.java new file mode 100644 index 00000000000..4c7f645b842 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventFieldContainsKeywordsPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.event; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public class EventFieldContainsKeywordsPredicate implements Predicate { + private final List keywords; + private final Function field; + + EventFieldContainsKeywordsPredicate(List keywords, Function field) { + this.keywords = keywords; + this.field = field; + } + + @Override + public boolean test(Event event) { + return keywords.stream() + .anyMatch(keyword -> field.apply(event).toLowerCase().contains(keyword.toLowerCase())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventFieldContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EventFieldContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/EventInfoContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/event/EventInfoContainsKeywordsPredicate.java new file mode 100644 index 00000000000..9dcc6fe9e6a --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventInfoContainsKeywordsPredicate.java @@ -0,0 +1,16 @@ +package seedu.address.model.event; + +import java.util.List; + +public class EventInfoContainsKeywordsPredicate extends EventFieldContainsKeywordsPredicate { + public EventInfoContainsKeywordsPredicate(List keywords) { + super(keywords, event -> event.getEventInfo().value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventInfoContainsKeywordsPredicate // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/EventName.java b/src/main/java/seedu/address/model/event/EventName.java new file mode 100644 index 00000000000..a1f1b623e1b --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventName.java @@ -0,0 +1,57 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidEventName(String)} + */ +public class EventName { + + public static final String MESSAGE_CONSTRAINTS = "Name can take in at most 100 characters and it should not be " + + "blank"; + + /** + * The first character of the name must not be a whitespace, + * otherwise " " (a blank string becomes a valid input. + * There is also a 100 characters constraint. + */ + public static final String VALIDATION_REGEX = "[^\\s].{0,99}"; + + public final String value; + + /** + * Constructs an {@code Name} + * @param name A valid event name. + */ + public EventName(String name) { + requireNonNull(name); + checkArgument(isValidEventName(name), MESSAGE_CONSTRAINTS); + value = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidEventName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof EventName + && value.equalsIgnoreCase(((EventName) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/EventNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/event/EventNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..453eab2d411 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventNameContainsKeywordsPredicate.java @@ -0,0 +1,16 @@ +package seedu.address.model.event; + +import java.util.List; + +public class EventNameContainsKeywordsPredicate extends EventFieldContainsKeywordsPredicate { + public EventNameContainsKeywordsPredicate(List keywords) { + super(keywords, event -> event.getEventName().value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventNameContainsKeywordsPredicate // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/EventParticipantsContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/event/EventParticipantsContainsKeywordsPredicate.java new file mode 100644 index 00000000000..98a7a8b74dd --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventParticipantsContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.event; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public class EventParticipantsContainsKeywordsPredicate implements Predicate { + private final List keywords; + private final Function> field; + + /** + * Constructor for EventParticipantsContainsKeywordsPredicate + * @param keywords + */ + public EventParticipantsContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + this.field = event -> event.getParticipantStrings(); + } + + @Override + public boolean test(Event event) { + return keywords.stream() + .anyMatch(keyword -> field.apply(event).stream() + .anyMatch(detail -> detail.toLowerCase().contains(keyword.toLowerCase()))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventParticipantsContainsKeywordsPredicate // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/Information.java b/src/main/java/seedu/address/model/event/Information.java new file mode 100644 index 00000000000..687eb5dadd1 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Information.java @@ -0,0 +1,56 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's information in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidInformation(String)} + */ +public class Information { + public static final String MESSAGE_CONSTRAINTS = "Info can take in at most 300 characters and it should not be " + + "blank"; + + /** + * The first character of the name must not be a whitespace, + * otherwise " " (a blank string becomes a valid input. + * There is also a 300 characters constraint. + */ + public static final String VALIDATION_REGEX = "[^\\s].{0,299}"; + + public final String value; + + /** + * Constructs an {@code Information} + * @param information A valid event information. + */ + public Information(String information) { + requireNonNull(information); + checkArgument(isValidInformation(information), MESSAGE_CONSTRAINTS); + value = information; + } + + /** + * Returns true if a given string is a valid information. + */ + public static boolean isValidInformation(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Information + && value.equalsIgnoreCase(((Information) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/UniqueEventList.java b/src/main/java/seedu/address/model/event/UniqueEventList.java new file mode 100644 index 00000000000..08266bd4301 --- /dev/null +++ b/src/main/java/seedu/address/model/event/UniqueEventList.java @@ -0,0 +1,126 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; + +public class UniqueEventList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList).sorted(); + + /** + * Returns true if the list contains an equivalent event as the given argument. + */ + public boolean contains(Event toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds an event to the list. + * The event must not already exist in the list. + */ + public void add(Event toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateEventException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the list. + * The event identity of {@code editedEvent} must not be the same as another existing event in the list. + */ + public void setEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new EventNotFoundException(); + } + + if (!target.equals(editedEvent) && contains(editedEvent)) { + throw new DuplicateEventException(); + } + + internalList.set(index, editedEvent); + } + + /** + * Removes the equivalent event from the list. + * The event must exist in the list. + */ + public void remove(Event toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new EventNotFoundException(); + } + } + + public void setEvents(UniqueEventList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + requireAllNonNull(events); + if (!eventsAreUnique(events)) { + throw new DuplicateEventException(); + } + + internalList.setAll(events); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueEventList // instanceof handles nulls + && internalList.equals(((UniqueEventList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code events} contains only unique events. + */ + private boolean eventsAreUnique(List events) { + for (int i = 0; i < events.size() - 1; i++) { + for (int j = i + 1; j < events.size(); j++) { + if (events.get(i).equals(events.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java new file mode 100644 index 00000000000..895bcc095fb --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java @@ -0,0 +1,7 @@ +package seedu.address.model.event.exceptions; + +public class DuplicateEventException extends RuntimeException { + public DuplicateEventException() { + super("Operation would result in duplicate events"); + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java new file mode 100644 index 00000000000..09d620650ec --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java @@ -0,0 +1,4 @@ +package seedu.address.model.event.exceptions; + +public class EventNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 60472ca22a0..65f6705040c 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -9,9 +9,9 @@ */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = "Addresses can take in any values, and it should not be blank"; - /* + /** * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ diff --git a/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..0e3fda5e60f --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicateAnd.java @@ -0,0 +1,17 @@ +package seedu.address.model.person; + +import java.util.List; + +public class AddressContainsKeywordsPredicateAnd extends FieldContainsKeywordsPredicateAnd { + + public AddressContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getAddress().value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..addf9f5c200 --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicateOr.java @@ -0,0 +1,17 @@ +package seedu.address.model.person; + +import java.util.List; + +public class AddressContainsKeywordsPredicateOr extends FieldContainsKeywordsPredicateOr { + + public AddressContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getAddress().value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsKeywordsPredicateOr // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Cca.java b/src/main/java/seedu/address/model/person/Cca.java new file mode 100644 index 00000000000..87b1d08f3f0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Cca.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.tag.Tag; + +/** + * Represents a Person's cca tag in the address book. + * Guarantees: immutable; is always valid + */ +public class Cca extends Tag { + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String MESSAGE_CONSTRAINTS = "Cca can only take alphanumeric values, and it should not be " + + "blank"; + + public final String value; + + /** + * Constructs a {@code Cca}. + * + * @param cca a cca tag. + */ + public Cca(String cca) { + super(requireNonNull(cca)); + checkArgument(isValidTagName(cca), MESSAGE_CONSTRAINTS); + this.value = cca.trim().toLowerCase(); + } + + @Override + public String getTagString() { + return this.value; + } + + public static boolean isValidTagName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Cca + && this.value.equals(((Cca) other).value)); + } +} diff --git a/src/main/java/seedu/address/model/person/CcaContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/CcaContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..975cff4dca0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/CcaContainsKeywordsPredicateAnd.java @@ -0,0 +1,19 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Cca} matches any of the keywords given. + */ +public class CcaContainsKeywordsPredicateAnd extends TagContainsKeywordsPredicateAnd { + public CcaContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getCcaStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CcaContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/CcaContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/CcaContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..33923635576 --- /dev/null +++ b/src/main/java/seedu/address/model/person/CcaContainsKeywordsPredicateOr.java @@ -0,0 +1,19 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Cca} matches any of the keywords given. + */ +public class CcaContainsKeywordsPredicateOr extends TagContainsKeywordsPredicateOr { + public CcaContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getCcaStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CcaContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java new file mode 100644 index 00000000000..074e733cade --- /dev/null +++ b/src/main/java/seedu/address/model/person/ContainsKeywordsPredicate.java @@ -0,0 +1,21 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public abstract class ContainsKeywordsPredicate implements Predicate { + private final List keywords; + private final Function field; + + ContainsKeywordsPredicate(List keywords, Function field) { + requireNonNull(keywords); + requireNonNull(field); + this.keywords = keywords; + this.field = field; + } + + +} diff --git a/src/main/java/seedu/address/model/person/Education.java b/src/main/java/seedu/address/model/person/Education.java new file mode 100644 index 00000000000..da152d6c7c0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Education.java @@ -0,0 +1,44 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.tag.Tag; +/** + * Represents a Person's education tag in the address book. + * Guarantees: immutable; is always valid + */ +public class Education extends Tag { + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String MESSAGE_CONSTRAINTS = "Education can only take alphanumeric values, and it should not " + + "be blank"; + + public final String value; + + /** + * Constructs a {@code Education}. + * + * @param education an education tag. + */ + public Education(String education) { + super(requireNonNull(education)); + checkArgument(isValidTagName(education), MESSAGE_CONSTRAINTS); + this.value = education.trim().toLowerCase(); + } + + public static boolean isValidTagName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String getTagString() { + return this.value; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Education + && this.value.equals(((Education) other).value)); + } +} diff --git a/src/main/java/seedu/address/model/person/EducationContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/EducationContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..47200051f5c --- /dev/null +++ b/src/main/java/seedu/address/model/person/EducationContainsKeywordsPredicateAnd.java @@ -0,0 +1,22 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Education} matches any of the keywords given. + */ +public class EducationContainsKeywordsPredicateAnd extends TagContainsKeywordsPredicateAnd { + + public EducationContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getEducationStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EducationContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } + + +} diff --git a/src/main/java/seedu/address/model/person/EducationContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/EducationContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..0f37095da8c --- /dev/null +++ b/src/main/java/seedu/address/model/person/EducationContainsKeywordsPredicateOr.java @@ -0,0 +1,22 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Education} matches any of the keywords given. + */ +public class EducationContainsKeywordsPredicateOr extends TagContainsKeywordsPredicateOr { + + public EducationContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getEducationStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EducationContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } + + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index f866e7133de..b8827bb6aa2 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -28,7 +28,7 @@ public class Email { private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars - private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX; + private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)+" + DOMAIN_LAST_PART_REGEX; public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; public final String value; diff --git a/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..9481ce9b9f5 --- /dev/null +++ b/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicateAnd.java @@ -0,0 +1,37 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +public class EmailContainsKeywordsPredicateAnd implements Predicate { + private final List keywords; + private final Function field; + + /** + * Constructor for EmailContainsKeywordPredicateAnd. THis class was detached from the other + * FieldContainsKeywordPredicate classes to support functionality for searching for a substring. + * + * @param keywords The list of keywords to be searched for. + */ + public EmailContainsKeywordsPredicateAnd(List keywords) { + this.keywords = keywords; + this.field = person -> person.getEmail().value; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubstringIgnoreCase(field.apply(person), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..eb8cfa74362 --- /dev/null +++ b/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicateOr.java @@ -0,0 +1,37 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +public class EmailContainsKeywordsPredicateOr implements Predicate { + private final List keywords; + private final Function field; + + /** + * Constructor for EmailContainsKeywordPredicateOr. THis class was detached from the other + * FieldContainsKeywordPredicate classes to support functionality for searching for a substring. + * + * @param keywords The list of keywords to be searched for. + */ + public EmailContainsKeywordsPredicateOr(List keywords) { + this.keywords = keywords; + this.field = person -> person.getEmail().value; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsSubstringIgnoreCase(field.apply(person), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicateOr // instanceof handles nulls + && super.equals(other)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/FieldContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/FieldContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..8a2cacd7ab6 --- /dev/null +++ b/src/main/java/seedu/address/model/person/FieldContainsKeywordsPredicateAnd.java @@ -0,0 +1,28 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Function; + +public abstract class FieldContainsKeywordsPredicateAnd extends ContainsKeywordsPredicate { + private final List keywords; + private final Function field; + + FieldContainsKeywordsPredicateAnd(List keywords, Function field) { + super(keywords, field); + this.keywords = keywords; + this.field = field; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .allMatch(keyword -> field.apply(person).toLowerCase().contains(keyword.toLowerCase())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FieldContainsKeywordsPredicateAnd // instanceof handles nulls + && keywords.equals(((FieldContainsKeywordsPredicateAnd) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/FieldContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/FieldContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..704be4e19cd --- /dev/null +++ b/src/main/java/seedu/address/model/person/FieldContainsKeywordsPredicateOr.java @@ -0,0 +1,29 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Function; + +public abstract class FieldContainsKeywordsPredicateOr extends ContainsKeywordsPredicate { + private final List keywords; + private final Function field; + + FieldContainsKeywordsPredicateOr(List keywords, Function field) { + super(keywords, field); + this.keywords = keywords; + this.field = field; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> field.apply(person).toLowerCase().contains(keyword.toLowerCase())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FieldContainsKeywordsPredicateOr // instanceof handles nulls + && keywords.equals(((FieldContainsKeywordsPredicateOr) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/Internship.java b/src/main/java/seedu/address/model/person/Internship.java new file mode 100644 index 00000000000..a0236154263 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Internship.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.tag.Tag; + +/** + * Represents a Person's internship tag in the address book. + * Guarantees: immutable; is always valid + */ +public class Internship extends Tag { + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String MESSAGE_CONSTRAINTS = "Internship can only take alphanumeric values, and it should not" + + " be blank"; + + public final String value; + + /** + * Constructs a {@code Internship}. + * + * @param internship an internship tag. + */ + public Internship(String internship) { + super(requireNonNull(internship)); + checkArgument(isValidTagName(internship), MESSAGE_CONSTRAINTS); + this.value = internship.trim().toLowerCase(); + } + + @Override + public String getTagString() { + return this.value; + } + + public static boolean isValidTagName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Internship + && this.value.equals(((Internship) other).value)); + } +} diff --git a/src/main/java/seedu/address/model/person/InternshipContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/InternshipContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..44d339c0f73 --- /dev/null +++ b/src/main/java/seedu/address/model/person/InternshipContainsKeywordsPredicateAnd.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Internship} matches any of the keywords given. + */ +public class InternshipContainsKeywordsPredicateAnd extends TagContainsKeywordsPredicateAnd { + + public InternshipContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getInternshipStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InternshipContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/InternshipContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/InternshipContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..26163404352 --- /dev/null +++ b/src/main/java/seedu/address/model/person/InternshipContainsKeywordsPredicateOr.java @@ -0,0 +1,22 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Internship} matches any of the keywords given. + */ +public class InternshipContainsKeywordsPredicateOr extends TagContainsKeywordsPredicateOr { + + public InternshipContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getInternshipStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EducationContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } + + +} diff --git a/src/main/java/seedu/address/model/person/Module.java b/src/main/java/seedu/address/model/person/Module.java new file mode 100644 index 00000000000..33ba48911e8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Module.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.tag.Tag; + +/** + * Represents a Person's cca tag in the address book. + * Guarantees: immutable; is always valid + */ +public class Module extends Tag { + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String MESSAGE_CONSTRAINTS = "Module can only take alphanumeric values, and it should not be " + + "blank"; + + public final String value; + + /** + * Constructs a {@code Module}. + * + * @param module a module tag. + */ + public Module(String module) { + super(requireNonNull(module)); + checkArgument(isValidTagName(module), MESSAGE_CONSTRAINTS); + this.value = module.trim().toLowerCase(); + } + + @Override + public String getTagString() { + return this.value; + } + + public static boolean isValidTagName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Module + && this.value.equals(((Module) other).value)); + } +} diff --git a/src/main/java/seedu/address/model/person/ModuleContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/ModuleContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..71ff05f4f25 --- /dev/null +++ b/src/main/java/seedu/address/model/person/ModuleContainsKeywordsPredicateAnd.java @@ -0,0 +1,17 @@ +package seedu.address.model.person; + +import java.util.List; + +public class ModuleContainsKeywordsPredicateAnd extends TagContainsKeywordsPredicateAnd { + + public ModuleContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getModuleStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/ModuleContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/ModuleContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..f47e01d0bd8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/ModuleContainsKeywordsPredicateOr.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Module} matches any of the keywords given. + */ +public class ModuleContainsKeywordsPredicateOr extends TagContainsKeywordsPredicateOr { + + public ModuleContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getModuleStrings()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InternshipContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..1b5ea24b219 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -13,7 +13,7 @@ public class Name { "Names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; @@ -48,7 +48,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.equalsIgnoreCase(((Name) other).fullName)); // state check } @Override diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..5379fdf77d1 --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicateAnd.java @@ -0,0 +1,21 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameContainsKeywordsPredicateAnd extends FieldContainsKeywordsPredicateAnd { + + public NameContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getName().fullName); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..b1c20f2a7d2 --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicateOr.java @@ -0,0 +1,21 @@ +package seedu.address.model.person; + +import java.util.List; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class NameContainsKeywordsPredicateOr extends FieldContainsKeywordsPredicateOr { + + public NameContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getName().fullName); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NameContainsKeywordsPredicateOr // instanceof handles nulls + && super.equals(other)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..50969ebbd27 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,38 +2,76 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; -import java.util.Set; +import java.util.stream.Collectors; +import seedu.address.model.event.Event; import seedu.address.model.tag.Tag; /** * Represents a Person in the address book. * Guarantees: details are present and not null, field values are validated, immutable. */ -public class Person { +public class Person implements Comparable { // Identity fields private final Name name; private final Phone phone; private final Email email; + private final Address address; // Data fields - private final Address address; - private final Set tags = new HashSet<>(); + private final List educations; + private final List internships; + private final List modules; + private final List ccas; /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Address address) { + requireAllNonNull(name, phone, email, address); this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); + this.educations = new ArrayList<>(); + this.internships = new ArrayList<>(); + this.modules = new ArrayList<>(); + this.ccas = new ArrayList<>(); + } + + /** + * Second constructor for Person. + */ + public Person(Name name, Phone phone, Email email, Address address, List educations, List internships, + List modules, List ccas) { + requireAllNonNull(name, phone, email, address, educations, internships, modules, ccas); + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.educations = educations; + this.internships = internships; + this.modules = modules; + this.ccas = ccas; + } + + /** + * Third constructor for Person. + */ + public Person(Person person, Event event) { + requireAllNonNull(person, event); + this.name = person.name; + this.phone = person.phone; + this.email = person.email; + this.address = person.address; + this.educations = person.educations; + this.internships = person.internships; + this.modules = person.modules; + this.ccas = person.ccas; } public Name getName() { @@ -52,14 +90,45 @@ public Address getAddress() { return address; } - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public List getEducations() { + return educations; + } + + public List getInternships() { + return internships; + } + + public List getModules() { + return modules; + } + + public List getCcas() { + return ccas; + } + + public Person addEvent(Event event) { + return new Person(this, event); + } + + public List getEducationStrings() { + return getEducations().stream().map(education -> education.getTagString()) + .collect(Collectors.toList()); } + public List getInternshipStrings() { + return getInternships().stream().map(internship -> internship.getTagString()) + .collect(Collectors.toList()); + } + + public List getModuleStrings() { + return getModules().stream().map(module -> module.getTagString()) + .collect(Collectors.toList()); + } + + public List getCcaStrings() { + return getCcas().stream().map(cca -> cca.getTagString()) + .collect(Collectors.toList()); + } /** * Returns true if both persons have the same name. * This defines a weaker notion of equality between two persons. @@ -92,32 +161,43 @@ public boolean equals(Object other) { && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && otherPerson.getEducations().equals(getEducations()) + && otherPerson.getInternships().equals(getInternships()) + && otherPerson.getModules().equals(getModules()) + && otherPerson.getCcas().equals(getCcas()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, address, educations, internships, modules, ccas); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") + builder.append("Name: ") + .append(getName()) + .append("\nPhone: ") .append(getPhone()) - .append("; Email: ") + .append("\nEmail: ") .append(getEmail()) - .append("; Address: ") - .append(getAddress()); + .append("\nAddress: ") + .append(getAddress()) + .append("\nEducations: ") + .append(getEducations()) + .append("\nInternships: ") + .append(getInternships()) + .append("\nModules: ") + .append(getModules()) + .append("\nCcas: ") + .append(getCcas()); - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } return builder.toString(); } + @Override + public int compareTo(Person other) { + return name.fullName.compareToIgnoreCase(other.getName().fullName); + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 872c76b382f..8e29da8d03e 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,8 +11,8 @@ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should only contain numbers, and should be between 3 and 10 digits long"; + public static final String VALIDATION_REGEX = "\\d{3,10}"; public final String value; /** diff --git a/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..3692fcda8c0 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicateAnd.java @@ -0,0 +1,17 @@ +package seedu.address.model.person; + +import java.util.List; + +public class PhoneContainsKeywordsPredicateAnd extends FieldContainsKeywordsPredicateAnd { + + public PhoneContainsKeywordsPredicateAnd(List keywords) { + super(keywords, person -> person.getPhone().value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhoneContainsKeywordsPredicateAnd // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..4ccdf4f6aec --- /dev/null +++ b/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicateOr.java @@ -0,0 +1,17 @@ +package seedu.address.model.person; + +import java.util.List; + +public class PhoneContainsKeywordsPredicateOr extends FieldContainsKeywordsPredicateOr { + + public PhoneContainsKeywordsPredicateOr(List keywords) { + super(keywords, person -> person.getPhone().value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhoneContainsKeywordsPredicateOr // instanceof handles nulls + && super.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicateAnd.java b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicateAnd.java new file mode 100644 index 00000000000..3443004ef78 --- /dev/null +++ b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicateAnd.java @@ -0,0 +1,30 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Function; + +public abstract class TagContainsKeywordsPredicateAnd extends ContainsKeywordsPredicate> { + private final List keywords; + private final Function> field; + + TagContainsKeywordsPredicateAnd(List keywords, Function> field) { + super(keywords, field); + this.keywords = keywords; + this.field = field; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .allMatch(keyword -> field.apply(person).stream() + .anyMatch(tag -> tag.contains(keyword.toLowerCase()))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicateAnd // instanceof handles nulls + && keywords.equals(((TagContainsKeywordsPredicateAnd) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicateOr.java b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicateOr.java new file mode 100644 index 00000000000..76b527169ba --- /dev/null +++ b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicateOr.java @@ -0,0 +1,30 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Function; + +public abstract class TagContainsKeywordsPredicateOr extends ContainsKeywordsPredicate> { + private final List keywords; + private final Function> field; + + TagContainsKeywordsPredicateOr(List keywords, Function> field) { + super(keywords, field); + this.keywords = keywords; + this.field = field; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> field.apply(person).stream() + .anyMatch(tag -> tag.toLowerCase().contains(keyword.toLowerCase()))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicateOr // instanceof handles nulls + && keywords.equals(((TagContainsKeywordsPredicateOr) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..c4d7403d247 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -46,6 +46,7 @@ public void add(Person toAdd) { throw new DuplicatePersonException(); } internalList.add(toAdd); + FXCollections.sort(internalList); } /** @@ -66,6 +67,7 @@ public void setPerson(Person target, Person editedPerson) { } internalList.set(index, editedPerson); + FXCollections.sort(internalList); } /** @@ -77,11 +79,13 @@ public void remove(Person toRemove) { if (!internalList.remove(toRemove)) { throw new PersonNotFoundException(); } + FXCollections.sort(internalList); } public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); + FXCollections.sort(internalList); } /** @@ -95,6 +99,7 @@ public void setPersons(List persons) { } internalList.setAll(persons); + FXCollections.sort(internalList); } /** diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7..c76d8fce823 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -1,43 +1,28 @@ package seedu.address.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; +public abstract class Tag { + public static final String CCA = "cca"; + public static final String EDUCATION = "education"; + public static final String INTERNSHIP = "internship"; + public static final String MODULE = "module"; public final String tagName; /** * Constructs a {@code Tag}. - * * @param tagName A valid tag name. */ public Tag(String tagName) { requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); this.tagName = tagName; } - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } + public abstract String getTagString(); @Override public int hashCode() { @@ -47,8 +32,16 @@ public int hashCode() { /** * Format state as text for viewing. */ + @Override public String toString() { - return '[' + tagName + ']'; + return tagName; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Tag + && this.tagName.equals(((Tag) other).tagName)); } } diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..94c94946117 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,13 +1,17 @@ package seedu.address.model.util; import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.List; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; @@ -17,27 +21,50 @@ * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { + private static final List ALEX_EDUCATIONS = Arrays.asList( + new Education[]{new Education("computer science"), new Education("nus")}); + private static final List ALEX_CCAS = Arrays.asList(new Cca[]{new Cca("bouldering")}); + private static final List ALEX_MODULES = Arrays.asList( + new Module[]{new Module("cs2030s"), new Module("cs2040s")}); + private static final List ALEX_INTERNSHIPS = Arrays.asList(new Internship[]{new Internship("shopee")}); + + private static final List BERNICE_EDUCATIONS = Arrays.asList( + new Education[]{new Education("computer engineering"), new Education("nus")}); + private static final List BERNICE_CCAS = Arrays.asList(new Cca[]{new Cca("cheerleading")}); + private static final List BERNICE_MODULES = Arrays.asList( + new Module[]{new Module("cs2030s"), new Module("cs2100")}); + private static final List BERNICE_INTERNSHIPS = Arrays.asList(new Internship[]{new Internship("gic")}); + + private static final List CHARLOTTE_EDUCATIONS = Arrays.asList( + new Education[]{new Education("business analytics"), new Education("nus")}); + private static final List CHARLOTTE_CCAS = Arrays.asList(new Cca[]{new Cca("gymnastic")}); + private static final List CHARLOTTE_MODULES = Arrays.asList( + new Module[]{new Module("cs2030"), new Module("ma2001")}); + private static final List CHARLOTTE_INTERNSHIPS = Arrays.asList(new Internship[]{new Internship("bosch")}); + + private static Person alexYeoh = new Person(new Name("Alex Yeoh"), new Phone("87438807"), + new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), + ALEX_EDUCATIONS, ALEX_INTERNSHIPS, ALEX_MODULES, ALEX_CCAS); + private static Person berniceYu = new Person(new Name("Bernice Yu"), new Phone("99272758"), + new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + BERNICE_EDUCATIONS, BERNICE_INTERNSHIPS, BERNICE_MODULES, BERNICE_CCAS); + private static Person charlotteOliveiro = new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + CHARLOTTE_EDUCATIONS, CHARLOTTE_INTERNSHIPS, CHARLOTTE_MODULES, CHARLOTTE_CCAS); + private static Person davidLi = new Person(new Name("David Li"), new Phone("91031282"), + new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43")); + private static Person irfanIbrahim = new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), + new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35")); + private static Person royBalakrishnan = new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), + new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31")); + public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; + + return new Person[]{alexYeoh, berniceYu, charlotteOliveiro, davidLi, irfanIbrahim, royBalakrishnan}; + } + + public static Event[] getSampleEvents() { + return new Event[]{}; } public static ReadOnlyAddressBook getSampleAddressBook() { @@ -45,16 +72,10 @@ public static ReadOnlyAddressBook getSampleAddressBook() { for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + for (Event sampleEvent : getSampleEvents()) { + sampleAb.addEvent(sampleEvent); + } return sampleAb; } - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedEvent.java b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java new file mode 100644 index 00000000000..2fb818219b0 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java @@ -0,0 +1,132 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; +import seedu.address.model.person.Name; + +/** + * Jackson-friendly version of {@link Event}. + */ +public class JsonAdaptedEvent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Event's %s field is missing!"; + + private final String eventName; + private final String info; + private final Set participants = new HashSet<>(); + private final String dateTime; + + /** + * Constructs a {@code JsonAdaptedEvent} with the given person details. + */ + @JsonCreator + public JsonAdaptedEvent(@JsonProperty("name") String eventName, + @JsonProperty("info") String info, + @JsonProperty("participants") List participants, + @JsonProperty("dateTime") String dateTime) { + this.eventName = eventName; + this.info = info; + this.participants.addAll(participants); + this.dateTime = dateTime; + } + + /** + * Converts a given {@code Event} into this class for Jackson use. + */ + public JsonAdaptedEvent(Event source) { + this.eventName = source.getEventName().value; + this.info = source.getEventInfo().value; + this.participants.addAll(new HashSet<>(source.getParticipants()).stream() + .map(JsonAdaptedName::new) + .collect(Collectors.toList())); + this.dateTime = source.getDateTime().toString(); + } + + /** + * Converts this Jackson-friendly adapted tag object into the model's {@code Event} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event. + */ + public Event toModelType() throws IllegalValueException { + Set modelParticipants = new HashSet<>(); + + if (eventName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + EventName.class.getSimpleName())); + } + if (!EventName.isValidEventName(eventName)) { + throw new IllegalValueException(EventName.MESSAGE_CONSTRAINTS); + } + final EventName modelEventName = new EventName(eventName); + + if (info == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Information.class.getSimpleName())); + } + if (!Information.isValidInformation(info)) { + throw new IllegalValueException(Information.MESSAGE_CONSTRAINTS); + } + final Information modelInfo = new Information(info); + + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + int[] dateTimeArr = convertDateTime(dateTime); + final DateTime modelDateTime = new DateTime(dateTimeArr[0], dateTimeArr[1], dateTimeArr[2], dateTimeArr[3], + dateTimeArr[4]); + + for (JsonAdaptedName curr : participants) { + if (curr == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + EventName.class.getSimpleName())); + } + if (!Name.isValidName(curr.getName())) { + throw new IllegalValueException(EventName.MESSAGE_CONSTRAINTS); + } + modelParticipants.add(new Name(curr.getName())); + } + return new Event(modelEventName, modelInfo, new ArrayList<>(modelParticipants), modelDateTime); + } + + /** + * Converts the date time into an int array that splits into year, month, day, hour and min. + * @param dateTime the string input of the date and time + * @return int array that contains year, month, day, hour and min + * @throws IllegalValueException if date or time is invalid + */ + public int[] convertDateTime(String dateTime) throws IllegalValueException { + String[] tempDateTime = dateTime.split(" "); + String date = tempDateTime[0]; + String time = tempDateTime[1]; + + if (!DateTime.isValidDate(date)) { + throw new IllegalValueException(DateTime.DATE_MESSAGE_CONSTRAINTS); + } + if (!DateTime.isValidTime(time)) { + throw new IllegalValueException(DateTime.TIME_MESSAGE_CONSTRAINTS); + } + String[] validDate = date.split("-"); + String[] validTime = time.split(":"); + int[] outputDateTime = new int[5]; + outputDateTime[0] = Integer.parseInt(validDate[0]); + outputDateTime[1] = Integer.parseInt(validDate[1]); + outputDateTime[2] = Integer.parseInt(validDate[2]); + outputDateTime[3] = Integer.parseInt(validTime[0]); + outputDateTime[4] = Integer.parseInt(validTime[1]); + + return outputDateTime; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedName.java b/src/main/java/seedu/address/storage/JsonAdaptedName.java new file mode 100644 index 00000000000..3caa57ecb5f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedName.java @@ -0,0 +1,47 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Name; + +public class JsonAdaptedName { + + private final String name; + + /** + * Constructs a {@code JsonAdaptedName} with the given {@code Name}. + */ + @JsonCreator + public JsonAdaptedName(String name) { + requireNonNull(name); + this.name = name; + } + + /** + * Converts a given {@code Name} into this class for Jackson use. + */ + public JsonAdaptedName(Name source) { + this.name = source.fullName; + } + + @JsonValue + public String getName() { + return name; + } + + /** + * Converts this Jackson-friendly adapted name object into the model's {@code Name} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted name. + */ + public Name toModelType() throws IllegalValueException { + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + return new Name(name); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..120598e610d 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -11,7 +11,11 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; @@ -28,7 +32,10 @@ class JsonAdaptedPerson { private final String phone; private final String email; private final String address; - private final List tagged = new ArrayList<>(); + private final Set ccas = new HashSet<>(); + private final Set educations = new HashSet<>(); + private final Set internships = new HashSet<>(); + private final Set modules = new HashSet<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. @@ -36,13 +43,25 @@ class JsonAdaptedPerson { @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + @JsonProperty("educations") List educations, + @JsonProperty("internships") List internships, + @JsonProperty("modules") List modules, + @JsonProperty("ccas") List ccas) { this.name = name; this.phone = phone; this.email = email; this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); + if (educations != null) { + this.educations.addAll(educations); + } + if (internships != null) { + this.internships.addAll(internships); + } + if (modules != null) { + this.modules.addAll(modules); + } + if (ccas != null) { + this.ccas.addAll(ccas); } } @@ -54,7 +73,16 @@ public JsonAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; - tagged.addAll(source.getTags().stream() + educations.addAll(new HashSet<>(source.getEducations()).stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + internships.addAll(new HashSet<>(source.getInternships()).stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + modules.addAll(new HashSet<>(source.getModules()).stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + ccas.addAll(new HashSet<>(source.getCcas()).stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); } @@ -65,9 +93,37 @@ public JsonAdaptedPerson(Person source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); + final Set modelCcas = new HashSet<>(); + final Set modelEducations = new HashSet<>(); + final Set modelInternships = new HashSet<>(); + final Set modelModules = new HashSet<>(); + + for (JsonAdaptedTag curr : educations) { + if (!Education.isValidTagName(curr.getTagName())) { + throw new IllegalValueException(Education.MESSAGE_CONSTRAINTS); + } + modelEducations.add(curr.toModelType("education")); + } + + for (JsonAdaptedTag curr : ccas) { + if (!Cca.isValidTagName(curr.getTagName())) { + throw new IllegalValueException(Cca.MESSAGE_CONSTRAINTS); + } + modelCcas.add(curr.toModelType("cca")); + } + + for (JsonAdaptedTag curr : internships) { + if (!Internship.isValidTagName(curr.getTagName())) { + throw new IllegalValueException(Internship.MESSAGE_CONSTRAINTS); + } + modelInternships.add(curr.toModelType("internship")); + } + + for (JsonAdaptedTag curr : modules) { + if (!Module.isValidTagName(curr.getTagName())) { + throw new IllegalValueException(Module.MESSAGE_CONSTRAINTS); + } + modelModules.add(curr.toModelType("module")); } if (name == null) { @@ -102,8 +158,7 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(modelName, modelPhone, modelEmail, modelAddress, new ArrayList<>(modelEducations), + new ArrayList<>(modelInternships), new ArrayList<>(modelModules), new ArrayList<>(modelCcas)); } - } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..0f926dde35e 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -4,6 +4,10 @@ import com.fasterxml.jackson.annotation.JsonValue; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.tag.Tag; /** @@ -11,6 +15,8 @@ */ class JsonAdaptedTag { + public static final String INVALID_TAGTYPE = "The tag type is invalid!"; + private final String tagName; /** @@ -25,7 +31,7 @@ public JsonAdaptedTag(String tagName) { * Converts a given {@code Tag} into this class for Jackson use. */ public JsonAdaptedTag(Tag source) { - tagName = source.tagName; + this.tagName = source.tagName; } @JsonValue @@ -38,11 +44,19 @@ public String getTagName() { * * @throws IllegalValueException if there were any data constraints violated in the adapted tag. */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); + public Tag toModelType(String tagType) throws IllegalValueException { + + switch (tagType) { + case Tag.CCA: + return new Cca(tagName); + case Tag.EDUCATION: + return new Education(tagName); + case Tag.INTERNSHIP: + return new Internship(tagName); + case Tag.MODULE: + return new Module(tagName); + default: + throw new IllegalValueException(INVALID_TAGTYPE); } - return new Tag(tagName); } - } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..f7c0293660e 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,6 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; /** @@ -20,8 +21,10 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_EVENT = "Events list contains duplicate event(s)."; private final List persons = new ArrayList<>(); + private final List events = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. @@ -38,6 +41,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { + + private static final String FXML = "EventListCard.fxml"; + + public final Event event; + + @FXML + private HBox cardPane; + @FXML + private Label eventname; + @FXML + private Label id; + @FXML + private Label info; + @FXML + private Label datetime; + @FXML + private FlowPane participants; + + /** + * Creates a {@code EventCode} with the given {@code Event} and index to display. + */ + public EventCardComponent(Event event, int displayedIndex) { + super(FXML); + this.event = event; + id.setText(displayedIndex + ". "); + eventname.setText(event.getEventName().toString()); + datetime.setText(event.getDateTime().displayDateTime()); + info.setText("Details: " + event.getEventInfo().toString()); + event.getParticipants().stream() + .sorted(Comparator.comparing(p -> p.fullName)) + .forEach(p -> participants.getChildren().add(new Label(p.fullName))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EventCardComponent)) { + return false; + } + + // state check + EventCardComponent card = (EventCardComponent) other; + return id.getText().equals(card.id.getText()) + && event.equals(card.event); + } +} diff --git a/src/main/java/seedu/address/ui/EventListPanel.java b/src/main/java/seedu/address/ui/EventListPanel.java new file mode 100644 index 00000000000..26646eba41f --- /dev/null +++ b/src/main/java/seedu/address/ui/EventListPanel.java @@ -0,0 +1,52 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.event.Event; + +/** + * Panel containing the list of events. + */ +public class EventListPanel extends UiPart { + private static final String FXML = "EventListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(EventListPanel.class); + + @FXML + private ListView eventListView; + @FXML + private Label header; + + /** + * Creates a {@code EventListPanel} with the given {@code ObservableList}. + */ + public EventListPanel(ObservableList eventList) { + super(FXML); + header.setText("EVENTS"); + eventListView.setItems(eventList); + eventListView.setCellFactory(listView -> new EventListPanel.EventListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of an {@code Event} using an {@code EventCard}. + */ + class EventListViewCell extends ListCell { + @Override + protected void updateItem(Event event, boolean empty) { + super.updateItem(event, empty); + + if (empty || event == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new EventCardComponent(event, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..c61cd47e491 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2122s2-cs2103t-w11-1.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..76bb9702dcd 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -32,6 +32,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private EventListPanel eventListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -50,6 +51,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane eventListPanelPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -113,6 +117,9 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + eventListPanel = new EventListPanel(logic.getFilteredEventList()); + eventListPanelPlaceholder.getChildren().add(eventListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCardComponent.java similarity index 60% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/PersonCardComponent.java index 7fc927bc5d9..81f8022c140 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCardComponent.java @@ -12,7 +12,7 @@ /** * An UI component that displays information of a {@code Person}. */ -public class PersonCard extends UiPart { +public class PersonCardComponent extends UiPart { private static final String FXML = "PersonListCard.fxml"; @@ -39,12 +39,18 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML - private FlowPane tags; + private FlowPane educations; + @FXML + private FlowPane internships; + @FXML + private FlowPane modules; + @FXML + private FlowPane ccas; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public PersonCardComponent(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); @@ -52,9 +58,20 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + person.getEducations().stream() + .sorted(Comparator.comparing(edu -> edu.tagName)) + .forEach(edu -> educations.getChildren().add(new Label(edu.tagName))); + person.getInternships().stream() + .sorted(Comparator.comparing(intern -> intern.tagName)) + .forEach(intern -> internships.getChildren().add(new Label(intern.tagName))); + person.getModules().stream() + .sorted(Comparator.comparing(module -> module.tagName)) + .forEach(module -> modules.getChildren().add(new Label(module.tagName))); + person.getCcas().stream() + .sorted(Comparator.comparing(cca -> cca.tagName)) + .forEach(cca -> ccas.getChildren().add(new Label(cca.tagName))); + } @Override @@ -65,12 +82,12 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof PersonCardComponent)) { return false; } // state check - PersonCard card = (PersonCard) other; + PersonCardComponent card = (PersonCardComponent) other; return id.getText().equals(card.id.getText()) && person.equals(card.person); } diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..a192ee6a56f 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -41,7 +41,7 @@ protected void updateItem(Person person, boolean empty) { setGraphic(null); setText(null); } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + setGraphic(new PersonCardComponent(person, getIndex() + 1).getRoot()); } } } diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..d1cc57485bf 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -120,6 +120,12 @@ -fx-text-fill: white; } +.cell_bigger_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 20px; + -fx-text-fill: #010504; +} + .cell_big_label { -fx-font-family: "Segoe UI Semibold"; -fx-font-size: 16px; @@ -337,16 +343,82 @@ -fx-background-radius: 0; } -#tags { +#header { + -fx-font-family: "Segoe UI Bold"; + -fx-font-size: 28px; + -fx-text-fill: #E6E6FA; + -fx-padding: 0 50 0 50; + -fx-border-radius: 10; + -fx-border-width: 1; + -fx-border-color: #E6E6FA; +} + +#educations { + -fx-hgap: 7; + -fx-vgap: 7; +} + +#internships { + -fx-hgap: 7; + -fx-vgap: 7; +} + +#modules { + -fx-hgap: 7; + -fx-vgap: 7; +} + +#ccas { -fx-hgap: 7; - -fx-vgap: 3; + -fx-vgap: 7; } -#tags .label { +#participants { + -fx-hgap: 7; + -fx-vgap: 7; +} + +#educations .label{ + -fx-text-fill: black; + -fx-background-color: #FFD700; + -fx-padding: 1 3 1 3; + -fx-background-radius: 6; + -fx-font-size: 12; + -fx-line-spacing: 5; +} + +#internships .label{ + -fx-text-fill: white; + -fx-background-color: #B22222; + -fx-padding: 1 3 1 3; + -fx-background-radius: 6; + -fx-font-size: 12; + -fx-line-spacing: 5; +} + +#modules .label{ -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-background-color: #008080; + -fx-padding: 1 3 1 3; + -fx-background-radius: 6; + -fx-font-size: 12; + -fx-line-spacing: 5; +} + +#ccas .label{ + -fx-text-fill: black; + -fx-background-color: #FF8C00; + -fx-padding: 1 3 1 3; + -fx-background-radius: 6; + -fx-font-size: 12; + -fx-line-spacing: 5; +} + +#participants .label{ + -fx-text-fill: black; + -fx-background-color: #90EE90; -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; - -fx-font-size: 11; + -fx-background-radius: 6; + -fx-font-size: 12; + -fx-line-spacing: 5; } diff --git a/src/main/resources/view/EventListCard.fxml b/src/main/resources/view/EventListCard.fxml new file mode 100644 index 00000000000..e478fb79e2c --- /dev/null +++ b/src/main/resources/view/EventListCard.fxml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/EventListPanel.fxml b/src/main/resources/view/EventListPanel.fxml new file mode 100644 index 00000000000..eb875694ae7 --- /dev/null +++ b/src/main/resources/view/EventListPanel.fxml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..6686e1e4175 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -8,11 +8,12 @@ + + - + @@ -33,25 +34,31 @@ - + - + - + - + - - - - - - - + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..0524d30a50e 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,49 @@ + - + - + - + - - + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml index 8836d323cc5..0fb57f12631 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/PersonListPanel.fxml @@ -1,8 +1,12 @@ + - - + + + + + diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json index 48831cc7674..d17eb7b5ced 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json @@ -4,11 +4,18 @@ "phone": "94351253", "email": "alice@example.com", "address": "123, Jurong West Ave 6, #08-111", - "tagged": [ "friends" ] + "educations": ["computer science"], + "internships":[], + "modules": ["cs2040s"], + "ccas": [] }, { "name": "Alice Pauline", "phone": "94351253", "email": "pauline@example.com", - "address": "4th street" + "address": "4th street", + "educations": ["computer science"], + "internships":[], + "modules": ["cs2040s"], + "ccas": [] } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json index ad3f135ae42..34551b67c74 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json @@ -3,6 +3,10 @@ "name": "Hans Muster", "phone": "9482424", "email": "invalid@email!3e", - "address": "4th street" + "address": "4th street", + "educations": [""], + "internships":[""], + "modules": [""], + "ccas": [""] } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json index f10eddee12e..00d1fb01e86 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json @@ -5,42 +5,63 @@ "phone" : "94351253", "email" : "alice@example.com", "address" : "123, Jurong West Ave 6, #08-111", - "tagged" : [ "friends" ] + "educations": ["NUS"], + "internships":["Facebook"], + "modules": ["CS1101S"], + "ccas": ["Basketball"] }, { "name" : "Benson Meier", "phone" : "98765432", "email" : "johnd@example.com", "address" : "311, Clementi Ave 2, #02-25", - "tagged" : [ "owesMoney", "friends" ] + "educations": ["SMU"], + "internships":["Amazon"], + "modules": ["CS3243"], + "ccas": ["Football"] }, { "name" : "Carl Kurz", "phone" : "95352563", "email" : "heinz@example.com", "address" : "wall street", - "tagged" : [ ] + "educations": [], + "internships":[], + "modules": [], + "ccas": [] }, { "name" : "Daniel Meier", "phone" : "87652533", "email" : "cornelia@example.com", "address" : "10th street", - "tagged" : [ "friends" ] + "educations": [], + "internships":[], + "modules": [], + "ccas": [] }, { "name" : "Elle Meyer", "phone" : "9482224", "email" : "werner@example.com", "address" : "michegan ave", - "tagged" : [ ] + "educations": [], + "internships":[], + "modules": [], + "ccas": [] }, { "name" : "Fiona Kunz", "phone" : "9482427", "email" : "lydia@example.com", "address" : "little tokyo", - "tagged" : [ ] + "educations": [], + "internships":[], + "modules": [], + "ccas": [] }, { "name" : "George Best", "phone" : "9482442", "email" : "anna@example.com", "address" : "4th street", - "tagged" : [ ] + "educations": [], + "internships":[], + "modules": [], + "ccas": [] } ] } diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..c3e80b24fc8 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -3,12 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.address.testutil.TypicalPersons.BOB; import java.io.IOException; import java.nio.file.Path; @@ -79,9 +79,9 @@ public void execute_storageThrowsIoException_throwsCommandException() { logic = new LogicManager(model, storage); // Execute add command - String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY - + ADDRESS_DESC_AMY; - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); + String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB; + Person expectedPerson = new PersonBuilder(BOB).build(); ModelManager expectedModel = new ModelManager(); expectedModel.addPerson(expectedPerson); String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION; @@ -101,7 +101,7 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandSuccess(String inputCommand, String expectedMessage, - Model expectedModel) throws CommandException, ParseException { + Model expectedModel) throws CommandException, ParseException { CommandResult result = logic.execute(inputCommand); assertEquals(expectedMessage, result.getFeedbackToUser()); assertEquals(expectedModel, model); @@ -128,7 +128,7 @@ private void assertCommandException(String inputCommand, String expectedMessage) * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandFailure(String inputCommand, Class expectedException, - String expectedMessage) { + String expectedMessage) { Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } @@ -141,7 +141,7 @@ private void assertCommandFailure(String inputCommand, Class expectedException, - String expectedMessage, Model expectedModel) { + String expectedMessage, Model expectedModel) { assertThrows(expectedException, expectedMessage, () -> logic.execute(inputCommand)); assertEquals(expectedModel, model); } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 5865713d5dd..6c523a6bb6c 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -20,6 +20,7 @@ import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; import seedu.address.testutil.PersonBuilder; @@ -37,7 +38,8 @@ public void execute_personAcceptedByModel_addSuccessful() throws Exception { CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); + assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), + commandResult.getFeedbackToUser()); assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); } @@ -138,15 +140,45 @@ public void setPerson(Person target, Person editedPerson) { throw new AssertionError("This method should not be called."); } + @Override + public void addEvent(Event event) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasEvent(Event event) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteEvent(Event event) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setEvent(Event target, Event editedEvent) { + throw new AssertionError("This method should not be called."); + } + @Override public ObservableList getFilteredPersonList() { throw new AssertionError("This method should not be called."); } + @Override + public ObservableList getFilteredEventList() { + throw new AssertionError("This method should not be called."); + } + @Override public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + + @Override + public void updateFilteredEventList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } } /** diff --git a/src/test/java/seedu/address/logic/commands/CancelEventCommandTest.java b/src/test/java/seedu/address/logic/commands/CancelEventCommandTest.java new file mode 100644 index 00000000000..49eb0d30fd3 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/CancelEventCommandTest.java @@ -0,0 +1,150 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showEventAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_EVENT; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; +import seedu.address.model.person.Person; + +public class CancelEventCommandTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @AfterEach + public void tearDown() { + model = null; + } + + @Test + public void execute_validIndexUnfilteredList_success() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Event eventToDelete = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + CancelEventCommand cancelEventCommand = new CancelEventCommand(INDEX_FIRST_EVENT); + + String expectedMessage = String.format(CancelEventCommand.MESSAGE_DELETE_EVENT_SUCCESS, eventToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + model.addEvent(eventToDelete); + + assertCommandSuccess(cancelEventCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Event eventToDelete = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + model.addEvent(eventToDelete); + + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredEventList().size() + 1); + CancelEventCommand cancelEventCommand = new CancelEventCommand(outOfBoundIndex); + + assertCommandFailure(cancelEventCommand, model, Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Event event = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + model.addEvent(event); + showEventAtIndex(model, INDEX_FIRST_EVENT); + + Event eventToDelete = model.getFilteredEventList().get(INDEX_FIRST_EVENT.getZeroBased()); + CancelEventCommand cancelEventCommand = new CancelEventCommand(INDEX_FIRST_EVENT); + + String expectedMessage = String.format(CancelEventCommand.MESSAGE_DELETE_EVENT_SUCCESS, eventToDelete); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteEvent(eventToDelete); + showNoEvent(expectedModel); + + assertCommandSuccess(cancelEventCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredListMultipleOutOfOrder_success() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Event eventToDelete1 = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + Event eventToDelete2 = new Event(new EventName("dinner"), new Information("at Bread Street Kitchen"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + model.addEvent(eventToDelete1); + model.addEvent(eventToDelete2); + StringBuilder eventsToDelete = new StringBuilder(); + eventsToDelete.append(eventToDelete1) + .append(System.lineSeparator()).append(eventToDelete2); + + CancelEventCommand cancelEventCommand = new CancelEventCommand( + new Index[]{INDEX_SECOND_PERSON, INDEX_FIRST_PERSON}); + + String expectedMessage = String.format(CancelEventCommand.MESSAGE_DELETE_EVENTS_SUCCESS, eventsToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteEvent(eventToDelete1); + expectedModel.deleteEvent(eventToDelete2); + + assertCommandSuccess(cancelEventCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredListMultipleInOrder_success() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Event eventToDelete1 = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + Event eventToDelete2 = new Event(new EventName("dinner"), new Information("at Bread Street Kitchen"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + model.addEvent(eventToDelete1); + model.addEvent(eventToDelete2); + StringBuilder eventsToDelete = new StringBuilder(); + eventsToDelete.append(eventToDelete2) + .append(System.lineSeparator()).append(eventToDelete1); + + CancelEventCommand cancelEventCommand = new CancelEventCommand( + new Index[]{INDEX_FIRST_PERSON, INDEX_SECOND_PERSON}); + + String expectedMessage = String.format(CancelEventCommand.MESSAGE_DELETE_EVENTS_SUCCESS, eventsToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteEvent(eventToDelete1); + expectedModel.deleteEvent(eventToDelete2); + + assertCommandSuccess(cancelEventCommand, model, expectedMessage, expectedModel); + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoEvent(Model model) { + model.updateFilteredEventList(p -> false); + + assertTrue(model.getFilteredEventList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..1846d1f6d13 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -3,10 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.testutil.Assert.assertThrows; import java.util.ArrayList; @@ -17,7 +20,9 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventNameContainsKeywordsPredicate; +import seedu.address.model.person.NameContainsKeywordsPredicateOr; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; @@ -34,8 +39,15 @@ public class CommandTestUtil { public static final String VALID_EMAIL_BOB = "bob@example.com"; public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; - public static final String VALID_TAG_HUSBAND = "husband"; - public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_EDUCATION_AMY = "Computer Science"; + public static final String VALID_EDUCATION_BOB = "Computer Engineering"; + public static final String VALID_CCA_AMY = "Netball"; + public static final String VALID_CCA_BOB = "Basketball"; + public static final String VALID_INTERNSHIP_AMY = "GIC"; + public static final String VALID_INTERNSHIP_BOB = "Bosch"; + public static final String VALID_MODULE_AMY = "CS2040S"; + public static final String VALID_MODULE_BOB = "CS2100"; + public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -45,14 +57,23 @@ public class CommandTestUtil { public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB; public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; - public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; - public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; - - public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names - public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones + public static final String EDUCATION_DESC_AMY = " " + PREFIX_EDUCATION + VALID_EDUCATION_AMY; + public static final String EDUCATION_DESC_BOB = " " + PREFIX_EDUCATION + VALID_EDUCATION_BOB; + public static final String INTERNSHIP_DESC_AMY = " " + PREFIX_INTERNSHIP + VALID_INTERNSHIP_AMY; + public static final String INTERNSHIP_DESC_BOB = " " + PREFIX_INTERNSHIP + VALID_INTERNSHIP_BOB; + public static final String MODULE_DESC_AMY = " " + PREFIX_MODULE + VALID_MODULE_AMY; + public static final String MODULE_DESC_BOB = " " + PREFIX_MODULE + VALID_MODULE_BOB; + public static final String CCA_DESC_AMY = " " + PREFIX_CCA + VALID_CCA_AMY; + public static final String CCA_DESC_BOB = " " + PREFIX_CCA + VALID_CCA_BOB; + + public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed + public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol - public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses - public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed + public static final String INVALID_EDUCATION_DESC = " " + PREFIX_EDUCATION + "NUS&"; // '&' not allowed + public static final String INVALID_INTERNSHIP_DESC = " " + PREFIX_INTERNSHIP + "A*"; // '*' not allowed + public static final String INVALID_MODULE_DESC = " " + PREFIX_MODULE + "CS2!00"; // '!' not allowed + public static final String INVALID_CCA_DESC = " " + PREFIX_CCA + "D@NCE"; // '@' not allowed for ccas public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; @@ -61,12 +82,24 @@ public class CommandTestUtil { public static final EditCommand.EditPersonDescriptor DESC_BOB; static { - DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_FRIEND).build(); - DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + DESC_AMY = new EditPersonDescriptorBuilder() + .withName(VALID_NAME_AMY) + .withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY) + .withEducation(VALID_EDUCATION_AMY) + .withInternship(VALID_INTERNSHIP_AMY) + .withModule(VALID_MODULE_AMY) + .withCca(VALID_CCA_AMY).build(); + DESC_BOB = new EditPersonDescriptorBuilder() + .withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_BOB) + .withEducation(VALID_EDUCATION_BOB) + .withInternship(VALID_INTERNSHIP_BOB) + .withModule(VALID_MODULE_BOB) + .withCca(VALID_CCA_BOB).build(); } /** @@ -111,6 +144,7 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri assertEquals(expectedAddressBook, actualModel.getAddressBook()); assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); } + /** * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the * {@code model}'s address book. @@ -119,10 +153,24 @@ public static void showPersonAtIndex(Model model, Index targetIndex) { assertTrue(targetIndex.getZeroBased() < model.getFilteredPersonList().size()); Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); - final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + //final String[] splitName = person.getName().fullName.split("\\s+"); + final String splitName = person.getName().fullName; + model.updateFilteredPersonList(new NameContainsKeywordsPredicateOr(Arrays.asList(splitName))); assertEquals(1, model.getFilteredPersonList().size()); } + /** + * Updates {@code model}'s filtered list to show only the event at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showEventAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredEventList().size()); + + Event event = model.getFilteredEventList().get(targetIndex.getZeroBased()); + final String splitName = event.getEventName().value; + model.updateFilteredEventList(new EventNameContainsKeywordsPredicate(Arrays.asList(splitName))); + + assertEquals(1, model.getFilteredEventList().size()); + } } diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 45a8c910ba1..2cb73f2c072 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -7,8 +7,11 @@ import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import seedu.address.commons.core.Messages; @@ -24,14 +27,25 @@ */ public class DeleteCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @AfterEach + public void tearDown() { + model = null; + } @Test public void execute_validIndexUnfilteredList_success() { Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, + personToDelete); ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); expectedModel.deletePerson(personToDelete); @@ -54,7 +68,8 @@ public void execute_validIndexFilteredList_success() { Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, + personToDelete); Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); expectedModel.deletePerson(personToDelete); @@ -67,7 +82,7 @@ public void execute_validIndexFilteredList_success() { public void execute_invalidIndexFilteredList_throwsCommandException() { showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + Index outOfBoundIndex = INDEX_SECOND_PERSON.getOriginalZeroBasedAsIndex(); // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); @@ -76,11 +91,86 @@ public void execute_invalidIndexFilteredList_throwsCommandException() { assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } + @Test + public void execute_validIndexUnfilteredListMultipleOutOfOrder_success() { + Person personToDelete1 = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personToDelete2 = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + Person personToDelete3 = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + StringBuilder personsToDelete = new StringBuilder(); + personsToDelete.append(personToDelete3) + .append(System.lineSeparator()).append(personToDelete1) + .append(System.lineSeparator()).append(personToDelete2); + + DeleteCommand deleteCommand = new DeleteCommand( + new Index[]{INDEX_THIRD_PERSON, INDEX_FIRST_PERSON, INDEX_SECOND_PERSON}); + + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_MULTIPLE_PERSON_SUCCESS, personsToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + // Order below does not affect deletion, it is done by name of person. + // Order of deletion decided by constructor of deleteCommand. + expectedModel.deletePerson(personToDelete1); + expectedModel.deletePerson(personToDelete2); + expectedModel.deletePerson(personToDelete3); + + assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredListMultipleInOrder_success() { + Person personToDelete1 = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personToDelete2 = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + Person personToDelete3 = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + StringBuilder personsToDelete = new StringBuilder(); + personsToDelete.append(personToDelete1) + .append(System.lineSeparator()).append(personToDelete2) + .append(System.lineSeparator()).append(personToDelete3); + + DeleteCommand deleteCommand = new DeleteCommand( + new Index[]{INDEX_FIRST_PERSON, INDEX_SECOND_PERSON, INDEX_THIRD_PERSON}); + + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_MULTIPLE_PERSON_SUCCESS, personsToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + // Order below does not affect deletion, it is done by name of person. + // Order of deletion decided by constructor of deleteCommand. + expectedModel.deletePerson(personToDelete1); + expectedModel.deletePerson(personToDelete2); + expectedModel.deletePerson(personToDelete3); + + assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredListMultipleReverseOrder_success() { + Person personToDelete1 = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personToDelete2 = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + Person personToDelete3 = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + StringBuilder personsToDelete = new StringBuilder(); + personsToDelete.append(personToDelete3) + .append(System.lineSeparator()).append(personToDelete2) + .append(System.lineSeparator()).append(personToDelete1); + + DeleteCommand deleteCommand = new DeleteCommand( + new Index[]{INDEX_THIRD_PERSON, INDEX_SECOND_PERSON, INDEX_FIRST_PERSON}); + + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_MULTIPLE_PERSON_SUCCESS, + personsToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + // Order below does not affect deletion, it is done by name of person. + // Order of deletion decided by constructor of deleteCommand. + expectedModel.deletePerson(personToDelete1); + expectedModel.deletePerson(personToDelete2); + expectedModel.deletePerson(personToDelete3); + + assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + } + @Test public void equals() { DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); - // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); @@ -106,4 +196,5 @@ private void showNoPerson(Model model) { assertTrue(model.getFilteredPersonList().isEmpty()); } + } diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index 214c6c2507b..ef2cbd95b95 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -6,7 +6,6 @@ import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; @@ -34,9 +33,33 @@ public class EditCommandTest { private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + @Test + public void execute_filteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); + EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, + new EditPersonDescriptorBuilder(personInFilteredList).withName(VALID_NAME_BOB).build()); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, + editedPerson); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().build(); + Person editedPerson = new PersonBuilder() + .withEducation(PersonBuilder.DEFAULT_EDUCATION) + .withInternship(PersonBuilder.DEFAULT_INTERNSHIP) + .withModule(PersonBuilder.DEFAULT_MODULE) + .withCca(PersonBuilder.DEFAULT_CCA).build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); @@ -54,14 +77,14 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() { Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased()); PersonBuilder personInList = new PersonBuilder(lastPerson); - Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withTags(VALID_TAG_HUSBAND).build(); + Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB).build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); + .withPhone(VALID_PHONE_BOB).build(); EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, + editedPerson); Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); expectedModel.setPerson(lastPerson, editedPerson); @@ -69,35 +92,6 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() { assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); } - @Test - public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_filteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - @Test public void execute_duplicatePersonUnfilteredList_failure() { Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java index e0288792e72..14116e8bc54 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java @@ -8,7 +8,6 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import org.junit.jupiter.api.Test; @@ -50,9 +49,5 @@ public void equals() { // different address -> returns false editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); - - // different tags -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build(); - assertFalse(DESC_AMY.equals(editedAmy)); } } diff --git a/src/test/java/seedu/address/logic/commands/EventCommandTest.java b/src/test/java/seedu/address/logic/commands/EventCommandTest.java new file mode 100644 index 00000000000..9376f63414e --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EventCommandTest.java @@ -0,0 +1,77 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; +import seedu.address.model.person.Person; + +public class EventCommandTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @AfterEach + public void tearDown() { + model = null; + } + + @Test + public void constructor_nullEvent_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new EventCommand(null, null, null, null)); + } + + @Test + public void execute_eventAcceptedByModel_addSuccessful() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + EventCommand eventCommand = new EventCommand(new Index[]{Index.fromOneBased(1)}, new EventName("lunch"), + new Information("at HDL"), new DateTime(2022, 10, 10, 11, 11)); + Event event = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + + String expectedMessage = String.format(EventCommand.MESSAGE_ARGUMENTS, + new ArrayList<>(List.of(person.getName())), event); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.addEvent(event); + + assertCommandSuccess(eventCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicateEvent_throwsCommandException() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + EventCommand eventCommand = new EventCommand(new Index[]{Index.fromOneBased(1)}, new EventName("lunch"), + new Information("at HDL"), new DateTime(2022, 10, 10, 11, 11)); + Event event = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2022, 10, 10, 11, 11)); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.addEvent(event); + + assertThrows(CommandException.class, + EventCommand.MESSAGE_DUPLICATE_EVENT, () -> eventCommand.execute(expectedModel)); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 9b15db28bbb..b0a0eb1a816 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -5,20 +5,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.Test; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameContainsKeywordsPredicateOr; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. @@ -29,10 +28,10 @@ public class FindCommandTest { @Test public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); + NameContainsKeywordsPredicateOr firstPredicate = + new NameContainsKeywordsPredicateOr(Collections.singletonList("first")); + NameContainsKeywordsPredicateOr secondPredicate = + new NameContainsKeywordsPredicateOr(Collections.singletonList("second")); FindCommand findFirstCommand = new FindCommand(firstPredicate); FindCommand findSecondCommand = new FindCommand(secondPredicate); @@ -55,29 +54,21 @@ public void equals() { } @Test - public void execute_zeroKeywords_noPersonFound() { + public void execute_multipleKeywords_zeroPersonsFound() { String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); + NameContainsKeywordsPredicateOr predicate = preparePredicate("Kurz Elle Kunz"); FindCommand command = new FindCommand(predicate); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredPersonList()); - } - - @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); + assertEquals(Arrays.asList(), model.getFilteredPersonList()); } /** * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + private NameContainsKeywordsPredicateOr preparePredicate(String userInput) { + List l = new ArrayList(); + l.add(0, userInput); + return new NameContainsKeywordsPredicateOr(l); } } diff --git a/src/test/java/seedu/address/logic/commands/RemoveTagCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveTagCommandTest.java new file mode 100644 index 00000000000..2399c8f299d --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/RemoveTagCommandTest.java @@ -0,0 +1,96 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +class RemoveTagCommandTest { + private static final String EDUCATION_STRING = "NUS"; + private static final String EDUCATION_STRING_2 = "NTU"; + private static final String INTERNSHIP_STRING = "Twitter"; + private static final String INTERNSHIP_STRING_2 = "Google"; + private static final String MODULE_STRING = "CS2040S"; + private static final String MODULE_STRING_2 = "CS3230"; + private static final String CCA_STRING = "Greyhats"; + private static final String CCA_STRING_2 = "NUSHackers"; + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final List emptyList = new ArrayList<>(); + private final List eduList = Arrays.asList(new Education(EDUCATION_STRING)); + private final List eduList2 = Arrays.asList(new Education(EDUCATION_STRING_2)); + private final List internshipList = Arrays.asList(new Internship(INTERNSHIP_STRING)); + private final List internshipList2 = Arrays.asList(new Internship(INTERNSHIP_STRING_2)); + private final List moduleList = Arrays.asList(new Module(MODULE_STRING)); + private final List moduleList2 = Arrays.asList(new Module(MODULE_STRING_2)); + private final List ccaList = Arrays.asList(new Cca(CCA_STRING)); + private final List ccaList2 = Arrays.asList(new Cca(CCA_STRING_2)); + + @Test + void execute_validIndexUnfilteredList_success() { + Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + RemoveTagCommand removeTagCommand = + new RemoveTagCommand(INDEX_FIRST_PERSON, eduList, emptyList, emptyList, emptyList); + String expectedMessage = String.format(RemoveTagCommand.MESSAGE_ARGUMENTS, + personToEdit.getName(), + "[]", personToEdit.getInternships(), personToEdit.getModules(), personToEdit.getCcas()); + + Person editedPerson = new Person( + personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), + emptyList, personToEdit.getInternships(), personToEdit.getModules(), personToEdit.getCcas()); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + expectedModel.setPerson(personToEdit, editedPerson); + + assertCommandSuccess(removeTagCommand, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + RemoveTagCommand firstCommand = new RemoveTagCommand(INDEX_FIRST_PERSON, + eduList, internshipList, moduleList, ccaList); + RemoveTagCommand secondCommand = new RemoveTagCommand(INDEX_FIRST_PERSON, + eduList2, internshipList2, moduleList2, ccaList2); + RemoveTagCommand thirdCommand = new RemoveTagCommand(INDEX_SECOND_PERSON, + eduList, internshipList, moduleList, ccaList); + + // same object -> returns true + assertTrue(firstCommand.equals(firstCommand)); + + // same values -> returns true + RemoveTagCommand firstCommandCopy = new RemoveTagCommand(INDEX_FIRST_PERSON, + eduList, internshipList, moduleList, ccaList); + assertTrue(firstCommand.equals(firstCommandCopy)); + + // different types -> returns false + assertFalse(firstCommand.equals(1)); + + // null -> returns false + assertFalse(firstCommand.equals(null)); + + // different person -> returns false + assertFalse(firstCommand.equals(thirdCommand)); + + // same person, different tags -> returns false + assertFalse(firstCommand.equals(secondCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/TagCommandTest.java b/src/test/java/seedu/address/logic/commands/TagCommandTest.java new file mode 100644 index 00000000000..d447b31cb35 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/TagCommandTest.java @@ -0,0 +1,162 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +public class TagCommandTest { + private static final String EDUCATION_STRING = "NUS"; + private static final String INTERNSHIP_STRING = "Facebook"; + private static final String MODULE_STRING = "CS1101S"; + private static final String CCA_STRING = "Basketball"; + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final List emptyList = new ArrayList<>(); + private final List eduList = Arrays.asList(new Education(EDUCATION_STRING)); + private final List internshipList = Arrays.asList(new Internship(INTERNSHIP_STRING)); + private final List moduleList = Arrays.asList(new Module(MODULE_STRING)); + private final List ccaList = Arrays.asList(new Cca(CCA_STRING)); + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + TagCommand tagCommand = new TagCommand(outOfBoundIndex, emptyList, emptyList, emptyList, emptyList); + + assertCommandFailure(tagCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexUnfilteredListAllMissing_failure() { + TagCommand tagCommand = new TagCommand(INDEX_FIRST_PERSON, emptyList, emptyList, emptyList, emptyList); + + assertCommandFailure(tagCommand, model, TagCommand.MESSAGE_NO_PARAMETERS); + } + + @Test + public void execute_validIndexUnfilteredListEduMissing_success() { + Person personToTag = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + + TagCommand tagCommandEduEmpty = new TagCommand( + INDEX_THIRD_PERSON, emptyList, internshipList, moduleList, ccaList); + + String expectedMessageEdu = String.format(TagCommand.MESSAGE_ARGUMENTS, + personToTag.getName(), + "[]", "[" + INTERNSHIP_STRING + "]", "[" + MODULE_STRING + "]", "[" + CCA_STRING + "]"); + + Person editedPersonEdu = new Person( + personToTag.getName(), personToTag.getPhone(), personToTag.getEmail(), personToTag.getAddress(), + emptyList, internshipList, moduleList, ccaList); + + ModelManager expectedModelEduEmpty = new ModelManager(model.getAddressBook(), new UserPrefs()); + + expectedModelEduEmpty.setPerson(personToTag, editedPersonEdu); + + assertCommandSuccess(tagCommandEduEmpty, model, expectedMessageEdu, expectedModelEduEmpty); + } + + @Test + public void execute_validIndexUnfilteredListInternshipMissing_success() { + Person personToTag = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + + TagCommand tagCommandInternshipEmpty = new TagCommand( + INDEX_THIRD_PERSON, eduList, emptyList, moduleList, ccaList); + + String expectedMessageInternship = String.format(TagCommand.MESSAGE_ARGUMENTS, + personToTag.getName(), + "[" + EDUCATION_STRING + "]", "[]", "[" + MODULE_STRING + "]", "[" + CCA_STRING + "]"); + + Person editedPersonInternship = new Person( + personToTag.getName(), personToTag.getPhone(), personToTag.getEmail(), personToTag.getAddress(), + eduList, emptyList, moduleList, ccaList); + + ModelManager expectedModelInternshipEmpty = new ModelManager(model.getAddressBook(), new UserPrefs()); + + expectedModelInternshipEmpty.setPerson(personToTag, editedPersonInternship); + + assertCommandSuccess(tagCommandInternshipEmpty, model, expectedMessageInternship, expectedModelInternshipEmpty); + } + + @Test + public void execute_validIndexUnfilteredListModuleMissing_success() { + Person personToTag = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + + TagCommand tagCommandModuleEmpty = new TagCommand( + INDEX_THIRD_PERSON, eduList, internshipList, emptyList, ccaList); + String expectedMessageModule = String.format(TagCommand.MESSAGE_ARGUMENTS, + personToTag.getName(), + "[" + EDUCATION_STRING + "]", "[" + INTERNSHIP_STRING + "]", "[]", "[" + CCA_STRING + "]"); + + Person editedPersonModule = new Person( + personToTag.getName(), personToTag.getPhone(), personToTag.getEmail(), personToTag.getAddress(), + eduList, internshipList, emptyList, ccaList); + + ModelManager expectedModelModuleEmpty = new ModelManager(model.getAddressBook(), new UserPrefs()); + + expectedModelModuleEmpty.setPerson(personToTag, editedPersonModule); + + assertCommandSuccess(tagCommandModuleEmpty, model, expectedMessageModule, expectedModelModuleEmpty); + } + + @Test + public void execute_validIndexUnfilteredListCcaMissing_success() { + Person personToTag = model.getFilteredPersonList().get(INDEX_THIRD_PERSON.getZeroBased()); + TagCommand tagCommandCcaEmpty = new TagCommand( + INDEX_THIRD_PERSON, eduList, internshipList, moduleList, emptyList); + + String expectedMessageCca = String.format(TagCommand.MESSAGE_ARGUMENTS, + personToTag.getName(), + "[" + EDUCATION_STRING + "]", "[" + INTERNSHIP_STRING + "]", "[" + MODULE_STRING + "]", "[]"); + + Person editedPersonCca = new Person( + personToTag.getName(), personToTag.getPhone(), personToTag.getEmail(), personToTag.getAddress(), + eduList, internshipList, moduleList, emptyList); + + ModelManager expectedModelCcaEmpty = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModelCcaEmpty.setPerson(personToTag, editedPersonCca); + + assertCommandSuccess(tagCommandCcaEmpty, model, expectedMessageCca, expectedModelCcaEmpty); + } + + @Test + public void equals() { + Module moduleTag = new Module("CS2103T"); + Module moduleTag2 = new Module("CS2101"); + + // same object -> returns true + assertTrue(moduleTag.equals(moduleTag)); + + // same values -> returns true + Module moduleTagCopy = new Module("CS2103T"); + assertTrue(moduleTag.equals(moduleTagCopy)); + + // different types -> returns false + assertFalse(moduleTag.equals(1)); + + // null -> returns false + assertFalse(moduleTag.equals(null)); + + // different tag -> returns false + assertFalse(moduleTag.equals(moduleTag2)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index 5cf487d7ebb..f5477ab90c2 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -1,32 +1,22 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalPersons.AMY; import static seedu.address.testutil.TypicalPersons.BOB; import org.junit.jupiter.api.Test; @@ -37,7 +27,6 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; import seedu.address.testutil.PersonBuilder; public class AddCommandParserTest { @@ -45,40 +34,18 @@ public class AddCommandParserTest { @Test public void parse_allFieldsPresent_success() { - Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); + Person expectedPerson = new PersonBuilder(BOB).build(); // whitespace only preamble assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple names - last name accepted - assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple phones - last phone accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple emails - last email accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple addresses - last address accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple tags - all accepted - Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); + + ADDRESS_DESC_BOB, new AddCommand(expectedPerson)); } @Test public void parse_optionalFieldsMissing_success() { // zero tags - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); - assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, + Person expectedPerson = new PersonBuilder(BOB).build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, new AddCommand(expectedPerson)); } @@ -110,24 +77,21 @@ public void parse_compulsoryFieldMissing_failure() { @Test public void parse_invalidValue_failure() { // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, + Name.MESSAGE_CONSTRAINTS); // invalid phone - assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, + Phone.MESSAGE_CONSTRAINTS); // invalid email - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB, + Email.MESSAGE_CONSTRAINTS); - // invalid address - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS); - // invalid tag - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS); + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, + Address.MESSAGE_CONSTRAINTS); // two invalid values, only first invalid value reported assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, @@ -135,7 +99,7 @@ public void parse_invalidValue_failure() { // non-empty preamble assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + + ADDRESS_DESC_BOB, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index d9659205b57..cdd87c30499 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -7,10 +7,6 @@ import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - import org.junit.jupiter.api.Test; import seedu.address.logic.commands.AddCommand; @@ -23,7 +19,6 @@ import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; @@ -70,10 +65,10 @@ public void parseCommand_exit() throws Exception { @Test public void parseCommand_find() throws Exception { - List keywords = Arrays.asList("foo", "bar", "baz"); + String keyword = "Alex"; FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + FindCommand.COMMAND_WORD + " n/" + keyword); + //assertEquals(new FindCommandParser().parse("n/" + keyword), command); } @Test diff --git a/src/test/java/seedu/address/logic/parser/CancelEventCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CancelEventCommandParserTest.java new file mode 100644 index 00000000000..ebd41d2d367 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/CancelEventCommandParserTest.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CancelEventCommand; + +public class CancelEventCommandParserTest { + + private CancelEventCommandParser parser = new CancelEventCommandParser(); + + @Test + public void parse_validArgs_returnsCancelEventCommand() { + assertParseSuccess(parser, "1", new CancelEventCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, " 1 ", new CancelEventCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, " 1 2 3 ", new CancelEventCommand( + new Index[]{INDEX_FIRST_PERSON, INDEX_SECOND_PERSON, INDEX_THIRD_PERSON})); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + // Contains non-zero integer -> Fail + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + // "+1" can be parsed into 1, should be detected and rejected -> Fail + assertParseFailure(parser, "+1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + // Contains non-zero integer in the multiple case -> Fail + assertParseFailure(parser, "1 a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + // Contains non-zero integer in the multiple case -> Fail + assertParseFailure(parser, "1 -1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + // Contains than 1 white space between 2 integers -> Fail + assertParseFailure(parser, "1 2 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + // Contains zero -> Fail + assertParseFailure(parser, "1 0 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + // Contains duplicate integer -> Fail + assertParseFailure(parser, "1 1 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CancelEventCommand.MESSAGE_USAGE)); + + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java index 27eaec84450..e1fb0b4e9b6 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java @@ -4,9 +4,12 @@ import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; import org.junit.jupiter.api.Test; +import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; /** @@ -23,10 +26,34 @@ public class DeleteCommandParserTest { @Test public void parse_validArgs_returnsDeleteCommand() { assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, " 1 ", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, " 1 2 3 ", + new DeleteCommand(new Index[]{INDEX_FIRST_PERSON, INDEX_SECOND_PERSON, INDEX_THIRD_PERSON})); } @Test public void parse_invalidArgs_throwsParseException() { - assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + // Contains non-zero integer -> Fail + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + // "+1" can be parsed into 1, should be detected and rejected -> Fail + assertParseFailure(parser, "+1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + // Contains non-zero integer in the multiple case -> Fail + assertParseFailure(parser, "1 a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + // Contains non-zero integer in the multiple case -> Fail + assertParseFailure(parser, "1 -1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + // Contains more than 1 white space between 2 integers -> Fail + assertParseFailure(parser, "1 2 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + // Contains zero -> Fail + assertParseFailure(parser, "1 0 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + // Contains duplicate integer -> Fail + assertParseFailure(parser, "1 1 3", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + } } diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index 2ff31522486..e3101703fe6 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -2,29 +2,19 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; @@ -40,12 +30,11 @@ import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; import seedu.address.testutil.EditPersonDescriptorBuilder; public class EditCommandParserTest { - private static final String TAG_EMPTY = " " + PREFIX_TAG; + private static final String TAG_EMPTY = " "; private static final String MESSAGE_INVALID_FORMAT = String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); @@ -58,12 +47,41 @@ public void parse_missingParts_failure() { assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); // no field specified - assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); + assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED_OR_INVALID); // no index and no field specified assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); } + @Test + public void parse_oneFieldSpecified_success() { + //name + Index targetIndex = INDEX_THIRD_PERSON; + String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); + EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + //phone + userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + //email + userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + //address + userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test public void parse_invalidPreamble_failure() { // negative index @@ -76,7 +94,7 @@ public void parse_invalidPreamble_failure() { assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); // invalid prefix being parsed as preamble - assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + assertParseFailure(parser, "1 x/ string", MESSAGE_INVALID_FORMAT); } @Test @@ -85,35 +103,23 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address - assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag // invalid phone followed by valid email assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); - // valid phone followed by invalid phone. The test case for invalid phone followed by valid phone + // invalid phone. The test case for invalid phone followed by valid phone // is tested at {@code parse_invalidValueFollowedByValidValue_success()} - assertParseFailure(parser, "1" + PHONE_DESC_BOB + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); - - // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited, - // parsing it together with a valid tag results in error - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - - // multiple invalid values, but only the first invalid value is captured - assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY, - Name.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); } @Test public void parse_allFieldsSpecified_success() { Index targetIndex = INDEX_SECOND_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND - + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; + String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + NAME_DESC_AMY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); @@ -130,82 +136,4 @@ public void parse_someFieldsSpecified_success() { assertParseSuccess(parser, userInput, expectedCommand); } - - @Test - public void parse_oneFieldSpecified_success() { - // name - Index targetIndex = INDEX_THIRD_PERSON; - String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // phone - userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // email - userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // address - userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // tags - userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND; - descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_multipleRepeatedFields_acceptsLast() { - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY - + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND - + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_invalidValueFollowedByValidValue_success() { - // no other valid values specified - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // other valid values specified - userInput = targetIndex.getOneBased() + EMAIL_DESC_BOB + INVALID_PHONE_DESC + ADDRESS_DESC_BOB - + PHONE_DESC_BOB; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_resetTags_success() { - Index targetIndex = INDEX_THIRD_PERSON; - String userInput = targetIndex.getOneBased() + TAG_EMPTY; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } } diff --git a/src/test/java/seedu/address/logic/parser/EventCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EventCommandParserTest.java new file mode 100644 index 00000000000..80c0f0503f5 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EventCommandParserTest.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EventCommand; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; + +public class EventCommandParserTest { + private EventCommandParser parser = new EventCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + EventCommand expectedEventCommand = new EventCommand(new Index[]{Index.fromOneBased(1)}, new EventName("lunch"), + new Information("at HDL"), new DateTime(2022, 10, 10, 11, 11)); + + assertParseSuccess(parser, " 1 name/lunch info/at HDL d/2022-10-10 t/11:11", + expectedEventCommand); + } + + @Test + public void parse_compulsoryFieldsMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, EventCommand.MESSAGE_USAGE); + + assertParseFailure(parser, " 1 name/lunch d/2022-10-10 t/11:11", + expectedMessage); + assertParseFailure(parser, " 1 info/at HDL d/2022-10-10 t/11:11", + expectedMessage); + assertParseFailure(parser, " 1 name/lunch info/at HDL t/11:11", + expectedMessage); + assertParseFailure(parser, " 1 name/lunch info/at HDL d/2022-10-10", + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, " 1 name/ info/at HDL d/2022-10-10 t/11:11", + EventName.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, " 1 name/lunch info/ d/2022-10-10 t/11:11", + Information.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, " 1 name/lunch info/at HDL d/ t/11:11", + DateTime.DATE_MESSAGE_CONSTRAINTS); + assertParseFailure(parser, " 1 name/lunch info/at HDL d/2022-10-10 t/", + DateTime.TIME_MESSAGE_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java index 70f4f0e79c4..14876a4f98f 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -1,15 +1,10 @@ package seedu.address.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; - -import java.util.Arrays; import org.junit.jupiter.api.Test; import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; public class FindCommandParserTest { @@ -17,18 +12,7 @@ public class FindCommandParserTest { @Test public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - @Test - public void parse_validArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); + assertParseFailure(parser, " ", FindCommand.MESSAGE_NO_PARAMETERS); } } diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..9c886a9c95b 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -9,13 +9,17 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.Set; +import java.util.List; import org.junit.jupiter.api.Test; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -25,14 +29,24 @@ public class ParserUtilTest { private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final String INVALID_TAG_CCA = "Tr@ck and F!eld"; + private static final String INVALID_TAG_MODULE = "CS****S"; + private static final String INVALID_TAG_INTERNSHIP = "#OCBC"; + private static final String INVALID_TAG_EDUCATION = "#NUS"; + private static final String EMPTY_SPACE = " "; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "123456"; private static final String VALID_ADDRESS = "123 Main Street #0505"; private static final String VALID_EMAIL = "rachel@example.com"; - private static final String VALID_TAG_1 = "friend"; - private static final String VALID_TAG_2 = "neighbour"; + private static final String VALID_TAG_CCA_1 = "Track"; + private static final String VALID_TAG_CCA_2 = "Tennis"; + private static final String VALID_TAG_MODULE_1 = "CS1231S"; + private static final String VALID_TAG_MODULE_2 = "CS2100"; + private static final String VALID_TAG_INTERNSHIP_1 = "Google"; + private static final String VALID_TAG_INTERNSHIP_2 = "Facebook"; + private static final String VALID_TAG_EDUCATION_1 = "NUS"; + private static final String VALID_TAG_EDUCATION_2 = "NTU"; private static final String WHITESPACE = " \t\r\n"; @@ -147,50 +161,140 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc Email expectedEmail = new Email(VALID_EMAIL); assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace)); } - + // broken from below @Test public void parseTag_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null, Tag.CCA)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null, Tag.EDUCATION)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null, Tag.INTERNSHIP)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null, Tag.MODULE)); + } + + @Test + public void parseTag_emptyString_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTag(EMPTY_SPACE, Tag.CCA)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(EMPTY_SPACE, Tag.EDUCATION)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(EMPTY_SPACE, Tag.INTERNSHIP)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(EMPTY_SPACE, Tag.MODULE)); } @Test public void parseTag_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG_CCA, Tag.CCA)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG_EDUCATION, Tag.EDUCATION)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG_INTERNSHIP, Tag.INTERNSHIP)); + assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG_MODULE, Tag.MODULE)); + } @Test public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception { - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1)); + Tag expectedTagCca = new Cca(VALID_TAG_CCA_1); + Tag expectedTagEducation = new Education(VALID_TAG_EDUCATION_1); + Tag expectedTagInternship = new Internship(VALID_TAG_INTERNSHIP_1); + Tag expectedTagModule = new Module(VALID_TAG_MODULE_1); + + assertEquals(expectedTagCca, ParserUtil.parseTag(VALID_TAG_CCA_1, Tag.CCA)); + assertEquals(expectedTagEducation, ParserUtil.parseTag(VALID_TAG_EDUCATION_1, Tag.EDUCATION)); + assertEquals(expectedTagInternship, ParserUtil.parseTag(VALID_TAG_INTERNSHIP_1, Tag.INTERNSHIP)); + assertEquals(expectedTagModule, ParserUtil.parseTag(VALID_TAG_MODULE_1, Tag.MODULE)); } @Test public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Exception { - String tagWithWhitespace = WHITESPACE + VALID_TAG_1 + WHITESPACE; - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace)); + String tagCcaWithWhitespace = WHITESPACE + VALID_TAG_CCA_1 + WHITESPACE; + String tagEducationWithWhitespace = WHITESPACE + VALID_TAG_EDUCATION_1 + WHITESPACE; + String tagInternshipWithWhitespace = WHITESPACE + VALID_TAG_INTERNSHIP_1 + WHITESPACE; + String tagModuleWithWhitespace = WHITESPACE + VALID_TAG_MODULE_1 + WHITESPACE; + + Tag expectedTagCca = new Cca(VALID_TAG_CCA_1); + Tag expectedTagEducation = new Education(VALID_TAG_EDUCATION_1); + Tag expectedTagInternship = new Internship(VALID_TAG_INTERNSHIP_1); + Tag expectedTagModule = new Module(VALID_TAG_MODULE_1); + + assertEquals(expectedTagCca, ParserUtil.parseTag(tagCcaWithWhitespace, Tag.CCA)); + assertEquals(expectedTagEducation, ParserUtil.parseTag(tagEducationWithWhitespace, Tag.EDUCATION)); + assertEquals(expectedTagInternship, ParserUtil.parseTag(tagInternshipWithWhitespace, Tag.INTERNSHIP)); + assertEquals(expectedTagModule, ParserUtil.parseTag(tagModuleWithWhitespace, Tag.MODULE)); } @Test public void parseTags_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null, Tag.CCA)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null, Tag.EDUCATION)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null, Tag.INTERNSHIP)); + assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null, Tag.MODULE)); } @Test public void parseTags_collectionWithInvalidTags_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG))); + assertThrows(ParseException.class, () -> ParserUtil.parseTags( + Arrays.asList(VALID_TAG_CCA_1, INVALID_TAG_CCA), Tag.CCA)); + assertThrows(ParseException.class, () -> ParserUtil.parseTags( + Arrays.asList(VALID_TAG_INTERNSHIP_1, INVALID_TAG_INTERNSHIP), Tag.INTERNSHIP)); + assertThrows(ParseException.class, () -> ParserUtil.parseTags( + Arrays.asList(VALID_TAG_EDUCATION_1, INVALID_TAG_EDUCATION), Tag.EDUCATION)); + assertThrows(ParseException.class, () -> ParserUtil.parseTags( + Arrays.asList(VALID_TAG_MODULE_1, INVALID_TAG_MODULE), Tag.MODULE)); } @Test public void parseTags_emptyCollection_returnsEmptySet() throws Exception { - assertTrue(ParserUtil.parseTags(Collections.emptyList()).isEmpty()); + assertTrue(ParserUtil.parseTags(Collections.emptyList(), Tag.CCA).isEmpty()); + assertTrue(ParserUtil.parseTags(Collections.emptyList(), Tag.EDUCATION).isEmpty()); + assertTrue(ParserUtil.parseTags(Collections.emptyList(), Tag.INTERNSHIP).isEmpty()); + assertTrue(ParserUtil.parseTags(Collections.emptyList(), Tag.MODULE).isEmpty()); } @Test public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { - Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); - Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + HashSet actualTagsCca = new HashSet<>( + ParserUtil.parseTags(Arrays.asList(VALID_TAG_CCA_1, VALID_TAG_CCA_2), Tag.CCA)); + HashSet actualTagsEducation = new HashSet<>( + ParserUtil.parseTags(Arrays.asList(VALID_TAG_EDUCATION_1, VALID_TAG_EDUCATION_2), Tag.EDUCATION)); + HashSet actualTagsInternship = new HashSet<>( + ParserUtil.parseTags(Arrays.asList(VALID_TAG_INTERNSHIP_1, VALID_TAG_INTERNSHIP_2), Tag.INTERNSHIP)); + HashSet actualTagsModule = new HashSet<>( + ParserUtil.parseTags(Arrays.asList(VALID_TAG_MODULE_1, VALID_TAG_MODULE_2), Tag.MODULE)); + + HashSet expectedTagsCca = new HashSet<>(Arrays.asList( + new Cca(VALID_TAG_CCA_1.toLowerCase()), + new Cca(VALID_TAG_CCA_2.toLowerCase()))); + HashSet expectedTagsEducation = new HashSet<>(Arrays.asList( + new Education(VALID_TAG_EDUCATION_1.toLowerCase()), + new Education(VALID_TAG_EDUCATION_2.toLowerCase()))); + HashSet expectedTagsInternship = new HashSet<>(Arrays.asList( + new Internship(VALID_TAG_INTERNSHIP_1.toLowerCase()), + new Internship(VALID_TAG_INTERNSHIP_2.toLowerCase()))); + HashSet expectedTagsModule = new HashSet<>(Arrays.asList( + new Module(VALID_TAG_MODULE_1.toLowerCase()), + new Module(VALID_TAG_MODULE_2.toLowerCase()))); + + assertEquals(expectedTagsCca, actualTagsCca); + assertEquals(expectedTagsEducation, actualTagsEducation); + assertEquals(expectedTagsInternship, actualTagsInternship); + assertEquals(expectedTagsModule, actualTagsModule); + } - assertEquals(expectedTagSet, actualTagSet); + @Test + public void parseTags_collectionWithDuplicateTags_returnsTagSet() throws Exception { + List actualTagsCca = ParserUtil.parseTags( + Arrays.asList(VALID_TAG_CCA_1, VALID_TAG_CCA_1), Tag.CCA); + List actualTagsEducation = ParserUtil.parseTags( + Arrays.asList(VALID_TAG_EDUCATION_1, VALID_TAG_EDUCATION_1), Tag.EDUCATION); + List actualTagsInternship = ParserUtil.parseTags( + Arrays.asList(VALID_TAG_INTERNSHIP_1, VALID_TAG_INTERNSHIP_1), Tag.INTERNSHIP); + List actualTagsModule = ParserUtil.parseTags( + Arrays.asList(VALID_TAG_MODULE_1, VALID_TAG_MODULE_1), Tag.MODULE); + + List expectedTagsCca = Arrays.asList(new Cca(VALID_TAG_CCA_1)); + List expectedTagsEducation = Arrays.asList(new Education(VALID_TAG_EDUCATION_1)); + List expectedTagsInternship = Arrays.asList(new Internship(VALID_TAG_INTERNSHIP_1)); + List expectedTagsModule = Arrays.asList(new Module(VALID_TAG_MODULE_1)); + + assertEquals(expectedTagsCca, actualTagsCca); + assertEquals(expectedTagsEducation, actualTagsEducation); + assertEquals(expectedTagsInternship, actualTagsInternship); + assertEquals(expectedTagsModule, actualTagsModule); } } diff --git a/src/test/java/seedu/address/logic/parser/RemoveTagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/RemoveTagCommandParserTest.java new file mode 100644 index 00000000000..4f07f541d99 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/RemoveTagCommandParserTest.java @@ -0,0 +1,151 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.RemoveTagCommand; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; +import seedu.address.model.tag.Tag; + +class RemoveTagCommandParserTest { + private final RemoveTagCommandParser parser = new RemoveTagCommandParser(); + private final String nonEmptyEducation = "Some education"; + private final String nonEmptyModule = "Some module"; + private final String nonEmptyInternship = "Some internship"; + private final String nonEmptyCca = "Some cca"; + + @Test + public void parse_indexSpecifiedOnePrefix_success() { + Set emptySet = new HashSet<>(); + ArrayList emptyList = new ArrayList<>(emptySet); + + Set eduSet = new HashSet<>(); + eduSet.add(new Education(nonEmptyEducation.trim().toLowerCase())); + ArrayList eduList = new ArrayList<>(eduSet); + + Set internSet = new HashSet<>(); + internSet.add(new Internship(nonEmptyInternship.trim().toLowerCase())); + ArrayList internList = new ArrayList<>(internSet); + + Set modSet = new HashSet<>(); + modSet.add(new Module(nonEmptyModule.trim().toLowerCase())); + ArrayList moduleList = new ArrayList<>(modSet); + + Set ccaSet = new HashSet<>(); + ccaSet.add(new Cca(nonEmptyCca.trim().toLowerCase())); + ArrayList ccaList = new ArrayList<>(ccaSet); + + RemoveTagCommand expectedCommandEduOnly = new RemoveTagCommand(INDEX_FIRST_PERSON, eduList, + emptyList, emptyList, emptyList); + + RemoveTagCommand expectedCommandInternOnly = new RemoveTagCommand(INDEX_FIRST_PERSON, emptyList, + internList, emptyList, emptyList); + + RemoveTagCommand expectedCommandModuleOnly = new RemoveTagCommand(INDEX_FIRST_PERSON, emptyList, + emptyList, moduleList, emptyList); + + RemoveTagCommand expectedCommandCcaOnly = new RemoveTagCommand(INDEX_FIRST_PERSON, emptyList, + emptyList, emptyList, ccaList); + + String userInputEduOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_EDUCATION + nonEmptyEducation; + + String userInputInternOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_INTERNSHIP + nonEmptyInternship; + + String userInputModOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_MODULE + nonEmptyModule; + + String userInputCcaOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_CCA + nonEmptyCca; + + assertParseSuccess(parser, userInputEduOnly, expectedCommandEduOnly); + assertParseSuccess(parser, userInputInternOnly, expectedCommandInternOnly); + assertParseSuccess(parser, userInputModOnly, expectedCommandModuleOnly); + assertParseSuccess(parser, userInputCcaOnly, expectedCommandCcaOnly); + } + + @Test + public void parse_indexSpecifiedMultiplePrefix_success() { + Set emptySet = new HashSet<>(); + ArrayList emptyList = new ArrayList<>(emptySet); + + Set eduSet = new HashSet<>(); + eduSet.add(new Education(nonEmptyEducation.trim().toLowerCase())); + ArrayList eduList = new ArrayList<>(eduSet); + + Set internSet = new HashSet<>(); + internSet.add(new Internship(nonEmptyInternship.trim().toLowerCase())); + ArrayList internList = new ArrayList<>(internSet); + + Set modSet = new HashSet<>(); + modSet.add(new Module(nonEmptyModule.trim().toLowerCase())); + ArrayList moduleList = new ArrayList<>(modSet); + + Set ccaSet = new HashSet<>(); + ccaSet.add(new Cca(nonEmptyCca.trim().toLowerCase())); + ArrayList ccaList = new ArrayList<>(ccaSet); + + RemoveTagCommand expectedCommandAll = new RemoveTagCommand(INDEX_FIRST_PERSON, eduList, + internList, moduleList, ccaList); + + RemoveTagCommand expectedCommandTwoPrefix = new RemoveTagCommand(INDEX_FIRST_PERSON, eduList, + emptyList, moduleList, emptyList); + + RemoveTagCommand expectedCommandThreePrefix = new RemoveTagCommand(INDEX_FIRST_PERSON, eduList, + emptyList, moduleList, ccaList); + + //Order of prefixes in user input should not matter + String userInputAll = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_EDUCATION + nonEmptyEducation + " " + + PREFIX_MODULE + nonEmptyModule + " " + + PREFIX_CCA + nonEmptyCca + " " + + PREFIX_INTERNSHIP + nonEmptyInternship; + + String userInputTwoPrefix = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_EDUCATION + nonEmptyEducation + " " + + PREFIX_MODULE + nonEmptyModule; + + String userInputThreePrefix = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_CCA + nonEmptyCca + " " + + PREFIX_EDUCATION + nonEmptyEducation + " " + + PREFIX_MODULE + nonEmptyModule; + + assertParseSuccess(parser, userInputAll, expectedCommandAll); + assertParseSuccess(parser, userInputTwoPrefix, expectedCommandTwoPrefix); + assertParseSuccess(parser, userInputThreePrefix, expectedCommandThreePrefix); + } + + @Test + public void parse_missingCompulsoryField_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveTagCommand.MESSAGE_USAGE); + + // no prefix + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD, expectedMessage); + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD + " 1", expectedMessage); + + // no input after prefix + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD + " 1 " + PREFIX_EDUCATION, expectedMessage); + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD + " 1 " + PREFIX_MODULE, expectedMessage); + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD + " 1 " + PREFIX_INTERNSHIP, expectedMessage); + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD + " 1 " + PREFIX_CCA, expectedMessage); + + // no index + assertParseFailure(parser, RemoveTagCommand.COMMAND_WORD + " " + nonEmptyEducation, expectedMessage); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ShowEventsCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ShowEventsCommandParserTest.java new file mode 100644 index 00000000000..55cef97e9b7 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ShowEventsCommandParserTest.java @@ -0,0 +1,42 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_PAST_EVENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_UPCOMING_EVENTS; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.ShowEventsCommand; + +public class ShowEventsCommandParserTest { + private ShowEventsCommandParser parser = new ShowEventsCommandParser(); + + @Test + public void parse_validCommand_success() { + ShowEventsCommand expectedCommand = new ShowEventsCommand(PREDICATE_SHOW_PAST_EVENTS); + assertParseSuccess(parser, " -past", expectedCommand); + + expectedCommand = new ShowEventsCommand(PREDICATE_SHOW_ALL_EVENTS); + assertParseSuccess(parser, " ", expectedCommand); + + expectedCommand = new ShowEventsCommand(PREDICATE_SHOW_UPCOMING_EVENTS); + assertParseSuccess(parser, " -upcoming ", expectedCommand); + } + + @Test + public void parse_invalidCommand_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowEventsCommand.MESSAGE_USAGE); + + assertParseFailure(parser, " invalid", expectedMessage); + } + + @Test + public void parse_validEmptyCommand_success() { + ShowEventsCommand expectedCommand = new ShowEventsCommand(PREDICATE_SHOW_ALL_EVENTS); + + assertParseSuccess(parser, "", expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ShowEventsCommandTest.java b/src/test/java/seedu/address/logic/parser/ShowEventsCommandTest.java new file mode 100644 index 00000000000..24cca8e6762 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ShowEventsCommandTest.java @@ -0,0 +1,91 @@ +package seedu.address.logic.parser; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_PAST_EVENTS; +import static seedu.address.model.Model.PREDICATE_SHOW_UPCOMING_EVENTS; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.ShowEventsCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; +import seedu.address.model.person.Person; + +public class ShowEventsCommandTest { + private Model model; + private Event eventPast; + private Event eventUpcoming; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + eventPast = new Event(new EventName("lunch"), new Information("at HDL"), + new ArrayList<>(List.of(person.getName())), new DateTime(2020, 10, 10, 11, 11)); + eventUpcoming = new Event(new EventName("dinner"), new Information("at Lavo"), + new ArrayList<>(List.of(person.getName())), new DateTime(2220, 10, 10, 11, 11)); + } + + @AfterEach + public void tearDown() { + model = null; + } + + @Test + public void execute_showAllEvents_success() { + ShowEventsCommand command = new ShowEventsCommand(PREDICATE_SHOW_ALL_EVENTS); + + model.addEvent(eventPast); + model.addEvent(eventUpcoming); + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + String expectedMessage = String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, + expectedModel.getFilteredEventList().size()); + + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void execute_showPastEvents_success() { + ShowEventsCommand command = new ShowEventsCommand(PREDICATE_SHOW_PAST_EVENTS); + + model.addEvent(eventUpcoming); + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + String expectedMessage = String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, 0); + + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertTrue(model.getFilteredEventList().isEmpty()); + } + + @Test + public void execute_showUpcomingEvents_success() { + ShowEventsCommand command = new ShowEventsCommand(PREDICATE_SHOW_UPCOMING_EVENTS); + + model.addEvent(eventUpcoming); + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + String expectedMessage = String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, 1); + + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertTrue(model.getFilteredEventList().size() == 1); + } + + +} + diff --git a/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java new file mode 100644 index 00000000000..d0d4d43625a --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java @@ -0,0 +1,151 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.TagCommand; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; +import seedu.address.model.tag.Tag; + +public class TagCommandParserTest { + private final TagCommandParser parser = new TagCommandParser(); + private final String nonEmptyEducation = "Some education"; + private final String nonEmptyModule = "Some module"; + private final String nonEmptyInternship = "Some internship"; + private final String nonEmptyCca = "Some cca"; + + @Test + public void parse_indexSpecifiedOnePrefix_success() { + Set emptySet = new HashSet<>(); + ArrayList emptyList = new ArrayList<>(emptySet); + + Set eduSet = new HashSet<>(); + eduSet.add(new Education(nonEmptyEducation.trim().toLowerCase())); + ArrayList eduList = new ArrayList<>(eduSet); + + Set internSet = new HashSet<>(); + internSet.add(new Internship(nonEmptyInternship.trim().toLowerCase())); + ArrayList internList = new ArrayList<>(internSet); + + Set modSet = new HashSet<>(); + modSet.add(new Module(nonEmptyModule.trim().toLowerCase())); + ArrayList moduleList = new ArrayList<>(modSet); + + Set ccaSet = new HashSet<>(); + ccaSet.add(new Cca(nonEmptyCca.trim().toLowerCase())); + ArrayList ccaList = new ArrayList<>(ccaSet); + + TagCommand expectedCommandEduOnly = new TagCommand(INDEX_FIRST_PERSON, eduList, + emptyList, emptyList, emptyList); + + TagCommand expectedCommandInternOnly = new TagCommand(INDEX_FIRST_PERSON, emptyList, + internList, emptyList, emptyList); + + TagCommand expectedCommandModuleOnly = new TagCommand(INDEX_FIRST_PERSON, emptyList, + emptyList, moduleList, emptyList); + + TagCommand expectedCommandCcaOnly = new TagCommand(INDEX_FIRST_PERSON, emptyList, + emptyList, emptyList, ccaList); + + String userInputEduOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_EDUCATION + nonEmptyEducation; + + String userInputInternOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_INTERNSHIP + nonEmptyInternship; + + String userInputModOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_MODULE + nonEmptyModule; + + String userInputCcaOnly = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_CCA + nonEmptyCca; + + assertParseSuccess(parser, userInputEduOnly, expectedCommandEduOnly); + assertParseSuccess(parser, userInputInternOnly, expectedCommandInternOnly); + assertParseSuccess(parser, userInputModOnly, expectedCommandModuleOnly); + assertParseSuccess(parser, userInputCcaOnly, expectedCommandCcaOnly); + } + + @Test + public void parse_indexSpecifiedMultiplePrefix_success() { + Set emptySet = new HashSet<>(); + ArrayList emptyList = new ArrayList<>(emptySet); + + Set eduSet = new HashSet<>(); + eduSet.add(new Education(nonEmptyEducation.trim().toLowerCase())); + ArrayList eduList = new ArrayList<>(eduSet); + + Set internSet = new HashSet<>(); + internSet.add(new Internship(nonEmptyInternship.trim().toLowerCase())); + ArrayList internList = new ArrayList<>(internSet); + + Set modSet = new HashSet<>(); + modSet.add(new Module(nonEmptyModule.trim().toLowerCase())); + ArrayList moduleList = new ArrayList<>(modSet); + + Set ccaSet = new HashSet<>(); + ccaSet.add(new Cca(nonEmptyCca.trim().toLowerCase())); + ArrayList ccaList = new ArrayList<>(ccaSet); + + TagCommand expectedCommandAll = new TagCommand(INDEX_FIRST_PERSON, eduList, + internList, moduleList, ccaList); + + TagCommand expectedCommandTwoPrefix = new TagCommand(INDEX_FIRST_PERSON, eduList, + emptyList, moduleList, emptyList); + + TagCommand expectedCommandThreePrefix = new TagCommand(INDEX_FIRST_PERSON, eduList, + emptyList, moduleList, ccaList); + + //Order of prefixes in user input should not matter + String userInputAll = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_EDUCATION + nonEmptyEducation + " " + + PREFIX_MODULE + nonEmptyModule + " " + + PREFIX_CCA + nonEmptyCca + " " + + PREFIX_INTERNSHIP + nonEmptyInternship; + + String userInputTwoPrefix = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_EDUCATION + nonEmptyEducation + " " + + PREFIX_MODULE + nonEmptyModule; + + String userInputThreePrefix = INDEX_FIRST_PERSON.getOneBased() + " " + + PREFIX_CCA + nonEmptyCca + " " + + PREFIX_EDUCATION + nonEmptyEducation + " " + + PREFIX_MODULE + nonEmptyModule; + + assertParseSuccess(parser, userInputAll, expectedCommandAll); + assertParseSuccess(parser, userInputTwoPrefix, expectedCommandTwoPrefix); + assertParseSuccess(parser, userInputThreePrefix, expectedCommandThreePrefix); + } + + @Test + public void parse_missingCompulsoryField_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE); + + // no prefix + assertParseFailure(parser, TagCommand.COMMAND_WORD, expectedMessage); + assertParseFailure(parser, TagCommand.COMMAND_WORD + " 1", expectedMessage); + + // no input after prefix + assertParseFailure(parser, TagCommand.COMMAND_WORD + " 1 " + PREFIX_EDUCATION, expectedMessage); + assertParseFailure(parser, TagCommand.COMMAND_WORD + " 1 " + PREFIX_MODULE, expectedMessage); + assertParseFailure(parser, TagCommand.COMMAND_WORD + " 1 " + PREFIX_INTERNSHIP, expectedMessage); + assertParseFailure(parser, TagCommand.COMMAND_WORD + " 1 " + PREFIX_CCA, expectedMessage); + + // no index + assertParseFailure(parser, TagCommand.COMMAND_WORD + " " + nonEmptyEducation, expectedMessage); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 87782528ecd..bbeac3e711b 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; @@ -18,6 +17,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.event.Event; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.testutil.PersonBuilder; @@ -46,8 +46,7 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() { @Test public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { // Two persons with the same identity fields - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); + Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); List newPersons = Arrays.asList(ALICE, editedAlice); AddressBookStub newData = new AddressBookStub(newPersons); @@ -73,8 +72,7 @@ public void hasPerson_personInAddressBook_returnsTrue() { @Test public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); + Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); assertTrue(addressBook.hasPerson(editedAlice)); } @@ -88,6 +86,7 @@ public void getPersonList_modifyList_throwsUnsupportedOperationException() { */ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); + private final ObservableList events = FXCollections.observableArrayList(); AddressBookStub(Collection persons) { this.persons.setAll(persons); @@ -97,6 +96,11 @@ private static class AddressBookStub implements ReadOnlyAddressBook { public ObservableList getPersonList() { return persons; } + + @Override + public ObservableList getEventList() { + return events; + } } } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..d426d71f9b5 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -15,7 +15,7 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.NameContainsKeywordsPredicateOr; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { @@ -118,7 +118,7 @@ public void equals() { // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicateOr(Arrays.asList(keywords))); assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); // resets modelManager to initial state for upcoming tests diff --git a/src/test/java/seedu/address/model/event/DateTimeTest.java b/src/test/java/seedu/address/model/event/DateTimeTest.java new file mode 100644 index 00000000000..a4fd988fa07 --- /dev/null +++ b/src/test/java/seedu/address/model/event/DateTimeTest.java @@ -0,0 +1,26 @@ +package seedu.address.model.event; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import java.time.DateTimeException; + +import org.junit.jupiter.api.Test; + +public class DateTimeTest { + + @Test + public void constructor_invalidDateTime_throwsDateTimeException() { + assertThrows(DateTimeException.class, () -> new DateTime(2022, 13, 12, 11, 11)); + } + + @Test + public void isValidDateTime() { + assertFalse(DateTime.isValidDateTime(2000, 10, 11, 9, 9)); + assertFalse(DateTime.isValidDateTime(2022, 2, 30, 9, 9)); + assertFalse(DateTime.isValidDateTime(2022, 4, 1, 9, 9)); + + assertTrue(DateTime.isValidDateTime(2023, 10, 10, 12, 8)); + } +} diff --git a/src/test/java/seedu/address/model/event/EventNameTest.java b/src/test/java/seedu/address/model/event/EventNameTest.java new file mode 100644 index 00000000000..f8d1a35f778 --- /dev/null +++ b/src/test/java/seedu/address/model/event/EventNameTest.java @@ -0,0 +1,34 @@ +package seedu.address.model.event; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class EventNameTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new EventName(null)); + } + + @Test + public void constructor_invalidEventName_throwsIllegalArgumentException() { + String invalidEventName = ""; + assertThrows(IllegalArgumentException.class, () -> new EventName(invalidEventName)); + } + + @Test + public void isValidEventName() { + assertThrows(NullPointerException.class, () -> EventName.isValidEventName(null)); + + assertFalse(EventName.isValidEventName("")); + assertFalse(EventName.isValidEventName("ssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssss")); + + assertTrue(EventName.isValidEventName("lunch")); + assertTrue(EventName.isValidEventName("climbing 101")); + assertTrue(EventName.isValidEventName("climbing-101")); + } +} diff --git a/src/test/java/seedu/address/model/event/InformationTest.java b/src/test/java/seedu/address/model/event/InformationTest.java new file mode 100644 index 00000000000..f1344f4411d --- /dev/null +++ b/src/test/java/seedu/address/model/event/InformationTest.java @@ -0,0 +1,38 @@ +package seedu.address.model.event; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class InformationTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Information(null)); + } + + @Test + public void constructor_invalidInformation_throwsIllegalArgumentException() { + String invalidInformation = ""; + assertThrows(IllegalArgumentException.class, () -> new Information(invalidInformation)); + } + + @Test + public void isValidInformation() { + assertThrows(NullPointerException.class, () -> Information.isValidInformation(null)); + + assertFalse(Information.isValidInformation("")); + assertFalse(Information.isValidInformation("ssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssss")); + + assertTrue(Information.isValidInformation("at HDL")); + assertTrue(Information.isValidInformation("learning the basics of climbing")); + assertTrue(Information.isValidInformation("climbing-drills")); + } +} diff --git a/src/test/java/seedu/address/model/person/CcaTest.java b/src/test/java/seedu/address/model/person/CcaTest.java new file mode 100644 index 00000000000..1c8c3440cc6 --- /dev/null +++ b/src/test/java/seedu/address/model/person/CcaTest.java @@ -0,0 +1,40 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class CcaTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Cca(null)); + } + + @Test + public void constructor_invalidName_throwsIllegalArgumentException() { + String invalidCca = " "; + assertThrows(IllegalArgumentException.class, () -> new Cca(invalidCca)); + } + + @Test + public void isValidTagName() { + // null cca + assertThrows(NullPointerException.class, () -> Cca.isValidTagName(null)); + + // invalid cca + assertFalse(Cca.isValidTagName("")); // empty string + assertFalse(Cca.isValidTagName(" ")); // spaces only + assertFalse(Cca.isValidTagName("^")); // only non-alphanumeric characters + assertFalse(Cca.isValidTagName("bouldering*")); // contains non-alphanumeric characters + + // valid cca + assertTrue(Cca.isValidTagName("bouldering")); // alphabets only + assertTrue(Cca.isValidTagName("12345")); // numbers only + assertTrue(Cca.isValidTagName("bould3ring")); // alphanumeric characters + assertTrue(Cca.isValidTagName("Bouldering")); // with capital letters + assertTrue(Cca.isValidTagName("NUS Bouldering and Climbing Team")); // long names + } +} diff --git a/src/test/java/seedu/address/model/person/EducationTest.java b/src/test/java/seedu/address/model/person/EducationTest.java new file mode 100644 index 00000000000..1b74a5f4fe6 --- /dev/null +++ b/src/test/java/seedu/address/model/person/EducationTest.java @@ -0,0 +1,40 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class EducationTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Education(null)); + } + + @Test + public void constructor_invalidName_throwsIllegalArgumentException() { + String invalidEducation = " "; + assertThrows(IllegalArgumentException.class, () -> new Education(invalidEducation)); + } + + @Test + public void isValidTagName() { + // null education + assertThrows(NullPointerException.class, () -> Education.isValidTagName(null)); + + // invalid education + assertFalse(Education.isValidTagName("")); // empty string + assertFalse(Education.isValidTagName(" ")); // spaces only + assertFalse(Education.isValidTagName("^")); // only non-alphanumeric characters + assertFalse(Education.isValidTagName("Computer Science*")); // contains non-alphanumeric characters + + // valid education + assertTrue(Education.isValidTagName("computer science")); // alphabets only + assertTrue(Education.isValidTagName("12345")); // numbers only + assertTrue(Education.isValidTagName("computer science 2nd year")); // alphanumeric characters + assertTrue(Education.isValidTagName("Computer Science")); // with capital letters + assertTrue(Education.isValidTagName("Computer Science specialising in software engineering")); // long names + } +} diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/person/EmailTest.java index bbcc6c8c98e..e9a051d2853 100644 --- a/src/test/java/seedu/address/model/person/EmailTest.java +++ b/src/test/java/seedu/address/model/person/EmailTest.java @@ -57,9 +57,6 @@ public void isValidEmail() { assertTrue(Email.isValidEmail("PeterJack.1190@example.com")); // period in local part assertTrue(Email.isValidEmail("PeterJack+1190@example.com")); // '+' symbol in local part assertTrue(Email.isValidEmail("PeterJack-1190@example.com")); // hyphen in local part - assertTrue(Email.isValidEmail("a@bc")); // minimal - assertTrue(Email.isValidEmail("test@localhost")); // alphabets only - assertTrue(Email.isValidEmail("123@145")); // numeric local part and domain name assertTrue(Email.isValidEmail("a1+be.d@example1.com")); // mixture of alphanumeric and special characters assertTrue(Email.isValidEmail("peter_jack@very-very-very-long-example.com")); // long domain name assertTrue(Email.isValidEmail("if.you.dream.it_you.can.do.it@example.com")); // long local part diff --git a/src/test/java/seedu/address/model/person/InternshipTest.java b/src/test/java/seedu/address/model/person/InternshipTest.java new file mode 100644 index 00000000000..26ce824f99e --- /dev/null +++ b/src/test/java/seedu/address/model/person/InternshipTest.java @@ -0,0 +1,40 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class InternshipTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Internship(null)); + } + + @Test + public void constructor_invalidName_throwsIllegalArgumentException() { + String invalidInternship = " "; + assertThrows(IllegalArgumentException.class, () -> new Internship(invalidInternship)); + } + + @Test + public void isValidTagName() { + // null internship + assertThrows(NullPointerException.class, () -> Internship.isValidTagName(null)); + + // invalid internship + assertFalse(Internship.isValidTagName("")); // empty string + assertFalse(Internship.isValidTagName(" ")); // spaces only + assertFalse(Internship.isValidTagName("^")); // only non-alphanumeric characters + assertFalse(Internship.isValidTagName("bytedance*")); // contains non-alphanumeric characters + + // valid internship + assertTrue(Internship.isValidTagName("bytedance")); // alphabets only + assertTrue(Internship.isValidTagName("12345")); // numbers only + assertTrue(Internship.isValidTagName("byt3danc3")); // alphanumeric characters + assertTrue(Internship.isValidTagName("ByteDance")); // with capital letters + assertTrue(Internship.isValidTagName("Oversea Chinese Banking Corporation")); // long names + } +} diff --git a/src/test/java/seedu/address/model/person/ModuleTest.java b/src/test/java/seedu/address/model/person/ModuleTest.java new file mode 100644 index 00000000000..3206625c5a0 --- /dev/null +++ b/src/test/java/seedu/address/model/person/ModuleTest.java @@ -0,0 +1,40 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ModuleTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Module(null)); + } + + @Test + public void constructor_invalidName_throwsIllegalArgumentException() { + String invalidModule = " "; + assertThrows(IllegalArgumentException.class, () -> new Module(invalidModule)); + } + + @Test + public void isValidTagName() { + // null module + assertThrows(NullPointerException.class, () -> Module.isValidTagName(null)); + + // invalid module + assertFalse(Module.isValidTagName("")); // empty string + assertFalse(Module.isValidTagName(" ")); // spaces only + assertFalse(Module.isValidTagName("^")); // only non-alphanumeric characters + assertFalse(Module.isValidTagName("cs2040s*")); // contains non-alphanumeric characters + + // valid module + assertTrue(Module.isValidTagName("discrete math")); // alphabets only + assertTrue(Module.isValidTagName("12345")); // numbers only + assertTrue(Module.isValidTagName("cs2040s")); // alphanumeric characters + assertTrue(Module.isValidTagName("CS2040S")); // with capital letters + assertTrue(Module.isValidTagName("Effective Communication for Computing Professionals")); // long names + } +} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java index f136664e017..c2b4ad44006 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java @@ -18,14 +18,17 @@ public void equals() { List firstPredicateKeywordList = Collections.singletonList("first"); List secondPredicateKeywordList = Arrays.asList("first", "second"); - NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList); + NameContainsKeywordsPredicateOr firstPredicate = + new NameContainsKeywordsPredicateOr(firstPredicateKeywordList); + NameContainsKeywordsPredicateOr secondPredicate = + new NameContainsKeywordsPredicateOr(secondPredicateKeywordList); // same object -> returns true assertTrue(firstPredicate.equals(firstPredicate)); // same values -> returns true - NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList); + NameContainsKeywordsPredicateOr firstPredicateCopy = + new NameContainsKeywordsPredicateOr(firstPredicateKeywordList); assertTrue(firstPredicate.equals(firstPredicateCopy)); // different types -> returns false @@ -41,34 +44,31 @@ public void equals() { @Test public void test_nameContainsKeywords_returnsTrue() { // One keyword - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + NameContainsKeywordsPredicateOr predicate = + new NameContainsKeywordsPredicateOr(Collections.singletonList("Alice")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice").build())); // Multiple keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); + predicate = new NameContainsKeywordsPredicateOr(Arrays.asList("Alice Bob")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); - // Mixed-case keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + predicate = new NameContainsKeywordsPredicateOr(Arrays.asList("aLIce")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice").build())); } @Test public void test_nameDoesNotContainKeywords_returnsFalse() { // Zero keywords - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); + NameContainsKeywordsPredicateOr predicate = new NameContainsKeywordsPredicateOr(Collections.emptyList()); assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); // Non-matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); + predicate = new NameContainsKeywordsPredicateOr(Arrays.asList("Carol")); assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Keywords match phone, email and address, but does not match name - predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); + predicate = new NameContainsKeywordsPredicateOr(Arrays.asList("12345", "alice@email.com", "Main", "Street")); assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") .withEmail("alice@email.com").withAddress("Main Street").build())); } diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java index b29c097cfd4..9a390655755 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/address/model/person/PersonTest.java @@ -6,8 +6,6 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; @@ -17,12 +15,6 @@ public class PersonTest { - @Test - public void asObservableList_modifyList_throwsUnsupportedOperationException() { - Person person = new PersonBuilder().build(); - assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0)); - } - @Test public void isSamePerson() { // same object -> returns true @@ -33,16 +25,16 @@ public void isSamePerson() { // same name, all other attributes different -> returns true Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); + .withAddress(VALID_ADDRESS_BOB).build(); assertTrue(ALICE.isSamePerson(editedAlice)); // different name, all other attributes same -> returns false editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); assertFalse(ALICE.isSamePerson(editedAlice)); - // name differs in case, all other attributes same -> returns false - Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); - assertFalse(BOB.isSamePerson(editedBob)); + // name differs in case, all other attributes same -> returns true + Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB).build(); + assertTrue(BOB.isSamePerson(editedBob)); // name has trailing spaces, all other attributes same -> returns false String nameWithTrailingSpaces = VALID_NAME_BOB + " "; @@ -83,9 +75,5 @@ public void equals() { // different address -> returns false editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); assertFalse(ALICE.equals(editedAlice)); - - // different tags -> returns false - editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); - assertFalse(ALICE.equals(editedAlice)); } } diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/person/PhoneTest.java index 8dd52766a5f..22593fca15f 100644 --- a/src/test/java/seedu/address/model/person/PhoneTest.java +++ b/src/test/java/seedu/address/model/person/PhoneTest.java @@ -31,10 +31,12 @@ public void isValidPhone() { assertFalse(Phone.isValidPhone("phone")); // non-numeric assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits + assertFalse(Phone.isValidPhone("93121534892")); // exactly 11 numbers + assertFalse(Phone.isValidPhone("124293842033123")); // long phone numbers // valid phone numbers assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers - assertTrue(Phone.isValidPhone("93121534")); - assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers + assertTrue(Phone.isValidPhone("931215343")); // exactly 9 numbers + assertTrue(Phone.isValidPhone("9312153489")); // exactly 10 numbers } } diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java index 1cc5fe9e0fe..8c85eac038f 100644 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; @@ -42,7 +41,7 @@ public void contains_personInList_returnsTrue() { @Test public void contains_personWithSameIdentityFieldsInList_returnsTrue() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB) .build(); assertTrue(uniquePersonList.contains(editedAlice)); } @@ -85,7 +84,7 @@ public void setPerson_editedPersonIsSamePerson_success() { @Test public void setPerson_editedPersonHasSameIdentity_success() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB) .build(); uniquePersonList.setPerson(ALICE, editedAlice); UniquePersonList expectedUniquePersonList = new UniquePersonList(); diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java deleted file mode 100644 index 64d07d79ee2..00000000000 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.model.tag; - -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class TagTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Tag(null)); - } - - @Test - public void constructor_invalidTagName_throwsIllegalArgumentException() { - String invalidTagName = ""; - assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName)); - } - - @Test - public void isValidTagName() { - // null tag name - assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); - } - -} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java b/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java new file mode 100644 index 00000000000..fe8be8792c8 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedEventTest.java @@ -0,0 +1,89 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedEvent.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.BENSON; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Event; +import seedu.address.model.event.EventName; +import seedu.address.model.event.Information; + +public class JsonAdaptedEventTest { + private static final String INVALID_NAME = ""; + private static final String INVALID_INFO = ""; + private static final String INVALID_DATETIME = "2022-30-11 10:10"; + + private static final String VALID_NAME = "lunch"; + private static final String VALID_INFO = "at HDL"; + private static final List VALID_PARTICIPANTS = + new ArrayList<>(List.of(new JsonAdaptedName(BENSON.getName().toString()))); + private static final String VALID_DATETIME = "2022-9-11 10:10"; + private static final Event VALID_EVENT = new Event(new EventName(VALID_NAME), new Information(VALID_INFO), + new ArrayList<>(List.of(BENSON.getName())), + new DateTime(2022, 9, 11, 10, 10)); + @Test + public void toModelType_validEventDetails_returnsEvent() throws Exception { + JsonAdaptedEvent event = new JsonAdaptedEvent(VALID_NAME, VALID_INFO, VALID_PARTICIPANTS, VALID_DATETIME); + assertEquals(VALID_EVENT, event.toModelType()); + } + + @Test + public void toModelType_invalidEventName_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(INVALID_NAME, VALID_INFO, VALID_PARTICIPANTS, VALID_DATETIME); + String expectedMessage = EventName.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidInfo_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(VALID_NAME, INVALID_INFO, VALID_PARTICIPANTS, VALID_DATETIME); + String expectedMessage = Information.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_invalidDateTime_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(VALID_NAME, VALID_INFO, VALID_PARTICIPANTS, INVALID_DATETIME); + String expectedMessage = DateTime.DATE_MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullEventName_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(null, VALID_INFO, VALID_PARTICIPANTS, VALID_DATETIME); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EventName.class.getSimpleName()); + + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } + + @Test + public void toModelType_nullInfo_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(VALID_NAME, null, VALID_PARTICIPANTS, VALID_DATETIME); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Information.class.getSimpleName()); + + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + + } + + @Test + public void toModelType_nullDateTime_throwsIllegalValueException() { + JsonAdaptedEvent event = + new JsonAdaptedEvent(VALID_NAME, VALID_INFO, VALID_PARTICIPANTS, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, DateTime.class.getSimpleName()); + + assertThrows(IllegalValueException.class, expectedMessage, event::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedNameTest.java b/src/test/java/seedu/address/storage/JsonAdaptedNameTest.java new file mode 100644 index 00000000000..e228914fd40 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedNameTest.java @@ -0,0 +1,45 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.AMY; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Name; + +class JsonAdaptedNameTest { + private static final String VALID_NAME_STRING = AMY.getName().toString(); + private static final String INVALID_NAME_STRING = "R@chel"; + + @Test + void toModelType_invalidName_throwsIllegalValueException() { + JsonAdaptedName name = new JsonAdaptedName(INVALID_NAME_STRING); + String expectedMessage = Name.MESSAGE_CONSTRAINTS; + + assertThrows(IllegalValueException.class, expectedMessage, name::toModelType); + } + + @Test + void toModelType_validName_returnsName() throws Exception { + JsonAdaptedName name = new JsonAdaptedName(VALID_NAME_STRING); + assertEquals(AMY.getName(), name.toModelType()); + } + + @Test + void getName_sameName_success() { + JsonAdaptedName name = new JsonAdaptedName(VALID_NAME_STRING); + assertEquals(VALID_NAME_STRING, name.getName()); + } + + @Test + void getName_differentName_failure() { + JsonAdaptedName name = new JsonAdaptedName(VALID_NAME_STRING); + assertNotEquals("Amy BeE", name.getName()); + assertNotEquals("Amy Beee", name.getName()); + assertNotEquals("AmyBeE", name.getName()); + assertNotEquals("AMY BeE", name.getName()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java index 83b11331cdb..e925d4210c6 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java @@ -3,9 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.AMY; import static seedu.address.testutil.TypicalPersons.BENSON; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -13,7 +14,11 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; @@ -22,33 +27,44 @@ public class JsonAdaptedPersonTest { private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final List INVALID_CCAS = Arrays.asList(new JsonAdaptedTag("track & field")); + private static final List INVALID_EDUCATIONS = Arrays.asList(new JsonAdaptedTag("computer " + + "science 2nd " + "year!")); + private static final List INVALID_INTERNSHIPS = Arrays.asList(new JsonAdaptedTag("bosch*")); + private static final List INVALID_MODULES = Arrays.asList(new JsonAdaptedTag("cs2040s~")); private static final String VALID_NAME = BENSON.getName().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); private static final String VALID_EMAIL = BENSON.getEmail().toString(); private static final String VALID_ADDRESS = BENSON.getAddress().toString(); - private static final List VALID_TAGS = BENSON.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList()); + private static final List VALID_EDUCATIONS = + AMY.getEducations().stream().map(JsonAdaptedTag::new).collect(Collectors.toList()); + private static final List VALID_INTERNSHIPS = + AMY.getEducations().stream().map(JsonAdaptedTag::new).collect(Collectors.toList()); + private static final List VALID_MODULES = + AMY.getEducations().stream().map(JsonAdaptedTag::new).collect(Collectors.toList()); + private static final List VALID_CCAS = + AMY.getEducations().stream().map(JsonAdaptedTag::new).collect(Collectors.toList()); @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { - JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON); - assertEquals(BENSON, person.toModelType()); + JsonAdaptedPerson person = new JsonAdaptedPerson(AMY); + assertEquals(AMY, person.toModelType()); } @Test public void toModelType_invalidName_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + VALID_EDUCATIONS, VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = Name.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullName_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, + VALID_EDUCATIONS, VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -56,14 +72,16 @@ public void toModelType_nullName_throwsIllegalValueException() { @Test public void toModelType_invalidPhone_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_EDUCATIONS, + VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = Phone.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, + VALID_EDUCATIONS, VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -71,14 +89,16 @@ public void toModelType_nullPhone_throwsIllegalValueException() { @Test public void toModelType_invalidEmail_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_EDUCATIONS, + VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = Email.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, + VALID_EDUCATIONS, VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -86,25 +106,53 @@ public void toModelType_nullEmail_throwsIllegalValueException() { @Test public void toModelType_invalidAddress_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_EDUCATIONS, + VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = Address.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_EDUCATIONS, + VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test - public void toModelType_invalidTags_throwsIllegalValueException() { - List invalidTags = new ArrayList<>(VALID_TAGS); - invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); + public void toModelType_invalidEducation_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); - assertThrows(IllegalValueException.class, person::toModelType); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, INVALID_EDUCATIONS, + VALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); + String expectedMessage = Education.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidInternship_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_EDUCATIONS, + INVALID_INTERNSHIPS, VALID_MODULES, VALID_CCAS); + String expectedMessage = Internship.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } + @Test + public void toModelType_invalidModule_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_EDUCATIONS, + VALID_INTERNSHIPS, INVALID_MODULES, VALID_CCAS); + String expectedMessage = Module.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidCca_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_EDUCATIONS, + VALID_INTERNSHIPS, VALID_MODULES, INVALID_CCAS); + String expectedMessage = Cca.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } } diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java index 188c9058d20..50b8cfc467e 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java @@ -43,5 +43,4 @@ public void toModelType_duplicatePersons_throwsIllegalValueException() throws Ex assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PERSON, dataFromFile::toModelType); } - } diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e..20ae2a7ba9b 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java @@ -1,16 +1,16 @@ package seedu.address.testutil; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.Arrays; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** * A utility class to help with building EditPersonDescriptor objects. @@ -36,7 +36,10 @@ public EditPersonDescriptorBuilder(Person person) { descriptor.setPhone(person.getPhone()); descriptor.setEmail(person.getEmail()); descriptor.setAddress(person.getAddress()); - descriptor.setTags(person.getTags()); + descriptor.setEducations(person.getEducations()); + descriptor.setInternships(person.getInternships()); + descriptor.setModules(person.getModules()); + descriptor.setCcas(person.getCcas()); } /** @@ -63,6 +66,7 @@ public EditPersonDescriptorBuilder withEmail(String email) { return this; } + /** * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. */ @@ -72,15 +76,38 @@ public EditPersonDescriptorBuilder withAddress(String address) { } /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} - * that we are building. + * Sets the {@code Education} of the {@code EditPersonDescriptor} that we are building. */ - public EditPersonDescriptorBuilder withTags(String... tags) { - Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); - descriptor.setTags(tagSet); + public EditPersonDescriptorBuilder withEducation(String education) { + descriptor.setEducations(Arrays.asList(new Education(education))); return this; } + /** + * Sets the {@code Internship} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withInternship(String internship) { + descriptor.setInternships(Arrays.asList(new Education(internship))); + return this; + } + + /** + * Sets the {@code Module} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withModule(String module) { + descriptor.setModules(Arrays.asList(new Module(module))); + return this; + } + + /** + * Sets the {@code Cca} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withCca(String cca) { + descriptor.setCcas(Arrays.asList(new Cca(cca))); + return this; + } + + public EditPersonDescriptor build() { return descriptor; } diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index 6be381d39ba..de29dee5f0c 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -1,15 +1,18 @@ package seedu.address.testutil; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; import seedu.address.model.person.Address; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Education; import seedu.address.model.person.Email; +import seedu.address.model.person.Internship; +import seedu.address.model.person.Module; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; -import seedu.address.model.util.SampleDataUtil; /** * A utility class to help with building Person objects. @@ -20,12 +23,20 @@ public class PersonBuilder { public static final String DEFAULT_PHONE = "85355255"; public static final String DEFAULT_EMAIL = "amy@gmail.com"; public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + public static final String DEFAULT_EDUCATION = "Computer Science"; + public static final String DEFAULT_INTERNSHIP = "GIC"; + public static final String DEFAULT_MODULE = "CS2040S"; + public static final String DEFAULT_CCA = "Netball"; private Name name; private Phone phone; private Email email; private Address address; - private Set tags; + private List educations; + private List internships; + private List modules; + private List ccas; + /** * Creates a {@code PersonBuilder} with the default details. @@ -35,7 +46,10 @@ public PersonBuilder() { phone = new Phone(DEFAULT_PHONE); email = new Email(DEFAULT_EMAIL); address = new Address(DEFAULT_ADDRESS); - tags = new HashSet<>(); + educations = new ArrayList<>(); + internships = new ArrayList<>(); + modules = new ArrayList<>(); + ccas = new ArrayList<>(); } /** @@ -46,7 +60,10 @@ public PersonBuilder(Person personToCopy) { phone = personToCopy.getPhone(); email = personToCopy.getEmail(); address = personToCopy.getAddress(); - tags = new HashSet<>(personToCopy.getTags()); + educations = personToCopy.getEducations(); + internships = personToCopy.getInternships(); + modules = personToCopy.getModules(); + ccas = personToCopy.getCcas(); } /** @@ -57,14 +74,6 @@ public PersonBuilder withName(String name) { return this; } - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. - */ - public PersonBuilder withTags(String ... tags) { - this.tags = SampleDataUtil.getTagSet(tags); - return this; - } - /** * Sets the {@code Address} of the {@code Person} that we are building. */ @@ -89,8 +98,47 @@ public PersonBuilder withEmail(String email) { return this; } + /** + * Sets the {@code Education} of the {@code Person} that we are building. + */ + public PersonBuilder withEducation(String education) { + educations.add(new Education(education)); + return this; + } + + /** + * Sets the {@code Internship} of the {@code Person} that we are building. + */ + public PersonBuilder withInternship(String internship) { + internships.add(new Internship(internship)); + return this; + } + + /** + * Sets the {@code Module} of the {@code Person} that we are building. + */ + public PersonBuilder withModule(String module) { + modules.add(new Module(module)); + return this; + } + + /** + * Sets the {@code Cca} of the {@code Person} that we are building. + */ + public PersonBuilder withCca(String cca) { + ccas.add(new Cca(cca)); + return this; + } + + private boolean isAllTagsEmpty() { + return educations.isEmpty() && internships.isEmpty() && modules.isEmpty() && ccas.isEmpty(); + } + + /** + * Creates a Person with the appropriate fields. + */ public Person build() { - return new Person(name, phone, email, address, tags); + return new Person(name, phone, email, address, educations, internships, modules, ccas); } } diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 90849945183..3a7661fefa0 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -1,15 +1,19 @@ package seedu.address.testutil; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERNSHIP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import java.util.Set; +import java.util.List; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.parser.Prefix; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; @@ -34,9 +38,6 @@ public static String getPersonDetails(Person person) { sb.append(PREFIX_PHONE + person.getPhone().value + " "); sb.append(PREFIX_EMAIL + person.getEmail().value + " "); sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); - person.getTags().stream().forEach( - s -> sb.append(PREFIX_TAG + s.tagName + " ") - ); return sb.toString(); } @@ -49,13 +50,26 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" ")); descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" ")); - if (descriptor.getTags().isPresent()) { - Set tags = descriptor.getTags().get(); - if (tags.isEmpty()) { - sb.append(PREFIX_TAG); - } else { - tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); - } + descriptor.getEducations().stream().map(education -> sb.append(PREFIX_EDUCATION) + .append(education.tagName) + .append(" ")); + descriptor.getInternships().stream().map(internship -> sb.append(PREFIX_INTERNSHIP) + .append(internship.tagName) + .append(" ")); + descriptor.getModules().stream().map(module -> sb.append(PREFIX_MODULE) + .append(module.tagName) + .append(" ")); + descriptor.getCcas().stream().map(cca -> sb.append(PREFIX_CCA) + .append(cca.tagName) + .append(" ")); + + return sb.toString(); + } + + private static String tagsToString(List tagList, Prefix prefix) { + StringBuilder sb = new StringBuilder(); + for (Tag t : tagList) { + sb.append(prefix).append(t.tagName).append(" "); } return sb.toString(); } diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e613937657..e08f621a120 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -9,4 +9,7 @@ public class TypicalIndexes { public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + public static final Index INDEX_FIRST_EVENT = Index.fromOneBased(1); + public static final Index INDEX_SECOND_EVENT = Index.fromOneBased(2); + public static final Index INDEX_THIRD_EVENT = Index.fromOneBased(3); } diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index fec76fb7129..3992f21e410 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -2,14 +2,16 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_CCA_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_INTERNSHIP_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import java.util.ArrayList; import java.util.Arrays; @@ -23,37 +25,76 @@ */ public class TypicalPersons { - public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") - .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + public static final Person ALICE = new PersonBuilder() + .withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111") + .withEmail("alice@example.com") .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") + .withEducation("NUS") + .withCca("Basketball") + .withInternship("Facebook") + .withModule("CS1101S").build(); + public static final Person BENSON = new PersonBuilder() + .withName("Benson Meier") .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); - public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); - public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); - public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); - public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); - public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); + .withEmail("johnd@example.com") + .withPhone("98765432") + .withEducation("SMU").withCca("Football") + .withInternship("Amazon") + .withModule("CS3243").build(); + public static final Person CARL = new PersonBuilder() + .withName("Carl Kurz") + .withPhone("95352563") + .withEmail("heinz@example.com") + .withAddress("wall street").build(); + public static final Person DANIEL = new PersonBuilder() + .withName("Daniel Meier") + .withPhone("87652533") + .withEmail("cornelia@example.com") + .withAddress("10th street").build(); + public static final Person ELLE = new PersonBuilder() + .withName("Elle Meyer") + .withPhone("9482224") + .withEmail("werner@example.com") + .withAddress("michegan ave").build(); + public static final Person FIONA = new PersonBuilder() + .withName("Fiona Kunz") + .withPhone("9482427") + .withEmail("lydia@example.com") + .withAddress("little tokyo").build(); + public static final Person GEORGE = new PersonBuilder() + .withName("George Best") + .withPhone("9482442") + .withEmail("anna@example.com") + .withAddress("4th street").build(); // Manually added - public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); - public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); + public static final Person HOON = new PersonBuilder() + .withName("Hoon Meier") + .withPhone("8482424") + .withEmail("stefan@example.com") + .withAddress("little india").build(); + public static final Person IDA = new PersonBuilder() + .withName("Ida Mueller") + .withPhone("8482131") + .withEmail("hans@example.com") + .withAddress("chicago ave").build(); // Manually added - Person's details found in {@code CommandTestUtil} - public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) - .build(); + public static final Person AMY = new PersonBuilder() + .withName(VALID_NAME_AMY) + .withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY) + .withEducation(VALID_EDUCATION_AMY) + .withCca(VALID_CCA_AMY) + .withInternship(VALID_INTERNSHIP_AMY) + .withModule(VALID_MODULE_AMY).build(); + public static final Person BOB = new PersonBuilder() + .withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_BOB).build(); public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER