diff --git a/.github/workflows/export-ug-to-pdf.yml b/.github/workflows/export-ug-to-pdf.yml new file mode 100644 index 00000000000..5abb0a53a3f --- /dev/null +++ b/.github/workflows/export-ug-to-pdf.yml @@ -0,0 +1,26 @@ +name: User Guide to PDF + +on: + push: + branches: + - master + paths: + - 'docs/UserGuide.md' + +jobs: + converttopdf: + name: Build PDF + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: baileyjm02/markdown-to-pdf@v1 + with: + input_path: docs/UserGuide.md + output_dir: export + images_dir: docs/images + image_import: images + build_html: false + - uses: actions/upload-artifact@v3 + with: + name: UserGuide + path: export/UserGuide.pdf diff --git a/.gitignore b/.gitignore index 71c9194e8bd..1b0f5264a24 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,9 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ + +# extra removals +/hs_err_pid[0-9]*.log +*.class +.vscode +bin/* diff --git a/README.md b/README.md index 13f5c77403f..402e266c6cf 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,10 @@ -[![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/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T11-1/tp/actions) ![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. - * 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. +# Contactmation + +Contactmation is a **desktop, contact management application** that is **optimized for team management and delegation of tasks through the Command Line Interface** (CLI). Contactmation efficiently tracks progress of your team projects. + +* For the detailed documentation of this project, see the **[Contactmation Product Website](https://ay2223s1-cs2103t-t11-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 108397716bd..3c2112acab1 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ repositories { } checkstyle { - toolVersion = '10.2' + setToolVersion('10.2') } test { @@ -41,8 +41,15 @@ task coverage(type: JacocoReport) { } dependencies { - String jUnitVersion = '5.4.0' - String javaFxVersion = '11' + String javaFxVersion = '18.0.2' + String jUnitVersion = '5.7.0' + + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' + implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' + + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' @@ -56,17 +63,15 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' - - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' - implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' - - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion - - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'contactmation.jar' +} + +run { + standardInput = System.in + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..bdbf0ae7056 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,52 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Li Zhaoqi - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/Eclipse-Dominator)] +[[portfolio](team/eclipse-dominator.md)] -* Role: Project Advisor +- Role: Developer +- Responsibilities: System designer, complex commands -### Jane Doe +### Eric Lee Ying Yao - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/autumn-sonata)] +[[portfolio](team/autumn-sonata.md)] -* Role: Team Lead -* Responsibilities: UI +- Role: Developer +- Responsibilities: Update storage components, database -### Johnny Doe +### Jason Christopher - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/jasonchristopher21)] +[[portfolio](team/jasonchristopher21.md)] -* Role: Developer -* Responsibilities: Data +- Role: Developer +- Responsibilities: Attributes, data modeling -### Jean Doe +### Connor Shihern Lim - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/connlim)] +[[portfolio](team/connlim.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +- Role: Developer +- Responsibilities: Implement Tasks, Quality Assurance, Bug fixing -### James Doe +### Mohamed Safwan S/O Abdul Wahab Lukuman - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/mohamedsaf1)] +[[portfolio](team/mohamedsaf1.md)] -* Role: Developer -* Responsibilities: UI +- Role: Developer +- Responsibilities: Group Functions, Code Quality and Code Integrity diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..6f9924f69aa 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -5,61 +5,62 @@ title: Developer Guide * Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} --------------------------------------------------------------------------------------------------------------------- +--- ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Design**
:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +
### Architecture -The ***Architecture Diagram*** given above explains the high-level design of the App. +The **_Architecture Diagram_** given above explains the high-level design of the App. Given below is a quick overview of main components and how they interact with each other. **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, -* 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. + +- 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. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. The rest of the App consists of four components. -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. - +- [**`UI`**](#ui-component): The UI of the App. +- [**`Logic`**](#logic-component): The command executor. +- [**`Model`**](#model-component): Holds the data of the App in memory. +- [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +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. +- 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. @@ -69,20 +70,34 @@ 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/AY2223S1-CS2103T-T11-1/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2223S1-CS2103T-T11-1/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-T11-1/tp/blob/master/src/main/resources/view/MainWindow.fxml) 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`. +- executes user commands using the `Logic` component. +- listens for changes to `Model` data so that the UI can be updated with the modified data. +- shows the an active view of the currently available content based on filter and current context. +- keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. + +The `DetailPanel` component of the `UI` will display a finer detail of the currently selected `DisplayItem` that is chosen inside Model. + +How `UI` displays and updates `DisplayItem`'s in list view. Below will illustrate how any `DisplayItem` is generated as an card UI and then added into the corresponding ListPanel. + +1. Whenever the `ObservableList` inside `Model` is modified or changed, each changed item will call `updateItem()` method inside the respective ListPanelCell inner class found inside the respective ListPanels. +2. The cell will then build the card for the item by calling the `xxxCard` class which represents an UI card object that will be used in the ListView Panel. +3. For each attribute in the item, the Card class will add a corresponding child element to the Card. +4. The generated Card is then added to the ListView. + +The Sequence Diagram below illustrate how the Person Cell is generated with its attributes when Person is changed. +![Update of PersonCard UI component](images/GenerateCardSequenceDiagram.png) + + ### Logic component @@ -93,45 +108,88 @@ Here's a (partial) class diagram of the `Logic` component: How the `Logic` component works: + 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. +1. The command can now takes in additional optional inputs (either from piping or from creation commands). 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`. +1. The command can also now create new commands / look for next commands to execute (e.g. seq, if else, piping) +1. Additional information that the command would pass to the next command will be then encapsulated along with the result of the command execution as a `CommandResult` object which is then returned back from `Logic`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("task delete 1")` API call. Note that this is a nested api call where a selector type is given (`task`, `person`, `team`) and followed by a secondary command. This is handled via a 2 layer parser and the type parser will parse the subsequent text. ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +We can illustrate another way of invoking delete command via piping 1 into delete command. This is likely called by some other command (`xxxComand` e.g. (task select 1 task delete)). + +![Interactions Inside the Logic Component for the `delete 1` Command](images/PipeSequenceDiagram.png) + +Here the object of the task to be deleted is passed in to the `setInput` parameters. Because there is already an input and no index were provided, the delete command will use the input set by the `xxxCommand` instead and delete that element from model. By using commands like `seq`, if else etc. We can essentially chain multiple commands together to form complicated commands. + +If you think about the implementation, this is highly similar to how Java Stream is implemented, where a pipe command is similar to a map command where data is being passed and re-evaluated. +
: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.
+ +#### Teams and Persons + +2 Layer commands like teams and persons (e.g., `team new`, `team delete`, ...) follow a similar sequence of interactions like above. + +

Changing of Team Scope

+ +However, during execution, team commands may update the context of the current team scope. To understand teams better, +Teams are analogous to folders in a basic file system, which can contain other folders (`Team`), or other +files (`Person` or `Task`). + +The Sequence Diagram below shows the interaction between the `Logic` and `Model` component when `execute("cg ..")` +is called. + +![Interactions Inside the Logic Component for the `cg ..` Command](images/ChangeTeamSequenceDiagram.png) + +This way of implementation maintains abstraction of details within the `Model` component from the `Logic` component. + +#### Parsing + Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: 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) +- 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. - +#### Tasks +Commands for tasks (e.g., `AddTaskCommand`, `MarkTaskCommand`, ...) follow a similar sequence of interactions within +the `Logic` component as the other commands described above. -The `Model` component, +However, during execution, tasks commands may update the context of the current tasks of a team. To understand tasks better, +it should be understood that Tasks are an attribute of a Teams object, which is used to indicate if a tasks is done (also known as marked), +or not (also known as unmarked). Moreover, progress of each task can be tracked with specific levels (namely 25%, 50%, 75% and 100 %). -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` 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 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) +Below is the class diagram that illustrates the interactions between Logic and Model components, in relation to tasks. -
: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.
+![Interactions Inside the Logic Component for the `cg ..` Command](images/TaskClassDiagram.png) - +This way of implementation of maintains abstraction of details of the `Logic` component, in the `Model` component. -
+### Model component + +**API** : [`Model.java`](https://github.com/AY2223S1-CS2103T-T11-1/tp/blob/master/src/main/java/seedu/address/model/Model.java) + + + +The `Model` component, + +- stores the address book data i.e., all `Person`, `Task`, `Group` objects which are contained in their respective Unique List object (inherited from `DisplayItemList`). +- stores the currently 'selected' `Person`, `Task`, `Group` objects (e.g., results of a search query or a change in group context) 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) + +Additional `Attribute`s are stored within `DisplayItem` (`AbstractDisplayItem`, `AbstractContainerItem`). ### Storage component @@ -139,30 +197,137 @@ The `Model` component, -The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) +The `Storage` component: + +- Can save both `AddressBook` data and user preference data in json format, and read them back into corresponding objects. +- Inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +- Depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`). + +#### Storage of different classes from the `Model` component + +To store the different classes from the `Model` component, different Json adapted classes (e.g. `JsonAdaptedPerson`, +`JsonAdaptedGroup`, ...) will be used to format, store and retrieve from Json files. These classes can then be +converted back into its `Model` based counterpart easily. + +For example, `JsonAdaptedPerson` can be converted back into a `Person` class. + +Here are the other classes in the `Storage` component used to store the `Model` component classes. The class +structure follows closely to the class structure within the `Model` component: + +![Json adapted version of model classes](images/StorageJsonSerializableAddressBookClassDiagram.png) + +These Json adapted classes are able to convert any `String` +representation of attributes within the Json files to its proper class attributes using the `toModelType` method. + +:exclamation: **Note:** Due to the nature of `AddressBookParser` being a singleton class, the `toModelType` method +in `JsonAdaptedAddressBookParser` is the only instance where its return type is `void`. + +The Sequence Diagram below illustrates how the Json file is converted back into an AddressBook. + +![Sequence Diagram reading a Json file to AddressBook](images/PersonRetrievalSequenceDiagram.png) + +#### Removing and adding parents to each group, person and task + +1. Note that in `JsonSerializableAddressBook`, the parenting is extracted and stored as an additional +attribute called `itemRelationship`. This attribute is a mapping from the `uuid` of a `person`, `group` +or `task` to the `uuids` of its respective parents. + + +2. When `Person`, `Group` and `Task` are converted into their `JsonAdapted` classes to be formatted +correctly in `AddressBook.json`, they will still hold on and store their respective `uuids`. + + +3. When building the `AddressBook` in `JsonSerializableAddressBook`, all `persons`, `groups` + and `tasks` are built using `toModelType` without their parents. + + +4. Within the `toModelType` method in `JsonSerializableAddressBook`, parents will then be added using the +`setParent` method in each `person`, `group` and `task` class. + + +5. The `AddressBook` with a completed tree-like structure consisting of `persons`, `groups` and `tasks` is returned +from the `toModelType` method in `JsonSerializableAddressBook`. ### Common classes Classes used by multiple components are in the `seedu.addressbook.commons` package. --------------------------------------------------------------------------------------------------------------------- +--- ## **Implementation** This section describes some noteworthy details on how certain features are implemented. +### Attributes Feature + +#### Implementation + +The attributes mechanism is facilitated by the creation of a new `Attribute` instance that is stored within the +`AttributesList` object that each `Person`, `Group` and `Task` have, as these three classes extend +`AbstractDisplayItem`, both directly and indirectly. + +To add a custom attribute, the user should provide an attribute name and an attribute value. These arguments are parsed +by the `AddFieldCommandParser`, and then matched with the known attributes in the `AttributeList`. + +The process of parsing an `Attribute` is also done by `ParserUtil::parseAttribute`, which triages the attribute name +if the attribute is considered as the default attributes (i.e. `Name`, `Email`, `Phone`, `Address`, `Description`) and +returns a new instance of that default attribute. If the attribute does not fall within the default attributes, then +it will create a new anonymous class that extends `AbstractAttribute`. + +The following sequence diagram shows how the add attribute operation works: + +![Sequence Diagram adding an Attribute to the Model](images/AddFieldSequenceDiagram1.png) + +#### Design Considerations + +* **Alternative 1 (current choice)**: Stores each `Attribute` in an `AttributeList` within each `AbstractDisplayItem` + instance (e.g. `Person`, `Task`, `Group`). + * Pros: Adheres to object-oriented principles, allows storing of non-string attributes. + * Cons: Might be more time expensive to check if attribute names are existing, as `equals` method to find matches. +

+* **Alternative 2**: Stores each `Attribute` in a `Attributes` object within each `AbstractDisplayItem`, with a class + to store a `Prefix` and the `AttributeName` + * Pros: Adheres to object-oriented principles, more efficient to search for existing attributes with the + implementation of `HashMap` for constant-time searching + * Cons: Needs an additional class `AttributePrefixes` to store the known `Prefix` objects that was + added by the user.

+* **Alternative 3**: Stores all known `Attribute` instances in a separate static class. + * Pros: All `Attribute`-related operations are stored within one accessible component + * Cons: Might violate composition and encapsulation relationships in the object-oriented design. + + +[Proposed] Detailed view page feature + +As we have already implemented the code to dynamically generate CSS for each individual attribute, we can fully implement detailed view page in the following 3 changes. + +1. Adds a detailed view page that model/UI can toggle to +2. Extends the `field add` command to allow users to set custom styles and display settings based on the binary bit flags. +3. Load the attribute in a similar way as to how the card is being generated in the detailed page. + +How `DetailPanel` displays all the details of a `DisplayItem` as an UI page. + +1. `UpdateContent(DisplayItem item)` will be called when the content of the UI needs to be updated. +2. The current content will be cleared. +3. Similar to how the card is generated by above, DetailPanel will iterate through all displayable Attributes and generate the corresponding UI components to be added to the details page. +4. The DetailPanel is now updated with the details of the new DisplayItem. + +Below shows a sequence diagram of how DetailPanel page is filled with contents of a DisplayItem. + +![Update of PersonCard UI component](images/UpdateDetailPanel.png) + +The proposed detailed view page allows users to have a more detailed analysis of each of the Display Items in address book and allows the user to view hidden detailed hid in list view from the display view list using the bit corresponding `MENU_OK` found in `AccessDisplayFlag.java` + + + ### \[Proposed\] Undo/redo feature #### Proposed Implementation The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +- `VersionedAddressBook#commit()` — Saves the current address book state in its history. +- `VersionedAddressBook#undo()` — Restores the previous address book state from its history. +- `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. @@ -223,14 +388,15 @@ 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. +- **Alternative 1 (current choice):** Saves the entire address book. + + - Pros: Easy to implement. + - Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by +- **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}_ @@ -238,18 +404,17 @@ _{more aspects and alternatives to be added}_ _{Explain here how the data archiving feature will be implemented}_ - --------------------------------------------------------------------------------------------------------------------- +--- ## **Documentation, logging, testing, configuration, dev-ops** -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +- [Documentation guide](Documentation.md) +- [Testing guide](Testing.md) +- [Logging guide](Logging.md) +- [Configuration guide](Configuration.md) +- [DevOps guide](DevOps.md) --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Requirements** @@ -257,73 +422,308 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +- They are tech-savvy +- Organised and consistent in using the app +- Have a need to effectively manage their team, through work progress and employee involvement +- Doesn’t mind putting extra time to make their life easier in the future -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: +- Manage employees +- Manage multiple projects and their progress + contributions from employees +- More effective under the hand of a tech-savvy user +- Meeting/time management +- Easily **personalizable** ### User stories 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 | 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 | - -*{More to be added}* +| Priority | As a …​ | I want to …​ | So that I can…​ | +| -------- | ------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `* * *` | basic user | add new contacts | add new contacts for use later | +| `* * *` | basic user | delete contacts | remove old unused contacts | +| `* * *` | basic user | view my current contacts | see all the contacts I have saved | +| `* * *` | basic user | group contacts into group | categorise my contacts | +| `* * *` | basic user | add additional info for each contact via a custom field | record extra information that i need to take note of regarding the contact | +| `*` | regular user | customise the colors of the user interface to my liking | be more comfortable using the app | +| `* *` | regular user | add custom commands acting as macros to the app | be more efficient with controls specific to my use. | +| `* *` | user | export my contacts as to csv or vcard | transfer general contact information to be used elsewhere or for sharing. | +| `* *` | user | add aliases to my contacts | search the contacts up faster | +| `* * *` | user | edit group information and group user information | edit and update team information should such details change | +| `* * *` | user | have an advertisement free experience | be more focused and not get distracted by ads | +| `* *` | user | transfer app data | keep my current data should i change devices or have multiple devices using the app | +| `* * *` | user | edit contact information | update my contacts with the most recent update | +| `* *` | user | have an responsive and snappy user experience | have a more enjoyable experience while using the app as well as increased efficiency | +| `* *` | user | undo a command | easily revert incorrect changes | +| `* *` | user | automatically save the data | be assured that i won’t lose data in the event the app exits improperly due to rare circumstances (e.g. computer crash) | +| `* *` | user | enjoy a simple and accessible user interface | easily understand and use the basic features without the need to extensively refer to a user guide | +| `* *` | user | enjoy a clean and zen-style gui | be more focused and not easily distracted. | +| `* * *` | developer | use the app solely using the keyboard (cli like input controls) | be fast and efficient since pressing a key is faster than reaching for the mouse | +| `*` | busy, working adult | have a clear separation between work contacts and other contacts | have a clear work-life balance | +| `* * *` | project manager | add extra information to each group of contacts | keep extra information regarding my team | +| `* * *` | project manager | add task to a group | record tasks to be done by my team | +| `* * *` | project manager | assign specific task to single or multiple users within a group | delegate tasks assignment when working in a team | +| `* *` | project manager | view the timeline of task completion within a group | track progress within the group | +| `* * *` | project manager | add additional information to a contact within a team | track bonus details and information regarding a member | +| `* * *` | project manager | add completion dates to some tasks assigned in the group | set deadlines to group specific tasks | +| `* * *` | project manager | add ability to add custom fields when adding contacts to a group | tailor fit the group to my project needs | +| `*` | project manager | add custom color palette to each group | feel more personalised to each team as well as better differentiate between different teams | +| `* * *` | project manager | change the completion status of tasks in a group | to indicate the if an assigned task has been completed by a member | +| `* *` | project manager | generate a progress report of the group progress (i.e. show task done at what time, how many percent done etc) | easily access and present those information to others (e.g. higher ups) as well as tracking the progress of the project. | +| `* * *` | project manager | have the ability to broadcast messages to my team members | disseminate information in a short time. | +| `* * *` | project manager | delegate work to each group efficiently via commands | cut down on time spent doing admin work | +| `* * *` | project manager | access and view the contacts of my team members | track progress of the team on an individual level | +| `* *` | project manager | organise my project team tasks with efficiency | spend less time doing admin work | +| `* *` | project manager | switch context easily between multiple groups | manage multiple teams | +| `* * *` | project manager | issue commands specific to the team | be more efficient and type fewer group related details | ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is `Contactmation` and the **Actor** is the `user`, unless specified otherwise) + +**Use case: UC01 - Delete a person** + +**MSS** + +1. User requests to list persons. +2. Contactmation shows a list of persons. +3. User requests to delete a specific person in the list. +4. Contactmation deletes the person. + + Use case ends. + +**Extensions** + +- 2a. The list is empty. + + Use case ends. + +- 3a. The given index is invalid. + + - 3a1. Contactmation shows an error message. + + Use case resumes at step 2. -**Use case: Delete a person** +**Use case: UC02 - Edit a person** **MSS** -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 +1. User requests to list persons. +2. Contactmation shows a list of persons. +3. User requests to edit a specific person in the list. +4. Contactmation shows a screen with the properties of the person. +5. User edits the properties to their liking and saves. +6. Contactmation saves the data. Use case ends. **Extensions** -* 2a. The list is empty. +- 2a. The list is empty. Use case ends. -* 3a. The given index is invalid. +- 3a. The given index is invalid. + + - 3a1. Contactmation shows an error message. + + Use case resumes at step 2. + +**Use case: UC03 - Create a team** - * 3a1. AddressBook shows an error message. +**MSS** + +1. User requests to create a team with a team name. +2. Contactmation creates the team. + + Use case ends. + +**Extensions** - Use case resumes at step 2. +- 1a. The team name is empty or invalid. -*{More to be added}* + - 1a1. Contactmation shows an error message. + + Use case ends. + +- 2a. The new team is in the current path. + + - 2a1. Contactmation adds the new team to the list of teams in this path. + + Use case ends. + +**Use case: UC04 - Enter a team context** + +**MSS** + +1. Contactmation shows a list of teams. +2. User requests to enter a team context. +3. Contactmation enters the team context. +4. Contactmation only shows information specified to the new team context. + + Use case ends. + +**Extensions** + +- 2a. The team selected is invalid. + + - 2a1. Contactmation shows an error message. + + Use case ends. + +**Use case: UC05 - Add a Task** + +**MSS** + +1. User requests to list groups. +2. Contactmation shows a list of groups. +3. User requests to add a task to a group in the list. +4. Contactmation adds the task. + + Use case ends. + +**Extensions** + +- 2a. The list is empty. + + Use case ends. + +- 3a. The given index is invalid. + + - 3a1. Contactmation shows an error message. + + Use case resumes at step 2. + +**Use case: UC06 - Set a Task completion status** + +**MSS** + +1. User requests to list tasks for a group. +2. Contactmation shows a list of tasks. +3. User requests to set the task completion status. +4. Contactmation sets the task completion status. + + Use case ends. + +**Extensions** + +- 2a. The list is empty. + + Use case ends. + +- 3a. The given index is invalid. + + - 3a1. Contactmation shows an error message. + + Use case resumes at step 2. + +- 4a. The given status is empty. + + - 4a1. Contactmation sets the task to `Done` + + Use case ends. + +**Use case: UC07 - Create a macro** + +**MSS** + +1. User enters the command to create a macro. +2. Contactmation creates and saves the macro. + + Use case ends. + +**Extensions** + +- 1a. The macro command is invalid. + + - 1a1. Contactmation shows an error message. + + Use case ends. + +**Use case: UC08 - Execute a macro** + +**MSS** + +1. User requests to use a macro. +2. Contactmation executes the commands defined by the macro. + + Use case ends. + +**Extensions** + +- 1a. The macro does not exist. + + - 2a1. Contactmation shows an error message. + + Use case ends. + + +**Use case: UC09 - Execute a foreach loop** + +**MSS** + +1. User requests to use a with a supplied command over selected item type. +2. Contactmation executes the command over the selected item type. + + Use case ends. + +**Extensions** + +- 1a. The command is invalid. + + - 1a1. Contactmation shows an error message. + + Use case ends. + +- 1b. The selected item type does not exist. + + - 1b1. Contactmation shows an error message. + + Use case ends. + +- 2a. The command fails on an item. + + - 2a1. Contactmation stops execution of the loop. + - 2a2. Contactmation displays an error message + + Use case ends. + +**Use case: UC10 - Use macros for repetitive tasks** + +**MSS** + +1. User **creates a macro (UC07)**. +2. User **executes a foreach loop (UC09)** with the macro. + + Use case ends. + +**Extensions** + +- 2a. The macro does not exist. + + - 2a1. Contactmation shows an error message. + + Use case ends. ### Non-Functional Requirements 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. - -*{More to be added}* +4. Should be usable with only a _command-line interface_. +5. Data should persist between app launches. +6. Should work with both 32-bit and 64-bit operating systems. +7. The system is not required to do any communication with the Internet (e.g. sending emails). +8. Should not use any images or terms that could be deemed as offensive. ### 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, Linux, Unix-like, MacOS +- **Command-line Interface**: A system that received user input in the form of lines of text +- **Task**: A piece of work that has to be done, that can be assigned to people and that may have a deadline --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Instructions for manual testing** @@ -338,18 +738,22 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 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. Download the `contactmation.jar` [file](https://github.com/AY2223S1-CS2103T-T11-1/tp/releases) and copy into an empty folder -1. Saving window preferences + 1. Double-click the jar file
Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + +2. Clearing the default sample contacts + 1. Run the `clear` command to remove existing data from the application.
+ Expected: Removes existing sample contacts, groups and/or tasks from the application. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +### Adding a person - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. - -1. _{ more test cases …​ }_ +1. Adding a person to the application + 1. Test Case: `person new n/John Doe`
+ Expected: Contact with name John Doe is created with index number 1. + 2. Test Case: `person new n/Caroline Smith p/86178049 e/caroline@gmail.com t/friend` + Expected: Contact with name Caroline Smith is created with index number 2. + Phone, Email and tag is present in the GUI. ### Deleting a person @@ -368,10 +772,54 @@ testers are expected to do more *exploratory* testing. 1. _{ more test cases …​ }_ -### Saving data - -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ - -1. _{ more test cases …​ }_ +### Group Commands + +1. Creating a group from the main window + 1. Test case: `team new cs2103`
+ Expected: A team with name `/cs2103` will appear in the left pane of the GUI. +2. Navigating to a Group + 1. Prerequisite: At least one team should be present in the GUI + 2. Test case: `cg 1`
+ Expected: GUI should now be in the scope of the first team in the list of teams. If the first team is `/cs2103`, + you can expect to see a `/cs2103` on the bottom left of the GUI. +3. Add a person in the group + 1. Prerequisite: You should now have navigated inside a group. + 2. Test case: `person new n/Eric`
+ Expected: A person named `Eric` should now be created inside the group. +4. Navigate back to the root group: + 1. Prerequisite: You should now have navigated inside a group. + 2. Test case: `cg /`
+ Expected: You should now be navigated back to the root group (the window that you first see when you start the + app). + + +### Task Commands + +1. Creating a task from a group + 1. Prerequisite: You should have at least one group, and you should now have navigated into a group. + 2. Test case: `task add t/Todo d/Finish Work`
+ Expected: A task named todo with description finish work should be visible in the right pane of the GUI. +2. Mark a task as done + 1. Prerequisite: You should have at least one task visible in the current state of the GUI. + 2. Test case: `task mark 1` + Expected: The task in the index 1 should now be marked as completed. +3. Mark a task as not done + 1. Prerequisite: You should have at least one task visible in the current state of the GUI, and the task should + have been marked as done. + 2. Test case: `task unmark 1` + Expected: The task in the index 1 should now be marked as incomplete. +4. Delete a task from the list of tasks + 1. Prerequisite: You should have at least one task visible in the current state of the GUI. + 2. Test case: `task delete 1` + Expected: The task in the index 1 should now be removed from the application, and should not be removed from the GUI. + +### Attribute Commands + +1. Create a new Attribute for a Person + 1. Prerequisite: You should have at least one person present in the GUI + 2. Test Case: `field add u/1 github johndoe123`
+ Expected: The attribute "github: johndoe123" should be visible in the GUI +2. Remove an attribute for a person + 1. Prerequisite: You should have at least one person present in the GUI, the person should have an attribute + 2. Test case: `field delete u/1 github`
+ Expected: The attribute "github: johndoe123" from the previous example should not be visible in GUI. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..0b0cb9bd3b2 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,1262 @@ 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. +## **Table of contents** + +1. [Introduction](#introduction) +2. [About](#about) + 1. [Purpose of guide](#purpose) + 2. [How to navigate the user guide](#user-guide-navigation) + 3. [Contactmation window guide](#contactmation-window-guide) + 4. [Prerequisites](#prerequisites) +3. [Quick start](#quick-start) +4. [Before you begin](#before-you-begin) + 1. [Standardised format style](#standardised-format-style) + 2. [Constraints on placeholder words](#constraints-on-placeholder-words) + 3. [Making groups within groups](#making-groups-within-groups) +5. [Features](#features) + 1. [Basic features](#basic-features) + 1. [General commands](#1-general-commands) + 1. [Clear command](#clear-command-clear) + 2. [Exit command](#exits-the-program-exit) + 3. [Add a field](#add-a-new-field-field-add) + 4. [Edit a field](#edit-a-field-field-edit) + 5. [Delete a field](#delete-a-field-field-delete) + 6. [Rename a group, person or task](#rename-the-name-of-items-on-the-screen) + 7. [Find command](#find-command-find) + 8. [List all contacts in team](#listing-all-contacts-in-current-team) + 2. [Team/Group commands](#2-teamgroup-commands) + 1. [Create a team](#create-a-team-team-new) + 2. [Delete a team](#delete-a-team-team-delete) + 3. [Navigate to a team](#navigate-to-a-team-cg) + 4. [Add new person to team](#add-new-contacts-within-a-team) + 5. [Remove person from team](#removing-contacts-from-team-team-remove) + 6. [Creating or deleting a subteam](#creating-and-deleting-a-subteam) + 7. [Find a team](#finding-a-team-find) + 3. [Person commands](#3-person-commands) + 1. [Add a person](#add-a-person-person-new) + 2. [Delete a person](#delete-a-person-person-delete) + 3. [Find a person](#finding-a-person-find) + 4. [Task commands](#4-task-commands) + 1. [Adding a task](#adding-a-task-to-a-team-task-add) + 2. [Deleting a task](#deleting-a-task-from-team-task-delete) + 3. [Mark task](#mark-a-task-task-mark) + 4. [Unmark task](#unmark-a-task-task-unmark) + 5. [Find a task](#finding-a-task-find) + 2. [Advanced features](#advanced-features) + 1. [Chaining](#advanced-features-overview-chaining) + 2. [Feature constraints](#advanced-feature-constraints) + 3. [Select command](#select-command) + 4. [Contains command](#contains-command) + 5. [Execute command](#execute-command) + 6. [Replace command](#replace-command) + 7. [Foreach command](#foreach-command) + 8. [If else command](#if--else-command) + 9. [Aliasing](#aliasing) + 10. [Custom command / macro](#custom-command--macro) +6. [FAQ](#faq) +7. [Future plans](#future-plans) +8. [Glossary](#glossary) +9. [Commands summary](#command-summary) + 1. [General commands](#general-commands-summary) + 2. [Contact commands](#contact-commands-summary) + 3. [Group commands](#group-commands-summary) + 4. [Task commands](#task-commands-summary) + 5. [Advanced commands](#advanced-commands-summary) -* Table of Contents -{:toc} +--- + +## **Introduction** + +> **What is Contactmation?** + +Contactmation is a powerful **desktop-based project and task management solution** that **helps you efficiently and +effectively manage many projects at once** through the [Command Line Interface (CLI)](#glossary). + +Contactmation will be able to help you save all your project member details, keep track of +each project, and delegate tasks to each project. + +> **Who is Contactmation for?** + +Contactmation is for **project managers and supervisors** who want to maintain an organised view of their +projects and streamline the management of their projects. + +--- + +## **About** + +### Purpose + +This guide will elaborate on all the features available in Contactmation that will help make your experience of using +Contactmation pain-free. + +Examples with real-world applications are present to give you a clearer idea of how the features +can be utilised. + +### User Guide Navigation + +This guide is broken into different sections that will aid you in better understanding our application. + +Refer to: + +- The [contactmation window guide](#contactmation-window-guide) for a guide on the application window. +- The [basic feature](#basic-features) section to get started on using the application. +- The [advanced feature](#advanced-features) section for powerful tools that will help make your experience of + using Contactmation much more streamlined and customisable. +- The [glossary](#glossary) for explanations to the different terms used throughout this guide. +- The [command summary](#command-summary) for a quick overview on how to use all our features. + +For a more detailed view of all the features present, please visit the [table of contents](#table-of-contents). + +### Contactmation Window Guide + +The following figure shows how our application would appear on your screen upon opening Contactmation. +Each part of our application will be labelled as such: + +![Contactmation ui elements](images/ContactmationUi.png) + +We will be referring to these terminologies throughout the user guide. + +### **Prerequisites** + +Before you start up Contactmation on your computer, + +- Ensure that `Java version 11` or above is installed on your device. Do refer to the [FAQ](#faq) if you need help with + checking whether `Java 11` is installed on your computer, or if you need help with installing `Java 11`. + +- The current version of Contactmation can only be used in a desktop, but should work on all operating systems (such as + Windows, macOS and Linux etc.) as long as `Java 11` is installed. + +--- + +## **Quick start** + +1. Ensure that the [prerequisites](#prerequisites) are met before installing Contactmation. + +2. Download the latest version of `contactmation.jar` from + [here](https://github.com/AY2223S1-CS2103T-T11-1/tp/releases). The file `contactmation.jar` + can be found under the `Assets` for each version of Contactmation. + +![Find the Contactmation jar file](images/QuickStartFindJar.png) + +3. Save `contactmation.jar` into a Desktop folder on your computer. This folder will now be the home folder + for Contactmation. Do look at the [FAQ](#faq) section for errors related to opening files used in Contactmation. + +4. Double-click on `contactmation.jar` to start up the application. You will be greeted with the current window + if everything goes well: + +![Contactmation ui main window](images/ContactmationUiClean.png) + +5. You may begin by referring to the [before you begin](#before-you-begin) section to get + started. + +6. You may then move on to the [basic features](#basic-features) section to get started on using + Contactmation. For additional, more powerful commands, refer to our [advanced features](#advanced-features), + especially if you are comfortable with the CLI or have prior programming experience. + +:bulb: **Tip**: If you wish to clear all default entries and begin with on a fresh slate, use the [clear](#clear-command-clear) command. + +--- + +## **Before you begin** + +Before you begin, you will need to know how to interact with Contactmation. + +You may only interact with Contactmation by typing [commands](#glossary) into the +[command box](#contactmation-window-guide). Upon hitting the `Enter` key, you will be able to execute the command +currently residing in the command box. + +> How do I clear the sample data in my newly downloaded version of Contactmation? + +Simply type in the word `clear` into the command box and press the `Enter` key on your desktop. All the sample +data will be wiped. Do note that this action is **irreversible**. + +> How do I properly write these commands? + +We will go through the standardised formats for each [basic](#basic-features) and [advanced](#advanced-features) +feature in their respective sections. + +> How do I read the standardised formats for each feature? + +These formats may look cryptic at first glance. Do visit the [Standardised Format Style](#standardised-format-style) +section to better understand how to read the formats. + +### Standardised Format Style --------------------------------------------------------------------------------------------------------------------- +This section aims to help you better understand the different terminologies used in the format section of each +feature description. -## Quick start +| Format | Explanation | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Square brackets** ([ ]) | Anything that is within the square brackets are entirely **optional**. You may skip anything wrapped in the square bracket and move on to the next [word](#glossary) in the [command sequence](#glossary). | +| **Angled brackets** (< >) | Anything within angled brackets are placeholder words. These words will be replaced by other words for the command to run. The replacing words will be specified in the format section for each feature. | +| **Ellipses** (`...`) | The word which these ellipses are attached to can be repeated multiple times in a single command. | -1. Ensure you have Java `11` or above installed in your Computer. +#### Combining the format styles -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +The **innermost** format in a word has the **highest** priority, while the **outermost** format in a word +has a **lower** priority than any format within it. However, since an outer format is considered after the inner format, +the outer format has a **stronger connotation** to it. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +Here is an additional example to solidify your understanding of how these formats make sense when combined. -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) +If the command format in the format section of a feature has `[t/tags...]`, that means that the **ellipses** +will have a higher priority than the **square brackets**. This means that `t/tags` can be repeated multiple +times, but `t/tags` can be entirely optional as well. Therefore, it is possible to **skip** `t/tags` altogether, or have +1 or more `t/tags` in the command sequence. -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.
- Some example commands you can try: +Other keywords utilised in the guide are defined in the [glossary](#glossary). - * **`list`** : Lists all contacts. +### Constraints on placeholder words - * **`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. +This section will help you understand what placeholder words in the format section for each command can be +replaced by. Placeholder words are words which are wrapped around angled brackets (`<>`) in the format section. +Do refer to the [standardised format style](#standardised-format-style) section for more information to +understand the format styles for each command. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +- The `NAME` of the person or task must be [alphanumeric](#glossary) and can contain white spaces. - * **`clear`** : Deletes all contacts. +- The `TEAM NAME` must be alphanumeric. - * **`exit`** : Exits the app. +- The `PHONE_NUMBER` is divided into 3 sections, namely the country code, area code and phone + number in that order. Both the country and area code can have **1-4 digits**, and the phone number can + be **3 digits or more**. All 3 sections must be separated by a white space. The `PHONE_NUMBER` may also + begin with a (`+`) symbol. The country and area codes are entirely **optional**. +- The `EMAIL` of the person must have an `@` symbol. We will refer to anything before the `@` + symbol as `PART 1` and anything after the symbol to be `PART 2`. -1. Refer to the [Features](#features) below for details of each command. + - `PART 1`: Can only contain alphanumeric characters and special characters such as (`+\_.-`). + You may only start or end `PART 1` with alphanumeric characters. --------------------------------------------------------------------------------------------------------------------- + - `PART 2`: Must have at least one period (`.`). We will refer to text separated by periods as + domain labels. -## Features + - `PART 2` must end with a domain label at least **2 characters long**. + + - Each domain label must start and end with alphanumeric characters. + - Each domain label can only consist of alphanumeric characters and hyphens. + +- The `ADDRESS` of a person can take any values, but it should not be blank. + +- The `TAG` must be alphanumeric. + +- The `INDEX` must be a positive whole number which cannot exceed the number of groups, contacts or tasks currently displayed in the + application window. + +- The `KEYWORD` and `MORE_KEYWORDS` must be alphanumeric. + +- The `TITLE` is [alphanumeric](#glossary) and can contain hyphens and underscores. + +- The `DESCRIPTION` has no restrictions. + +- The `ITEM` can only be replaced with either `task`, `person` or `team`. +- The `SELECTED_ITEM` are item that are selected via a select command or passed down from other commands [See select command](#select-command) + + - e.g. + + - `task select 1` will select the first task and that first task will be considered the `SELECTED_ITEM` + +### Making groups within groups + +This section aims to help you understand the concept of creating groups under other groups. + +> What is "making groups within groups"? + +Here is a scenario. Let us say you are a boss of a company called **Just_Incorporated**, and you would like to track +the different departments in your company. Therefore, the departments in the company +(e.g. `Marketing` and `Research_And_Development` department) could act as groups within Contactmation. + +However, let us also say that you are closely monitoring a particular group in the `Research_And_Development` +department, and let us call this group is `Vero_Ltd`. Then this group, `Vero_Ltd`, would +fall under the **`Research_And_Development`** group in Contactmation. + +Here is how the grouping for this scenario would look like in theory: + +![Group within group illustration](images/Group_within_group_illustration.png) + +This will be how the grouping looks like in Contactmation: + +![Group within group illustration in contactmation](images/Group_within_group_illustration_contactmation.png) + +Note that `Vero_Ltd` is listed in group 3 as `/Research_And_Development/Vero_Ltd`. +This means that `Vero_Ltd` is within the group `Research_And_Development`, and this is shown with +the name of the group `Vero_Ltd` appearing after `Research_And_Development` followed by a slash (`/`) +separating the two names. + +Now that you understand what it means to have a group inside another group, you might want +to view specific information about a certain group and ignore other group information. + +> How do I restrict what I see on the display to only a specific group? + +Let us say you are only interested in seeing everything related to the `Research_And_Development` department +and nothing else. In this case, you might want to restrict what you see on the Contactmation display +to only view information within `Research_And_Development`. + +When you do this, all the other information in other groups (e.g. `Marketing`) will no longer +appear on your Contactmation display. Do not worry, all the other information is not lost. Information +about other groups are simply hidden from your view so that you can focus on `Research_And_Development`. + +To "zoom into" a specific group, you can use the [cg](#navigate-to-a-team) command.
+**:information_source: How to type the commands properly:**
+ +For more information on how to type the command, please visit the sections on +[standardised format styling](#standardised-format-style) and [placeholder constraints](#constraints-on-placeholder-words) +that is used to type all commands. + +
+ +:bulb: **Tip**: The [cg](#navigate-to-a-team) command is also used to view all your other groups again. To do this, just type `cg ..` or `cg /` into the command box. + +Following the above example, `cg 2` needs to be typed into the command box for you to view +`Research_And_Development`, since the `Research_And_Development` department is listed as group 2 in the +Contactmation display. + +The following screen should appear upon pressing `Enter`: + +![Successful cg execution](images/ChangeGroupCommandDisplayExample.png) + +We can see that the `Marketing` and `Research_And_Development` departments can no longer be seen. +We are not able to see the `Marketing` department because now, we are focusing solely on the +`Research_And_Development` department. + +> What happened to the `Research_And_Development` department? + +At the bottom left-hand corner of the [application window](#contactmation-window-guide), we can see +the current group we are in. + +Therefore, what we are seeing now is **any groups, contacts or tasks that exist within `Research_And_Development` +only**. Since `Vero_Ltd` is under the `Research_And_Development` group, it still can be seen on the display. + +> How do I create a group within another group? + +Let's assume you want to create another group, `Kong_Pte_Ltd`, under the `Vero_Ltd` group. +So, you will have to use the `cg` command to first "zoom in" to `Vero_Ltd` and restrict what you are seeing +on the display. So we will need to type `cg 1` in the [command box](#contactmation-window-guide) and execute the +statement. + +Now, you are within the `Vero_Ltd` group. You may now create a subgroup within `Vero_Ltd` by executing the +[team new](#create-a-team) command. In this case, you can type `team new Kong_Pte_Ltd` to create `Kong_Pte_Ltd` +within `Vero_Ltd`: + +![Add subgroup to group](images/AddGroupCommandDisplayExample.png) + +The `Kong_Pte_Ltd` group is now created within `Vero_Ltd`! + +--- + +## **Features** + +With Contactmation, you can not only use the [**basic features**](#basic-features) to manage your group project, but also use +[**advanced features**](#advanced-features) to customise and streamline your experience in Contactmation. + +However, if you are a new user of Contactmation, we recommend sticking +to just the basic features. The basic features are more than enough to provide for all your needs +with respect to group, contact and task management, whereas the advanced features only help with +increasing the efficiency of using the application on large numbers of groups, contacts and tasks. + +Do visit the [standardised format style](#standardised-format-style) and the [format constraint](#constraints-on-placeholder-words) sections before writing commands in Contactmation. + +## Basic features + +Now that you have finished setting up Contactmation, let’s start performing basic [commands](#glossary) with Contactmation. +As Contactmation aims to help you manage your contacts, project groups and tasks, we will start off by performing +a range of basic commands varying from adding a person to manipulating tasks and groups. -**:information_source: Notes about the command format:**
+The basic features are categorised as the following: -* 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`. +1. [General Commands](#1-general-commands) +2. [Team/Group Commands](#2-teamgroup-commands) +3. [Contact Commands](#3-contact-commands) +4. [Task Commands](#4-task-commands) -* 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`. +## 1. General commands -* 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. +These commands can be applied to any group, contact or task. -* 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. +### Clear command: `clear` -* 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. +This command clears all group, contact and task entries from the application. You can write this command if you are, +for example, leaving a company and deciding to wipe all data related to the company. -* 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`. +:exclamation: **Caution: This action is irreversible! Please make sure that you really want to +delete all your information before doing so.** + +**Format:** `clear` + +### Find command: `find` + +This command allows you to search for a person, group or task that matches a given `KEYWORD`. Searches may also include `MORE_KEYWORDS` +to further narrow the search for a contact, subgroup or task within the current [scope](#glossary). + +**Format:** [` find []`](#constraints-on-placeholder-words) + +**Examples:** + +- `person find John Doe` +
This command finds a person named John Doe +- `task find task1 task2` +
This command finds tasks whose name matches task1 or task2 +- `team find team1 team2` +
This command finds teams whose name matches team1 or team2 + +![Finding a person by name](images/FindPerson.png) + +To reset the find filters, you can use the [List command](#listing-all-contacts-in-current-team). + +### Listing all data in current team + +Contactmation supports the `list` command that displays all of your contacts in the current team to a list of persons. +To execute the command, ensure that you have navigated to the desired team, then execute the command `list`. If you +are currently not in the scope of any team, the `list` command will display all of your contacts in Contactmation by +default. + +**Format**: `list` + +![Listing all data](images/ListCommand.png) + +### Exits the program: `exit` + +When this command is executed, the program will **save** all the groups, contacts +and tasks present in Contactmation and **closes** the application. + +**Format:** `exit` + +### Add a new field: `field add` + +This command is for you to add additional information to a group, task, or person. This extra information +will appear as text below the name of the group, task or person, as shown below: + +![field add default](images/FieldAddDefault.png) + +Here, we can see that additional information that "`Funding`" is "`On hold`" in the group `Research_and_development`. + +A field can provide additional information to the [item](#constraints-on-placeholder-words) when it is needed. + +**Format:** + +- `field add u/ ` to add a new field to the person at the current `INDEX`. + +- `field add g/ ` to add a new field to the group at the current `INDEX`. + +- `field add t/ ` to add a new field to the task at the current `INDEX`. +- ` field add ` to add a new field to the selected task/person/group. [See select command](#select-command) + +For example, let us say we have a task `Do paperwork`, and we want to add additional information to it. We can +call the command `field add t/1 Priority High` to add a field with the `NAME` as `Priority` and +the `DESCRIPTION` as `High` to the task with `INDEX` 1. + +![Add field to task](images/AddFieldUserGuideExample.png) + +### Edit a field: `field edit` + +This command will attempt to edit an **existing** field that belongs to a group, task or person. It replaces the +description of an existing field name to a new description. + +Do visit the [glossary](#glossary) for more details on what a field is. + +However, if the field name does not exist, then the field should be added first through the `field add` command +explained above. Field names are case-sensitive. + +**Format**: + +- `field edit u/ ` to edit the field named `FIELD NAME` to the person at + the current `INDEX`. +- `field edit g/ ` to edit the field named `FIELD NAME` to the group at + the current `INDEX`. +- `field edit t/ ` to edit the field named `FIELD NAME` to the task at + the current `INDEX`. +- ` field edit ` to edit the field named `FIELD NAME` to the selected item. [See select command](#select-command) + +Here is an example. Let us say that we have a group, `Research_and_development`, that has +its "`Funding`" field to be "`On hold`". + +![Edit Field Example](images/FieldAddDefault.png) + +Let us now change "`On hold`" to "`No longer on hold`", using the command `field edit g/2 Funding No longer on hold`. + +![Edit field updated Example](images/EditFieldDefault.png) + +The field is now successfully updated! + +### Delete a field: `field delete` + +The command will attempt to delete a field that belongs to a group, task or person. +A field can provide additional information to the [item](#constraints-on-placeholder-words) when it is needed. + +**Format:** + +- `field delete u/ ` to delete a field `` from the person at the current `INDEX`. + +- `field delete g/ ` to delete a field `` from the group at the current `INDEX`. + +- `field delete t/ ` to delete a field `` from the task at the current `INDEX`. +- ` field delete ` to delete a field `` from the task at the current `INDEX`. [See select command](#select-command) + +Continuing from the example in [add a new field](#add-a-new-field-field-add), let us now +delete the `Priority High` field. To do this, we have to write `field delete t/1 Priority` +to delete the `Priority` field in the first task listed on the Contactmation display. + +![Delete field example](images/DeleteFieldUgExample.png) + +As we can see, the `Priority High` field is now deleted from the `Do paperwork` task. + +### Rename the name of items on the screen + +If you want to rename any [item](#constraints-on-placeholder-words) in the app, this `rename` command is for you. + +
+**:information_source: Note:**
+ +In the following sections, `` here refers to the **new name that you +want the group, contact or task to be renamed to**.
-### Viewing help : `help` +**Format:** + +- `rename g/ ` to rename the group at the listed `INDEX`. + +- `rename u/ ` to rename the contact at the listed `INDEX`. + +- `rename t/ ` to rename the task at the listed `INDEX`. + +- ` rename ` to rename the selected item. [See select command](#select-command) + +**Example:** + +- `rename g/1 Marketing` + + - this will rename the first **group** in your list to `Marketing`. + +- `person select 1 rename Jimmy` [See select command](#select-command) + - this will rename the first **user** in your list to `Jimmy`. + +![Renaming a team to marketing](images/rename_cmd.png) + +### Find command: `find` + +Searches for a contact, group or task that matches the given `KEYWORD`. Searches may also include `MORE_KEYWORDS` +to further expand the search for a contact, subgroup or task within the [current team](#contactmation-window-guide). + +**Format:** ` find []` -Shows a message explaning how to access the help page. +**Examples:** -![help message](images/helpMessage.png) +- `person find John Doe` -Format: `help` + - This means that we will find all persons in the current team with the current +- `task find task1 task2` -### Adding a person: `add` +## 2. Team/Group commands -Adds a person to the address book. +Contactmation allows you to group your contacts into teams. This section will showcase the different commands that +can be used on groups. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Team-related commands in Contactmation begin with the `team` keyword. + +### Create a team: `team new` + +This command helps you create a new group/team name in the current team. + +
+**:information_source: Note:**
+For more information on creating teams within teams, check [this](#making-groups-within-groups) +section on what it really means to create teams within teams. -
:bulb: **Tip:** -A person can have any number of tags (including 0)
-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` +**Format**: `team new ` + +The above command creates a new team with a specified team name. Do refer to the +[constraints on placeholder words](#constraints-on-placeholder-words) section for more information on what +you can type in `TEAM NAME`. + +**Examples**: -### Listing all persons : `list` +- `team new Vongola_X` -Shows a list of all persons in the address book. +- `team new vero-employees` -Format: `list` +![Create Team Screenshot](images/user-guide-img/CreateTeamScreenshot.png) -### Editing a person : `edit` +As seen in the example above, we are able to add team `vero-employees` to our +[group list](#contactmation-window-guide). -Edits an existing person in the address book. +### Delete a team: `team delete` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +To delete a team from Contactmation, you can use the `team delete` command followed by the team number seen in +the current display of the team list. -* 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, …​ -* 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. +**Format**: -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. +- `team delete ` +- `team select team delete` [See select command](#select-command) -### Locating persons by name: `find` +**Examples**: -Finds persons whose names contain any of the given keywords. +- `team delete 1` + - The above command deletes team number 1 in the [team list](#contactmation-window-guide). -Format: `find KEYWORD [MORE_KEYWORDS]` +> What happens to all the people and tasks in that current team when I delete the team? -* 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` +All `persons` and `tasks` will be transferred to the [root group](#glossary). -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +### Navigate to a team: `cg` -### Deleting a person : `delete` +To perform commands specific to a team, you will have to navigate first to that specific team. You can use the `cg` +command to navigate to a specified team. -Deletes the specified person from the address book. +:bulb: **Tip**: Please take a look at [making groups within groups](#making-groups-within-groups) +for a tutorial on team navigation before executing this command. -Format: `delete INDEX` +**Formats**: -* Deletes 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, …​ +- `cg ` +- `team select cg` [See select command](#select-command) -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. +- `cg ..` moves you out of the group and into the parent group. To better understand this, let us have the following + example: -### Clearing all entries : `clear` +![Change group example](images/ChangeGroupExample.png) -Clears all entries from the address book. +Here, we can see that we are in the group `/Research_and_development/Vero_ltd`. +However, when we use the `cg ..` command, we are able to move to the `/Research_and_development` group. -Format: `clear` +![Change group example out](images/ChangeGroupExample2.png) -### Exiting the program : `exit` +When we are in the `/Research_and_development` group, we can see that there is a group in the [group list](#contactmation-window-guide). +That is the group we were in at first, before we used the `cg ..` command! -Exits the program. +- `cg /` moves you to the [root group](#glossary) in Contactmation. + From the root group, you are able to **see all the groups currently existing in Contactmation**. -Format: `exit` +![Change group example root](images/ChangeGroupExample3.png) -### Saving the data +Following the previous examples in for the `cg` command, we see the `/Research_and_development` group, +`/Research_and_development/Vero_ltd` and an additional group, `/Marketing`, all at once! -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +**Examples**: -### Editing the data file +- `cg 3` + - The above command allows you to navigate to team number 3 in your current [team list](#contactmation-window-guide). + - Before: + ![Create Team Screenshot](images/user-guide-img/NavigateTeamBefore.png) + - After: + ![Create Team Screenshot](images/user-guide-img/NavigateTeamAfter.png) +- `cg ..` -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. +![going back to a parent group](images/cg_back.png) + +- `cg /` + +![Going back to root directory](images/cg_home.png) + +
+**:information_source: Note for experienced programmers:**
+If you are familiar with UNIX-based operating systems such as Linux or familiar with using terminal, the navigation +command (`cg`) in Contactmation follows a similar syntax to the change directory command (`cd`). -
: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]` +### Add new contacts within a team + +Once you have [navigated to a team](#navigate-to-a-team-cg), you can add a new contact within that team, which is done through the same command +as [adding a contact](#create-a-contact) to Contactmation. + +### Removing contacts from team: `team remove` + +Removes the contact from the current group by their currently specified `INDEX` as shown in +the application window. + +There is an important distinction between a `team remove` and a `team delete`. In `team remove`, +the `INDEX` refers to a person, whereas in `team delete`, we are deleting a team altogether +from Contactmation. + +
+**:information_source: Note:**
+This command simply **removes a person from the team you are currently in**. But this does not +mean that the person is deleted from Contactmation! The person can still be found in the [root group](#glossary). + +
+ +**Format:** `team remove ` + +**Example:** + +- `team remove 3` + - This command removes contact number 3 in the specified team, and transfers the contact to + the [root group](#glossary). + +### Creating and Deleting a subteam + +Contactmation allows the creation and deletion of a subteam within a team using the same command as +[creating a team](#create-a-team-team-new) and [deleting a team](#delete-a-team-team-delete). + +### Finding a team: `find` + +Contactmation allows for searching of teams with the [find command](#find-command-find). + +## 3. Person commands + +Person commands are used to add and manage people within Contactmation. This section will showcase the different +commands that can be used on persons. + +Contact-related commands precede with the `person` keyword. + +### Add a Person: `person new` + +Let us start off by adding a person to Contactmation. To add a contact, you can use the command `person new`, +followed by the name of the person. You can also choose to provide the phone number, email and address +to each person, or add a tag to identify each person. + +**Format**: `person new n/ [p/] [e/] [a/
] [t/]...` + +**Examples**: + +To add a person with the name 'Alice Green', you may execute the following commmand. + +- `person new n/Alice Green` + +To add a person with the name 'Hilbert Stewart', whose phone number is +91 368 91829383, email is +hilbertstewart@gmail.com, address is '68 Hudson Street', and is tagged as friend, you may execute the following +command. + +- `person new n/Hilbert Stewart p/+91 368 91829383 e/hilbertstewart@gmail.com a/68 Hudson Street t/friend` + +![Add Person Screenshot](images/user-guide-img/PersonNewScreenshot.png) + +### Delete a person: `person delete` + +Suppose that you want to remove a person from Contactmation. You can use the `person delete` command to delete a contact +from the list of persons in the current scope. + +**Format**: + +- [`person delete `](#constraints-on-placeholder-words) +- `person select person delete` [See select command](#select-command) + +The index refers to the index number shown in the displayed person list. The index should follow the format described +by the [constraints on placeholder words](#constraints-on-placeholder-words). + +**Example**: + +To delete person number 1 in the list of persons, you can execute: + +- `person delete 1` + +### Assign a user to a group: `assign` + +If you want to place an existing `person` into a `group`, you can use this command. + +
+**:information_source: Note:**
+ +This command only works if the `person` is not part of the `group` yet. + +
+ +**Format:** + +- `assign u/ g/` + +**Example:** + +If you want to add the first person in your [person list](#contactmation-window-guide) into the second group on +your [group list](#contactmation-window-guide), your command will be `assign u/1 g/2`. + +### Finding a person: `find` + +Contactmation allows for searching of person with the [find command](#find-command-find). + +--- + +## 4. Task commands + +After adding your **contacts**, and allocating them into **teams**, you can give them **tasks**! +This section will showcase the different commands that can be used on tasks. + +Task-related commands precede with the `task` keyword. + +
+**:information_source: Notes about tasks:**
+ +Tasks can only be added if you are within a team. To check whether you are in a team, look at your +[current team](#contactmation-window-guide) and ensure it is not the [root group](#glossary), which +is when the current team is (`/`). + +
+ +### Adding a task to a team: `task add` + +Adds a new task to an existing group scope. This group **cannot be the root group**. + +![Create Team Screenshot](images/user-guide-img/TaskAddScreenshot.png) + +**Format:** `task add t/ d/<DESCRIPTION>` + +**Example:** + +- `task add t/Complete all CS2103T homework d/Give description here` + +### Deleting a task from team: `task delete` + +Deletes an existing task from a group by their `INDEX` within the current [scope](#glossary). + +Format: + +- `task delete <INDEX>` +- `task select <INDEX> task delete` [See select command](#select-command) + +Example: + +- `task delete 1` + - This command deletes the first task in the task list. + +### Mark a task: `task mark` + +Marks a task as complete, with the current time as the completed time of the task. + +Format: + +- `task mark <INDEX>` +- `mark <INDEX>` (task here can be optional!) +- `task select <INDEX> mark` [See select command](#select-command) + +Example: + +- `task mark 1` + - This command marks the first task in the task list. +- An example of a marked task + +![An example of marked task](images/marked.png) + +### Unmark a task: `task unmark` + +Unmarks a task. The task will return to be `Incomplete`. + +Format: + +- `task unmark <INDEX>` +- `unmark <INDEX>` (task here can be optional!) +- `task select <INDEX> unmark` [See select command](#select-command) + +Example: + +- `task unmark 1` + + - This command unmarks the first task in the task list. + +- An example of a unmarked task + +![](images/unmarked.png) + +### Finding a task: `find` + +Contactmation allows for searching of tasks with the [find command](#find-command-find). + +## **Advanced features** + +Now, there might be many things that you wish to do with managing your tasks and groups. However, it feels really, +really tedious to perform multiple functions one after the other. +Are you a power user? Are you good with logic? Well this section is for you! Supercharge your user experience by +adding and customizing your own commands and features! + +Firstly, let’s understand what these commands are and how these commands work in Contactmation. + +### Advanced features overview: Chaining + +Most of the commands in Contactmation can take in an input and give an output. This is similar to how your functions +work in programming and mathematics. + +For instance, take the command `ops`. This command can take in a value, perform some operators on it and returns +the value. Another command is the command `float`. This command allows you to create a floating point value and return +it. + +Many commands in Contactmation have this functionality, and you can in turn **chain multiple commands together to +perform complicated tasks** that suits your needs. + +So, how do we chain multiple commands together? We can use the `|` and `;` and the `seq` command to do so. The way +this commands work is extremely similar to how `|` and `;` works on a UNIX operating system. You can chain multiple +commands together like such: + +- `seq <command 1> [| command 3]...` +- `seq <command 1> [; command 3]...` + +Whenever a pipe symbol (`|`) is encountered, the output of the previous commands is then passed to the next command. + +Whenever (`;`) is used, the output of the previous commands are not passed on. + +All commands that produce an output supports the use of `|` to “pipe” their output to the subsequent commands. + +#### Advanced feature constraints + +While these advanced features can make your Contactmation experience a lot smoother, it is also subject to certain +limitations. These are the following constraints for each keyword in the format section of each advanced feature +command: + +- The `MACRO WORD` is alphanumeric but hyphens and underscores are allowed. It must also begin with a letter. +- `INPUT` is a string of any length. + +Here are some commands that will aid you in gaining better control over Contactmation: + +### Select command + +This command allows you to select a specific group, contact or task by their `INDEX`. While this command does nothing +by itself, it is useful as a precursor to chaining other commands after it. + +A list of such commands includes: + +- `<ITEM> delete` commands like [`task delete`](#deleting-a-task-from-team-task-delete), [`team delete`](#delete-a-team-team-delete), [`person delete`](#delete-a-person-person-delete) +- [`task mark`](#mark-a-task-task-mark) and [`task unmark`](#unmark-a-task-task-unmark) +- [`cg`](#navigate-to-a-team-cg) command +- [`rename`](#rename-the-name-of-items-on-the-screen) command +- field related commands + - [`field edit`](#edit-a-field-field-edit) command + - [`field delete`](#delete-a-field-field-delete) command + - [`field add`](#add-a-new-field-field-add) command + +**Format:** `<ITEM> select <INDEX> <COMMAND> [...]` + +**Example:** + +- `task select 3 mark` + +![Select command ui](images/user-guide-img/SelectCommandUI.png) + +### Contains command + +You can use the `contains` command which takes in an item and checks if it contains a certain attribute. If it does, +then the attribute description will be shown in the result display if there is no further piping. + +**Format:** `<ITEM> contains <ATTRIBUTE>` + +**Example:** + +- `task select 1 contains bug` + +![Contains command ui](images/user-guide-img/ContainsCommandUI.png) + +Here, we see that there are no `bug` attribute in the task `New Burger Recipes`. + +### Execute command + +This command allows for the running of a `command` on a piped string. + +**Format:** `<INPUT> | e` + +**Example:** + +- `Who lives in a pineapple under the sea | e` + +### Replace command + +This command replaces a piece of text with another piece of text. + +**Format:** `r <TEXT TO REPLACE> <TEXT TO BE REPLACED>` + +**Example:** + +- `r tetss te%ssts` + +### Foreach command + +Iterations can increase our workflow efficiency several fold, and through the `foreach` command, we can now cycle +through all entries of an item type in the current scope and apply a command to them. This can be especially powerful +when combined with piping to do complex executions with a single command! + +**Format:** `<ITEM> foreach <COMMAND>` + +**Example:** + +- `task foreach mark` + +![Foreach command ui](images/user-guide-img/ForEachCommandUI.png) + +### If / else command + +This command behaves exactly like if else statements in programming languages. If the `CRITERIA` specified is met, +then the command sequence will execute `COMMAND IF`, else it will execute `COMMAND ELSE` instead. The command +ensures that the application cannot run `COMMAND IF` and `COMMAND ELSE` in the same command sequence. + +> **Note:** If else commands cannot be nested in other if else commands directly. + +**Format:** `if [[CRITERIA]] ;; [[COMMAND IF]] ;; [[COMMAND ELSE]]` + +**Example:** + +- `task select 1 if [[contains bug]] ;; [[mark]] ;; [[task delete]]` + +### Aliasing + +Aliasing is very useful to have in case you do not agree with the default naming scheme in Contactmation! Here’s +how it works: + +**Format:** `alias <NEW COMMAND NAME> <COMMAND>` + +**Example:** + +- `alias group team` + +If you feel that you are more comfortable using the keyword `group` to represent `team`. After running `alias group team`, you are now able to use the command `group` as if it was a `team`! + +![Aliasing command ui](images/user-guide-img/AliasingCommandUI.png) + +### Custom command / Macro + +Do you ever feel tired from typing the same commands over and over again? Do you find yourself highlighting your +commands and copying and pasting them? Macros are available in our application to solve this problem of yours. + +All you have to do is assign the command sequence to a single word, or multiple words separated by hyphens and +underscores only. After that, when the word is typed into the command box, the command sequence it is used to +represent will run! + +**Format:** `macro <MACRO WORD> <COMMAND SEQUENCE>` + +**Example:** + +- `macro markeverytask task foreach mark` + +This will produce the following output: + +![Custom command ui 1](images/CustomCommandUi1) + +When `markeverytask` is typed into the command box, all tasks become marked! + +![Custom command ui 2](images/user-guide-img/MarkEveryTaskDone.png) + +### Piecing multiple commands together + +Here’s another example. You have just completed fixed a bunch of bugs you would like to mark off all tasks that +was bugged as complete. + +![Multiple commands ui](images/MultipleCommandsUi) + +Well, you know that you defined your custom field’s type as `bug` and you can see that task 1, 2 and 3 are bug +related tasks with the `Severity` labelled as a custom field in the bugs. + +Well, you could of course just do `mark` commands 3 times and mark all the tasks, but what if there are a few +hundred of those pesky bug tasks that you and your team fixed? + +Luckily for you, Contactmation supports the automation of commands! + +Here is an example of a command sequence to search through all tasks and mark all tasks which have bug severity +ratings: + +`task foreach if [[contains bug]] ;; [[mark]]` + +Using just 1 command sequence, you are able to do the work that many normal commands would similarly achieve and +mark hundreds of tasks in a matter of seconds! + +## **FAQ** + +> How do I check whether `Java 11` is installed on my computer? + +This depends on the type of computer you are using. + +#### If you are using a Windows device: + +1. Click the search icon in the task bar on your desktop. + +![Desktop taskbar](images/Desktop-taskbar.png) + +2. Search for `Command Prompt` and open the application. +3. Type in `java -version` in the `Command Prompt` application. You should see something similar to this: + +![Windows desktop terminal](images/WindowsCommandPromptCheckJavaVersion.png) + +4. When you press the `Enter` key on your computer, you should be greeted with something similar to this: + +![Windows desktop terminal executed](images/WindowsCommandPromptCheckJavaVersionExecute.png) + +5. The `Java` version in this example is listed as `java version 11.0.10`. + +#### If you are using a Macbook: + +1. Open the terminal by clicking on `Launchpad` and searching for `terminal` in the search bar. + +![Macbook taskbar](images/MacbookDesktopTaskbar.png) + +2. Follow steps 3 onwards from [here](#if-you-are-using-a-windows-device). + +#### If you are using a Linux machine: + +Due to the wide variety of Linux distributions out there, you will need to search online +on how you can check for `Java 11` for your respective distribution. + +Generally, the process in checking for your `Java` version on your Linux distribution is to: + +1. Open the terminal. + +2. Type `java -version` and see if there is an output. If there is no output, then `Java` is not installed. If there is an output, then check if the version for `Java` is correct. Refer to part 3-5 of the Windows section of this question for more information. + +Here are some helpful [guides](https://phoenixnap.com/kb/check-java-version-linux) +for popular Linux distributions such as Ubuntu. + +> How can I install `Java 11`? + +Follow the guide for installing `Java 11` [here](https://www.javatpoint.com/javafx-how-to-install-java). + +> Why does my group name start with a `/`? + +You might encounter this when adding a group to Contactmation: + +![Group in Contactmation](images/FaqGroupSlash.png) + +This is because the `Marketing` group is under the [root group](#glossary), which is named by +default with a slash (`/`). Therefore, when a group name is `/Marketing`, it means that the `Marketing` +group is a subgroup of the root group. + +> How do I save my preferred window size when viewing Contactmation? + +You can do this by exiting using the [exit](#exits-the-program-exit) command. + +> How do I check whether I have access rights to Contactmation on my computer? + +Contactmation must be stored in a location that does not have administrative rights to function properly, +such as `C:/` program files. + +You can simply save Contactmation into your `D:/` drive instead of your `C:/` drive, or store Contactmation +on your Desktop. + +To check the location of Contactmation, do the following steps: + +1. Right-click on Contactmation. + +2. Click on `Properties`. + +3. You should see the following pop-up: + +![address book pop up](images/AddressBookUgLocation.png) + +4. Here, we can see that under `Location`, we have `C:/` and `Desktop`. This means + that the location Contactmation does not need administrative rights to access. Therefore, + there should be no problem in starting and running Contactmation. + +> Why is there an error in my result display stating: `Unable to save your information!`? + +This is due to an error in saving your Contactmation details in a separate file. +To combat this, use the following steps: + +1. Check if you need administrative rights to access the home folder where Contactmation is stored. + +2. Your home folder should look something like this at first. + +![Save file folder](images/FaqSaveFileError.png) + +3. There are several files of note, which consists of the `data` folder, `config` file and `preferences` + file. If the `data` folder is not present, then the following steps can be ignored for the + `data` folder. If the `data` folder is present, then you should also find an `addressbook` file + within the `data` folder. The following steps are for `config`, `preferences` and the `addressbook` file. + +4. Right-click on the file, and select `Properties`. You will be greeted a pop-up similar to this: + +![Save file property pop up](images/FaqSaveFileError2.png) + +5. Under `Attributes` in the `General` section, ensure that `Read-only` is **not ticked**. + +6. Click `Apply` and `OK` to save your changes. + +7. Repeat steps 4 to 6 with the `config`, `preferences` and the `addressbook` file. + +> Will this application also apply to a general, non-professional user? + +This depends on what you will be using Contactmation for. It still can be used simply as an application +for simply saving and organizing contacts. + +## **Future plans** + +Our future plans for Contactmation includes: + +- The ability to delegate tasks to individuals. +- Contacting any person through the application simply by clicking their email, phone number + etc. +- Releasing a version of Contactmation on the mobile platform. +- The ability to synchronize data between multiple copies of Contactmation on your mobile and desktop. +- A pop-up window that shows the detailed form of descriptions to the user. +- A for loop command to iterate through and count groups, contacts or tasks by their attribute. + +## **Glossary** + +| Vocabulary | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| CLI / Command Line Interface | You can only interact with the application through text, which is typed in the [command box](#contactmation-window-guide). | +| Command / Command sequence | What you would write in the command box to interact with the application. | +| Contact | A contact with contact information. | +| Alphanumeric | The text can contain capitalised and non-capitalised alphabets and numbers only. | +| Field | A way to represent additional information for a group, person or task. | +| Team | A container that contains people that work on a similar project. | +| Index | The numerical placing of a group, contact or task in the current application display. | +| Item | An item can refer to a group, contact or task. | +| Pipe | The output of the previous section of commands will be used as input for the next set of commands. | +| Root group | Refers to the application being able to view all groups in the display. | +| Task | Assigned to people or groups. | +| Word | Text in a command sequence that is separated from other words by a white space. | -_Details coming soon ..._ +## **Command summary** --------------------------------------------------------------------------------------------------------------------- +### General commands summary -## FAQ +| Command | Format | +| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Clear all [items](#glossary) from Contactmation | `clear` | +| Exit Contactmation | `exit` | +| Add additional information to an item | `field add u/<INDEX> <FIELD NAME> <DESCRIPTION>` or `field add g/<INDEX> <FIELD NAME> <DESCRIPTION>` or `field add t/<INDEX> <FIELD NAME> <DESCRIPTION>` | +| Edit some additional information in an item | `field edit u/<INDEX> <FIELD NAME> <NEW DESCRIPTION>` or `field edit g/<INDEX> <FIELD NAME> <NEW DESCRIPTION>` or `field edit t/<INDEX> <FIELD NAME> <NEW DESCRIPTION>` | +| Delete some additional information in an item | `field delete u/<INDEX> <FIELD NAME>` or `field delete g/<INDEX> <FIELD NAME>` or `field delete t/<INDEX> <FIELD NAME>` | +| Rename the name of items on the screen | `rename u/<INDEX> <new name>` or `rename g/<INDEX> <new name>` or `rename t/<INDEX> <new name>` or `<ITEM> select <INDEX> rename <new name>` | +| Find a person, group or task | `<ITEM> find <KEYWORD> [<MORE_KEYWORDS>]` | -**Q**: How do I transfer my data to another Computer?<br> -**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. +### Group commands summary --------------------------------------------------------------------------------------------------------------------- +| Command | Format | +| ---------------------------------- | ---------------------------------------------------------- | +| Adding new team | `team new <NAME>` | +| Delete team | `team delete <INDEX>` or `team select <INDEX> team delete` | +| Changing teams | `cg <INDEX>` or `cg ..` or `cg /` | +| Removing contact from current team | `team remove <Contact INDEX>` | +| Finding a team | `team find <KEYWORD> [<MORE_KEYWORDS>]` | -## Command summary +### Person commands summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` <br> e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`<br> e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`<br> e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`<br> e.g., `find James Jake` -**List** | `list` -**Help** | `help` +| Command | Format | +| ------------------------------------- | ------------------------------------------------------------------------------- | +| Adding new person to current context | `person new n/<NAME> [p/<PHONE_NUMBER>] [e/<EMAIL>] [a/<ADDRESS>] [t/<TAG>...]` | +| Delete a person | `person delete <INDEX>` or `person select <INDEX> person delete` | +| Assigning a user to an existing group | `assign u/<INDEX> g/<INDEX>` | +| List all contacts in a team | `list` | +| Finding/filtering persons | `person find <keywords>` | + +### Task commands summary + +| Command | Format | +| ---------------------------------- | ------------------------------------------------------------------------ | +| Adding new task to current context | `task new t/<title> d/<description>` | +| Delete a task | `task delete <INDEX>` or `task select <INDEX> task delete` | +| Marking a task as complete | `task mark <INDEX>` or `mark <INDEX>` or task select <INDEX> mark` | +| Marking a task as incomplete | `task unmark <INDEX>` or `unmark <INDEX>` or task select <INDEX> unmark` | +| Finding/filtering tasks | `task find <keywords>` | + +### Advanced commands summary + +| Command | Format | +| ------------------------ | ------------------------------------------------------- | -------------------------------------------------- | +| Aliasing | `alias <NEW COMMAND NAME> <COMMAND>` | +| Saving macros | `macro <NEW COMMAND NAME> <COMMANDS TO CHAIN>` | +| Deleting Custom Commands | `rmMacro <COMMAND NAME>` | +| Chaining/seq | `seq <command 1> [ | command 3]... OR seq <command 1> [; command 3]...` | +| Contains | `<ITEM> contains <ATTRIBUTE>` | +| Execute | `<INPUT> | e` | +| Foreach | `<ITEM> foreach <COMMAND>` | +| If else | `if [[CRITERIA]] ;; [[COMMAND IF]] ;; [[COMMAND ELSE]]` | +| Macro | `macro <MACRO WORD> <COMMAND SEQUENCE>` | +| Replace | `r <TEXT TO REPLACE> <TEXT TO BE REPLACED>` | +| Select | `<ITEM> select <INDEX> <COMMAND> [...]` | +| Create/convert int | `int <integer>` | +| Create/convert float | `float <float>` | +| Create/convert String | `str <String>` | +| Print | `<...> | print` | + +[Back to top](#table-of-contents) diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..b4587cdb10b 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Contactmation" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103T-T11-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/diagrams/AddFieldSequenceDiagram.puml b/docs/diagrams/AddFieldSequenceDiagram.puml new file mode 100644 index 00000000000..318e2d9a891 --- /dev/null +++ b/docs/diagrams/AddFieldSequenceDiagram.puml @@ -0,0 +1,62 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddFieldCommandParser" as AddFieldCommandParser LOGIC_COLOR +participant ":AddFieldCommand" as AddFieldCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":DisplayItem" as DisplayItem MODEL_COLOR +end box + +[-> LogicManager : execute() +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand() +activate AddressBookParser + +create AddFieldCommandParser +AddressBookParser -> AddFieldCommandParser +activate AddFieldCommandParser + +create AddFieldCommand +AddFieldCommandParser -> AddFieldCommand +activate AddFieldCommand + +AddFieldCommand -> DisplayItem : selectFromRightModel() +activate DisplayItem + +DisplayItem --> AddFieldCommand +deactivate DisplayItem + +AddFieldCommand -> DisplayItem : addAttribute() +activate DisplayItem + +DisplayItem --> AddFieldCommand +deactivate DisplayItem + +create CommandResult +AddFieldCommand -> CommandResult +activate CommandResult + +CommandResult --> AddFieldCommand +deactivate CommandResult + +AddFieldCommand --> AddFieldCommandParser +deactivate AddFieldCommand + +AddFieldCommandParser --> AddressBookParser +deactivate AddFieldCommandParser + +AddressBookParser --> LogicManager +deactivate AddressBookParser + +[<-- LogicManager +deactivate LogicManager + + +@enduml diff --git a/docs/diagrams/AddFieldSequenceDiagramModel.puml b/docs/diagrams/AddFieldSequenceDiagramModel.puml new file mode 100644 index 00000000000..bf53300a3de --- /dev/null +++ b/docs/diagrams/AddFieldSequenceDiagramModel.puml @@ -0,0 +1,43 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "persons:UniquePersonList" as UniquePersonList MODEL_COLOR +participant "person:Person" as Person MODEL_COLOR +participant "fields:Fields" as Fields MODEL_COLOR +participant ":Field" as Field MODEL_COLOR +end box + +activate Model + +Model -> UniquePersonList : addField("github") +activate UniquePersonList + +loop until each Person in persons has been iterated + UniquePersonList -> Person : addField("github") + activate Person + + Person -> Fields : addField("github") + activate Fields + + create Field + Fields -> Field : Field("github") + activate Field + + Field --> Fields + deactivate Field + + Fields --> Person + deactivate Fields + + Person --> UniquePersonList + deactivate Person +end + +UniquePersonList --> Model +deactivate UniquePersonList + +deactivate Model +@enduml diff --git a/docs/diagrams/ChangeTeamSequenceDiagram.puml b/docs/diagrams/ChangeTeamSequenceDiagram.puml new file mode 100644 index 00000000000..cbf71c49476 --- /dev/null +++ b/docs/diagrams/ChangeTeamSequenceDiagram.puml @@ -0,0 +1,54 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant "c:ChangeTeamCommand" as ChangeTeamCommand 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("cg ..") +activate LogicManager + +LogicManager -> ChangeTeamCommand : execute() + +activate ChangeTeamCommand + +alt Go back to previous context + + ChangeTeamCommand -> Model : getContextContainerParent() + activate Model + Model --> ChangeTeamCommand : toSwitch + deactivate Model + +else else + + ChangeTeamCommand -> Model : getTeamAtIndex() + activate Model + Model --> ChangeTeamCommand : toSwitch + deactivate Model + +end + +ChangeTeamCommand -> Model : updateContextContainer(toSwitch) +activate Model +Model --> ChangeTeamCommand +deactivate Model + +create CommandResult +ChangeTeamCommand -> CommandResult +activate CommandResult + +CommandResult --> ChangeTeamCommand +deactivate CommandResult + +ChangeTeamCommand --> LogicManager : result +deactivate ChangeTeamCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..3f01bdd0394 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -4,7 +4,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 ":TaskCommandParser" as TaskCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -13,34 +13,28 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("task delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("task delete 1") activate AddressBookParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create TaskCommandParser +AddressBookParser -> TaskCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +AddressBookParser -> TaskCommandParser: parse("delete 1") +activate TaskCommandParser create DeleteCommand -DeleteCommandParser -> DeleteCommand +TaskCommandParser -> DeleteCommand activate DeleteCommand -DeleteCommand --> DeleteCommandParser : d +DeleteCommand --> TaskCommandParser deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser -'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +TaskCommandParser --> AddressBookParser +deactivate TaskCommandParser AddressBookParser --> LogicManager : d deactivate AddressBookParser @@ -48,7 +42,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deleteTask(TaskAtIndexOne) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/GenerateCardSequenceDiagram.puml b/docs/diagrams/GenerateCardSequenceDiagram.puml new file mode 100644 index 00000000000..a000b950749 --- /dev/null +++ b/docs/diagrams/GenerateCardSequenceDiagram.puml @@ -0,0 +1,63 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":PersonListViewCell" as PersonListViewCell UI_COLOR +participant ":PersonCard" as PersonCard UI_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Person" as Person MODEL_COLOR +participant "attribute:Attribute" as Attribute MODEL_COLOR +end box + +[-> PersonListViewCell : updateItem(person, empty) +activate PersonListViewCell + +opt person != null && empty == False + + create PersonCard + PersonListViewCell -> PersonCard: PersonCard(person, index) + activate PersonCard + + PersonCard -> Person: getName() + activate Person + + Person --> PersonCard: name + deactivate Person + + + PersonCard -> Person: getAttributes() + activate Person + Person --> PersonCard: List of Attributes + deactivate Person + + loop for all attribute in Attributes + PersonCard -> Attribute: canBeDisplayedInList() + activate Attribute + Attribute --> PersonCard: result + deactivate Attribute + opt result == True + PersonCard -> Attribute: asUi() + activate Attribute + Attribute --> PersonCard: uiElement + deactivate Attribute + + PersonCard -> PersonCard: addChild(uiElement) + activate PersonCard + + PersonCard --> PersonCard + deactivate PersonCard + end + + end + + PersonCard --> PersonListViewCell + deactivate PersonCard + +end + +[<--PersonListViewCell +deactivate PersonListViewCell + +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..2a5e4f0527a 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -31,6 +31,9 @@ LogicManager -right->"1" AddressBookParser AddressBookParser ..> XYZCommand : creates > XYZCommand -up-|> Command + +XYZCommand --> "0..*" XYZCommand + LogicManager .left.> Command : executes > LogicManager --> Model diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..cd78128ade5 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,46 +5,86 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <<Rectangle>>{ -Class "<<interface>>\nReadOnlyAddressBook" as ReadOnlyAddressBook -Class "<<interface>>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs -Class "<<interface>>\nModel" as Model -Class AddressBook -Class ModelManager -Class UserPrefs - -Class UniquePersonList -Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag + Class "<<interface>>\nReadOnlyAddressBook" as ReadOnlyAddressBook + Class "<<interface>>\nDisplayItem" as DisplayItem + Class "<<interface>>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs + Class "<<interface>>\nAttribute" as Attribute + Class "<<interface>>\nModel" as Model + Class "{{abstract}}\nDisplayItemList" as DisplayItemList<? extend DisplayItem> + Class "{{abstract}}\nAbstractSingleItem" as AbstractSingleItem + Class "{{abstract}}\nAbstractDisplayItem" as AbstractDisplayItem + Class "{{abstract}}\nAbstractAttribute" as AbstractAttribute + + Class AddressBook + Class ModelManager + Class UserPrefs + + Class UniquePersonList + Class UniqueTaskList + Class UniqueGroupList + Class AttributeList + + Class Person + Class Task + Class Group + Class Name + Class "... other attributes" as attr + Class Description } Class HiddenOutside #FFFFFF HiddenOutside ..> Model +AttributeList --> "0..*" Attribute +AbstractDisplayItem --> "1" AttributeList + AddressBook .up.|> ReadOnlyAddressBook +DisplayItem ..> Attribute + +Task --|> AbstractDisplayItem +Person --|> AbstractDisplayItem +Group --|> AbstractSingleItem + +AbstractDisplayItem ..|> DisplayItem +AbstractDisplayItem --> "0..*" Attribute +AbstractDisplayItem --> "0..1" DisplayItem +AbstractSingleItem --|> AbstractDisplayItem + +AbstractAttribute .left.|> Attribute + +Name --|> AbstractAttribute +attr --|> AbstractAttribute +Description --|> AbstractAttribute + +AbstractDisplayItem *--> "1" Name +Task *--> "1" Description + +DisplayItemList *--> "*" DisplayItem + +UniqueGroupList --|> DisplayItemList +UniqueTaskList --|> DisplayItemList +UniquePersonList --|> DisplayItemList + +DisplayItemList -[hidden]right-> Person + ModelManager .up.|> Model -Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook -ModelManager -right-> "1" UserPrefs +ModelManager -right-> "1" AddressBook +Model ..> ReadOnlyUserPrefs +Model ..> ReadOnlyAddressBook +ModelManager -left-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +AddressBook *--> "1" UniqueTaskList +AddressBook *--> "1" UniqueGroupList -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +UniqueTaskList --> "~* all" Task +UniqueGroupList --> "~* all" Group +UniquePersonList --> "~* all" Person -ModelManager -->"~* filtered" Person +ModelManager --->"~* filtered" Person +ModelManager --->"~* filtered" Task +ModelManager --->"~* filtered" Group @enduml diff --git a/docs/diagrams/PersonRetrievalSequenceDiagram.puml b/docs/diagrams/PersonRetrievalSequenceDiagram.puml new file mode 100644 index 00000000000..b44777b4df4 --- /dev/null +++ b/docs/diagrams/PersonRetrievalSequenceDiagram.puml @@ -0,0 +1,28 @@ +@startuml +!include style.puml + +box Storage LOGIC_COLOR_T1 +participant ":JsonAddressBookStorage" as JsonAddressBookStorage LOGIC_COLOR +participant ":JsonSerializableAddressBook" as JsonSerializableAddressBook LOGIC_COLOR +participant ":JsonAdaptedPerson" as JsonAdaptedPerson LOGIC_COLOR +end box + +[-> JsonAddressBookStorage : readAddressBook() +activate JsonAddressBookStorage + +JsonAddressBookStorage -> JsonSerializableAddressBook : toModelType() +activate JsonSerializableAddressBook + +loop until all JsonAdaptedPerson has been iterated through + JsonSerializableAddressBook -> JsonAdaptedPerson : toModelType() + activate JsonAdaptedPerson + JsonAdaptedPerson --> JsonSerializableAddressBook : Person + deactivate JsonAdaptedPerson +end + +JsonSerializableAddressBook --> JsonAddressBookStorage : AddressBook +deactivate JsonSerializableAddressBook + +[<--JsonAddressBookStorage +deactivate JsonAddressBookStorage +@enduml diff --git a/docs/diagrams/PipeSequenceDiagram.puml b/docs/diagrams/PipeSequenceDiagram.puml new file mode 100644 index 00000000000..f21361e959e --- /dev/null +++ b/docs/diagrams/PipeSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":xxxCommand" as xxxCommand LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TaskCommandParser" as TaskCommandParser 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 + +activate xxxCommand +xxxCommand -> AddressBookParser : parseCommand("task delete") +activate AddressBookParser + +create TaskCommandParser +AddressBookParser -> TaskCommandParser + + +AddressBookParser -> TaskCommandParser: parse("delete") +activate TaskCommandParser + +create DeleteCommand +TaskCommandParser -> DeleteCommand +activate DeleteCommand + +DeleteCommand --> TaskCommandParser +deactivate DeleteCommand + +TaskCommandParser --> AddressBookParser +deactivate TaskCommandParser + +AddressBookParser --> xxxCommand : d +deactivate AddressBookParser + + +xxxCommand -> DeleteCommand : setInput(TaskToDelete) +activate DeleteCommand + +DeleteCommand --> xxxCommand +deactivate DeleteCommand + + +xxxCommand -> DeleteCommand : execute() +activate DeleteCommand + +DeleteCommand -> Model : deleteTask(TaskToDelete) +activate Model + +Model --> DeleteCommand +deactivate Model + +create CommandResult +DeleteCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteCommand +deactivate CommandResult + +DeleteCommand --> xxxCommand : result +deactivate DeleteCommand + +[<--xxxCommand +deactivate xxxCommand +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..26c8c828a56 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,8 +18,6 @@ package "AddressBook Storage" #F4F6F6{ Class "<<interface>>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag } } @@ -37,7 +35,5 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/StorageJsonSerializableAddressBookClassDiagram.puml b/docs/diagrams/StorageJsonSerializableAddressBookClassDiagram.puml new file mode 100644 index 00000000000..7f406ca7ce4 --- /dev/null +++ b/docs/diagrams/StorageJsonSerializableAddressBookClassDiagram.puml @@ -0,0 +1,33 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor STORAGE_COLOR +skinparam classBackgroundColor STORAGE_COLOR + +package "JsonSerializableAddressBook Storage" #F4F6F6{ +Class JsonSerializableAddressBook +Class JsonAdaptedAddressBookParser +Class JsonAdaptedCustomCommandBuilder +abstract Class JsonAdaptedDisplayItem +Class JsonAdaptedAbstractAttribute +Class JsonAdaptedPerson +Class JsonAdaptedTask +Class JsonAdaptedGroup +Class JsonAdaptedTag +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> JsonSerializableAddressBook + +JsonSerializableAddressBook --> "*" JsonAdaptedPerson +JsonSerializableAddressBook --> "*" JsonAdaptedTask +JsonSerializableAddressBook --> "*" JsonAdaptedGroup +JsonSerializableAddressBook --> "1" JsonAdaptedAddressBookParser +JsonAdaptedPerson -up-|> JsonAdaptedDisplayItem +JsonAdaptedTask -up-|> JsonAdaptedDisplayItem +JsonAdaptedGroup -up-|> JsonAdaptedDisplayItem +JsonAdaptedAddressBookParser --> "*" JsonAdaptedCustomCommandBuilder +JsonAdaptedDisplayItem --> "*" JsonAdaptedAbstractAttribute +JsonAdaptedDisplayItem --> "*" JsonAdaptedTag + +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..7b69198c6ff 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -9,12 +9,19 @@ Class "<<interface>>\nUi" as Ui Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow +Class DetailPanel Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard Class StatusBarFooter Class CommandBox + +Class PersonListPanel +Class GroupListPanel +Class TaskListPanel + +Class PersonCard +Class GroupCard +Class TaskCard } package Model <<Rectangle>> { @@ -33,21 +40,32 @@ UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" TaskListPanel +MainWindow *-down-> "1" GroupListPanel MainWindow *-down-> "1" StatusBarFooter +MainWindow *-down-> "1" DetailPanel MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +TaskListPanel -down-> "*" TaskCard +GroupListPanel -down-> "*" GroupCard MainWindow -left-|> UiPart +TaskListPanel --|> UiPart +GroupListPanel --|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +DetailPanel --|> UiPart PersonCard ..> Model +TaskCard ..> Model +GroupCard ..> Model +DetailPanel ..> Model UiManager -right-> Logic MainWindow -left-> Logic 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 <<rectangle>> { 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 <<rectangle>> { 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 <<rectangle>> { 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 <<rectangle>> { 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/diagrams/UpdateDetailPanel.puml b/docs/diagrams/UpdateDetailPanel.puml new file mode 100644 index 00000000000..cd7033bdf47 --- /dev/null +++ b/docs/diagrams/UpdateDetailPanel.puml @@ -0,0 +1,49 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":DetailPanel" as DetailPanel UI_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "displayItem:DisplayItem" as DisplayItem MODEL_COLOR +participant "attribute:Attribute" as Attribute MODEL_COLOR +end box + +[-> DetailPanel : updateContent(displayItem) +activate DetailPanel + +DetailPanel -> DisplayItem: getName() +activate DisplayItem + +DisplayItem --> DetailPanel: name +deactivate DisplayItem + +DetailPanel -> DisplayItem: getAttributes() +activate DisplayItem +DisplayItem --> DetailPanel: List of Attributes +deactivate DisplayItem + +loop for all attribute in Attributes + DetailPanel -> Attribute: canBeDisplayed() + activate Attribute + Attribute --> DetailPanel: result + deactivate Attribute + opt result == True + DetailPanel -> Attribute: asUi() + activate Attribute + Attribute --> DetailPanel: uiElement + deactivate Attribute + + DetailPanel -> DetailPanel: addChild(uiElement) + activate DetailPanel + + DetailPanel --> DetailPanel + deactivate DetailPanel + end +end + +[<--DetailPanel +deactivate DetailPanel + +@enduml diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml index fad8b0adeaa..a5e65717101 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/style.puml @@ -41,10 +41,11 @@ skinparam Class { FontColor #FFFFFF BorderThickness 1 BorderColor #FFFFFF - StereotypeFontColor #FFFFFF + StereotypeFontColor #000000 FontName Arial } + skinparam Actor { BorderColor USER_COLOR Color USER_COLOR diff --git a/docs/images/AddFieldSequenceDiagram.png b/docs/images/AddFieldSequenceDiagram.png new file mode 100644 index 00000000000..e9cf74fc557 Binary files /dev/null and b/docs/images/AddFieldSequenceDiagram.png differ diff --git a/docs/images/AddFieldSequenceDiagram1.png b/docs/images/AddFieldSequenceDiagram1.png new file mode 100644 index 00000000000..bf67c7deb13 Binary files /dev/null and b/docs/images/AddFieldSequenceDiagram1.png differ diff --git a/docs/images/AddFieldUserGuideExample.png b/docs/images/AddFieldUserGuideExample.png new file mode 100644 index 00000000000..e71dcf07560 Binary files /dev/null and b/docs/images/AddFieldUserGuideExample.png differ diff --git a/docs/images/AddGroupCommandDisplayExample.png b/docs/images/AddGroupCommandDisplayExample.png new file mode 100644 index 00000000000..e371f6b5023 Binary files /dev/null and b/docs/images/AddGroupCommandDisplayExample.png differ diff --git a/docs/images/AddressBookUgLocation.png b/docs/images/AddressBookUgLocation.png new file mode 100644 index 00000000000..be64095853c Binary files /dev/null and b/docs/images/AddressBookUgLocation.png differ diff --git a/docs/images/ChangeGroupCommandDisplayExample.png b/docs/images/ChangeGroupCommandDisplayExample.png new file mode 100644 index 00000000000..c1d7605c5a3 Binary files /dev/null and b/docs/images/ChangeGroupCommandDisplayExample.png differ diff --git a/docs/images/ChangeGroupExample.png b/docs/images/ChangeGroupExample.png new file mode 100644 index 00000000000..28af966e02f Binary files /dev/null and b/docs/images/ChangeGroupExample.png differ diff --git a/docs/images/ChangeGroupExample2.png b/docs/images/ChangeGroupExample2.png new file mode 100644 index 00000000000..d8c80daafff Binary files /dev/null and b/docs/images/ChangeGroupExample2.png differ diff --git a/docs/images/ChangeGroupExample3.png b/docs/images/ChangeGroupExample3.png new file mode 100644 index 00000000000..3f27a4bd13f Binary files /dev/null and b/docs/images/ChangeGroupExample3.png differ diff --git a/docs/images/ChangeTeamSequenceDiagram.png b/docs/images/ChangeTeamSequenceDiagram.png new file mode 100644 index 00000000000..eb44552f8b6 Binary files /dev/null and b/docs/images/ChangeTeamSequenceDiagram.png differ diff --git a/docs/images/ContactmationUi.png b/docs/images/ContactmationUi.png new file mode 100644 index 00000000000..a7b5dd9f7da Binary files /dev/null and b/docs/images/ContactmationUi.png differ diff --git a/docs/images/ContactmationUiClean.png b/docs/images/ContactmationUiClean.png new file mode 100644 index 00000000000..36493154027 Binary files /dev/null and b/docs/images/ContactmationUiClean.png differ diff --git a/docs/images/CustomCommandUi1 b/docs/images/CustomCommandUi1 new file mode 100644 index 00000000000..88e0b435a3d Binary files /dev/null and b/docs/images/CustomCommandUi1 differ diff --git a/docs/images/CustomCommandUi2 b/docs/images/CustomCommandUi2 new file mode 100644 index 00000000000..46409a3019f Binary files /dev/null and b/docs/images/CustomCommandUi2 differ diff --git a/docs/images/DeleteFieldUgExample.png b/docs/images/DeleteFieldUgExample.png new file mode 100644 index 00000000000..e434dd0f8bf Binary files /dev/null and b/docs/images/DeleteFieldUgExample.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..fae90d075f7 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/Desktop-taskbar.png b/docs/images/Desktop-taskbar.png new file mode 100644 index 00000000000..5b4a3f5dbdf Binary files /dev/null and b/docs/images/Desktop-taskbar.png differ diff --git a/docs/images/EditFieldDefault.png b/docs/images/EditFieldDefault.png new file mode 100644 index 00000000000..5e0382f6dbd Binary files /dev/null and b/docs/images/EditFieldDefault.png differ diff --git a/docs/images/FaqGroupSlash.png b/docs/images/FaqGroupSlash.png new file mode 100644 index 00000000000..54f1a4433a4 Binary files /dev/null and b/docs/images/FaqGroupSlash.png differ diff --git a/docs/images/FaqSaveFileError.png b/docs/images/FaqSaveFileError.png new file mode 100644 index 00000000000..e8cf068bdae Binary files /dev/null and b/docs/images/FaqSaveFileError.png differ diff --git a/docs/images/FaqSaveFileError2.png b/docs/images/FaqSaveFileError2.png new file mode 100644 index 00000000000..4311616a078 Binary files /dev/null and b/docs/images/FaqSaveFileError2.png differ diff --git a/docs/images/FieldAddDefault.png b/docs/images/FieldAddDefault.png new file mode 100644 index 00000000000..df1773606f7 Binary files /dev/null and b/docs/images/FieldAddDefault.png differ diff --git a/docs/images/FindPerson.png b/docs/images/FindPerson.png new file mode 100644 index 00000000000..2330c8a17be Binary files /dev/null and b/docs/images/FindPerson.png differ diff --git a/docs/images/GenerateCardSequenceDiagram.png b/docs/images/GenerateCardSequenceDiagram.png new file mode 100644 index 00000000000..36807ffcbe6 Binary files /dev/null and b/docs/images/GenerateCardSequenceDiagram.png differ diff --git a/docs/images/Group_within_group_illustration.png b/docs/images/Group_within_group_illustration.png new file mode 100644 index 00000000000..4309abd34a2 Binary files /dev/null and b/docs/images/Group_within_group_illustration.png differ diff --git a/docs/images/Group_within_group_illustration_contactmation.png b/docs/images/Group_within_group_illustration_contactmation.png new file mode 100644 index 00000000000..6d945a571ff Binary files /dev/null and b/docs/images/Group_within_group_illustration_contactmation.png differ diff --git a/docs/images/ListCommand.png b/docs/images/ListCommand.png new file mode 100644 index 00000000000..df1fb958d34 Binary files /dev/null and b/docs/images/ListCommand.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..7e74fc47ab7 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/MacbookDesktopTaskbar.png b/docs/images/MacbookDesktopTaskbar.png new file mode 100644 index 00000000000..8c2d6792949 Binary files /dev/null and b/docs/images/MacbookDesktopTaskbar.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..94f40d1254e 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/MultipleCommandsUi b/docs/images/MultipleCommandsUi new file mode 100644 index 00000000000..5665fca617c Binary files /dev/null and b/docs/images/MultipleCommandsUi differ diff --git a/docs/images/PersonRetrievalSequenceDiagram.png b/docs/images/PersonRetrievalSequenceDiagram.png new file mode 100644 index 00000000000..9fa31bcc610 Binary files /dev/null and b/docs/images/PersonRetrievalSequenceDiagram.png differ diff --git a/docs/images/PipeSequenceDiagram.png b/docs/images/PipeSequenceDiagram.png new file mode 100644 index 00000000000..6c9d9489747 Binary files /dev/null and b/docs/images/PipeSequenceDiagram.png differ diff --git a/docs/images/QuickStartFindJar.png b/docs/images/QuickStartFindJar.png new file mode 100644 index 00000000000..7ccde6784b1 Binary files /dev/null and b/docs/images/QuickStartFindJar.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..4c478fe6c38 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StorageJsonSerializableAddressBookClassDiagram.png b/docs/images/StorageJsonSerializableAddressBookClassDiagram.png new file mode 100644 index 00000000000..77cf731108b Binary files /dev/null and b/docs/images/StorageJsonSerializableAddressBookClassDiagram.png differ diff --git a/docs/images/TaskClassDiagram.png b/docs/images/TaskClassDiagram.png new file mode 100644 index 00000000000..a1766bd5471 Binary files /dev/null and b/docs/images/TaskClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..7e4b059a635 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..ee8163b6bec 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UpdateDetailPanel.png b/docs/images/UpdateDetailPanel.png new file mode 100644 index 00000000000..c3399f941ae Binary files /dev/null and b/docs/images/UpdateDetailPanel.png differ diff --git a/docs/images/WindowsCommandPromptCheckJavaVersion.png b/docs/images/WindowsCommandPromptCheckJavaVersion.png new file mode 100644 index 00000000000..b02c9ba79b0 Binary files /dev/null and b/docs/images/WindowsCommandPromptCheckJavaVersion.png differ diff --git a/docs/images/WindowsCommandPromptCheckJavaVersionExecute.png b/docs/images/WindowsCommandPromptCheckJavaVersionExecute.png new file mode 100644 index 00000000000..8e628a3b34b Binary files /dev/null and b/docs/images/WindowsCommandPromptCheckJavaVersionExecute.png differ diff --git a/docs/images/autumn-sonata.png b/docs/images/autumn-sonata.png new file mode 100644 index 00000000000..7185b381ae5 Binary files /dev/null and b/docs/images/autumn-sonata.png differ diff --git a/docs/images/cg_back.png b/docs/images/cg_back.png new file mode 100644 index 00000000000..a6322d8903e Binary files /dev/null and b/docs/images/cg_back.png differ diff --git a/docs/images/cg_home.png b/docs/images/cg_home.png new file mode 100644 index 00000000000..ccef5fd3240 Binary files /dev/null and b/docs/images/cg_home.png differ diff --git a/docs/images/connlim.png b/docs/images/connlim.png new file mode 100644 index 00000000000..605c27b97b0 Binary files /dev/null and b/docs/images/connlim.png differ diff --git a/docs/images/eclipse-dominator.png b/docs/images/eclipse-dominator.png new file mode 100644 index 00000000000..e8060b0b594 Binary files /dev/null and b/docs/images/eclipse-dominator.png differ diff --git a/docs/images/jasonchristopher21.png b/docs/images/jasonchristopher21.png new file mode 100644 index 00000000000..e838ffe9fed Binary files /dev/null and b/docs/images/jasonchristopher21.png differ diff --git a/docs/images/marked.png b/docs/images/marked.png new file mode 100644 index 00000000000..150c390ab81 Binary files /dev/null and b/docs/images/marked.png differ diff --git a/docs/images/mohamedsaf1.png b/docs/images/mohamedsaf1.png new file mode 100644 index 00000000000..16a228eed0c Binary files /dev/null and b/docs/images/mohamedsaf1.png differ diff --git a/docs/images/rename_cmd.png b/docs/images/rename_cmd.png new file mode 100644 index 00000000000..1826df12380 Binary files /dev/null and b/docs/images/rename_cmd.png differ diff --git a/docs/images/unmarked.png b/docs/images/unmarked.png new file mode 100644 index 00000000000..d43b3c4fad6 Binary files /dev/null and b/docs/images/unmarked.png differ diff --git a/docs/images/user-guide-img/AliasingCommandUI.png b/docs/images/user-guide-img/AliasingCommandUI.png new file mode 100644 index 00000000000..6070d4e98d0 Binary files /dev/null and b/docs/images/user-guide-img/AliasingCommandUI.png differ diff --git a/docs/images/user-guide-img/ContainsCommandUI.png b/docs/images/user-guide-img/ContainsCommandUI.png new file mode 100644 index 00000000000..ab23ee70e41 Binary files /dev/null and b/docs/images/user-guide-img/ContainsCommandUI.png differ diff --git a/docs/images/user-guide-img/CreateTeamScreenshot.png b/docs/images/user-guide-img/CreateTeamScreenshot.png new file mode 100644 index 00000000000..43c2b9ca74e Binary files /dev/null and b/docs/images/user-guide-img/CreateTeamScreenshot.png differ diff --git a/docs/images/user-guide-img/ForEachCommandUI.png b/docs/images/user-guide-img/ForEachCommandUI.png new file mode 100644 index 00000000000..0f24c470ed7 Binary files /dev/null and b/docs/images/user-guide-img/ForEachCommandUI.png differ diff --git a/docs/images/user-guide-img/MarkEveryTaskDone.png b/docs/images/user-guide-img/MarkEveryTaskDone.png new file mode 100644 index 00000000000..cbdd1596874 Binary files /dev/null and b/docs/images/user-guide-img/MarkEveryTaskDone.png differ diff --git a/docs/images/user-guide-img/NavigateTeamAfter.png b/docs/images/user-guide-img/NavigateTeamAfter.png new file mode 100644 index 00000000000..252ed5c692c Binary files /dev/null and b/docs/images/user-guide-img/NavigateTeamAfter.png differ diff --git a/docs/images/user-guide-img/NavigateTeamBefore.png b/docs/images/user-guide-img/NavigateTeamBefore.png new file mode 100644 index 00000000000..5ec7b407d3e Binary files /dev/null and b/docs/images/user-guide-img/NavigateTeamBefore.png differ diff --git a/docs/images/user-guide-img/PersonNewScreenshot.png b/docs/images/user-guide-img/PersonNewScreenshot.png new file mode 100644 index 00000000000..67f241ec2d6 Binary files /dev/null and b/docs/images/user-guide-img/PersonNewScreenshot.png differ diff --git a/docs/images/user-guide-img/SelectCommandUI.png b/docs/images/user-guide-img/SelectCommandUI.png new file mode 100644 index 00000000000..eb517da9eb1 Binary files /dev/null and b/docs/images/user-guide-img/SelectCommandUI.png differ diff --git a/docs/images/user-guide-img/TaskAddScreenshot.png b/docs/images/user-guide-img/TaskAddScreenshot.png new file mode 100644 index 00000000000..ea0f2a5b992 Binary files /dev/null and b/docs/images/user-guide-img/TaskAddScreenshot.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..5eafed2f02f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,18 @@ --- layout: page -title: AddressBook Level-3 +title: Contactmation --- -[![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/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T11-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T11-1/tp/branch/master/graph/badge.svg?token=PUKESWS2WM)](https://codecov.io/gh/AY2223S1-CS2103T-T11-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). - -* 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. +Contactmation is a **desktop, contact management application** that is **optimized for team management and delegation of tasks through the Command Line Interface** (CLI). Contactmation efficiently tracks progress of your team projects. +- If you are interested in using Contactmation, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +- If you are interested about developing Contactmation, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +- Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) diff --git a/docs/team/autumn-sonata.md b/docs/team/autumn-sonata.md new file mode 100644 index 00000000000..152c081fe08 --- /dev/null +++ b/docs/team/autumn-sonata.md @@ -0,0 +1,94 @@ +--- +layout: page +title: Eric Lee's Project Portfolio Page +--- + +### Project: Contactmation + +Contactmation is a powerful **desktop based project and task management solution** that **helps you efficiently and +effectively manage many projects at once** through the Command Line Interface (CLI). + +Contactmation will be able to help you save all your project member details, keep track of +each project, and delegate tasks to each project. + +My contributions to the projects are listed below. + +### Features implemented + +#### Storage component + +Extension of all `Storage` classes to support the `Model` component of the project. + +Due to the complexity of having nested `Groups` and the fact that `Person` and `Task` may +also be nested, there is a need for conversion between a flat `json` file structure and +the tree-like structure in the `AddressBook`. + +Additionally, the `Storage` component must also +support the saving of states of `Alias`, `Macros / Custom commands`, `Abstracted attributes`, +`Task time completion date and times`. The save state of `unique ids` are also needed +that help identify each `Person`, `Group` and `Task` in their `JsonAdapted` status. This will +be needed to correctly reproduce the tree-like structure of the `AddressBook`. + +Error checking for each component and attribute is also put in place to check for corrupted data. + +### Code contribution + +Code contribution for this project is listed in [RepoSense](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=autumn-sonata&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other). + +### Main Enhancements implemented + +- Extension of all `Storage` classes to support the `Model` component of the project. [#62](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/62) + - Add `JsonAdaptedAbstractDisplayItem` to abstract components for `JsonAdaptedPerson`, `JsonAdaptedGroup` + and `JsonAdaptedTask`. [#62](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/62) + - Add `JsonAdaptedAbstractAttribute` to convert all `AbstractAttribute` to Json format and correctly parse them back into + their respective classes. + - Add `JsonAdaptedCustomCommandBuilder` to save alias and macros. +- Updated the **testing for the storage components** that increased code coverage by `5.58%`. [#168](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/168) +- Made builder classes for **easier testing of model classes**. [#155](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/155) + - Add `GroupBuilder`, `PersonBuilder` and `TaskBuilder` for easy building of `Group`, `Person` and `Task` during testing. [#155](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/155) + - Add `TypicalGroups`, `TypicalPersons` and `TypicalTasks` for standardised test cases of valid `Group`, `Person` and `Task`. [#166](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/166) + - Add `invalidGroupAddressBook`, `invalidTaskAddressBook` to existing `invalidPersonAddressBook` for checking of Json file data retrieval. [#166](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/166) + - Add `invalidAndValidGroupAddressBook`, `invalidAndValidTaskAddressBook` to existing `invalidAndValidPersonAddressBook` for checking of errors in Json file data retrieval. [#166](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/166) + - Add typical, duplicate and invalid Json files in `JsonAddressBookStorageTest` for testing of save and load functions in `JsonSerializableAddressBookTest`. [#166](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/166) + - Add additional test data in `SampleDataUtil` that is a duplicate of the test data Json files. [#157](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/157) + +<div style="page-break-after: always;"></div> + +### Contributions to the user guide + +I structured the user guide and rewrote the following sections [#172](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/172): + +- Introduction +- About + - Purpose + - User guide navigation + - Contactmation Window Guide + - Prerequisites +- Quick start +- Before you begin + - Standardised Format Style + - Combining the format styles + - Constraints on placeholder words + - Making groups within groups +- Overview of feature, basic feature and advanced features. + - Add field command + - Advanced +- FAQ section +- Glossary +- Advanced commands in the Commands summary section + +### Contributions to the developer guide + +- Wrote the storage component section. [#47](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/47) +- Added the `PlantUML` diagrams relating to the storage components in the storage section. [#47](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/47) + +<div style="page-break-after: always;"></div> + +### Pull Requests reviewed + +Here are some of the pull requests that I have reviewed with non-trivial comments +in the code review. + +- Hard coding of regex. [#151](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/151) +- Importing errors. [#167](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/167) +- Developer guide diagram missing. [#50](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/50) diff --git a/docs/team/connlim.md b/docs/team/connlim.md new file mode 100644 index 00000000000..48e4cc7c854 --- /dev/null +++ b/docs/team/connlim.md @@ -0,0 +1,74 @@ +--- +layout: page +title: Connor Lim's Project Portfolio Page +--- + +# Project: Contactmation + +Contactmation is a powerful **desktop-based project and task management solution** that **helps you efficiently and +effectively manage many projects at once** through the CLI. + +## Summary of Contributions + +[Code contributed](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=connlim&breakdown=true) + +[Pull requests](https://github.com/AY2223S1-CS2103T-T11-1/tp/pulls?q=is%3Apr+author%3Aconnlim) + +### Enhancements Implemented + +- Tasks + - Created a model for `task` subcommands to allow for easy addition of future `task` commands in the + format `task [subcommand]`. ([#36](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/36) + , [#56](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/56)) + - Added the ability to add Tasks to a Team. ([#38](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/38)) + - Added the ability to mark Tasks as complete or + incomplete. ([#56](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/56)) + - Added the ability to delete Tasks. ([#56](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/56)) +- Bug fixes + - Added more rigorous validation of phone number. ([#105](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/105)) + - Fixed no error message when trying to add a Task with an empty + name. ([#137](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/137)) + - Fixed random print statements appearing in the + console. ([#129](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/129)) + - Fixed non-specific error message when adding existing user to already-assigned + group. ([#108](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/108)) + +### Contributions to the UG + +- Wrote section about Tasks. +- Added a GitHub Workflow to automatically export the UG to + PDF. ([#154](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/154)) +- Fixed the formatting and screenshots ([#177](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/177)) + +### Contributions to the DG + +Added the following: + +- User stories ([#25](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/25)) +- Non-functional requirements ([#25](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/25)) +- Use cases with MSS ([#25](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/25) + , [#173](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/173)) +- Glossary ([#25](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/25)) + +### Contributions to team-based tasks + +- Created an issue for tracking progress of `AboutUs` + page. ([#9](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/9)) +- Note-taking during team meetings. +- Writing of initial user stories. +- Actively participated in weekly group discussions. +- Created milestones for management of team progress towards early releases. + +### Review/mentoring contributions + +- Reviewed numerous + PRs ([list of reviewed PRs](https://github.com/AY2223S1-CS2103T-T11-1/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3Aconnlim)) +- Reviewed the major user guide + changes. ([#172](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/172#pullrequestreview-1170043487)) +- Assisted team in setting up IntelliJ IDEA with autoformatting and checkstyle. + +### Contributions beyond the project team + +- Assisted a fellow student with build log problems. ([#316](https://github.com/nus-cs2103-AY2223S1/forum/issues/316)) +- Reported bugs for other teams during the PE-D ([list of reports](https://github.com/connlim/ped/issues)). + diff --git a/docs/team/eclipse-dominator.md b/docs/team/eclipse-dominator.md new file mode 100644 index 00000000000..bb2218c99ad --- /dev/null +++ b/docs/team/eclipse-dominator.md @@ -0,0 +1,97 @@ +--- +layout: page +title: Zhaoqi's Project Portfolio Page +--- + +### Project: Contactmation + +Contactmation is a powerful **desktop-based project and task management solution** that **helps you efficiently and +effectively manage many projects at once** through the CLI. + +## Summary of Contributions + +[Code contributed](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=Eclipse-Dominator&breakdown=true) + +[Pull requests](https://github.com/AY2223S1-CS2103T-T11-1/tp/pulls?q=is%3Apr+author%3Aeclipse-dominator) + +### Enhancements Implemented + +- Design of Model + - Designed how the 3 items task, person, and group are functioned together as well as each of the individual abstractions and implementations ([#33](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/33)) + - Made all items (person, task, group) extends AbstractDisplayItem which uses DisplayItem Inteface. ([#33](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/33)) + - Designed how nested groups functions (This is implemented by mohamedsaf1) + - Added an ability to force model to refresh the UI view of all elements + - Designed and implemented how attributes are being displayed as cards to the UI. ([#57](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/57)) + - Designed and added bit flags to denote attribute styles and properties as well as displayitem styles and properties ([#57](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/57)) + - Proposed the use of the above to create custom, detailed view page of each elements (not implemented in time) +- Design of UI + + - Updated UI to create 3 additional views to contain Task and Group ([#40](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/40)) + - Added UI Component to display current group scope. ([#40](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/40)) + +- Design of Logic + + - Designed and implemented macros, command compilation, logical command, aliases in logic + - Revamped command logic to allow logic to parse data between each other ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Designed the use of PIPING to allow command to transfer information to one another ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Implemented foreach commands to batch apply commands to all list views + - Updated addressbook parser to use a Singleton design ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Swapped addressbook parser to use a HashMap to map commands to actual commands instead of switch case ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Implemented Seq commands ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Implemented commands to create new terms (int, float, str) ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Implemented if else commands ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Enabled user to chain and save complex commands ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Enabled user to use alias to change the default command text ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + - Implemented the renaming of DisplayItem ([#72](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/72)) + +- Bug fixes and refactorings + - Improved on robustness of validation of phone number to handle unexpected user phone numbers in response to Issue [#105](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/105) ([#170](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/170)) + - Fixed field add ignoring duplicate types ([#170](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/170)) + - Fixed uncaught exceptions in field edit ([#170](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/170)) + - Fixed aliasing not working for custom command + - Fixed Phone attribute being read as Address after reopening Contactmation ([#161](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/161)) + - Refactored commands with similar codes (delete, select, into singular commands) ([#153](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/153)) + - Fixed incorrect regex when creating new groups Issue [#99](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/99) and related duplicates + - Fixes bug where unmark and mark commands resets custom fields (from internal testing) ([#147](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/147)) + - Fixed missing find command (due to not added to the parser) Issue [#77](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/77) and related duplicates ([#143](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/143)) + - Fixed task foreach commands (due to not added to the parser) issue [#103](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/103) and related duplicates ([#143](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/143)) + - Updated exception handling to allow users to know when the save file is write protected based on issue [#75](https://github.com/AY2223S1-CS2103T-T11-1/tp/issues/75) and related duplicates ([#170](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/170)) + +### Contributions to the UG + +- Wrote section in advanced user guide section +- Partially updated the command summary table ([#148](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/148)) +- Fixed the formatting and screenshots ([#177](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/177)) +- Created the default dummy save data to be used as basis for other screenshots. ([#145](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/145)) + +### Contributions to the DG + +Added the following: + +- Updated model UML ([#48](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/48)) +- Updated ui UML ([#48](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/48)) +- Added sequence diagrams for how DisplayItems are being generated as visible UI ([#48](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/48)) +- Updated logic UML +- Added explanation to how the model and logic system works. ([#181](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/181)) +- Added Detailed View to future implementations. ([#181](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/181)) + +### Contributions to team-based tasks + +- Created the team repository +- Actively participated in weekly group discussions. +- Created v1.2 Features Demo +- Created v1.3 Features Demo +- Created PED temporary report +- Created `documentation bug` and `formatting bug` tags to differentiate between the different bugs reported by others + +### Review/mentoring contributions + +- Reviewed numerous + PRs ([list of reviewed PRs](https://github.com/AY2223S1-CS2103T-T11-1/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3Aeclipse-dominator)) +- Reviewed the major user guide + changes. ([#172](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/172#pullrequestreview-1170043487)) +- Assisted team in suggesting ideas and fixes + +### Contributions beyond the project team + +- Reported bugs for other teams during the PE-D ([list of reports](https://github.com/Eclipse-Dominator/ped/issues)). diff --git a/docs/team/jasonchristopher21.md b/docs/team/jasonchristopher21.md new file mode 100644 index 00000000000..93024de6243 --- /dev/null +++ b/docs/team/jasonchristopher21.md @@ -0,0 +1,36 @@ +--- +layout: page +title: Jason Christopher's Project Portfolio Page +--- + +### Project: Contactmation + +Contactmation is a powerful desktop based team management solution that helps its users efficiently and +effectively manage many projects and groups at once. It is written in Java and has about 15 kLoc. + +Given below are my contributions to the project: + +* **New Feature**: Added the ability to store attributes within Person, Task and Group. + * What it does: allows the user to add custom attributes to Person, Task and Group. + * Justification: this feature improves the product significantly by allowing the user to add any attribute + to Person, Task or Group without boundaries, e.g. adding a field for `github`, `youtube`, etc. + +* **Code Contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=cs2103t-t11-1&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-09-16&tabOpen=true&zFR=false&tabType=authorship&tabAuthor=jasonchristopher21&tabRepo=AY2223S1-CS2103T-T11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Project management**: + * Managed releases `v1.3.trial` (1 release) on GitHub + +* **Enhancements to existing features**: + * Wrote additional tests for existing features to increase coverage [in progress]. + +* **Documentation**: + * User Guide: + * Added documentation for the team features: [\#149]() + * Did cosmetic tweaks to existing documentation of basic and general features: [\#149]() + * Developer Guide: + * Added implementation details of the attributes feature. [\#49]() + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#47](), [\#48](), [\#62](), [\#68]() + * Reported bugs and suggestions for other teams in the class + (examples: [Bugs found during PE-D](https://github.com/jasonchristopher21/ped/issues)) diff --git a/docs/team/mohamedsaf1.md b/docs/team/mohamedsaf1.md new file mode 100644 index 00000000000..a3bc8da5088 --- /dev/null +++ b/docs/team/mohamedsaf1.md @@ -0,0 +1,50 @@ +--- +layout: page +title: Mohamed Safwan's Project Portfolio Page +--- + +### Project: Contactmation + +Contactmation is a powerful **desktop based team management solution** that **helps its users efficiently and +effectively manage many projects and groups at once.** + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to add/delete teams. + * What it does: allows the user to add teams in Contactmation. Preceding add commands can be reversed by using the + delete command. + * Justification: This feature covers the basic functionality of the product. + * Highlights: This feature was utilised for other components of Contactmation such as Tasks and Contacts. +* **New Feature**: Added group commands. + * What it does: allows the user to create and navigate through groups. + * Justification: This is an extension to the attribute `teams`, and it allows further classification of members of projects for users. + * Highlights: This feature works like 'folders' and scoping is used to create groups within groups for diversification of groups for an organisation. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.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-09-16&tabOpen=true&tabType=authorship&tabAuthor=mohamedsaf1&tabRepo=AY2223S1-CS2103T-T11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Project management**: + * Managed release of `v1.4` on GitHub + +* **Enhancements to existing features**: + * Wrote additional tests for existing features in the Logic component to increase coverage from 33% to 39% (Pull requests [\#167](https://github.com/AY2223S1-CS2103T-T11-1/tp/commit/2630b8c9c52e0a68b9f946f940a6cd62e6c5c571)) + * Enhanced the storage and implementations of items in the App through "parenting" of DisplayItems. + +* **Documentation**: + * User Guide: + * Added documentation for introduction and task features [\#160](https://github.com/AY2223S1-CS2103T-T11-1/tp/commit/17a3f70a4f9825613839151bd7d512f6f37ae55b) + * Did cosmetic tweaks to existing documentation of basic features with input of images.: [\#160](https://github.com/AY2223S1-CS2103T-T11-1/tp/commit/dc1c9fc2582e9c40dbd81a9a188fd7370f11228b) + * Developer Guide: + * Added implementation details for certain features in `Model` component. [\#50](https://github.com/AY2223S1-CS2103T-T11-1/tp/commit/343c52432355c65a629d6445bbefa911db4d72f9) + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#37](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/37), + [\#45](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/45), + [\#163](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/163), + [\#166](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/166), + [\#168](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/168), + [\#169](https://github.com/AY2223S1-CS2103T-T11-1/tp/pull/169) + * Contributed to forum discussions + * Reported bugs and suggestions for other teams. + * Some parts of the testing implementation was inspired by other team projects in CS2103T. + + diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..e4737edea25 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(1, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -69,9 +69,11 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}. <br> - * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s address book and + * {@code userPrefs}. <br> + * The data from the sample address book will be used instead if {@code storage}'s address book is + * not found, or an empty address book will be used instead if errors occur when reading + * {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { Optional<ReadOnlyAddressBook> addressBookOptional; @@ -99,8 +101,8 @@ private void initLogging(Config config) { /** * Returns a {@code Config} using the file at {@code configFilePath}. <br> - * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead - * if {@code configFilePath} is null. + * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead if + * {@code configFilePath} is null. */ protected Config initConfig(Path configFilePath) { Config initializedConfig; @@ -120,11 +122,11 @@ protected Config initConfig(Path configFilePath) { initializedConfig = configOptional.orElse(new Config()); } catch (DataConversionException e) { logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. " - + "Using default config properties"); + + "Using default config properties"); initializedConfig = new Config(); } - //Update config file in case it was missing to begin with or there are new/unused fields + // Update config file in case it was missing to begin with or there are new/unused fields try { ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); } catch (IOException e) { @@ -134,9 +136,8 @@ protected Config initConfig(Path configFilePath) { } /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, - * or a new {@code UserPrefs} with default configuration if errors occur when - * reading from the file. + * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, or a new + * {@code UserPrefs} with default configuration if errors occur when reading from the file. */ protected UserPrefs initPrefs(UserPrefsStorage storage) { Path prefsFilePath = storage.getUserPrefsFilePath(); @@ -148,14 +149,14 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { initializedPrefs = prefsOptional.orElse(new UserPrefs()); } catch (DataConversionException e) { logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. " - + "Using default user prefs"); + + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); initializedPrefs = new UserPrefs(); } - //Update prefs file in case it was missing to begin with or there are new/unused fields + // Update prefs file in case it was missing to begin with or there are new/unused fields try { storage.saveUserPrefs(initializedPrefs); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..886c657e7c9 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_INVALID_TEAM_DISPLAYED_INDEX = "The team index provided is invalid"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_EMPTY_NAME = "Name is empty! \n%1$s"; } diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java index 19124db485c..3915ae7e8e4 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java @@ -18,4 +18,11 @@ public IllegalValueException(String message) { public IllegalValueException(String message, Throwable cause) { super(message, cause); } + + /** + * @param cause of the main exception + */ + public IllegalValueException(Throwable cause) { + super(cause); + } } diff --git a/src/main/java/seedu/address/commons/util/FunctionalInterfaces.java b/src/main/java/seedu/address/commons/util/FunctionalInterfaces.java new file mode 100644 index 00000000000..9c6e2cce505 --- /dev/null +++ b/src/main/java/seedu/address/commons/util/FunctionalInterfaces.java @@ -0,0 +1,57 @@ +package seedu.address.commons.util; + +import java.util.function.Function; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Class to include some functional interfaces for general use + */ +public final class FunctionalInterfaces { + /** + * Interface for extracting index from model + */ + @FunctionalInterface + public interface Getter<U> { + U apply(Model model, Index index) throws CommandException; + } + + /** + * Interface for modifying item from model + */ + @FunctionalInterface + public interface Changer<U> { + void apply(Model model, U item) throws CommandException; + } + + /** + * Interface for retrieving item from model + */ + @FunctionalInterface + public interface Retriever<U> { + U apply(Model model) throws CommandException; + } + + /** + * Represents a function that can throw an exception + */ + @FunctionalInterface + public interface ThrowFunction<T, R, E extends Exception> { + R apply(T u) throws E; + } + + /** + * Wraps throwable functions into Funtion that throws runtime Exception + */ + public static <T, R> Function<T, R> throwingFunctionWrapper(ThrowFunction<T, R, ? extends Exception> thrower) { + return in -> { + try { + return thrower.apply(in); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + }; + } +} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..8fe5e9c1c53 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -1,11 +1,11 @@ package seedu.address.commons.util; +import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Arrays; /** * Helper functions for handling strings. @@ -14,14 +14,18 @@ public class StringUtil { /** * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - * <br>examples:<pre> + * Ignores case, but a full word match is required. + * <br> + * examples: + * + * <pre> * containsWordIgnoreCase("ABc def", "abc") == true * containsWordIgnoreCase("ABc def", "DEF") == true * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match - * </pre> + * </pre> + * * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); @@ -34,7 +38,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String preppedSentence = sentence; String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); - return Arrays.stream(wordsInPreppedSentence) + return stream(wordsInPreppedSentence) .anyMatch(preppedWord::equalsIgnoreCase); } @@ -52,7 +56,9 @@ public static String getDetails(Throwable t) { * Returns true if {@code s} represents a non-zero unsigned integer * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE} <br> * Will return false for any other non-null string input - * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains + * whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { @@ -65,4 +71,27 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + /** + * Returns the proper case formatting of a given string, by capitalising the start of each word, setting the + * remaining letters of each word as lower case and placing the proper number of whitespaces in between words. + * + * @param s A string to be formatted. + * @return The formatted string s in proper case. + */ + public static String properCase(String s) { + return stream(s.split("\\s+")) + .map(w -> singleWordProperCase(w)) + .reduce("", (x, y) -> x + " " + y, (x, y) -> x + y).trim(); + } + + /** + * Capitalises the start of the string and setting the remaining letters in lower case. + * + * @param s A string to be formatted. + * @return The formatted string in proper case. + */ + private static String singleWordProperCase(String s) { + return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + } } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..8c071e20144 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,10 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; +import seedu.address.model.item.AbstractSingleItem; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * API of the Logic component @@ -16,10 +19,11 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; @@ -33,6 +37,12 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList<Person> getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of teams */ + ObservableList<Group> getFilteredGroupList(); + + /** Returns an unmodifiable view of the filtered list of tasks */ + ObservableList<Task> getFilteredTaskList(); + /** * Returns the user prefs' address book file path. */ @@ -47,4 +57,6 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + + AbstractSingleItem getContainer(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..f06c3009e9f 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,6 +1,7 @@ package seedu.address.logic; import java.io.IOException; +import java.nio.file.AccessDeniedException; import java.nio.file.Path; import java.util.logging.Logger; @@ -14,7 +15,10 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; +import seedu.address.model.item.AbstractSingleItem; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; import seedu.address.storage.Storage; /** @@ -22,6 +26,11 @@ */ public class LogicManager implements Logic { public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; + + private static final String PERM_ERR = + "Unable to save your information!\nPlease check the FAQ section of the user guide for more information." + + "The user guide can be accessed through Help in the menu. \n%s"; + private final Logger logger = LogsCenter.getLogger(LogicManager.class); private final Model model; @@ -34,7 +43,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + addressBookParser = AddressBookParser.get(); } @Override @@ -47,6 +56,9 @@ public CommandResult execute(String commandText) throws CommandException, ParseE try { storage.saveAddressBook(model.getAddressBook()); + } catch (AccessDeniedException e) { + logger.info("Permission Error: " + e.getMessage()); + throw new CommandException(String.format(PERM_ERR, e)); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -78,4 +90,19 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + + @Override + public ObservableList<Group> getFilteredGroupList() { + return model.getFilteredTeamList(); + } + + @Override + public AbstractSingleItem getContainer() { + return model.getContextContainer(); + } + + @Override + public ObservableList<Task> getFilteredTaskList() { + return model.getFilteredTaskList(); + } } diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..6e3caa24320 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -8,7 +8,7 @@ /** * Clears the address book. */ -public class ClearCommand extends Command { +public class ClearCommand extends PureCommand { public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..35d4f00036f 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -8,6 +8,8 @@ */ public abstract class Command { + public abstract Command setInput(Object additionalData) throws CommandException; + /** * Executes the command and returns the result message. * diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..d2041bb2667 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import java.util.Objects; +import java.util.Optional; /** * Represents the result of a command execution. @@ -17,21 +18,28 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + private Optional<?> result; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, Object result) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.result = Optional.ofNullable(result); + } + + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + this(feedbackToUser, showHelp, exit, null); } /** - * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, and other fields + * set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, null); } public String getFeedbackToUser() { @@ -46,6 +54,10 @@ public boolean isExit() { return exit; } + public Optional<?> getResult() { + return result; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -59,8 +71,8 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && showHelp == otherCommandResult.showHelp + && exit == otherCommandResult.exit; } @Override diff --git a/src/main/java/seedu/address/logic/commands/CustomCommandBuilder.java b/src/main/java/seedu/address/logic/commands/CustomCommandBuilder.java new file mode 100644 index 00000000000..d7275e4abd2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CustomCommandBuilder.java @@ -0,0 +1,80 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Class with the ability to build a custom command. + */ +public class CustomCommandBuilder { + + public static final String MESSAGE_CONSTRAINTS = "Custom commands cannot be empty!"; + + private final String repr; + private final String commandData; + + /** + * Creates a custom command + * + * @param repr name of command + * @param commandData command details + */ + public CustomCommandBuilder(String repr, String commandData) { + this.repr = repr; + this.commandData = commandData; + } + + /** + * Retrieves the macro shortcut that represent this command. + * + * @return the string representing the macro word. + */ + public String getRepr() { + return repr; + } + + /** + * Retrieves the initial command that the macro represents. + */ + public String getCommandData() { + return commandData; + } + + /** + * Builds the custom command based on the stored information + */ + public Command build() { + return new Command() { + private Object o = null; + + @Override + public Command setInput(Object additionalData) throws CommandException { + o = additionalData; + return this; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + try { + return AddressBookParser.get().parseCommand(commandData).setInput(o).execute(model); + } catch (ParseException e) { + throw new CommandException(e.getMessage()); + } + } + }; + } + + @Override + public int hashCode() { + return repr.hashCode() ^ commandData.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof CustomCommandBuilder) + && ((CustomCommandBuilder) obj).repr.equals(repr) + && ((CustomCommandBuilder) obj).commandData.equals(commandData); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..be2c3e69550 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,53 +1,108 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import java.util.List; +import java.util.function.Predicate; -import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Getter; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.item.DisplayItem; /** - * Deletes a person identified using it's displayed index from the address book. + * Marks a task as complete */ -public class DeleteCommand extends Command { +public class DeleteCommand<U extends DisplayItem> extends Command { + public static final String SUBCOMMAND_WORD = "delete"; - public static final String COMMAND_WORD = "delete"; + public static final String MESSAGE_USAGE = + "<type> delete: delete the item with the given index\n" + + "e.g. task delete 1"; - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + public static final String MESSAGE_DELETE_SUCCESS = "Deleted Object: %s"; + + + private static final String NUMBER = "\\s*[\\-+]?[0-9]+\\s*"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; private final Index targetIndex; + private final Getter<U> converter; + private final Changer<U> deleter; + private final Predicate<Object> verifier; + private U toDelete = null; - public DeleteCommand(Index targetIndex) { + /** + * Constructor to select a task + */ + public DeleteCommand(Index targetIndex, Getter<U> converter, Changer<U> deleter, Predicate<Object> verifier) { + requireNonNull(converter); this.targetIndex = targetIndex; + this.converter = converter; + this.deleter = deleter; + this.verifier = verifier; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List<Person> lastShownList = model.getFilteredPersonList(); + if (toDelete == null && targetIndex == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (toDelete == null) { + toDelete = converter.apply(model, targetIndex); } + deleter.apply(model, toDelete); + return new CommandResult(String.format(MESSAGE_DELETE_SUCCESS, toDelete.toString())); + } + + - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + /** + * Returns a parser to parse userinput for select command + */ + public static <U extends DisplayItem> Parser<DeleteCommand<U>> parser(Getter<U> converter, + Changer<U> deleter, Predicate<Object> verifier) { + + return new Parser<DeleteCommand<U>>() { + @Override + public DeleteCommand<U> parse(String args) throws ParseException { + if (args == null || args.trim().equals("")) { + return new DeleteCommand<U>(null, converter, deleter, verifier); + } else if (!args.matches(NUMBER)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(args); + return new DeleteCommand<U>(index, converter, deleter, verifier); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !verifier.test(additionalData)) { + return this; + } + // this is verified by verifier + toDelete = (U) additionalData; + return this; } @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + public boolean equals(Object obj) { + return this == obj + || (obj instanceof DeleteCommand + && ((targetIndex != null && targetIndex.equals(((DeleteCommand<?>) obj).targetIndex)) + || ((targetIndex == null) && (((DeleteCommand<?>) obj).targetIndex == null))) + && (((toDelete != null) && (toDelete.equals(((DeleteCommand<?>) obj).toDelete))) + || (toDelete == ((DeleteCommand<?>) obj).toDelete))); } } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -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_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.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.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -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. " - + "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" - + "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_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List<Person> lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * 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; - - 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<Tag> updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set<Tag> tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional<Name> getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional<Phone> getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional<Email> getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional<Address> 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<Tag> tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional<Set<Tag>> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..e4b327159ba 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -5,7 +5,7 @@ /** * Terminates the program. */ -public class ExitCommand extends Command { +public class ExitCommand extends PureCommand { public static final String COMMAND_WORD = "exit"; diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..a9e3d731913 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -2,41 +2,78 @@ import static java.util.Objects.requireNonNull; -import seedu.address.commons.core.Messages; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Retriever; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.item.NameContainsKeywordsPredicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ -public class FindCommand extends Command { +public class FindCommand<T extends DisplayItem> extends Command { + + public static final String SUBCOMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = "type find" + + ": Finds all items whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: person find alice bob charlie"; - public static final String COMMAND_WORD = "find"; + public static final String SUCCESS_MESSAGE = "%1$d items found!"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names 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"; - private final NameContainsKeywordsPredicate predicate; + private NameContainsKeywordsPredicate<T> predicate; + private final Changer<Predicate<T>> changer; + private final Retriever<Integer> getSize; - public FindCommand(NameContainsKeywordsPredicate predicate) { + /** + * Constructor for find command + * + * @param predicate filtering tool + * @param changer sam to filter specific model list + * @param getSize sam to get final size of filtered elements + */ + public FindCommand(NameContainsKeywordsPredicate<T> predicate, Changer<Predicate<T>> changer, + Retriever<Integer> getSize) { + requireNonNull(changer); + requireNonNull(getSize); this.predicate = predicate; + if (predicate == null) { + this.predicate = new NameContainsKeywordsPredicate<T>(List.of()); + } + this.changer = changer; + this.getSize = getSize; } @Override - public CommandResult execute(Model model) { + public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - model.updateFilteredPersonList(predicate); + changer.apply(model, predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(SUCCESS_MESSAGE, getSize.apply(model))); } @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 + || (other instanceof FindCommand // instanceof handles nulls + && predicate.equals(((FindCommand<?>) other).predicate)); // state check + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof String)) { + return this; + } + predicate = new NameContainsKeywordsPredicate<>(Arrays.asList(additionalData.toString().split("\\s+"))); + return this; } } diff --git a/src/main/java/seedu/address/logic/commands/ForEachCommand.java b/src/main/java/seedu/address/logic/commands/ForEachCommand.java new file mode 100644 index 00000000000..ff553498433 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ForEachCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.util.FunctionalInterfaces.Retriever; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.item.DisplayItem; + +/** + * Marks a task as complete + */ +public class ForEachCommand extends PureCommand { + public static final String SUBCOMMAND_WORD = "foreach"; + + public static final String MESSAGE_USAGE = + "<type> foreach: for each item in the chosen list, execute subsequent commands with that item as context\n" + + "e.g. task for each mark"; + + private static final String ON_COMPLETE = "Completed task loop! (failed: %d/%d executions)"; + + private final Command nextCmd; + private final Retriever<List<? extends DisplayItem>> retriever; + + /** + * Constructor for task foreach command + */ + public ForEachCommand(String nextCmd, Retriever<List<? extends DisplayItem>> retriever) throws ParseException { + requireNonNull(retriever); + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error: \n" + ps.getMessage()); + } + this.retriever = retriever; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<? extends DisplayItem> lastShownList = new ArrayList<>(retriever.apply(model)); + int[] skipped = {0, lastShownList.size()}; + lastShownList.forEach(t -> { + try { + nextCmd.setInput(t); + nextCmd.execute(model); + } catch (CommandException e) { + skipped[0]++; + } + }); + return new CommandResult(String.format(ON_COMPLETE, skipped[0], skipped[1])); + } + + + /** + * Returns a parser to parse user input for ForEach commands + * + * @param retriever SAM to retrieve the list of item from model + * @return parser of foreach command + */ + public static Parser<ForEachCommand> parser(Retriever<List<? extends DisplayItem>> retriever) { + return new Parser<ForEachCommand>() { + @Override + public ForEachCommand parse(String userInput) throws ParseException { + return new ForEachCommand(userInput.trim(), retriever); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..778a3fb99c0 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -5,7 +5,7 @@ /** * Format full help instructions for every command for display. */ -public class HelpCommand extends Command { +public class HelpCommand extends PureCommand { public static final String COMMAND_WORD = "help"; diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..7d4e5aa68c2 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,24 +1,22 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import seedu.address.model.Model; /** * Lists all persons in the address book to the user. */ -public class ListCommand extends Command { +public class ListCommand extends PureCommand { 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 in the current context!"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.refresh(); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/PureCommand.java b/src/main/java/seedu/address/logic/commands/PureCommand.java new file mode 100644 index 00000000000..2990f7d8520 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PureCommand.java @@ -0,0 +1,12 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** Pure Command represent a command that does not take in additional input */ +public abstract class PureCommand extends Command implements PureCommandInterface { + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/PureCommandInterface.java b/src/main/java/seedu/address/logic/commands/PureCommandInterface.java new file mode 100644 index 00000000000..316afef7a43 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PureCommandInterface.java @@ -0,0 +1,7 @@ +package seedu.address.logic.commands; + +/** + * Interface to label a Command as a pure command + */ +public interface PureCommandInterface { +} diff --git a/src/main/java/seedu/address/logic/commands/RenameCommand.java b/src/main/java/seedu/address/logic/commands/RenameCommand.java new file mode 100644 index 00000000000..ec9c1d940ed --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RenameCommand.java @@ -0,0 +1,175 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +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.AccessDisplayFlags; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.person.Person; +import seedu.address.model.task.Task; + +/** Command to rename a group/task/person */ +public class RenameCommand extends Command { + + public static final String MESSAGE_USAGE = "rename :Renames a given item.\n" + + "[t|u|g]/id [new name]"; + + public static final String COMMAND_WORD = "rename"; + + public static final String NO_INPUT = "No input item is chosen!"; + public static final String MESSAGE_DUPLICATE = "An item with the same name already exist!"; + public static final String OUT_OF_BOUNDS = "ID selected is out of bounds!"; + public static final String SUCCESS_MSG = "The item have been renamed!"; + public static final String INVALID_FORMAT = "The item cannot be renamed to such!"; + + private DisplayItem itemToRename = null; + private int renameType = 0; + private final String newName; + + private final Index targetIndex; + + /** + * Command to rename a displayitem + */ + public RenameCommand(Index selectedIndex, int renameType, String newName) { + this.renameType = renameType; + this.targetIndex = selectedIndex; + this.newName = newName; + } + + /** + * Constructor called when the user is expected to pass in the displayItem + */ + public RenameCommand(String newName) { + this.targetIndex = null; + this.newName = newName; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof DisplayItem)) { + itemToRename = null; + return this; + } + + if (((DisplayItem) additionalData).getTypeFlag() == AccessDisplayFlags.GROUP) { + renameType = 1; + } else if (((DisplayItem) additionalData).getTypeFlag() == AccessDisplayFlags.PERSON) { + renameType = 2; + } else if (((DisplayItem) additionalData).getTypeFlag() == AccessDisplayFlags.TASK) { + renameType = 3; + } else { + assert false; // this shouldnt be reached + } + + itemToRename = (DisplayItem) additionalData; + return this; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (renameType == 0) { + throw new CommandException(NO_INPUT); + } + if (renameType == 1) { + executeForGroup(model); + } else if (renameType == 2) { + executeForPerson(model); + } else if (renameType == 3) { + executeForTask(model); + } + + return new CommandResult(SUCCESS_MSG); + } + + void executeForTask(Model model) throws CommandException { + List<Task> lastShownList = model.getFilteredTaskList(); + Task target; + if (targetIndex == null && itemToRename == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + if (itemToRename == null) { + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + target = lastShownList.get(targetIndex.getZeroBased()); + } else { + target = (Task) itemToRename; + } + + Task tmp = new Task(newName, "dummy"); + tmp.setParent(target.getParent()); + + if (model.hasTask(tmp)) { + throw new CommandException(MESSAGE_DUPLICATE); + } + + target.rename(newName); + model.refresh(); + } + + void executeForPerson(Model model) throws CommandException { + List<Person> lastShownList = model.getFilteredPersonList(); + Person target; + if (targetIndex == null && itemToRename == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + if (itemToRename == null) { + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + target = lastShownList.get(targetIndex.getZeroBased()); + } else { + target = (Person) itemToRename; + } + + Person tmp = new Person(newName); + tmp.setParent(model.getContextContainer()); + + if (model.hasPerson(tmp)) { + throw new CommandException(MESSAGE_DUPLICATE); + } + target.rename(newName); + model.refresh(); + } + + void executeForGroup(Model model) throws CommandException { + List<Group> lastShownList = model.getFilteredTeamList(); + Group target; + if (targetIndex == null && itemToRename == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + if (itemToRename == null) { + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + target = lastShownList.get(targetIndex.getZeroBased()); + } else { + target = (Group) itemToRename; + } + if (!Group.isValidGroupName(newName)) { + throw new CommandException(INVALID_FORMAT); + } + Group tmp = new Group(newName); + tmp.setParent(target.getParent()); + + if (model.hasTeam(tmp)) { + throw new CommandException(MESSAGE_DUPLICATE); + } + target.rename(newName); + model.refresh(); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java new file mode 100644 index 00000000000..fc30825ab4a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.FunctionalInterfaces.Getter; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.item.DisplayItem; + +/** + * Marks a task as complete + */ +public class SelectCommand extends PureCommand { + public static final String SUBCOMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = + "<type> select: selects an item and execute subsequent commands with that item as context\n" + + "e.g. task select 1 contains description"; + + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<index>[0-9]+)\\s+(?<commands>.*)"); + + private final Index targetIndex; + private final Command nextCmd; + private final Getter<DisplayItem> converter; + + /** + * Constructor to select a task + */ + public SelectCommand(Index targetIndex, String nextCmd, Getter<DisplayItem> converter) throws ParseException { + requireNonNull(converter); + this.targetIndex = targetIndex; + this.converter = converter; + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error: \n" + ps.getMessage()); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + nextCmd.setInput(converter.apply(model, targetIndex)); + return nextCmd.execute(model); + } + + /** + * Returns a parser to parse userinput for select command + */ + public static Parser<SelectCommand> parser(Getter<DisplayItem> fun) { + return new Parser<SelectCommand>() { + @Override + public SelectCommand parse(String args) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(matcher.group("index")); + return new SelectCommand(index, matcher.group("commands"), fun); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/TeamInputCommand.java b/src/main/java/seedu/address/logic/commands/TeamInputCommand.java new file mode 100644 index 00000000000..0f664841e48 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/TeamInputCommand.java @@ -0,0 +1,22 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.group.Group; + +/** + * Represents the commands that can take in team as an additonal parameter + */ +public abstract class TeamInputCommand extends Command { + + protected Group group = null; + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof Group)) { + group = null; + return this; + } + this.group = (Group) additionalData; + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/AliasCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/AliasCommand.java new file mode 100644 index 00000000000..a08b89b3ace --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/AliasCommand.java @@ -0,0 +1,72 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to create an alias to a currently existing command + */ +public class AliasCommand extends PureCommand { + + public static final String COMMAND_WORD = "alias"; + private static final String INVALID_INPUT = "Invalid syntax!"; + private static final String INVALID_REPLACEMENT = "%s don't exist right now!"; + private static final String ALIAS_ALR_EXIST = "%s already exist right now!"; + private static final String INVALID_NAME = "The command name you chose is not available!\n" + + "Name should be unique with no space, start with a letter, can contain only numbers and letters"; + private static final String USE_EXAMPLE = "create [name] [code]\ne.g. markAllTask task foreach mark"; + + private final String alias; + private final String key; + + /** + * Creates stores the necessary data for alias command + */ + public AliasCommand(String alias, String key) { + this.alias = alias; + this.key = key; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + AddressBookParser.get().addAlias(alias, key); + return new CommandResult(String.format("%s -> %s has been added!", alias, key), false, false); + } + + /** + * Creates a parser to parse user input for alias command + */ + public static Parser<AliasCommand> parser() { + return new Parser<AliasCommand>() { + @Override + public AliasCommand parse(String userInput) throws ParseException { + userInput = userInput.trim(); + String[] tokens = userInput.split("\\s+", 2); + String alias = tokens[0].trim(); + String key = tokens[1].trim(); + if (tokens.length != 2 || tokens[1].trim().length() == 0) { + throw new ParseException(INVALID_INPUT + "\n" + USE_EXAMPLE); + } + + if (AddressBookParser.get().isKeyAvailable(key)) { + throw new ParseException(String.format(INVALID_REPLACEMENT, key)); + } + + if (!AddressBookParser.get().isKeyAvailable(alias)) { + throw new ParseException(String.format(ALIAS_ALR_EXIST, key)); + } + + if (AddressBookParser.isValidName(alias)) { + return new AliasCommand(alias, key); + } + + throw new ParseException(INVALID_NAME); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/CreateCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/CreateCommand.java new file mode 100644 index 00000000000..ac4cb2547dd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/CreateCommand.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.CustomCommandBuilder; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to allow the user to create their custom commands + */ +public class CreateCommand extends PureCommand { + + public static final String COMMAND_WORD = "macro"; + private static final String INVALID_INPUT = "Invalid syntax!"; + private static final String WRONG_CODE = "The code you entered cannot be compiled!"; + private static final String INVALID_NAME = "The command name you chose is not available!\n" + + "Name should be unique with no space, start with a letter, can contain only numbers and letters"; + private static final String USE_EXAMPLE = "create [name] [code]\ne.g. markAllTask task foreach mark"; + + private final CustomCommandBuilder builder; + + public CreateCommand(CustomCommandBuilder builder) { + this.builder = builder; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + AddressBookParser.get().addCommand(builder); + return new CommandResult(String.format("%s has been added!", builder.getRepr()), false, false); + } + + /** + * Creates a parser to parse user input for create command + */ + public static Parser<CreateCommand> parser() { + return new Parser<CreateCommand>() { + @Override + public CreateCommand parse(String userInput) throws ParseException { + userInput = userInput.trim(); + String[] tokens = userInput.split("\\s+", 2); + if (tokens.length < 2 || tokens[1].trim().length() == 0) { + throw new ParseException(INVALID_INPUT + "\n" + USE_EXAMPLE); + } + String key = tokens[0].trim(); + String value = tokens[1].trim(); + if (!AddressBookParser.isValidCommand(value)) { + throw new ParseException(WRONG_CODE); + } + if (AddressBookParser.isValidName(key) && AddressBookParser.get().isKeyAvailable(key)) { + return new CreateCommand(new CustomCommandBuilder(key, value)); + } + throw new ParseException(INVALID_NAME); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/DeleteCustomCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/DeleteCustomCommand.java new file mode 100644 index 00000000000..0b6ef5c7a6a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/DeleteCustomCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command for user to delete a user defined command + */ +public class DeleteCustomCommand extends PureCommand { + + public static final String COMMAND_WORD = "rmMacro"; + private static final String INVALID_INPUT = "Invalid syntax!\nrmCommand [command name]"; + private static final String COMMAND_DONT_EXIST = "%s don't exist!"; + + private final String key; + + public DeleteCustomCommand(String key) { + this.key = key; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (AddressBookParser.get().isKeyAvailable(key)) { + throw new CommandException(String.format(COMMAND_DONT_EXIST, key)); + } + AddressBookParser.get().deleteCommand(key); + return new CommandResult(String.format("%s has been removed!", key), false, false); + } + + /** + * Creates a parser to parse user input for delete custom user command command + */ + public static Parser<DeleteCustomCommand> parser() { + return new Parser<DeleteCustomCommand>() { + @Override + public DeleteCustomCommand parse(String userInput) throws ParseException { + userInput = userInput.trim(); + if (userInput.length() == 0) { + throw new ParseException(INVALID_INPUT); + } + return new DeleteCustomCommand(userInput); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/ExecuteCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/ExecuteCommand.java new file mode 100644 index 00000000000..ac071129de4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/ExecuteCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * A command to execute some string input as commands + */ +public class ExecuteCommand extends Command { + public static final String COMMAND_WORD = "e"; + private static final String INVALID_INPUT = "I don't understand the command"; + + private String commands; + private String bonus = ""; + + /** + * Constructor for execute command + * + * @param bonus command to run after the parsed command finishes execution + */ + public ExecuteCommand(String bonus) { + if (bonus == null || bonus.trim().length() == 0) { + this.bonus = ""; + } else { + this.bonus = bonus; + } + } + + public ExecuteCommand() { + this.bonus = ""; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + try { + Command c = AddressBookParser.get().parseCommand(commands); + CommandResult res = c.execute(model); + if (bonus.equals("")) { + return new CommandResult(res.getFeedbackToUser(), false, false, + res.getResult().orElse(null)); + } + return AddressBookParser.quickCommand(bonus, res.getResult().orElse(null)).execute(model); + } catch (ParseException e) { + throw new CommandException(INVALID_INPUT); + } + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null) { + commands = ""; + return this; + } + commands = additionalData.toString().trim(); + return this; + } + + /** + * Creates a parser to parse user input for execute command + */ + public static Parser<ExecuteCommand> parser() { + return new Parser<ExecuteCommand>() { + @Override + public ExecuteCommand parse(String userInput) throws ParseException { + if (userInput == null) { + userInput = ""; + } + userInput = userInput.trim(); + ParserUtil.Pair p = ParserUtil.splitPipe(userInput); + if (p.getSecond().trim().length() == 0) { + return new ExecuteCommand(null); + } + return new ExecuteCommand(p.getSecond()); + } + }; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/FloatCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/FloatCommand.java new file mode 100644 index 00000000000..2892502a68d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/FloatCommand.java @@ -0,0 +1,63 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to allow the user to create a float number + */ +public class FloatCommand extends PureCommand { + public static final String COMMAND_WORD = "float"; + private static final String INVALID_INPUT = "The input is not an float"; + + private Float num; + private String next; + + /** + * Constructor to create a new float + * + * @param num + * @param next + */ + public FloatCommand(Float num, String next) { + this.num = num; + this.next = next; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (next == null || next.equals("")) { + return new CommandResult(String.format("created %f", num), false, false, num); + } + return AddressBookParser.quickCommand(next, num).execute(model); + } + + /** + * Returns a parser to parse user input for Float command + */ + public static Parser<FloatCommand> parser() { + return new Parser<FloatCommand>() { + @Override + public FloatCommand parse(String userInput) throws ParseException { + ParserUtil.Pair p = ParserUtil.splitPipe(userInput); + + if (p.getFirst().length() == 0) { + throw new ParseException(INVALID_INPUT); + } + Float num; + try { + num = Float.parseFloat(p.getFirst()); + } catch (NumberFormatException e) { + throw new ParseException(INVALID_INPUT); + } + return new FloatCommand(num, p.getSecond()); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/IntCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/IntCommand.java new file mode 100644 index 00000000000..d312c9c2c82 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/IntCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to allow the user to create an integer + */ +public class IntCommand extends PureCommand { + + public static final String COMMAND_WORD = "int"; + private static final String INVALID_INPUT = "The input is not an integer"; + + private Integer num; + private String next; + + /** + * Constructor to create a int command + */ + public IntCommand(Integer num, String next) { + this.num = num; + this.next = next; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (next == null || next.equals("")) { + return new CommandResult(String.format("created %d", num), false, false, num); + } + return AddressBookParser.quickCommand(next, num).execute(model); + } + + /** + * Returns a parser to parse user input for int command + */ + public static Parser<IntCommand> parser() { + return new Parser<IntCommand>() { + @Override + public IntCommand parse(String userInput) throws ParseException { + userInput = userInput.trim(); + ParserUtil.Pair p = ParserUtil.splitPipe(userInput); + if (p.getFirst().length() == 0) { + throw new ParseException(INVALID_INPUT); + } + Integer num; + try { + num = Integer.parseInt(p.getFirst()); + } catch (NumberFormatException e) { + throw new ParseException(INVALID_INPUT); + } + return new IntCommand(num, p.getSecond()); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/creationcommand/StringCommand.java b/src/main/java/seedu/address/logic/commands/creationcommand/StringCommand.java new file mode 100644 index 00000000000..3687de1f0cf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/creationcommand/StringCommand.java @@ -0,0 +1,56 @@ +package seedu.address.logic.commands.creationcommand; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to allow the user to create a string + */ +public class StringCommand extends PureCommand { + + public static final String COMMAND_WORD = "str"; + private static final String INVALID_INPUT = "The input cannot be empty"; + + private String val; + private String next; + + /** + * Constructor to create a string command + */ + public StringCommand(String val, String next) { + this.val = val; + this.next = next; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (next == null || next.equals("")) { + return new CommandResult(String.format("created %s", val), false, false, val); + } + return AddressBookParser.quickCommand(next, val).execute(model); + + } + + /** + * Parser to parse user input for str command + */ + public static Parser<StringCommand> parser() { + return new Parser<StringCommand>() { + @Override + public StringCommand parse(String userInput) throws ParseException { + ParserUtil.Pair p = ParserUtil.splitPipe(userInput); + String val = p.getFirst().trim(); + if (val.length() == 0) { + throw new ParseException(INVALID_INPUT); + } + return new StringCommand(val, p.getSecond()); + } + }; + } +} diff --git a/src/main/java/seedu/address/logic/commands/fields/AddFieldCommand.java b/src/main/java/seedu/address/logic/commands/fields/AddFieldCommand.java new file mode 100644 index 00000000000..6fe9a3ca993 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/fields/AddFieldCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands.fields; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.item.DisplayItem; + +// @@author jasonchristopher21 +/** Command to rename a group/task/person */ +public class AddFieldCommand extends FieldCommand { + + public static final String SUBCOMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + + " :Assigns an attribute to a given item.\n" + + getFullCommand(SUBCOMMAND_WORD) + " [t|u|g]/id [type] [value]\n" + + "e.g. field add t/1 bug low severity\n" + + " OR task select 1 field add bug low severity\n" + + "Adds a new custom field called bug to attribute"; + + public static final String NO_INPUT = "No input item is chosen!"; + public static final String MESSAGE_DUPLICATE = "An item with the same name already exist!"; + public static final String OUT_OF_BOUNDS = "ID selected is out of bounds!"; + public static final String SUCCESS_MSG = "The item have been renamed!"; + public static final String INVALID_FORMAT = "The item cannot be renamed to such!"; + + private String type; + private String data; + private String ftype; + private Index index; + + /** + * Constructor to add a custom field + */ + public AddFieldCommand(Index index, String ftype, String type, String data) { + this.index = index; + this.type = type; + this.ftype = ftype; + this.data = data; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + DisplayItem item; + try { + item = selectFromRightModel(model, ftype, index); + } catch (ParseException e) { + throw new CommandException(MESSAGE_INVALID_COMMAND_FORMAT); + } + if (item == null && sItem == null) { + throw new CommandException(NO_INPUT); + } + if (item == null) { + item = sItem; + } + if (item.getAttribute(type).isPresent()) { + throw new CommandException(MESSAGE_DUPLICATE); + } + Attribute<?> attribute; + try { + attribute = ParserUtil.parseAttribute(type, data); + } catch (ParseException e) { + throw new CommandException(e.getMessage()); + } + item.addAttribute(attribute); + model.refresh(); + return new CommandResult(SUCCESS_MSG); + } +} diff --git a/src/main/java/seedu/address/logic/commands/fields/DeleteFieldCommand.java b/src/main/java/seedu/address/logic/commands/fields/DeleteFieldCommand.java new file mode 100644 index 00000000000..eb514cf7e78 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/fields/DeleteFieldCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands.fields; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.item.DisplayItem; + +// @@author jasonchristopher21 +/** Command to rename a group/task/person */ +public class DeleteFieldCommand extends FieldCommand { + + public static final String SUBCOMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + + " :Deletes an custom attribute to a given item.\n" + + getFullCommand(SUBCOMMAND_WORD) + " [t|u|g]/id [type]" + + "e.g. field delete t/1 bug" + + " OR task select 1 field delete bug\n" + + "Delete bug attribute of task 1"; + + public static final String NO_INPUT = "No input item is chosen!"; + public static final String MESSAGE_DUPLICATE = "An item with the same name already exist!"; + public static final String OUT_OF_BOUNDS = "ID selected is out of bounds!"; + public static final String SUCCESS_MSG = "The item have been renamed!"; + public static final String INVALID_FORMAT = "The item cannot be renamed to such!"; + + private String type; + private String ftype; + private Index index; + + /** + * Constructor to create a delete field command + */ + public DeleteFieldCommand(Index index, String ftype, String type) { + this.index = index; + this.type = type; + this.ftype = ftype; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + DisplayItem item; + try { + item = selectFromRightModel(model, ftype, index); + } catch (ParseException e) { + throw new CommandException(MESSAGE_INVALID_COMMAND_FORMAT); + } + if (item == null && sItem == null) { + throw new CommandException(NO_INPUT); + } + if (item == null) { + item = sItem; + } + item.deleteAttribute(type); + model.refresh(); + return new CommandResult(SUCCESS_MSG); + } +} diff --git a/src/main/java/seedu/address/logic/commands/fields/EditFieldCommand.java b/src/main/java/seedu/address/logic/commands/fields/EditFieldCommand.java new file mode 100644 index 00000000000..4bbfa0bacb6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/fields/EditFieldCommand.java @@ -0,0 +1,69 @@ +package seedu.address.logic.commands.fields; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.item.DisplayItem; + +// @@author jasonchristopher21 +/** Command to rename a group/task/person */ +public class EditFieldCommand extends FieldCommand { + + public static final String SUBCOMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + + " :Edits an existing attribute of a given item.\n" + + getFullCommand(SUBCOMMAND_WORD) + " [t|u|g]/id [type] [value]\n" + + "e.g. field edit t/1 bug low severity" + + " OR task select 1 field edit bug low severity\n" + + "Edit bug attribute of task to low severity"; + + public static final String NO_INPUT = "No input item is chosen!"; + public static final String MESSAGE_DUPLICATE = "An item with the same name already exist!"; + public static final String OUT_OF_BOUNDS = "ID selected is out of bounds!"; + public static final String SUCCESS_MSG = "The item have been renamed!"; + public static final String INVALID_FORMAT = "The item cannot be renamed to such!"; + + private String type; + private String data; + private String ftype; + private Index index; + + /** + * Constructor to create the data needed for editing attributes + * + * @param index + * @param ftype + * @param type + * @param data + */ + public EditFieldCommand(Index index, String ftype, String type, String data) { + this.index = index; + this.type = type; + this.ftype = ftype; + this.data = data; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + DisplayItem item; + try { + item = selectFromRightModel(model, ftype, index); + } catch (ParseException e) { + throw new CommandException(MESSAGE_INVALID_COMMAND_FORMAT); + } + if (item == null && sItem == null) { + throw new CommandException(NO_INPUT); + } + if (item == null) { + item = sItem; + } + item.editAttribute(type, data); + model.refresh(); + return new CommandResult(SUCCESS_MSG); + } +} diff --git a/src/main/java/seedu/address/logic/commands/fields/FieldCommand.java b/src/main/java/seedu/address/logic/commands/fields/FieldCommand.java new file mode 100644 index 00000000000..4fc59a8e743 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/fields/FieldCommand.java @@ -0,0 +1,65 @@ +package seedu.address.logic.commands.fields; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.item.DisplayItem; + +/** + * Commands for Tasks + */ +public abstract class FieldCommand extends Command { + + public static final String COMMAND_WORD = "field"; + private static final Pattern PATTERN = Pattern.compile("(?<type>[gtl])/(?<id>[0-9]+)\\s+(?<rest>.*)"); + protected DisplayItem sItem = null; + + /** + * Returns the complete command phrase for the task command with given subCommand + * + * @param subcommand The subcommand to be added + * @return The complete command phrase + */ + static String getFullCommand(String subcommand) { + return COMMAND_WORD + " " + subcommand; + } + + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof DisplayItem)) { + sItem = null; + return this; + } + sItem = (DisplayItem) additionalData; + return this; + } + + protected DisplayItem selectFromRightModel(Model model, String type, Index targetIndex) + throws ParseException, CommandException { + + switch (type) { + case "g": + return model.getFromFilteredTeams(targetIndex); + case "t": + return model.getFromFilteredTasks(targetIndex); + case "u": + return model.getFromFilteredPerson(targetIndex); + default: + return null; + } + } + + public static String getRestOfArgs(String args) { + Matcher match = PATTERN.matcher(args.trim()); + if (!match.matches()) { + return args.trim(); + } + return match.group("rest").trim(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/logicalcommand/CheckTaskCompleteCommand.java b/src/main/java/seedu/address/logic/commands/logicalcommand/CheckTaskCompleteCommand.java new file mode 100644 index 00000000000..caad344235f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/logicalcommand/CheckTaskCompleteCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands.logicalcommand; + +import java.time.LocalDateTime; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * Command to check whether a task is complete + */ +public class CheckTaskCompleteCommand extends Command { + + public static final String COMMAND_WORD = "isComplete"; + private static final String NO_SELECTED = "No task was selected!"; + private static final String INVALID_INPUT = "The input is not of type Task!"; + + private Task item = null; + + public CheckTaskCompleteCommand() {} + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof Task)) { + throw new CommandException(INVALID_INPUT); + } + item = (Task) additionalData; + return this; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (item == null) { + throw new CommandException(NO_SELECTED); + } + LocalDateTime dt = item.getCompletedTime(); + return new CommandResult(String.format("result is %s", dt != null), false, false, dt != null); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/logicalcommand/CmpCommand.java b/src/main/java/seedu/address/logic/commands/logicalcommand/CmpCommand.java new file mode 100644 index 00000000000..dad49f32dae --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/logicalcommand/CmpCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands.logicalcommand; + +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to add compare between object + */ +public class CmpCommand extends Command { + + public static final String COMMAND_WORD = "cmp"; + private static final String INVALID_INPUT = "The unknown operator"; + private static final String USE_MESSAGE = "op [< <= > >= == !=] Object"; + private static final String MISSINGINPUT = "Missing input"; + + private Object num = null; + private Function<Object, Boolean> func; + + /** + * Constructor to create a command command + */ + public CmpCommand(String op, String opNum) throws ParseException { + switch (op) { + case "==": + func = x -> opNum.equals(x.toString()); + break; + case "<": + func = x -> opNum.compareTo(x.toString()) < 0; + break; + case ">": + func = x -> opNum.compareTo(x.toString()) > 0; + break; + case "!=": + func = x -> !opNum.equals(x.toString()); + break; + default: + assert false; + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (num == null) { + throw new CommandException(MISSINGINPUT); + } + return new CommandResult(String.format("result: %s", func.apply(num)), false, false, func.apply(num)); + } + + /** + * Returns a parser to parse user input for Cmp Command + * + * @return + */ + public static Parser<CmpCommand> parser() { + return new Parser<CmpCommand>() { + @Override + public CmpCommand parse(String userInput) throws ParseException { + userInput = userInput.trim(); + if (userInput.length() == 0) { + throw new ParseException(INVALID_INPUT + "\n" + USE_MESSAGE); + } + Matcher res = Pattern.compile("([=<>!]+)\\s*(.*)").matcher(userInput); + if (!res.matches()) { + throw new ParseException(INVALID_INPUT + "\n" + USE_MESSAGE); + } + String op = res.group(1); + String val = res.group(2); + return new CmpCommand(op, val); + } + + }; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null) { + throw new CommandException(MISSINGINPUT); + } + + num = additionalData; + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/logicalcommand/ContainsAttributeCommand.java b/src/main/java/seedu/address/logic/commands/logicalcommand/ContainsAttributeCommand.java new file mode 100644 index 00000000000..a5e5063384f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/logicalcommand/ContainsAttributeCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands.logicalcommand; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.item.DisplayItem; + +/** + * Command to check if a displayItem contains a certain attribute. + */ +public class ContainsAttributeCommand extends Command { + + public static final String COMMAND_WORD = "contains"; + private static final String NO_SELECTED = "No item were selected!"; + private static final String INVALID_INPUT = "The input is not of type DisplayItem!"; + + private DisplayItem item = null; + private final String attributeType; + + public ContainsAttributeCommand(String attributeType) { + this.attributeType = attributeType.trim(); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof DisplayItem)) { + throw new CommandException(INVALID_INPUT); + } + item = (DisplayItem) additionalData; + return this; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (item == null) { + throw new CommandException(NO_SELECTED); + } + Boolean res = item.getAttribute(attributeType).isPresent(); + return new CommandResult(String.format("result is %s", res), false, false, res); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/logicalcommand/IfCommand.java b/src/main/java/seedu/address/logic/commands/logicalcommand/IfCommand.java new file mode 100644 index 00000000000..17f27783dfc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/logicalcommand/IfCommand.java @@ -0,0 +1,88 @@ +package seedu.address.logic.commands.logicalcommand; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Represent a command to do if else + */ +public class IfCommand extends Command { + + public static final String COMMAND_WORD = "if"; + public static final String MESSAGE_USAGE = "if [[logical commands]] ;; [[if true command]] ;; [[if else command]]"; + + private static final String NOT_BOOLEAN_COMMAND = "The command did not return a boolean conditional!"; + + private Object details = null; + private final Command ifC; + private final Command trueC; + private final Command elseC; + + /** + * Constructor to create a if else command + * + * @param ifString conditional command + * @param trueString command if true + * @param elseString command if false + * @throws ParseException thrown when if else cannot parse this command + */ + public IfCommand(String ifString, String trueString, String elseString) throws ParseException { + requireNonNull(ifString); + requireNonNull(trueString); + AddressBookParser p = AddressBookParser.get(); + try { + + ifC = p.parseCommand(ifString); + trueC = p.parseCommand(trueString); + if (elseString == null || elseString.equals("")) { + elseC = null; + } else { + elseC = p.parseCommand(elseString); + } + } catch (Exception e) { + throw new ParseException("Syntax error parsing if"); + } + } + + /** + * Constructor for when else statement is optional + * + * @param ifString + * @param trueString + * @throws ParseException + */ + public IfCommand(String ifString, String trueString) throws ParseException { + this(ifString, trueString, null); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + details = additionalData; + return this; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + ifC.setInput(details); + CommandResult res = ifC.execute(model); + Boolean result = (Boolean) res.getResult().filter(v -> v instanceof Boolean) + .orElseThrow(() -> new CommandException(NOT_BOOLEAN_COMMAND)); + + if (result) { + trueC.setInput(details); + trueC.execute(model); + } else if (elseC != null) { + elseC.setInput(details); + elseC.execute(model); + } + + return new CommandResult("if command has been executed!"); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/logicalcommand/SeqCommand.java b/src/main/java/seedu/address/logic/commands/logicalcommand/SeqCommand.java new file mode 100644 index 00000000000..6f6a6f2525a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/logicalcommand/SeqCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands.logicalcommand; + +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Commands to allow the user to chain commands together + */ +public class SeqCommand extends Command { + + public static final String COMMAND_WORD = "seq"; + private static final String USE_MESSAGE = "seq command1 (; command2) (| command3) (; ...) (| ...)"; + + private Object ctx; + private List<String> replacers; + + public SeqCommand(List<String> replacements) throws ParseException { + this.replacers = replacements; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + AddressBookParser compiler = AddressBookParser.get(); + Object prevResult = ctx; + Object toApply = ctx; + String tmp; + CommandResult ret = new CommandResult("All commands failed or no commands were supplied"); + int count = 0; + int fail = 0; + for (int i = 0; i < replacers.size(); i++) { + tmp = replacers.get(i).trim(); + if (tmp.equals(";")) { + toApply = ctx; + prevResult = ctx; + continue; + } else if (tmp.equals("|")) { + toApply = prevResult; + continue; + } + count++; + try { + Command c = compiler.parseCommand(tmp); + c.setInput(toApply); + ret = c.execute(model); + prevResult = ret.getResult().orElse(null); + } catch (ParseException pe) { + fail++; + } + } + return ret; + } + + /** + * Parser to parse user input for seq command + * + * @return + */ + public static Parser<SeqCommand> parser() { + return new Parser<SeqCommand>() { + @Override + public SeqCommand parse(String userInput) throws ParseException { + if (userInput.trim().length() == 0) { + throw new ParseException(USE_MESSAGE); + } + List<String> res = Arrays.asList(userInput.trim().split("((?<=[;\\|])|(?=[;\\|]\\s*))")); + return new SeqCommand(res); + } + }; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + this.ctx = additionalData; + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/operators/OpsCommand.java b/src/main/java/seedu/address/logic/commands/operators/OpsCommand.java new file mode 100644 index 00000000000..81fcdf18b18 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/operators/OpsCommand.java @@ -0,0 +1,104 @@ +package seedu.address.logic.commands.operators; + +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to do arithmatic operations + */ +public class OpsCommand extends Command { + + public static final String COMMAND_WORD = "ops"; + private static final String INVALID_INPUT = "The unknown operator"; + private static final String USE_MESSAGE = "op [+-*/] number"; + private static final String RUNTIME_ERR = "Unable to convert to number"; + private static final String DIV0 = "Cannot divide by zero"; + private static final String MISSINGINPUT = "Missing input of type float"; + + private Float num = null; + private Function<Float, Float> func; + + /** + * Constructor for operation command + */ + public OpsCommand(String op, Float opNum) throws ParseException { + switch (op) { + case "/": + if (opNum == 0) { + throw new ParseException(DIV0); + } + func = x -> x / opNum; + break; + case "*": + func = x -> x * opNum; + break; + case "+": + func = x -> x + opNum; + break; + case "-": + func = x -> x - opNum; + break; + default: + assert false; + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (num == null) { + throw new CommandException(MISSINGINPUT); + } + return new CommandResult(String.format("result: %f", func.apply(num)), false, false, func.apply(num)); + } + + /** + * Parser to parse user input operations command + * + * @return + */ + public static Parser<OpsCommand> parser() { + return new Parser<OpsCommand>() { + @Override + public OpsCommand parse(String userInput) throws ParseException { + userInput = userInput.trim(); + if (userInput.length() == 0) { + throw new ParseException(INVALID_INPUT + "\n" + USE_MESSAGE); + } + Matcher res = Pattern.compile("([\\/+\\-*])\\s*([\\-+]?[0-9]+[.]?[0-9]*)\\s*").matcher(userInput); + if (!res.matches()) { + throw new ParseException(INVALID_INPUT + "\n" + USE_MESSAGE); + } + String op = res.group(1); + Float val = Float.parseFloat(res.group(2)); + return new OpsCommand(op, val); + } + + }; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null) { + throw new CommandException(RUNTIME_ERR); + } + if (!(additionalData instanceof Float)) { + try { + num = Float.parseFloat(additionalData.toString()); + } catch (NumberFormatException e) { + throw new CommandException(RUNTIME_ERR); + } + return this; + } + + num = (Float) additionalData; + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/operators/PrintCommand.java b/src/main/java/seedu/address/logic/commands/operators/PrintCommand.java new file mode 100644 index 00000000000..10151bfb487 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/operators/PrintCommand.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands.operators; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to print the value of an object + */ +public class PrintCommand extends Command { + + public static final String COMMAND_WORD = "print"; + + private String toPrint = "No values were supplied!"; + + public PrintCommand() throws ParseException {} + + @Override + public CommandResult execute(Model model) throws CommandException { + return new CommandResult(toPrint); + } + + /** + * Returns a parser to parse user input for print command + */ + public static Parser<PrintCommand> parser() { + return new Parser<PrintCommand>() { + @Override + public PrintCommand parse(String userInput) throws ParseException { + return new PrintCommand(); + } + + }; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null) { + return this; + } + toPrint = additionalData.toString(); + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/operators/StringReplaceCommand.java b/src/main/java/seedu/address/logic/commands/operators/StringReplaceCommand.java new file mode 100644 index 00000000000..43b57d2928e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/operators/StringReplaceCommand.java @@ -0,0 +1,72 @@ +package seedu.address.logic.commands.operators; + +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Command to do string replacements + */ +public class StringReplaceCommand extends Command { + + public static final String COMMAND_WORD = "r"; + private static final String INVALID_INPUT = "Missing Strings"; + private static final String USE_MESSAGE = "replace $replacement txt\\txt %s to replace"; + + private String txt = null; + private List<String> replacers; + + public StringReplaceCommand(List<String> replacements) throws ParseException { + this.replacers = replacements; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + String target; + if (replacers.size() == 2) { + txt = replacers.get(0); + target = replacers.get(1); + } else { + if (replacers.size() != 1 || txt == null) { + throw new CommandException(INVALID_INPUT); + } + target = replacers.get(0); + } + target = target.replaceAll("%s", txt); + + return new CommandResult("result is: " + target, false, false, target); + } + + /** + * Returns a parser to parse user input for replace command. + */ + public static Parser<StringReplaceCommand> parser() { + return new Parser<StringReplaceCommand>() { + @Override + public StringReplaceCommand parse(String userInput) throws ParseException { + + if (userInput.trim().length() == 0) { + throw new ParseException(USE_MESSAGE); + } + List<String> val = Arrays.asList(userInput.trim().split("\\\\", 2)); + return new StringReplaceCommand(val); + } + }; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || additionalData.toString().trim() == "") { + txt = null; + return this; + } + txt = additionalData.toString(); + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/persons/AddPersonCommand.java similarity index 51% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/address/logic/commands/persons/AddPersonCommand.java index 71656d7c5c8..073c8fc26b4 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/persons/AddPersonCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.persons; import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; @@ -7,6 +7,9 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Person; @@ -14,24 +17,24 @@ /** * Adds a person to the address book. */ -public class AddCommand extends Command { +public class AddPersonCommand extends PersonCommand implements PureCommandInterface { - public static final String COMMAND_WORD = "add"; + public static final String SUBCOMMAND_WORD = "new"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\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"; + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + ": Adds a person to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + getFullCommand(SUBCOMMAND_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"; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; @@ -41,7 +44,7 @@ public class AddCommand extends Command { /** * Creates an AddCommand to add the specified {@code Person} */ - public AddCommand(Person person) { + public AddPersonCommand(Person person) { requireNonNull(person); toAdd = person; } @@ -49,19 +52,24 @@ public AddCommand(Person person) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - + toAdd.setParent(model.getContextContainer()); if (model.hasPerson(toAdd)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), false, false, toAdd); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); + || (other instanceof AddPersonCommand // instanceof handles nulls + && toAdd.equals(((AddPersonCommand) other).toAdd)); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; } } diff --git a/src/main/java/seedu/address/logic/commands/persons/PersonCommand.java b/src/main/java/seedu/address/logic/commands/persons/PersonCommand.java new file mode 100644 index 00000000000..f66954632e8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/persons/PersonCommand.java @@ -0,0 +1,36 @@ +// @@author connlim +package seedu.address.logic.commands.persons; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Person; + +/** + * Commands for Tasks + */ +public abstract class PersonCommand extends Command { + + public static final String COMMAND_WORD = "person"; + protected Person person = null; + + /** + * Returns the complete command phrase for the task command with given subCommand + * + * @param subcommand The subcommand to be added + * @return The complete command phrase + */ + public static String getFullCommand(String subcommand) { + return COMMAND_WORD + " " + subcommand; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof Person)) { + person = null; + return this; + } + person = (Person) additionalData; + return this; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/tasks/AddTaskCommand.java b/src/main/java/seedu/address/logic/commands/tasks/AddTaskCommand.java new file mode 100644 index 00000000000..14fe6a22c2a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tasks/AddTaskCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +// @@author connlim + +/** + * Create a task and assign it to a group + */ +public class AddTaskCommand extends TaskCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + ": Adds a task to the address book current team. " + + "Parameters: " + PREFIX_TITLE + "NAME " + PREFIX_DESCRIPTION + "Description"; + + public static final String MESSAGE_SUCCESS = "New task have been added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists!"; + public static final String MESSAGE_NEED_TO_BE_IN_TEAM = "This task cannot be created here!"; + + private final Task toAdd; + + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public AddTaskCommand(Task task) { + requireNonNull(task); + toAdd = task; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getContextContainer() == null) { + throw new CommandException(MESSAGE_NEED_TO_BE_IN_TEAM); + } + toAdd.setParent(model.getContextContainer()); + if (model.hasTask(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.addTask(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), false, false, toAdd); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTaskCommand // instanceof handles nulls + && toAdd.equals(((AddTaskCommand) other).toAdd)); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + // creation of task do not require previous input + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/tasks/MarkTaskCommand.java b/src/main/java/seedu/address/logic/commands/tasks/MarkTaskCommand.java new file mode 100644 index 00000000000..3d69d79243a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tasks/MarkTaskCommand.java @@ -0,0 +1,53 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +// @@author connlim + +/** + * Marks a task as complete + */ +public class MarkTaskCommand extends TaskCommand { + public static final String SUBCOMMAND_WORD = "mark"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + ": Marks the task as completed\n" + + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1\n"; + + public static final String COMPLETE_SUCESS = " task %s is marked as complete%n"; + public static final String ALREADY_MARKED = " task %s is already completed%n"; + + private final Index targetIndex; + + public MarkTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (task == null && targetIndex == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + if (task == null) { + task = model.getFromFilteredTasks(targetIndex); + } + + Task newTask = task.mark(); + if (newTask == task) { + throw new CommandException(String.format(ALREADY_MARKED, task)); + } + model.setTask(task, task.mark()); + model.refresh(); + return new CommandResult(String.format(COMPLETE_SUCESS, task)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/tasks/TaskCommand.java b/src/main/java/seedu/address/logic/commands/tasks/TaskCommand.java new file mode 100644 index 00000000000..b078e9e4786 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tasks/TaskCommand.java @@ -0,0 +1,37 @@ +// @@author connlim +package seedu.address.logic.commands.tasks; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.task.Task; + +/** + * Commands for Tasks + */ +public abstract class TaskCommand extends Command { + + public static final String COMMAND_WORD = "task"; + public static final String INVALID_INPUT = "This command cannot take in non task element"; + protected Task task = null; + + /** + * Returns the complete command phrase for the task command with given subCommand + * + * @param subcommand The subcommand to be added + * @return The complete command phrase + */ + public static String getFullCommand(String subcommand) { + return "task " + subcommand; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof Task)) { + task = null; + return this; + } + + task = (Task) additionalData; + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/tasks/UnmarkTaskCommand.java b/src/main/java/seedu/address/logic/commands/tasks/UnmarkTaskCommand.java new file mode 100644 index 00000000000..f1ffdad97b0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tasks/UnmarkTaskCommand.java @@ -0,0 +1,51 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +// @@author connlim + +/** + * Unmarks a task as complete. + */ +public class UnmarkTaskCommand extends TaskCommand { + public static final String SUBCOMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + ": Marks the task as incomplete\n" + + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1\n"; + + public static final String ALREADY_UNMARKED = " task %s is already incomplete%n"; + public static final String UNMARK_SUCCESS = " task %s is marked as incomplete%n"; + + private final Index targetIndex; + + public UnmarkTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (targetIndex == null && task == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + if (task == null) { + task = model.getFromFilteredTasks(targetIndex); + } + + Task newTask = task.unmark(); + if (newTask == task) { + throw new CommandException(ALREADY_UNMARKED); + } + model.setTask(task, task.unmark()); + return new CommandResult(String.format(UNMARK_SUCCESS, task)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/teams/AddTeamCommand.java b/src/main/java/seedu/address/logic/commands/teams/AddTeamCommand.java new file mode 100644 index 00000000000..0626fee4db4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/teams/AddTeamCommand.java @@ -0,0 +1,54 @@ +// @@author mohamedsaf1 + +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +/** + * Add a team to the address book. + */ +public class AddTeamCommand extends TeamCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "new"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + " " + SUBCOMMAND_WORD + + ": Add a new group in the current " + + "group scope if the group name does not currently exist.\n" + + "The group name should only begin with a letter and " + + "be alphanumeric and have hyphens and/or underscores only\n" + + "Parameters: group_name/group_within_group_name\n" + + "Example: " + COMMAND_WORD + " " + SUBCOMMAND_WORD + " group_c345"; + // + "Example: " + COMMAND_WORD + " group_1/group_a\n" + + public static final String MESSAGE_SUCCESS = "New team added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This team already exists in the address book"; + + private Group toAdd; + + public AddTeamCommand(Group toAdd) { + this.toAdd = toAdd; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + toAdd.setParent(model.getContextContainer()); + if (model.hasTeam(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + model.addTeam(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), false, false, toAdd); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + // this method does not take in an input + return this; + } +} diff --git a/src/main/java/seedu/address/logic/commands/teams/AddUserToTeamCommand.java b/src/main/java/seedu/address/logic/commands/teams/AddUserToTeamCommand.java new file mode 100644 index 00000000000..b9f028dfb37 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/teams/AddUserToTeamCommand.java @@ -0,0 +1,70 @@ +// @@author mohamedsaf1 +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USER; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.item.exceptions.ItemCannotBeParentException; +import seedu.address.model.person.Person; + +/** + * Adds an existing user to a team + */ +public class AddUserToTeamCommand extends PureCommand { + public static final String COMMAND_WORD = "assign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Assigns an existing contact to an existing team with the specified index\n" + + "Parameters: " + + PREFIX_GROUP + "INDEX of team" + + PREFIX_USER + "INDEX of contact\n" + + "Where INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " u/1 t/2\n"; + + public static final String ASSIGN_SUCCESS = " Contact %s have been assigned to %s%n"; + + private final Index userIndex; + private final Index grpIndex; + + /** + * Assigns user to group based on their given index + */ + public AddUserToTeamCommand(Index userIndex, Index grpIndex) { + this.userIndex = userIndex; + this.grpIndex = grpIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Group> lastShownTeamList = model.getFilteredTeamList(); + List<Person> lastShownUserList = model.getFilteredPersonList(); + + if (grpIndex.getZeroBased() >= lastShownTeamList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TEAM_DISPLAYED_INDEX); + } + + if (userIndex.getZeroBased() >= lastShownUserList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Group grp = lastShownTeamList.get(grpIndex.getZeroBased()); + Person user = lastShownUserList.get(userIndex.getZeroBased()); + try { + user.setParent(grp); + } catch (ItemCannotBeParentException e) { + throw new CommandException(String.format("%s is already in the group!", user.getName())); + } + return new CommandResult(String.format(ASSIGN_SUCCESS, user, grp)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/teams/ChangeTeamCommand.java b/src/main/java/seedu/address/logic/commands/teams/ChangeTeamCommand.java new file mode 100644 index 00000000000..92474d1ba9f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/teams/ChangeTeamCommand.java @@ -0,0 +1,87 @@ +// @@author mohamedsaf1 +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.TeamInputCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.item.AbstractSingleItem; + +/** + * Changes a current working context of the team + */ +public class ChangeTeamCommand extends TeamInputCommand { + public static final String COMMAND_WORD = "cg"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Changes the current context to the index specified\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1\n" + + "To go back to the previous context, use\n" + + COMMAND_WORD + " .."; + + public static final String SWITCH_SUCCESS = " switched to %s%n"; + + private final Index targetIndex; + private final int status; + // status table + // 1 - normal/read from index + // 0 - traverse up 1 directory + // -1 - traverse to root + // 2 - use setter + + /** + * Constructor for cg command + */ + public ChangeTeamCommand(Index targetIndex) { + this.targetIndex = targetIndex; + status = 1; + } + + /** + * Constructor when cg is expected to receive an input + */ + public ChangeTeamCommand(int status) { + this.targetIndex = null; + this.status = status; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + AbstractSingleItem toSwitch; + if (status == 1) { + List<Group> lastShownList = model.getFilteredTeamList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TEAM_DISPLAYED_INDEX); + } + + toSwitch = lastShownList.get(targetIndex.getZeroBased()); + } else if (status == 0) { + if (model.getContextContainer() != null) { + toSwitch = model.getContextContainer().getParent(); + } else { + return new CommandResult("No more parent!"); + } + } else if (status == -1) { + toSwitch = null; + } else { + assert status == -2; + if (group == null) { + throw new CommandException("Method takes in an input of group!"); + } + toSwitch = group; + } + + model.updateContextContainer(toSwitch); + return new CommandResult(String.format(SWITCH_SUCCESS, toSwitch == null ? "root" : toSwitch.toString())); + } +} diff --git a/src/main/java/seedu/address/logic/commands/teams/RemoveUserFromTeamCommand.java b/src/main/java/seedu/address/logic/commands/teams/RemoveUserFromTeamCommand.java new file mode 100644 index 00000000000..0715c7ee2c7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/teams/RemoveUserFromTeamCommand.java @@ -0,0 +1,73 @@ +// @@author mohamedsaf1 + +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.person.Person; + +/** + * Removes a user from the current context + */ +public class RemoveUserFromTeamCommand extends TeamCommand { + public static final String SUBCOMMAND_WORD = "remove"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + " " + SUBCOMMAND_WORD + + ": Removes the user specified by the index from the current team when in the team context\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " " + SUBCOMMAND_WORD + " 1\n"; + + public static final String REMOVAL_SUCCESS = " User %s has been removed from %s%n"; + public static final String INVALID_INPUT = "This method takes in only Person types!"; + + private Person toRemove = null; + private final Index targetIndex; + + public RemoveUserFromTeamCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof Person)) { + toRemove = null; + return this; + } + toRemove = (Person) additionalData; + return this; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + AbstractSingleItem currContext = model.getContextContainer(); + if (currContext == null) { + return new CommandResult("You are not in any team scope right now!"); + } + + if (toRemove == null) { + List<Person> lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + toRemove = lastShownList.get(targetIndex.getZeroBased()); + } else if (toRemove == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + toRemove.removeParent(currContext); + model.updateContextContainer(currContext); + return new CommandResult(String.format(REMOVAL_SUCCESS, toRemove, currContext)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/teams/TeamCommand.java b/src/main/java/seedu/address/logic/commands/teams/TeamCommand.java new file mode 100644 index 00000000000..f6a581ddbd7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/teams/TeamCommand.java @@ -0,0 +1,22 @@ +// @@author connlim +package seedu.address.logic.commands.teams; + +import seedu.address.logic.commands.Command; + +/** + * Commands for Tasks + */ +public abstract class TeamCommand extends Command { + + public static final String COMMAND_WORD = "team"; + + /** + * Returns the complete command phrase for the task command with given subCommand + * + * @param subcommand The subcommand to be added + * @return The complete command phrase + */ + public static String getFullCommand(String subcommand) { + return COMMAND_WORD + " " + subcommand; + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -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_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; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -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 - */ -public class AddCommandParser implements Parser<AddCommand> { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - 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<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * 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/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..0e84823d237 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -3,29 +3,186 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import java.util.HashMap; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import seedu.address.logic.commands.AddCommand; 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.CustomCommandBuilder; 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.RenameCommand; +import seedu.address.logic.commands.creationcommand.AliasCommand; +import seedu.address.logic.commands.creationcommand.CreateCommand; +import seedu.address.logic.commands.creationcommand.DeleteCustomCommand; +import seedu.address.logic.commands.creationcommand.ExecuteCommand; +import seedu.address.logic.commands.creationcommand.FloatCommand; +import seedu.address.logic.commands.creationcommand.IntCommand; +import seedu.address.logic.commands.creationcommand.StringCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.fields.FieldCommand; +import seedu.address.logic.commands.logicalcommand.CheckTaskCompleteCommand; +import seedu.address.logic.commands.logicalcommand.CmpCommand; +import seedu.address.logic.commands.logicalcommand.ContainsAttributeCommand; +import seedu.address.logic.commands.logicalcommand.IfCommand; +import seedu.address.logic.commands.logicalcommand.SeqCommand; +import seedu.address.logic.commands.operators.OpsCommand; +import seedu.address.logic.commands.operators.PrintCommand; +import seedu.address.logic.commands.operators.StringReplaceCommand; +import seedu.address.logic.commands.persons.PersonCommand; +import seedu.address.logic.commands.tasks.MarkTaskCommand; +import seedu.address.logic.commands.tasks.TaskCommand; +import seedu.address.logic.commands.tasks.UnmarkTaskCommand; +import seedu.address.logic.commands.teams.AddUserToTeamCommand; +import seedu.address.logic.commands.teams.ChangeTeamCommand; +import seedu.address.logic.commands.teams.TeamCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.fields.FieldCommandParser; +import seedu.address.logic.parser.logiccommands.CheckTaskCompleteCommandParser; +import seedu.address.logic.parser.logiccommands.ContainsAttributeCommandParser; +import seedu.address.logic.parser.logiccommands.IfCommandParser; +import seedu.address.logic.parser.persons.PersonCommandParser; +import seedu.address.logic.parser.tasks.MarkTaskCommandParser; +import seedu.address.logic.parser.tasks.TaskCommandParser; +import seedu.address.logic.parser.tasks.UnmarkTaskCommandParser; +import seedu.address.logic.parser.teams.TeamCommandParser; /** * Parses user input. */ public class AddressBookParser { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); + private static final Pattern NAME_CHECK = Pattern.compile("([a-zA-Z][a-zA-Z0-9_\\-]*)"); + private static AddressBookParser bp = null; + private static Map<String, ThrowFunction<String, Command>> defaultMapper; + + private final Map<String, CustomCommandBuilder> bonusMapper; + private final Map<String, String> aliasMapper; + + @FunctionalInterface + private interface ThrowFunction<T, R> { + R apply(T t) throws ParseException; + } + + static { + defaultMapper = new HashMap<>(); + defaultMapper.put(ClearCommand.COMMAND_WORD, k -> new ClearCommand()); + defaultMapper.put(ListCommand.COMMAND_WORD, k -> new ListCommand()); + defaultMapper.put(ExitCommand.COMMAND_WORD, k -> new ExitCommand()); + defaultMapper.put(HelpCommand.COMMAND_WORD, k -> new HelpCommand()); + defaultMapper.put(ClearCommand.COMMAND_WORD, k -> new ClearCommand()); + defaultMapper.put(ChangeTeamCommand.COMMAND_WORD, k -> new ChangeTeamCommandParser().parse(k)); + defaultMapper.put(TaskCommand.COMMAND_WORD, k -> new TaskCommandParser().parse(k)); + defaultMapper.put(AddUserToTeamCommand.COMMAND_WORD, k -> + new seedu.address.logic.parser.teams.AddUserToTeamCommandParser().parse(k)); + defaultMapper.put(TeamCommand.COMMAND_WORD, k -> new TeamCommandParser().parse(k)); + defaultMapper.put(CheckTaskCompleteCommand.COMMAND_WORD, k -> new CheckTaskCompleteCommandParser().parse(k)); + defaultMapper.put(ContainsAttributeCommand.COMMAND_WORD, k -> new ContainsAttributeCommandParser().parse(k)); + defaultMapper.put(IfCommand.COMMAND_WORD, k -> new IfCommandParser().parse(k)); + defaultMapper.put(MarkTaskCommand.SUBCOMMAND_WORD, k -> new MarkTaskCommandParser().parse(k)); + defaultMapper.put(UnmarkTaskCommand.SUBCOMMAND_WORD, k -> new UnmarkTaskCommandParser().parse(k)); + defaultMapper.put(RenameCommand.COMMAND_WORD, k -> new RenameCommandParser().parse(k)); + defaultMapper.put(PersonCommand.COMMAND_WORD, k -> new PersonCommandParser().parse(k)); + defaultMapper.put(FloatCommand.COMMAND_WORD, k -> FloatCommand.parser().parse(k)); + defaultMapper.put(IntCommand.COMMAND_WORD, k -> IntCommand.parser().parse(k)); + defaultMapper.put(StringCommand.COMMAND_WORD, k -> StringCommand.parser().parse(k)); + defaultMapper.put(SeqCommand.COMMAND_WORD, k -> SeqCommand.parser().parse(k)); + defaultMapper.put(OpsCommand.COMMAND_WORD, k -> OpsCommand.parser().parse(k)); + defaultMapper.put(PrintCommand.COMMAND_WORD, k -> PrintCommand.parser().parse(k)); + defaultMapper.put(StringReplaceCommand.COMMAND_WORD, k -> StringReplaceCommand.parser().parse(k)); + defaultMapper.put(CreateCommand.COMMAND_WORD, k -> CreateCommand.parser().parse(k)); + defaultMapper.put(DeleteCustomCommand.COMMAND_WORD, k -> DeleteCustomCommand.parser().parse(k)); + defaultMapper.put(AliasCommand.COMMAND_WORD, k -> AliasCommand.parser().parse(k)); + defaultMapper.put(FieldCommand.COMMAND_WORD, k -> new FieldCommandParser().parse(k)); + defaultMapper.put(CmpCommand.COMMAND_WORD, k -> CmpCommand.parser().parse(k)); + defaultMapper.put(ExecuteCommand.COMMAND_WORD, k -> ExecuteCommand.parser().parse(k)); + } + + private AddressBookParser() { + bonusMapper = new HashMap<>(); + aliasMapper = new HashMap<>(); + } + + public static AddressBookParser get() { + if (bp == null) { + bp = new AddressBookParser(); + } + return bp; + } + /** - * Used for initial separation of command word and args. + * Checks if the name is a valid command name */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); + public static boolean isValidName(String test) { + if (test == null) { + return false; + } + return NAME_CHECK.matcher(test.trim()).matches(); + } + + /** + * Checks if the name provided is currently available to be used + */ + public boolean isKeyAvailable(String key) { + return !defaultMapper.containsKey(key) + && !bonusMapper.containsKey(key) + && !aliasMapper.containsKey(key); + } + + /** + * Adds a custom command macro + */ + public void addCommand(CustomCommandBuilder builder) { + bonusMapper.put(builder.getRepr(), builder); + } + + /** + * Adds an alias to an existing command + */ + public void addAlias(String alias, String command) { + if (aliasMapper.containsKey(command)) { + addAlias(alias, aliasMapper.get(command)); + return; + } + if (defaultMapper.containsKey(command) || bonusMapper.containsKey(command)) { + aliasMapper.put(alias, command); + } + } + + public Map<String, String> getAliasMapper() { + return aliasMapper; + } + + public Map<String, CustomCommandBuilder> getBonusMapper() { + return bonusMapper; + } + + /** + * Deletes the repr command from parser of it exist. + */ + public void deleteCommand(String repr) { + if (repr == null) { + return; + } + if (aliasMapper.containsKey(repr)) { + aliasMapper.remove(repr); + return; + } + + if (bonusMapper.containsKey(repr)) { + bonusMapper.remove(repr); + } + + for (String value : aliasMapper.keySet()) { + if (aliasMapper.get(value).equals(repr)) { + aliasMapper.remove(value); + } + } + } /** * Parses user input into command for execution. @@ -40,37 +197,42 @@ public Command parseCommand(String userInput) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); } - final String commandWord = matcher.group("commandWord"); + String commandWord = matcher.group("commandWord").trim(); final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + while (aliasMapper.containsKey(commandWord)) { + commandWord = aliasMapper.get(commandWord); + } + if (defaultMapper.containsKey(commandWord)) { + return defaultMapper.get(commandWord).apply(arguments); + } else if (bonusMapper.containsKey(commandWord)) { + return bonusMapper.get(commandWord).build(); + } + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + /** + * Returns a command that runs to execute and parse in ctx as the input + */ + public static Command quickCommand(String toExecute, Object ctx) throws CommandException { + try { + Command ret = AddressBookParser.get().parseCommand(toExecute); + ret.setInput(ctx); + return ret; + } catch (ParseException e) { + throw new CommandException(e.getMessage()); } } + /** + * Returns true if the given command is valid + */ + public static boolean isValidCommand(String toExecute) { + try { + AddressBookParser.get().parseCommand(toExecute); + return true; + } catch (ParseException e) { + return false; + } + } } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..bfb7e9b6476 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -70,7 +70,7 @@ private static List<PrefixPosition> findPrefixPositions(String argsString, Prefi * {@code fromIndex} = 0, this method returns 5. */ private static int findPrefixPosition(String argsString, String prefix, int fromIndex) { - int prefixIndex = argsString.indexOf(" " + prefix, fromIndex); + int prefixIndex = argsString.toLowerCase().indexOf(" " + prefix.toLowerCase(), fromIndex); return prefixIndex == -1 ? -1 : prefixIndex + 1; // +1 as offset for whitespace } diff --git a/src/main/java/seedu/address/logic/parser/ChangeTeamCommandParser.java b/src/main/java/seedu/address/logic/parser/ChangeTeamCommandParser.java new file mode 100644 index 00000000000..c01d65e5071 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ChangeTeamCommandParser.java @@ -0,0 +1,43 @@ +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.teams.ChangeTeamCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author autumn-sonata +/** + * Parses input arguments and creates a new ChangeTeamCommand object + */ +public class ChangeTeamCommandParser implements Parser<ChangeTeamCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the + * DeleteCommand + * and returns a DeleteCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ChangeTeamCommand parse(String args) throws ParseException { + try { + if (args.trim().equals("..")) { + return new ChangeTeamCommand(0); + } + + if (args.trim().equals("/")) { + return new ChangeTeamCommand(-1); + } + + if (args.trim().equals("")) { + return new ChangeTeamCommand(-2); + } + Index index = ParserUtil.parseIndex(args); + return new ChangeTeamCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeTeamCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..da07d77bcc2 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -1,7 +1,8 @@ package seedu.address.logic.parser; /** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + * Contains Command Line Interface (CLI) syntax definitions common to multiple + * commands */ public class CliSyntax { @@ -11,5 +12,12 @@ public class CliSyntax { 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_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_TITLE = new Prefix("t/"); + public static final Prefix PREFIX_GROUP = new Prefix("g/"); + public static final Prefix PREFIX_USER = new Prefix("u/"); + public static final Prefix PREFIX_STATUS = new Prefix("s/"); + /* Prefix for Fields */ + public static final FieldPrefixes PREFIX_FIELD = new FieldPrefixes(); } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -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_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.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser<EditCommand> { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection<String> tags} into a {@code Set<Tag>} 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<Tag>} containing zero tags. - */ - private Optional<Set<Tag>> parseTagsForEdit(Collection<String> tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection<String> 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/FieldPrefixes.java b/src/main/java/seedu/address/logic/parser/FieldPrefixes.java new file mode 100644 index 00000000000..e8c508002fd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FieldPrefixes.java @@ -0,0 +1,116 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Contains the prefixes of the fields declared by the user + */ +public class FieldPrefixes { + + // A list of Prefix instances + private List<Prefix> prefixes; + + // Contains the pairings of the prefix and field name + private HashMap<Prefix, String> map; + + /** + * Constructs a new FieldPrefixes instance + */ + public FieldPrefixes() { + prefixes = new ArrayList<>(); + map = new HashMap<>(); + } + + /** + * Checks if the given prefix has previously been declared by the user. + * + * @param prefix A given Prefix instance. + * @return true if prefix is in the list of known prefixes, false otherwise. + */ + public boolean contains(Prefix prefix) { + return prefixes.contains(prefix); + } + + /** + * Adds a prefix and a corresponding field name to the model. + * + * @param prefix The Prefix instance representing the prefix of the field. + * @param name The name of the field. + * @param model {@code Model} which the prefix and field name should be added + * on. + * @throws ParseException if the prefix or the field has been declared + * previously. + */ + public void addPrefix(Prefix prefix, String name, Model model) throws ParseException { + if (matchesDefaultPrefixes(prefix) || prefixes.contains(prefix)) { + throw new ParseException("Prefix has been stored previously. Enter a different prefix"); + } + if (map.values().contains(name)) { + throw new ParseException("Field has been stored previously. Enter a different field"); + } + prefixes.add(prefix); + map.put(prefix, name); + } + + /** + * Removes a prefix from the known list of prefixes. + * + * @param prefix The Prefix instance to be removed + * @throws ParseException if the Prefix can not be found. + */ + public void removePrefix(Prefix prefix) throws ParseException { + if (!prefixes.contains(prefix)) { + throw new ParseException("Field not found"); + } + prefixes.remove(prefix); + map.remove(prefix); + } + + /** + * Removes a field from the known list of fields. + * + * @param fieldName The name of the field to be removed. + * @param model {@code Model} which the field name should be removed from. + * @throws ParseException if the field name can not be found. + */ + public void removeField(String fieldName, Model model) throws ParseException { + Prefix prefix; + List<Prefix> lst = map.entrySet().stream() + .filter(entry -> fieldName.equalsIgnoreCase(entry.getValue())) + .map(entry -> entry.getKey()) + .collect(Collectors.toList()); + if (lst.isEmpty()) { + throw new ParseException("Field not found"); + } + prefix = lst.get(0); + map.remove(prefix); + prefixes.remove(prefix); + } + + /** + * Checks if a given prefix is the same as the five default prefixes + * contained in CLI Syntax. + * + * @param prefix A given Prefix instance. + * @return true if the prefix matches the default prefixes, false otherwise. + */ + public boolean matchesDefaultPrefixes(Prefix prefix) { + return prefix.equals(PREFIX_ADDRESS) + || prefix.equals(PREFIX_NAME) + || prefix.equals(PREFIX_PHONE) + || prefix.equals(PREFIX_TAG) + || prefix.equals(PREFIX_EMAIL); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..ed6b4e20d7b 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,33 +1,52 @@ package seedu.address.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - import java.util.Arrays; +import java.util.function.Predicate; +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Retriever; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.item.NameContainsKeywordsPredicate; /** - * Parses input arguments and creates a new FindCommand object + * Parses user input and returns a find command for finding items */ -public class FindCommandParser implements Parser<FindCommand> { +public class FindCommandParser<T extends DisplayItem> implements Parser<FindCommand<T>> { + + private final Changer<Predicate<T>> changer; + private final Retriever<Integer> getSize; + + /** + * Constructor to create a find command parser + * + * @param changer sam to set boolean of observable list + * @param getSize sam to receive length of observable list + */ + public FindCommandParser(Changer<Predicate<T>> changer, Retriever<Integer> getSize) { + this.changer = changer; + this.getSize = getSize; + } /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. + * 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 */ - public FindCommand parse(String args) throws ParseException { + public FindCommand<T> parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + return new FindCommand<T>(null, changer, getSize); } String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindCommand<T>( + new NameContainsKeywordsPredicate<T>(Arrays.asList(nameKeywords)), + changer, + getSize); } } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..98f378693c6 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,6 +1,9 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.StringUtil.properCase; +import static seedu.address.model.AccessDisplayFlags.DEFAULT; +import static seedu.address.model.AccessDisplayFlags.DEFAULT_STYLE; import java.util.Collection; import java.util.HashSet; @@ -9,10 +12,16 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.attribute.AbstractAttribute; +import seedu.address.model.attribute.Address; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Description; +import seedu.address.model.attribute.Email; +import seedu.address.model.attribute.Field; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.Phone; +import seedu.address.model.group.Group; +import seedu.address.model.group.Path; import seedu.address.model.tag.Tag; /** @@ -21,10 +30,13 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + private static final String PATH_VALIDATION_REGEX = "([a-zA-Z0-9_-]+\\/?)+([a-zA-Z0-9_-]+)"; + private static final String PERSON_NAME_PATTERN = "[A-Za-z][a-zA-Z \\-]*"; /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing + * whitespaces will be trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -36,8 +48,8 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { } /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. + * Parses a {@code String name} into a {@code Name}. Leading and trailing whitespaces will be + * trimmed. * * @throws ParseException if the given {@code name} is invalid. */ @@ -51,8 +63,8 @@ public static Name parseName(String name) throws ParseException { } /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. + * Parses a {@code String phone} into a {@code Phone}. Leading and trailing whitespaces will be + * trimmed. * * @throws ParseException if the given {@code phone} is invalid. */ @@ -66,8 +78,8 @@ public static Phone parsePhone(String phone) throws ParseException { } /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. + * Parses a {@code String address} into an {@code Address}. Leading and trailing whitespaces will be + * trimmed. * * @throws ParseException if the given {@code address} is invalid. */ @@ -81,8 +93,8 @@ public static Address parseAddress(String address) throws ParseException { } /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. + * Parses a {@code String email} into an {@code Email}. Leading and trailing whitespaces will be + * trimmed. * * @throws ParseException if the given {@code email} is invalid. */ @@ -96,8 +108,7 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. + * 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. */ @@ -121,4 +132,174 @@ public static Set<Tag> parseTags(Collection<String> tags) throws ParseException } return tagSet; } + + /** + * Parses {@code String path} into a {@code Group}. Leading and trailing whitespaces will be + * trimmed. + * + * @param path to the currently nested group. + * @return a Path object that specifies a group based on that path. + * @throws ParseException if the given {@Code path} is not valid. + */ + public static Path parsePath(String path) throws ParseException { + requireNonNull(path); + String trimmedPath = path.trim(); + if (!isValidPath(trimmedPath)) { + throw new ParseException(Path.MESSAGE_CONSTRAINTS); + } + return new Path(trimmedPath); + } + + /** + * Parses {@code String group} into a {@code Group}. + * + * @param group name of group that is currently being accessed. + * @return a Group object that refers to a specified team. + * @throws ParseException if the given {@Code Group} is not valid. + */ + public static Group parseGroup(String group) throws ParseException { + requireNonNull(group); + if (!Group.isValidGroupName(group)) { + throw new ParseException(Group.MESSAGE_CONSTRAINTS); + } + + return new Group(group); + } + + /** + * Parses {@code String field} into a {@code Field}. + * + * @param fieldName the name of the field that is to be created. + * @return A Field instance containing the name of the field. + * @throws ParseException if the field name is invalid. + */ + public static Field parseField(String fieldName) throws ParseException { + requireNonNull(fieldName); + if (!Field.isValidField(fieldName)) { + throw new ParseException(Field.MESSAGE_CONSTRAINTS); + } + return new Field(fieldName); + } + + /** + * Parses an attribute based on a given type and value of the attribute. + * Sets the display and style format to the default format. + * + * @param modelTypeName The type of the attribute. + * @param modelValue The value of the attribute. + * @param <T> The type parameter of the attribute value. + * @return An Attribute instance with the provided name and value. + * @throws ParseException if the value does not meet the specified parsing format. + */ + public static <T> Attribute<?> parseAttribute(String modelTypeName, T modelValue) throws ParseException { + return parseAttribute(modelTypeName, modelValue, DEFAULT, DEFAULT_STYLE); + } + + /** + * Parses an attribute based on a given type, value, display format and style format. + * + * @param modelTypeName The type of the attribute. + * @param modelValue The value of the attribute. + * @param modelDisplayFormat The display format of the attribute. + * @param modelStyleFormat The style format of the attribute. + * @param <T> The type of the attribute value. + * @return An Attribute instance with the provided name, value, display and style format. + * @throws ParseException if the value does not meet the specified parsing format. + */ + public static <T> Attribute<?> parseAttribute(String modelTypeName, T modelValue, int modelDisplayFormat, + int modelStyleFormat) throws ParseException { + modelTypeName = properCase(modelTypeName); + //@@author autumn-sonata + Attribute<?> modelAttribute; + switch (modelTypeName) { + case Address.TYPE: + if (!Address.isValidAddress((String) modelValue)) { + throw new ParseException(Address.MESSAGE_CONSTRAINTS); + } + modelAttribute = new Address((String) modelValue); + break; + case Description.TYPE: + modelAttribute = new Description((String) modelValue); + break; + case Email.TYPE: + if (!Email.isValidEmail((String) modelValue)) { + throw new ParseException(Email.MESSAGE_CONSTRAINTS); + } + modelAttribute = new Email((String) modelValue); + break; + case Name.TYPE: + if (!Name.isValidName((String) modelValue)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + modelAttribute = new Name((String) modelValue); + break; + case Phone.TYPE: + if (!Phone.isValidPhone((String) modelValue)) { + throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + } + modelAttribute = new Phone((String) modelValue); + break; + default: + modelAttribute = new AbstractAttribute<Object>(modelTypeName, modelValue, + modelDisplayFormat, modelStyleFormat) {}; + } + return modelAttribute; + //@@author + } + + /** + * Checks if the path is valid. Only alphanumeric, hyphen, underscore and slash are allowed. + * + * @param path to reach the current AbstractContainerItem. + * @return true if path is valid, false otherwise. + */ + public static boolean isValidPath(String path) { + return path.matches(PATH_VALIDATION_REGEX); + } + + + + /** + * Splits the str by "|" + */ + public static Pair splitPipe(String str) { + String[] userInputs = str.trim().split("\\s*\\|\\s*", 2); + String strFirst = userInputs[0]; + String strSecond = ""; + + if (userInputs.length == 2) { + strSecond = userInputs[1]; + } + return Pair.of(strFirst, strSecond); + } + + /** + * Static class to represent a Pair + */ + public static class Pair { + private String first; + private String second; + + private Pair(String first, String second) { + this.first = first; + this.second = second; + } + + static Pair of(String a, String b) { + return new Pair(a, b); + } + + @Override + public String toString() { + return String.format("%s, %s", first, second); + } + + public String getFirst() { + return first; + } + + public String getSecond() { + return second; + } + } } diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/address/logic/parser/Prefix.java index c859d5fa5db..48b5c94de20 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/address/logic/parser/Prefix.java @@ -1,10 +1,17 @@ package seedu.address.logic.parser; +import static java.util.Objects.requireNonNull; + +import java.util.regex.Pattern; + /** * A prefix that marks the beginning of an argument in an arguments string. * E.g. 't/' in 'add James t/ friend'. */ public class Prefix { + + private static final Pattern PREFIX_VALIDATION_REGEX = Pattern.compile("\\p{Alnum}*/"); + private final String prefix; public Prefix(String prefix) { @@ -19,6 +26,17 @@ public String toString() { return getPrefix(); } + /** + * Checks whether a string is a valid prefix. + * + * @param prefix a String representing a prefix. + * @return true if the string can be a prefix, false otherwise. + */ + public static boolean isValidPrefix(String prefix) { + requireNonNull(prefix); + return PREFIX_VALIDATION_REGEX.matcher(prefix).matches(); + } + @Override public int hashCode() { return prefix == null ? 0 : prefix.hashCode(); diff --git a/src/main/java/seedu/address/logic/parser/RenameCommandParser.java b/src/main/java/seedu/address/logic/parser/RenameCommandParser.java new file mode 100644 index 00000000000..d1c452461bf --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RenameCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RenameCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to rename a item for a displayitem + */ +public class RenameCommandParser implements Parser<RenameCommand> { + private static final Pattern ADD_ATTRIBUTE_COMMAND_FORMAT = Pattern + .compile("(?<type>[ugt])/(?<id>[0-9]+)\\s+(?<newname>.+)"); + + @Override + public RenameCommand parse(String args) throws ParseException { + final Matcher matcher = ADD_ATTRIBUTE_COMMAND_FORMAT.matcher(args.trim()); + + if (args.trim().length() == 0) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RenameCommand.MESSAGE_USAGE)); + } + + if (!matcher.matches()) { + return new RenameCommand(args.trim()); + } + + Index index = null; + + try { + index = ParserUtil.parseIndex(matcher.group("id").trim()); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RenameCommand.MESSAGE_USAGE), pe); + } + + String newName = matcher.group("newname"); + String type = matcher.group("type"); + ParserUtil.parseName(newName); + + if (type.equals("u")) { + return new RenameCommand(index, 2, newName); + } + if (type.equals("g")) { + return new RenameCommand(index, 1, newName); + } + + if (type.equals("t")) { + return new RenameCommand(index, 3, newName); + } + + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RenameCommand.MESSAGE_USAGE)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java index 158a1a54c1c..c151777a89b 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java @@ -14,4 +14,5 @@ public ParseException(String message) { public ParseException(String message, Throwable cause) { super(message, cause); } + } diff --git a/src/main/java/seedu/address/logic/parser/fields/AddFieldCommandParser.java b/src/main/java/seedu/address/logic/parser/fields/AddFieldCommandParser.java new file mode 100644 index 00000000000..553ddbfd356 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/fields/AddFieldCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser.fields; + + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.fields.AddFieldCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author jasonchristopher21 +/** + * Parses input arguments and creates a new AddFieldCommand object + */ +public class AddFieldCommandParser implements Parser<AddFieldCommand> { + + @Override + public AddFieldCommand parse(String args) throws ParseException { + args = args.trim(); + Pattern p = Pattern.compile("([gtu])/([0-9]+)\\s+([a-zA-Z][a-zA-Z0-9]*)\\s+(.*)"); + Pattern p2 = Pattern.compile("([a-zA-Z][a-zA-Z0-9]*)\\s+(.*)"); + Matcher m = p.matcher(args.trim()); + if (m.matches()) { + return new AddFieldCommand(ParserUtil.parseIndex(m.group(2)), m.group(1), m.group(3), m.group(4)); + } + m = p2.matcher(args); + if (m.matches()) { + return new AddFieldCommand(null, "0", m.group(1), m.group(2)); + } + throw new ParseException(AddFieldCommand.MESSAGE_USAGE); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/fields/DeleteFieldCommandParser.java b/src/main/java/seedu/address/logic/parser/fields/DeleteFieldCommandParser.java new file mode 100644 index 00000000000..455a490308e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/fields/DeleteFieldCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.fields; + + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.fields.DeleteFieldCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author jasonchristopher21 +/** + * Parses input arguments and creates a new TaskCommand object + */ +public class DeleteFieldCommandParser implements Parser<DeleteFieldCommand> { + + @Override + public DeleteFieldCommand parse(String args) throws ParseException { + args = args.trim(); + Pattern p = Pattern.compile("([gtu])/([0-9]+)\\s+([a-zA-Z][a-zA-Z0-9]*)"); + Pattern p2 = Pattern.compile("([a-zA-Z][a-zA-Z0-9]*)"); + Matcher m = p.matcher(args); + if (m.matches()) { + return new DeleteFieldCommand(ParserUtil.parseIndex(m.group(2)), m.group(1), m.group(3)); + } else if (p2.matcher(args).matches()) { + return new DeleteFieldCommand(null, "0", args.trim()); + } + throw new ParseException(DeleteFieldCommand.INVALID_FORMAT); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/fields/EditFieldCommandParser.java b/src/main/java/seedu/address/logic/parser/fields/EditFieldCommandParser.java new file mode 100644 index 00000000000..8652d0d2b46 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/fields/EditFieldCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser.fields; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.fields.EditFieldCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author jasonchristopher21 +/** + * Parses input arguments and creates a new TaskCommand object + */ +public class EditFieldCommandParser implements Parser<EditFieldCommand> { + + @Override + public EditFieldCommand parse(String args) throws ParseException { + args = args.trim(); + Pattern p = Pattern.compile("([gtu])/([0-9]+)\\s+([a-zA-Z][a-zA-Z0-9]*)\\s+(.*)"); + Pattern p2 = Pattern.compile("([a-zA-Z][a-zA-Z0-9]*)\\s+(.*)"); + Matcher m = p.matcher(args); + if (m.matches()) { + return new EditFieldCommand(ParserUtil.parseIndex(m.group(2)), m.group(1), m.group(3), m.group(4)); + } + m = p2.matcher(args); + if (m.matches()) { + return new EditFieldCommand(null, "0", m.group(1), m.group(2)); + } + throw new ParseException(EditFieldCommand.MESSAGE_USAGE); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/fields/FieldCommandParser.java b/src/main/java/seedu/address/logic/parser/fields/FieldCommandParser.java new file mode 100644 index 00000000000..d74c8ce107b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/fields/FieldCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser.fields; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.fields.AddFieldCommand; +import seedu.address.logic.commands.fields.DeleteFieldCommand; +import seedu.address.logic.commands.fields.EditFieldCommand; +import seedu.address.logic.commands.fields.FieldCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser for all Task commands + */ +public class FieldCommandParser implements Parser<FieldCommand> { + private static final String MESSAGE_USAGE = FieldCommand.COMMAND_WORD + " [add|delete|edit]"; + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<subcommandWord>\\S+)(?<arguments>.*)"); + + /** + * Parses user input into command for execution. The input must be a valid subcommand for Task. + * There should not be a TaskCommand prefix in the input. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public FieldCommand parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("subcommandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + case AddFieldCommand.SUBCOMMAND_WORD: + return new AddFieldCommandParser().parse(arguments); + case EditFieldCommand.SUBCOMMAND_WORD: + return new EditFieldCommandParser().parse(arguments); + case DeleteFieldCommand.SUBCOMMAND_WORD: + return new DeleteFieldCommandParser().parse(arguments); + default: + throw new ParseException(MESSAGE_USAGE); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/logiccommands/CheckTaskCompleteCommandParser.java b/src/main/java/seedu/address/logic/parser/logiccommands/CheckTaskCompleteCommandParser.java new file mode 100644 index 00000000000..683abef3998 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/logiccommands/CheckTaskCompleteCommandParser.java @@ -0,0 +1,16 @@ +package seedu.address.logic.parser.logiccommands; + +import seedu.address.logic.commands.logicalcommand.CheckTaskCompleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input to check is task complete comand + */ +public class CheckTaskCompleteCommandParser implements Parser<CheckTaskCompleteCommand> { + + @Override + public CheckTaskCompleteCommand parse(String userInput) throws ParseException { + return new CheckTaskCompleteCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/logiccommands/ContainsAttributeCommandParser.java b/src/main/java/seedu/address/logic/parser/logiccommands/ContainsAttributeCommandParser.java new file mode 100644 index 00000000000..a8de9ee338b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/logiccommands/ContainsAttributeCommandParser.java @@ -0,0 +1,17 @@ +package seedu.address.logic.parser.logiccommands; + +import seedu.address.logic.commands.logicalcommand.ContainsAttributeCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input to check contains attribute command + */ +public class ContainsAttributeCommandParser implements Parser<ContainsAttributeCommand> { + + @Override + public ContainsAttributeCommand parse(String userInput) throws ParseException { + return new ContainsAttributeCommand(userInput); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/logiccommands/IfCommandParser.java b/src/main/java/seedu/address/logic/parser/logiccommands/IfCommandParser.java new file mode 100644 index 00000000000..9f394987b9b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/logiccommands/IfCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser.logiccommands; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.logicalcommand.IfCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Commands to represent a if else logical flow + */ +public class IfCommandParser implements Parser<IfCommand> { + + private static final Pattern NO_ELSE = Pattern + .compile("\\[\\[\\s*(?<ifcheck>.*)\\s*\\]\\]\\s*;;\\s*\\[\\[\\s*(?<true>.*)\\s*\\]\\]"); + private static final Pattern WITH_ELSE = Pattern + .compile( + "\\[\\[\\s*(?<ifcheck>.*)\\s*\\]\\]\\s*;;\\s*\\[\\[\\s*(?<true>.*)" + + "\\s*\\]\\]\\s*;;\\s*\\[\\[\\s*(?<else>.*)\\s*\\]\\]"); + + @Override + public IfCommand parse(String userInput) throws ParseException { + // "[[logical commands]] ;; [[if true command]] ;; [[if else command]]" + userInput = userInput.trim(); + String check; + String ifTrue; + String ifFalse = null; + Matcher m = WITH_ELSE.matcher(userInput); + Matcher mElse = NO_ELSE.matcher(userInput); + if (m.matches()) { + check = m.group("ifcheck"); + ifTrue = m.group("true"); + ifFalse = m.group("else"); + } else if (mElse.matches()) { + check = mElse.group("ifcheck"); + ifTrue = mElse.group("true"); + } else { + throw new ParseException(IfCommand.MESSAGE_USAGE); + } + + return new IfCommand(check, ifTrue, ifFalse); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/persons/AddPersonCommandParser.java b/src/main/java/seedu/address/logic/parser/persons/AddPersonCommandParser.java new file mode 100644 index 00000000000..d9c624cc891 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/persons/AddPersonCommandParser.java @@ -0,0 +1,78 @@ +package seedu.address.logic.parser.persons; + +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_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.commons.util.FunctionalInterfaces; +import seedu.address.logic.commands.persons.AddPersonCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.attribute.Name; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddPersonCommandParser implements Parser<AddPersonCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand and returns an + * AddCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddPersonCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Set<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Person person = new Person(name.fullName); + person.setTags(tagList); + + try { + argMultimap.getValue(PREFIX_PHONE) + .map(FunctionalInterfaces.throwingFunctionWrapper(ParserUtil::parsePhone)) + .ifPresent(person::addAttribute); + + argMultimap.getValue(PREFIX_EMAIL) + .map(FunctionalInterfaces.throwingFunctionWrapper(ParserUtil::parseEmail)) + .ifPresent(person::addAttribute); + + argMultimap.getValue(PREFIX_ADDRESS) + .map(FunctionalInterfaces.throwingFunctionWrapper(ParserUtil::parseAddress)) + .ifPresent(person::addAttribute); + } catch (RuntimeException e) { + throw new ParseException(e.getMessage()); + } + + return new AddPersonCommand(person); + } + + /** + * 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/persons/PersonCommandParser.java b/src/main/java/seedu/address/logic/parser/persons/PersonCommandParser.java new file mode 100644 index 00000000000..47721cd9122 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/persons/PersonCommandParser.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser.persons; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.ForEachCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.persons.AddPersonCommand; +import seedu.address.logic.commands.persons.PersonCommand; +import seedu.address.logic.parser.FindCommandParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Person; + +/** + * Parser for all Task commands + */ +public class PersonCommandParser implements Parser<Command> { + private static final String MESSAGE_USAGE = PersonCommand.COMMAND_WORD + " [new|delete|select|find]"; + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<subcommandWord>\\S+)(?<arguments>.*)"); + + /** + * Parses user input into command for execution. The input must be a valid subcommand for Task. + * There should not be a TaskCommand prefix in the input. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("subcommandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + case AddPersonCommand.SUBCOMMAND_WORD: + return new AddPersonCommandParser().parse(arguments); + case DeleteCommand.SUBCOMMAND_WORD: + return DeleteCommand + .<Person>parser((m, i) -> m.getFromFilteredPerson(i), (m, item) -> m.deletePerson(item), + o -> o instanceof Person) + .parse(arguments); + case SelectCommand.SUBCOMMAND_WORD: + return SelectCommand.parser((m, i) -> m.getFromFilteredPerson(i)).parse(arguments); + case ForEachCommand.SUBCOMMAND_WORD: + return ForEachCommand.parser(m -> m.getFilteredPersonList()).parse(arguments); + case FindCommand.SUBCOMMAND_WORD: + return new FindCommandParser<Person>((m, p) -> m.updateFilteredPersonList(p), + m -> m.getFilteredPersonList().size()).parse(arguments); + default: + throw new ParseException(MESSAGE_USAGE); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/tasks/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/tasks/AddTaskCommandParser.java new file mode 100644 index 00000000000..0ac2d100306 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/tasks/AddTaskCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser.tasks; + +import static seedu.address.commons.core.Messages.MESSAGE_EMPTY_NAME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.tasks.AddTaskCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.attribute.Name; +import seedu.address.model.task.Task; + +// @@author connlim + +/** + * Parses input arguments and creates a new TaskCommand object + */ +public class AddTaskCommandParser implements Parser<AddTaskCommand> { + + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + @Override + public AddTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_DESCRIPTION); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_DESCRIPTION) || !argMultimap.getPreamble() + .isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + + String name = argMultimap.getValue(PREFIX_TITLE).get(); + if (name.length() == 0) { + throw new ParseException(String.format(MESSAGE_EMPTY_NAME, AddTaskCommand.MESSAGE_USAGE)); + } + Name parsedName = ParserUtil.parseName(name); + String address = argMultimap.getValue(PREFIX_DESCRIPTION).get(); + + Task task = new Task(parsedName.fullName, address); + + return new AddTaskCommand(task); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/tasks/MarkTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/tasks/MarkTaskCommandParser.java new file mode 100644 index 00000000000..60305a9ad11 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/tasks/MarkTaskCommandParser.java @@ -0,0 +1,25 @@ +package seedu.address.logic.parser.tasks; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.tasks.MarkTaskCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author connlim +/** + * Parses input arguments and creates a new MarkTaskCommand object + */ +public class MarkTaskCommandParser implements Parser<MarkTaskCommand> { + + @Override + public MarkTaskCommand parse(String args) throws ParseException { + if (args.trim().equals("")) { + return new MarkTaskCommand(null); + } else { + Index index = ParserUtil.parseIndex(args); + return new MarkTaskCommand(index); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/tasks/TaskCommandParser.java b/src/main/java/seedu/address/logic/parser/tasks/TaskCommandParser.java new file mode 100644 index 00000000000..f9d7ac061de --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/tasks/TaskCommandParser.java @@ -0,0 +1,72 @@ +package seedu.address.logic.parser.tasks; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.ForEachCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.tasks.AddTaskCommand; +import seedu.address.logic.commands.tasks.MarkTaskCommand; +import seedu.address.logic.commands.tasks.TaskCommand; +import seedu.address.logic.commands.tasks.UnmarkTaskCommand; +import seedu.address.logic.parser.FindCommandParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.Task; + +/** + * Parser for all Task commands + */ +public class TaskCommandParser implements Parser<Command> { + private static final String MESSAGE_USAGE = TaskCommand.COMMAND_WORD + " [add|delete|mark|unmark|select]"; + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<subcommandWord>\\S+)(?<arguments>.*)"); + + /** + * Parses user input into command for execution. The input must be a valid subcommand for Task. + * There should not be a TaskCommand prefix in the input. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("subcommandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + case AddTaskCommand.SUBCOMMAND_WORD: + return new AddTaskCommandParser().parse(arguments); + case DeleteCommand.SUBCOMMAND_WORD: + return DeleteCommand + .<Task>parser((m, i) -> m.getFromFilteredTasks(i), (m, task) -> m.deleteTask(task), + o -> o instanceof Task) + .parse(arguments); + case MarkTaskCommand.SUBCOMMAND_WORD: + return new MarkTaskCommandParser().parse(arguments); + case UnmarkTaskCommand.SUBCOMMAND_WORD: + return new UnmarkTaskCommandParser().parse(arguments); + case SelectCommand.SUBCOMMAND_WORD: + return SelectCommand.parser((m, i) -> m.getFromFilteredTasks(i)).parse(arguments); + case ForEachCommand.SUBCOMMAND_WORD: + return ForEachCommand.parser(m -> m.getFilteredTaskList()).parse(arguments); + case FindCommand.SUBCOMMAND_WORD: + return new FindCommandParser<Task>((m, p) -> m.updateFilteredTaskList(p), + m -> m.getFilteredTaskList().size()).parse(arguments); + default: + throw new ParseException(MESSAGE_USAGE); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/tasks/UnmarkTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/tasks/UnmarkTaskCommandParser.java new file mode 100644 index 00000000000..1842f436dfe --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/tasks/UnmarkTaskCommandParser.java @@ -0,0 +1,24 @@ +package seedu.address.logic.parser.tasks; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.tasks.UnmarkTaskCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author connlim +/** + * Parses input arguments and creates a new UnmarkTaskCommand object + */ +public class UnmarkTaskCommandParser implements Parser<UnmarkTaskCommand> { + + @Override + public UnmarkTaskCommand parse(String args) throws ParseException { + if (args.trim().equals("")) { + return new UnmarkTaskCommand(null); + } + + Index index = ParserUtil.parseIndex(args); + return new UnmarkTaskCommand(index); + } +} diff --git a/src/main/java/seedu/address/logic/parser/teams/AddTeamCommandParser.java b/src/main/java/seedu/address/logic/parser/teams/AddTeamCommandParser.java new file mode 100644 index 00000000000..042fcd95e51 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/teams/AddTeamCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser.teams; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.teams.AddTeamCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.Group; + +//@@author autumn-sonata +/** + * Parses input arguments and creates a new TeamCommand object + */ +public class AddTeamCommandParser implements Parser<AddTeamCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the + * AddGroupCommand. + * + * @param args refer to the subsequent arguments after the initial command word. + * @return an AddGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTeamCommand parse(String args) throws ParseException { + try { + requireNonNull(args); + String trimmedArgs = args.trim(); + Group newGroup = ParserUtil.parseGroup(trimmedArgs); + return new AddTeamCommand(newGroup); + } catch (ParseException pe) { + throw new ParseException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, AddTeamCommand.MESSAGE_USAGE), + pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/teams/AddUserToTeamCommandParser.java b/src/main/java/seedu/address/logic/parser/teams/AddUserToTeamCommandParser.java new file mode 100644 index 00000000000..c33483e12fc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/teams/AddUserToTeamCommandParser.java @@ -0,0 +1,42 @@ +package seedu.address.logic.parser.teams; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USER; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.teams.AddUserToTeamCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; + + + +/** + * Parses input arguments and creates a new AddUserToTeamCommand object + */ +public class AddUserToTeamCommandParser implements Parser<AddUserToTeamCommand> { + @Override + public AddUserToTeamCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_USER, PREFIX_GROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_USER, PREFIX_GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddUserToTeamCommand.MESSAGE_USAGE)); + } + + Index userIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_USER).get()); + Index grpIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_GROUP).get()); + + return new AddUserToTeamCommand(userIndex, grpIndex); + } + + 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/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/teams/RemoveUserFromTeamCommandParser.java similarity index 52% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/address/logic/parser/teams/RemoveUserFromTeamCommandParser.java index 522b93081cc..4d7a15367ec 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/teams/RemoveUserFromTeamCommandParser.java @@ -1,29 +1,31 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.teams; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.teams.RemoveUserFromTeamCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.exceptions.ParseException; /** - * Parses input arguments and creates a new DeleteCommand object + * Parses input arguments and creates a new ChangeTeamCommand object */ -public class DeleteCommandParser implements Parser<DeleteCommand> { - +public class RemoveUserFromTeamCommandParser implements Parser<RemoveUserFromTeamCommand> { /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand + * Parses the given {@code String} of arguments in the context of the + * DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ - public DeleteCommand parse(String args) throws ParseException { + public RemoveUserFromTeamCommand parse(String args) throws ParseException { try { Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + return new RemoveUserFromTeamCommand(index); } catch (ParseException pe) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveUserFromTeamCommand.MESSAGE_USAGE), pe); } } - } diff --git a/src/main/java/seedu/address/logic/parser/teams/TeamCommandParser.java b/src/main/java/seedu/address/logic/parser/teams/TeamCommandParser.java new file mode 100644 index 00000000000..a996c96803d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/teams/TeamCommandParser.java @@ -0,0 +1,77 @@ +package seedu.address.logic.parser.teams; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.ForEachCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.teams.AddTeamCommand; +import seedu.address.logic.commands.teams.RemoveUserFromTeamCommand; +import seedu.address.logic.commands.teams.TeamCommand; +import seedu.address.logic.parser.FindCommandParser; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.Group; + +/** + * Parser for Team Command + */ +public class TeamCommandParser implements Parser<Command> { + + private static final String MESSAGE_USAGE = TeamCommand.COMMAND_WORD + " [new|delete|remove]"; + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<subcommandWord>\\S+)(?<arguments>.*)"); + + /** + * Parses user input into command for execution. The input must be a valid subcommand for Task. + * There should not be a TaskCommand prefix in the input. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("subcommandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + case AddTeamCommand.SUBCOMMAND_WORD: + return new AddTeamCommandParser().parse(arguments); + + case DeleteCommand.SUBCOMMAND_WORD: + return DeleteCommand + .<Group>parser((m, i) -> m.getFromFilteredTeams(i), (m, i) -> m.deleteTeam(i), + o -> o instanceof Group) + .parse(arguments); + + case RemoveUserFromTeamCommand.SUBCOMMAND_WORD: + return new RemoveUserFromTeamCommandParser().parse(arguments); + + case SelectCommand.SUBCOMMAND_WORD: + return SelectCommand.parser((m, i) -> m.getFromFilteredTeams(i)).parse(arguments); + + case ForEachCommand.SUBCOMMAND_WORD: + return ForEachCommand.parser(m -> m.getFilteredTeamList()).parse(arguments); + + case FindCommand.SUBCOMMAND_WORD: + return new FindCommandParser<Group>((m, p) -> m.updateFilteredTeamList(p), + m -> m.getFilteredTeamList().size()).parse(arguments); + + default: + throw new ParseException(MESSAGE_USAGE); + } + } + +} diff --git a/src/main/java/seedu/address/model/AccessDisplayFlags.java b/src/main/java/seedu/address/model/AccessDisplayFlags.java new file mode 100644 index 00000000000..a14fdc3d055 --- /dev/null +++ b/src/main/java/seedu/address/model/AccessDisplayFlags.java @@ -0,0 +1,38 @@ +package seedu.address.model; + +/** + * Enum to denote the binary bit flag of access and print permission of attributes and DisplayItems. + */ +public final class AccessDisplayFlags { + // flags for storage and display permissions + public static final int DISPLAY_OK = 1; + public static final int MENU_OK = 1 << 1; + public static final int GROUP = 1 << 2; + public static final int TASK = 1 << 3; + public static final int PERSON = 1 << 4; + public static final int HIDE_TYPE = 1 << 5; + public static final int ACCESS_OK = 0b11100; + public static final int DEFAULT = 0b011111; + + // flags for style and formatting of labels + public static final int BOLD = 1; + public static final int ITALIC = 1 << 1; + public static final int UNDERLINE = 1 << 2; + public static final int STRIKETHROUGH = 1 << 3; + public static final int DROPSHADOW = 1 << 4; + + // where there is a conflict, left > center > right + public static final int LEFT_JUSTIFY = 1 << 5; + public static final int CENTER_JUSTIFY = 1 << 6; + public static final int RIGHT_JUSTIFY = 1 << 7; + + // where there is a conflict, normal > big > small + // big font will show as normal when in menu view ! + public static final int FONT_SIZE_NORMAL = 1 << 8; + public static final int FONT_SIZE_BIG = 1 << 9; + public static final int FONT_SIZE_SMALL = 1 << 10; + + // public static final int DEFAULT_STYLE = 0b00100100000; + public static final int DEFAULT_STYLE = 0b00100100000; + public static final int HEADER_STYLE = 0b01001010101; +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..665b74a3994 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,28 +3,38 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; import javafx.collections.ObservableList; +import seedu.address.model.group.Group; +import seedu.address.model.group.UniqueGroupList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.task.Task; +import seedu.address.model.task.UniqueTaskList; /** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) + * Wraps all data at the address-book level Duplicates are not allowed (by .weakEquality comparison) */ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueGroupList teams; + private final UniqueTaskList tasks; /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid + * duplication between constructors. See + * https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. + * Note that non-static init blocks are not recommended to use. There are other ways to avoid + * duplication among constructors. */ { persons = new UniquePersonList(); + teams = new UniqueGroupList(); + tasks = new UniqueTaskList(); } public AddressBook() {} @@ -37,16 +47,22 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { resetData(toBeCopied); } - //// list overwrite operations - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of the person list with {@code persons}. {@code persons} must not contain + * duplicate persons. */ public void setPersons(List<Person> persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the group list with {@code groups}. {@code persons} must not contain + * duplicate persons. + */ + public void setGroups(List<Group> groups) { + this.teams.setItems(groups); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -54,6 +70,8 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setGroups(newData.getTeamsList()); + setTasks(newData.getTasksList()); } //// person-level operations @@ -67,17 +85,17 @@ public boolean hasPerson(Person person) { } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Adds a person to the address book. The person must not already exist in the address book. */ public void addPerson(Person p) { persons.add(p); + persons.sort(); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * Replaces the given person {@code target} in the list with {@code editedPerson}. {@code target} + * must exist in the address book. The person identity of {@code editedPerson} must not be the same + * as another existing person in the address book. */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); @@ -86,18 +104,117 @@ public void setPerson(Person target, Person editedPerson) { } /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. + * Removes {@code key} from this {@code AddressBook}. {@code key} must exist in the address book. */ public void removePerson(Person key) { persons.remove(key); } + /** + * Removes all person that satisfies the predicate + * + * @param predicate + */ + public void removePersonIf(Predicate<Person> predicate) { + persons.removeIf(predicate); + } + + /** + * Applies effect for each person + */ + public void forEachPerson(Consumer<? super Person> consumer) { + persons.forEach(consumer); + } + + //// person-level operations + + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + public boolean hasGroup(Group team) { + requireNonNull(team); + return teams.contains(team); + } + + /** + * Adds a person to the address book. The person must not already exist in the address book. + */ + public void addTeam(Group g) { + teams.add(g); + teams.sort(); + } + + /** + * Removes {@code grp} from this {@code AddressBook}. {@code grp} must exist in the address book. + */ + public void removeTeam(Group grp) { + teams.remove(grp); + } + + /** + * Removes all teams that satisfies the predicate + * + * @param predicate + */ + public void removeTeamIf(Predicate<Group> predicate) { + teams.removeIf(predicate); + } + + /** + * Returns true if a task with the same identity as {@code task} exists. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + + /** + * Adds a task to the team in the address book. + */ + public void addTask(Task task) { + requireNonNull(task); + tasks.add(task); + tasks.sort(); + } + + /** + * Replaces the contents of the task list with {@code tasks}. {@code tasks} must not contain + * duplicate tasks. + */ + public void setTasks(List<Task> tasks) { + this.tasks.setItems(tasks); + } + + /** + * Removes {@code task} from its group. Task must exist in address book. + */ + public void removeTask(Task task) { + requireNonNull(task); + tasks.remove(task); + } + + public void setTask(Task target, Task editedTask) { + requireNonNull(editedTask); + + tasks.setTask(target, editedTask); + tasks.sort(); + } + + /** + * Removes all tasks that satisfies the predicate + * + * @param predicate + */ + public void removeTaskIf(Predicate<Task> predicate) { + tasks.removeIf(predicate); + } + //// util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; + return String.format("%d persons, %d teams, %d task", persons.asUnmodifiableObservableList().size(), + teams.asUnmodifiableObservableList().size(), tasks.asUnmodifiableObservableList().size()); // TODO: refine later } @@ -109,12 +226,24 @@ public ObservableList<Person> getPersonList() { @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)); + || (other instanceof AddressBook // instanceof handles nulls + && persons.equals(((AddressBook) other).persons) + && teams.equals(((AddressBook) other).teams) + && tasks.equals(((AddressBook) other).tasks)); } @Override public int hashCode() { - return persons.hashCode(); + return persons.hashCode() ^ teams.hashCode() ^ tasks.hashCode(); + } + + @Override + public ObservableList<Group> getTeamsList() { + return teams.asUnmodifiableObservableList(); + } + + @Override + public ObservableList<Task> getTasksList() { + return tasks.asUnmodifiableObservableList(); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..3eaaab7b5fe 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,11 +1,19 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.List; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.index.Index; +import seedu.address.model.group.Group; +import seedu.address.model.group.exceptions.GroupOutOfBoundException; +import seedu.address.model.item.AbstractSingleItem; import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonOutOfBoundException; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.TaskOutOfBoundException; /** * The API of the Model component. @@ -14,6 +22,12 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate<Person> PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate<Task> PREDICATE_SHOW_ALL_TASKS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate<Group> PREDICATE_SHOW_ALL_GROUPS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -53,10 +67,16 @@ public interface Model { ReadOnlyAddressBook getAddressBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code person} exists in + * the address book. */ boolean hasPerson(Person person); + /** + * Refreshes the addressbook lists + */ + void refresh(); + /** * Deletes the given person. * The person must exist in the address book. @@ -72,16 +92,118 @@ public interface Model { /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The person identity of {@code editedPerson} must not be the same as another + * existing person in the address book. */ void setPerson(Person target, Person editedPerson); - /** Returns an unmodifiable view of the filtered person list */ + /** + * Returns true if the task with the same identity as {@code task} exists in + * the address book + * + * @param task The task to check identity against + * @return true if the task exists, false otherwise + */ + boolean hasTask(Task task); + + /** + * Deletes the given task. + * The task must exist in the address book. + * + * @param task The task to delete. + */ + void deleteTask(Task task); + + /** + * Adds the given task. + * {@code task} must not already exist in the address book. + * + * @param task The task to add. + */ + void addTask(Task task); + + /** + * Returns an unmodifiable view of the filtered task list + */ + ObservableList<Task> getFilteredTaskList(); + + /** + * Returns an unmodifiable view of the filtered person list + */ ObservableList<Person> getFilteredPersonList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * 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<Person> predicate); + + /** + * Updates the filter of the filtered person list to filter by the given + * {@code predicates}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPersonList(List<Predicate<Person>> predicates); + + /** + * Updates the current scope to a new container + */ + void updateContextContainer(AbstractSingleItem container); + + /** + * Receives the current scope + */ + AbstractSingleItem getContextContainer(); + + boolean hasTeam(Group grp); + + void deleteTeam(Group grp); + + void addTeam(Group grp); + + void updateFilteredTeamList(Predicate<Group> predicate); + + void updateFilteredTeamList(List<Predicate<Group>> predicates); + + /** + * Updates the filter of the filtered task list to filter by the given + * {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTaskList(Predicate<Task> predicate); + + /** + * Updates the filter of the filtered task list to filter by the given + * {@code predicates}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTaskList(List<Predicate<Task>> predicates); + + /** + * Returns an unmodifiable view of the filtered team list + */ + ObservableList<Group> getFilteredTeamList(); + + void setTask(Task target, Task editedTask); + + /** + * Retrieves the person object from the current PersonList based on the Index + */ + Person getFromFilteredPerson(Index index) throws PersonOutOfBoundException; + + /** + * Retrieves the person object from the current PersonList based on the Index + */ + Group getFromFilteredTeams(Index index) throws GroupOutOfBoundException; + + /** + * Retrieves the person object from the current PersonList based on the Index + */ + Task getFromFilteredTasks(Index index) throws TaskOutOfBoundException; + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..ac3ee1a6b9f 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,8 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.List; +import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; @@ -11,7 +13,14 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.model.group.Group; +import seedu.address.model.group.exceptions.GroupOutOfBoundException; +import seedu.address.model.item.AbstractSingleItem; import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonOutOfBoundException; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.TaskOutOfBoundException; /** * Represents the in-memory model of the address book data. @@ -21,7 +30,10 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; + private final FilteredList<Task> filteredTasks; private final FilteredList<Person> filteredPersons; + private final FilteredList<Group> filteredTeams; + private Optional<AbstractSingleItem> currentContext = Optional.empty(); /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,13 +46,26 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredTeams = new FilteredList<>(this.addressBook.getTeamsList()); + filteredTasks = new FilteredList<>(this.addressBook.getTasksList()); } public ModelManager() { this(new AddressBook(), new UserPrefs()); } - //=========== UserPrefs ================================================================================== + /** + * Updates all gui + */ + @Override + public void refresh() { + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + updateFilteredTeamList(PREDICATE_SHOW_ALL_GROUPS); + } + + // =========== UserPrefs + // ================================================================================== @Override public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { @@ -75,7 +100,8 @@ public void setAddressBookFilePath(Path addressBookFilePath) { userPrefs.setAddressBookFilePath(addressBookFilePath); } - //=========== AddressBook ================================================================================ + // =========== AddressBook + // ================================================================================ @Override public void setAddressBook(ReadOnlyAddressBook addressBook) { @@ -87,6 +113,8 @@ public ReadOnlyAddressBook getAddressBook() { return addressBook; } + //// person level methods and accessors + @Override public boolean hasPerson(Person person) { requireNonNull(person); @@ -101,7 +129,7 @@ public void deletePerson(Person target) { @Override public void addPerson(Person person) { addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredPersonList(List.of()); } @Override @@ -111,7 +139,63 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } - //=========== Filtered Person List Accessors ============================================================= + //// group level methods and accessors + + @Override + public boolean hasTeam(Group grp) { + requireNonNull(grp); + return addressBook.hasGroup(grp); + } + + @Override + public void deleteTeam(Group target) { + // delete all subteams + addressBook.removeTeamIf(grp -> grp.isPartOfContext(target)); + addressBook.removeTaskIf(tsks -> tsks.isPartOfContext(target)); + addressBook.forEachPerson(p -> p.removeParent(target)); + addressBook.removeTeam(target); + } + + @Override + public void addTeam(Group grp) { + addressBook.addTeam(grp); + updateFilteredTeamList(List.of()); + } + + //// task level methods and accessors + + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); + } + + @Override + public void deleteTask(Task task) { + requireNonNull(task); + addressBook.removeTask(task); + } + + @Override + public void addTask(Task task) { + addressBook.addTask(task); + updateFilteredTaskList(List.of()); + } + + @Override + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + addressBook.setTask(target, editedTask); + } + + @Override + public ObservableList<Task> getFilteredTaskList() { + return filteredTasks; + } + + // =========== Filtered Person List Accessors + // ============================================================= /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of @@ -124,10 +208,110 @@ public ObservableList<Person> getFilteredPersonList() { @Override public void updateFilteredPersonList(Predicate<Person> predicate) { - requireNonNull(predicate); + if (predicate == null) { + updateFilteredPersonList(List.of()); + return; + } + updateFilteredPersonList(List.of(predicate)); + } + + @Override + public void updateFilteredPersonList(List<Predicate<Person>> predicates) { + requireNonNull(predicates); + Predicate<Person> predicate = p -> { + return currentContext.map(cxt -> p.isPartOfContext(cxt)).orElse(true) + && predicates.stream().map(pred -> pred.test(p)).allMatch(res -> res == true); + }; + filteredPersons.setPredicate(predicate); } + @Override + public Person getFromFilteredPerson(Index index) throws PersonOutOfBoundException { + requireNonNull(index); + List<Person> filteredList = getFilteredPersonList(); + int indexNum = index.getZeroBased(); + if (filteredList.isEmpty() || indexNum < 0 || indexNum >= filteredList.size()) { + throw new PersonOutOfBoundException(filteredList.size(), indexNum + 1); + } + return filteredList.get(indexNum); + } + + // =========== Filtered Teams List Accessors + // ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList<Group> getFilteredTeamList() { + return filteredTeams; + } + + @Override + public void updateFilteredTeamList(Predicate<Group> predicate) { + if (predicate == null) { + updateFilteredTeamList(List.of()); + return; + } + updateFilteredTeamList(List.of(predicate)); + } + + @Override + public void updateFilteredTeamList(List<Predicate<Group>> predicates) { + requireNonNull(predicates); + Predicate<Group> predicate = g -> { + return currentContext.map(cxt -> g.isPartOfContext(cxt)).orElse(true) + && predicates.stream().map(pred -> pred.test(g)).allMatch(res -> res == true); + }; + + filteredTeams.setPredicate(predicate); + } + + @Override + public Group getFromFilteredTeams(Index index) throws GroupOutOfBoundException { + requireNonNull(index); + List<Group> filteredList = getFilteredTeamList(); + int indexNum = index.getZeroBased(); + if (filteredList.isEmpty() || indexNum < 0 || indexNum >= filteredList.size()) { + throw new GroupOutOfBoundException(filteredList.size(), indexNum + 1); + } + return filteredList.get(indexNum); + } + + // filtered tasks list accessors ======== + @Override + public void updateFilteredTaskList(Predicate<Task> predicate) { + if (predicate == null) { + updateFilteredTaskList(List.of()); + return; + } + updateFilteredTaskList(List.of(predicate)); + } + + @Override + public void updateFilteredTaskList(List<Predicate<Task>> predicates) { + requireNonNull(predicates); + Predicate<Task> predicate = t -> { + return currentContext.map(cxt -> t.isPartOfContext(cxt)).orElse(true) + && predicates.stream().map(pred -> pred.test(t)).allMatch(res -> res == true); + }; + + filteredTasks.setPredicate(predicate); + } + + @Override + public Task getFromFilteredTasks(Index index) throws TaskOutOfBoundException { + requireNonNull(index); + List<Task> filteredList = getFilteredTaskList(); + int indexNum = index.getZeroBased(); + if (filteredList.isEmpty() || indexNum < 0 || indexNum >= filteredList.size()) { + throw new TaskOutOfBoundException(filteredList.size(), indexNum + 1); + } + return filteredList.get(indexNum); + } + @Override public boolean equals(Object obj) { // short circuit if same object @@ -143,8 +327,21 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && userPrefs.equals(other.userPrefs) + && filteredPersons.equals(other.filteredPersons) + && filteredTeams.equals(other.filteredTeams); } + @Override + public void updateContextContainer(AbstractSingleItem container) { + currentContext = Optional.ofNullable(container); + updateFilteredPersonList(List.of()); + updateFilteredTeamList(List.of()); + updateFilteredTaskList(List.of()); + } + + @Override + public AbstractSingleItem getContextContainer() { + return currentContext.orElse(null); + } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..b11ebdd6d65 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,9 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * Unmodifiable view of an address book @@ -14,4 +16,16 @@ public interface ReadOnlyAddressBook { */ ObservableList<Person> getPersonList(); + /** + * Returns an unmodifiable view of the teams list. + * This list will not contain any duplicate teams but may contain subteams. + */ + ObservableList<Group> getTeamsList(); + + // /** + // * Returns an unmodifiable view of all the tasks. + // * This list will not contain any duplicate tasks. + // */ + ObservableList<Task> getTasksList(); + } diff --git a/src/main/java/seedu/address/model/attribute/AbstractAttribute.java b/src/main/java/seedu/address/model/attribute/AbstractAttribute.java new file mode 100644 index 00000000000..d33077243b0 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/AbstractAttribute.java @@ -0,0 +1,205 @@ +package seedu.address.model.attribute; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.AccessDisplayFlags.BOLD; +import static seedu.address.model.AccessDisplayFlags.CENTER_JUSTIFY; +import static seedu.address.model.AccessDisplayFlags.DEFAULT; +import static seedu.address.model.AccessDisplayFlags.DEFAULT_STYLE; +import static seedu.address.model.AccessDisplayFlags.DISPLAY_OK; +import static seedu.address.model.AccessDisplayFlags.DROPSHADOW; +import static seedu.address.model.AccessDisplayFlags.FONT_SIZE_BIG; +import static seedu.address.model.AccessDisplayFlags.FONT_SIZE_NORMAL; +import static seedu.address.model.AccessDisplayFlags.FONT_SIZE_SMALL; +import static seedu.address.model.AccessDisplayFlags.HIDE_TYPE; +import static seedu.address.model.AccessDisplayFlags.ITALIC; +import static seedu.address.model.AccessDisplayFlags.LEFT_JUSTIFY; +import static seedu.address.model.AccessDisplayFlags.MENU_OK; +import static seedu.address.model.AccessDisplayFlags.RIGHT_JUSTIFY; +import static seedu.address.model.AccessDisplayFlags.STRIKETHROUGH; +import static seedu.address.model.AccessDisplayFlags.UNDERLINE; + +import java.util.HashMap; +import java.util.Map; + +import javafx.scene.Node; +import javafx.scene.control.Label; + +/** + * Creates an Abstract class to handle repeated and overused methods when making Attributes. + */ +public abstract class AbstractAttribute<T> implements Attribute<T> { + + public static final String SAVE_KEY_TYPE_NAME = "type"; + public static final String SAVE_KEY_VALUE = "content"; + public static final String SAVE_KEY_DISPLAY_FORMAT = "display_format"; + public static final String SAVE_KEY_STYLE_FORMAT = "style_format"; + + protected T value; + protected String typeName; + private int accessCtrl; + private int styleFlag; + + /** + * Creates an instance of an abstract attribute class + */ + public AbstractAttribute(String typeName, T value, int accessCtrl, int styleFlag) { + requireNonNull(typeName); + + this.typeName = typeName.trim(); + this.value = value; + this.accessCtrl = accessCtrl; + this.styleFlag = styleFlag; + } + + public AbstractAttribute(String typeName, T value) { + this(typeName, value, DEFAULT, DEFAULT_STYLE); + } + + @Override + public boolean isNameMatch(String name) { + return typeName.trim().equalsIgnoreCase(name.trim()); + } + + @Override + public boolean isAllFlagMatch(int flag) { + return (accessCtrl & flag) == flag; + } + + @Override + public boolean isAnyFlagMatch(int flag) { + return (accessCtrl & flag) > 0; + } + + @Override + public boolean isAnyStyleMatch(int flag) { + return (styleFlag & flag) > 0; + } + + @Override + public boolean isAllStyleMatch(int flag) { + return (styleFlag & flag) == flag; + } + + @Override + public T getAttributeContent() { + return value; + } + + @Override + public String getAttributeType() { + return typeName; + } + + @Override + public boolean isVisibleInMenu() { + return isAllFlagMatch(MENU_OK); + } + + @Override + public boolean isDisplayable() { + return isAllFlagMatch(DISPLAY_OK); + } + + @Override + public <U> boolean isSameType(Attribute<U> o) { + return o.getAttributeType().toLowerCase().equals(typeName.toLowerCase()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AbstractAttribute // instanceof handles nulls + && typeName.equals(((AbstractAttribute<?>) other).typeName) + && value.equals(((AbstractAttribute<?>) other).value)); // state check + } + + @Override + public String toString() { + if (value == null) { + return ""; + } + if (isAllFlagMatch(HIDE_TYPE)) { + return value.toString(); + } + return String.format("%s: %s", typeName, value); + } + + @Override + public Node getJavaFxRepresentation() { + String txt; + if (isAllFlagMatch(HIDE_TYPE)) { + txt = value.toString(); + } else { + txt = String.format("%s: %s", typeName, value); + } + + Label ret = new Label(); + ret.setText(txt); + ret.setStyle(getFormatCss()); + return ret; + } + + @Override + public int hashCode() { + return typeName.hashCode() ^ value.hashCode() ^ accessCtrl ^ styleFlag; + } + + @Override + public Map<String, Object> toSaveableData() { + Map<String, Object> ret = new HashMap<>(); + ret.put(SAVE_KEY_TYPE_NAME, typeName); + ret.put(SAVE_KEY_VALUE, value); + ret.put(SAVE_KEY_DISPLAY_FORMAT, accessCtrl); + ret.put(SAVE_KEY_STYLE_FORMAT, styleFlag); + + return ret; + } + + protected String getFormatCss() { + return getFormatCss(true); + } + + protected String getFormatCss(boolean isInMenu) { + StringBuilder sb = new StringBuilder("-fx-font: normal"); + double size = 12; + + if (isAllStyleMatch(BOLD)) { + sb.append(" bold"); + } + if (isAllStyleMatch(ITALIC)) { + sb.append(" italic"); + } + if (isAllStyleMatch(FONT_SIZE_SMALL)) { + size = 8; + } + if (isAllStyleMatch(FONT_SIZE_BIG) && !isInMenu) { + size = 32; + } + if (isAllStyleMatch(FONT_SIZE_NORMAL)) { + size = 10; + } + + sb.append(String.format(" %fpt 'Segoe UI';", size)); + + if (isAllStyleMatch(UNDERLINE)) { + sb.append(" -fx-underline: true;"); + } + if (isAllStyleMatch(STRIKETHROUGH)) { + sb.append(" -fx-strikethrough: true;"); + } + if (isAllStyleMatch(DROPSHADOW)) { + sb.append(" -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 10, 0, 0, 0);"); + } + if (isAllStyleMatch(LEFT_JUSTIFY)) { + sb.append(" -fx-text-alignment: left;"); + } + if (isAllStyleMatch(CENTER_JUSTIFY)) { + sb.append(" -fx-text-alignment: center;"); + } + if (isAllStyleMatch(RIGHT_JUSTIFY)) { + sb.append(" -fx-text-alignment: right;"); + } + return sb.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/attribute/Address.java b/src/main/java/seedu/address/model/attribute/Address.java new file mode 100644 index 00000000000..3deb254dd1d --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Address.java @@ -0,0 +1,45 @@ +package seedu.address.model.attribute; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; + +import seedu.address.logic.parser.Prefix; + +/** + * Represents a Person's address in the address book. Guarantees: immutable; is valid as declared in + * {@link #isValidAddress(String)} + */ +public class Address extends AbstractAttribute<String> implements PrefixedAttribute { + + public static final String TYPE = "Address"; + public static final String MESSAGE_CONSTRAINTS = "Addresses can take 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. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + /** + * Constructs an {@code Address}. + * + * @param address A valid address. + */ + public Address(String address) { + super(TYPE, address); + checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); + value = address; + } + + /** + * Returns true if a given string is a valid email. + */ + public static boolean isValidAddress(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public Prefix getPrefix() { + return PREFIX_ADDRESS; + } +} diff --git a/src/main/java/seedu/address/model/attribute/Attribute.java b/src/main/java/seedu/address/model/attribute/Attribute.java new file mode 100644 index 00000000000..400a2d83498 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Attribute.java @@ -0,0 +1,86 @@ +package seedu.address.model.attribute; + +import java.util.Map; + +import javafx.scene.Node; + +/** + * Interface to represent the information required to represent one attribute as + * well as methods to handle conversion of data and represenation of data in the + * GUI. + */ +public interface Attribute<T> { + /** + * Get the type label of this attribute. + * + * @return + */ + String getAttributeType(); + + /** + * Get the content of this attribute. + * + * @return + */ + T getAttributeContent(); + + /** + * Checks whether a string has the same name as the attribute, regardless + * of the case. + * + * @return true if the string is equal to the attribute name, false otherwise + */ + boolean isNameMatch(String name); + + /** + * Returns true if the attribute can be displayed in the menu screen. + */ + boolean isVisibleInMenu(); + + /** + * Returns true if the attribute can be displayed graphically. + * + * @return + */ + boolean isDisplayable(); + + /** + * Returns true if all bits of flag is true + */ + boolean isAllFlagMatch(int flag); + + /** + * Returns true if any bit of the flag is true + */ + boolean isAnyFlagMatch(int flag); + + /** + * Returns true of any of the bits of the style flag settings is true + */ + boolean isAnyStyleMatch(int flag); + + /** + * Returns true of all of the bits of the style flag settings is true + */ + boolean isAllStyleMatch(int flag); + + /** + * Returns the UI representation of the attribute to be added into the Javafx + * parent. + * + * @return + */ + Node getJavaFxRepresentation(); + + /** + * Checks if this object has the same type as another attribute. + */ + <U> boolean isSameType(Attribute<U> o); + + /** + * Returns a map representation of attribute that is saveable by Storage. + * + * @return + */ + Map<String, Object> toSaveableData(); +} diff --git a/src/main/java/seedu/address/model/attribute/AttributeList.java b/src/main/java/seedu/address/model/attribute/AttributeList.java new file mode 100644 index 00000000000..8a37d47a3a5 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/AttributeList.java @@ -0,0 +1,261 @@ +package seedu.address.model.attribute; + +import static seedu.address.commons.util.StringUtil.properCase; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.attribute.exceptions.AttributeNotFoundException; +import seedu.address.model.attribute.exceptions.DuplicateAttributeException; + +/** + * Represents a Person's custom fields pairings in the address book. + */ +public class AttributeList { + + // For logging purposes before updating UI + private static final Logger logger = LogsCenter.getLogger(AttributeList.class); + + // A list of fields + private final List<Attribute<?>> attributeList; + + /** + * Constructs a new Fields instance. + */ + public AttributeList() { + attributeList = new ArrayList<>(); + } + + /** + * Creates an Attribute instance from a given attributeName and value. + * + * @param attributeName the name of the attribute. + * @param value the value to be stored in the attribute. + * @param <T> the type parameter of the value stored in the attribute. + * @return an {@code Attribute} instance with the specified attributeName and value. + */ + public <T> Attribute<T> createAttributeInstance(String attributeName, T value) { + String name = properCase(attributeName); + return new AbstractAttribute<T>(name, value) {}; + } + + /** + * Creates an Attribute instance from a given attributeName and value. + * + * @param attributeName the name of the attribute. + * @param value the value to be stored in the attribute. + * @param <T> the type parameter of the value stored in the attribute. + * @return an {@code Attribute} instance with the specified attributeName and value. + */ + public <T> Attribute<T> createAttributeInstance(String attributeName, T value, int setting, int style) { + String name = properCase(attributeName); + return new AbstractAttribute<T>(name, value, setting, style) {}; + } + + /** + * Adds a field to the list of fields. + * + * @param attribute A Field instance to be added to the list. + */ + public void addAttribute(Attribute<?> attribute) { + attributeList.add(attribute); + logger.info(String.format("Attribute added successfully: %s", attribute.getAttributeType())); + } + + /** + * Adds a field to the list of fields by a given field name. + * + * @param <T> type of value + * @param attributeName the name of the Field instance to be added to the list. + * @param value the value of the field. + */ + public <T> void addAttribute(String attributeName, T value) throws AttributeException { + if (findAttribute(attributeName) != null) { + throw new DuplicateAttributeException(attributeName, attributeName); + } + Attribute<T> attribute = createAttributeInstance(attributeName, value); + this.addAttribute(attribute); + } + + /** + * Adds a field to the list of fields by a given field name. + * + * @param <T> type of value + * @param attributeName the name of the Field instance to be added to the list. + * @param value the value of the field. + */ + public <T> void addAttribute(String attributeName, T value, int setting, int style) throws AttributeException { + if (this.findAttribute(attributeName) != null) { + throw new DuplicateAttributeException(attributeName, attributeName); + } + Attribute<T> attribute = createAttributeInstance(attributeName, value, setting, style); + this.addAttribute(attribute); + } + + /** + * Adds an attribute given an attributeName. + * + * @param attributeName + * @param <T> + * @throws AttributeException + */ + public <T> void addAttribute(String attributeName) throws AttributeException { + this.addAttribute(attributeName, ""); + } + + /** + * Finds an attribute that matches the attribute name. + * + * @param attributeName + * @return + */ + public Attribute<?> findAttribute(String attributeName) { + List<Attribute<?>> lst = attributeList.stream() + .filter(attr -> attr.isNameMatch(attributeName)) + .collect(Collectors.toList()); + if (lst.isEmpty()) { + return null; + } + return lst.get(0); + } + + /** + * Edits the attribute associated with the attributeName to the attributeValue. + * + * @param attributeName the name of the attribute to be edited. + * @param attributeValue the new value of the attribute to be edited. + */ + public void editAttribute(String attributeName, String attributeValue) throws AttributeException { + Attribute<?> oldAttribute = findAttribute(attributeName); + if (oldAttribute == null) { + throw new AttributeException("No attribute found: " + attributeName); + } + Attribute<?> newAttribute = createAttributeInstance(attributeName, attributeValue); + updateAttribute(oldAttribute, newAttribute); + logger.info("Attribute edited successfully"); + } + + /** + * Deletes an attribute + * + * @param type + */ + public void removeAttribute(String type) throws AttributeException { + if (attributeList.stream().noneMatch(attr -> attr.isNameMatch(type))) { + throw new AttributeNotFoundException(type); + } + attributeList.removeIf(attr -> attr.isNameMatch(type)); + } + + /** + * Removes a field from the list of fields. + * + * @param attribute A field to be removed from the list. + * @return true if the Field was removed successfully, false otherwise. + */ + public boolean removeAttribute(Attribute<?> attribute) { + return attributeList.remove(attribute); + } + + /** + * Removes a field from the list of fields. + * + * @param attributeName The name of the field to be removed from the list. + */ + public void removeField(String attributeName) { + List<Attribute<?>> attributesToRemove = attributeList.stream() + .filter(attribute -> attribute.getAttributeType().equalsIgnoreCase(attributeName)) + .collect(Collectors.toList()); + attributeList.removeAll(attributesToRemove); + } + + /** + * Updates the Field object with a new Field object. + * + * @param oldAttribute The old Field object from the Person. + * @param newAttribute The new Field object to be updated. + */ + public void updateAttribute(Attribute<?> oldAttribute, Attribute<?> newAttribute) throws AttributeException { + int index = attributeList.indexOf(oldAttribute); + if (index < 0) { + throw new AttributeException("Attribute not found"); + } + attributeList.set(index, newAttribute); + } + + /** + * Retrieves the value of a field given by a specified name. + * + * @param name The name of the field to be searched. + * @return the value of the field queried. + */ + public Object retrieveFieldValue(String name) { + for (Attribute<?> attribute : attributeList) { + if (attribute.getAttributeType().equals(name)) { + return attribute.getAttributeContent(); + } + } + return null; + } + + /** + * Adds all items from a given list of fields to the list stored in the Fields object. + * + * @param attributeList A list of fields to add. + */ + public void addAll(List<Attribute<?>> attributeList) { + this.attributeList.addAll(attributeList); + } + + /** + * Adds all items from a given Fields instance. + * + * @param attributeList A Fields object containing field information to be added from. + */ + public void addAll(AttributeList attributeList) { + if (attributeList != null && !attributeList.isEmpty()) { + this.attributeList.addAll(attributeList.toList()); + } + } + + /** + * Returns a List representation of the {@code Fields} instance. + * + * @return a List containing the {@code Field} instances. + */ + public List<Attribute<?>> toList() { + return new ArrayList<>(attributeList); + } + + /** + * Checks if the {@code Fields} is empty. + * + * @return true if there are no {@code Field} instances stored in this {@code Fields} object, false + * otherwise. + */ + public boolean isEmpty() { + return attributeList.isEmpty(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + attributeList.forEach(builder::append); + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof AttributeList) { + AttributeList obj = (AttributeList) o; + return attributeList.equals(obj.attributeList); + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/attribute/Description.java b/src/main/java/seedu/address/model/attribute/Description.java new file mode 100644 index 00000000000..71745d1e670 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Description.java @@ -0,0 +1,26 @@ +package seedu.address.model.attribute; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; + +import seedu.address.logic.parser.Prefix; + +/** + * Creates an attribute to represent a description of a display Item + */ +public class Description extends AbstractAttribute<String> implements PrefixedAttribute { + + public static final String TYPE = "Description"; + + public Description(String string) { + super(TYPE, string); + } + + @Override + public Prefix getPrefix() { + return PREFIX_DESCRIPTION; + } + + public void edit(String newData) { + this.value = newData; + } +} diff --git a/src/main/java/seedu/address/model/attribute/Email.java b/src/main/java/seedu/address/model/attribute/Email.java new file mode 100644 index 00000000000..70f132795c0 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Email.java @@ -0,0 +1,72 @@ +package seedu.address.model.attribute; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; + +import seedu.address.logic.parser.Prefix; + +/** + * Represents a Person's email in the address book. Guarantees: immutable; is valid as declared in + * {@link #isValidEmail(String)} + */ +public class Email extends AbstractAttribute<String> implements PrefixedAttribute { + + public static final String TYPE = "Email"; + private static final String SPECIAL_CHARACTERS = "+_.-"; + public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " + + "and adhere to the following constraints:\n" + + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " + + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " + + "characters.\n" + + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " + + "separated by periods.\n" + + "The domain name must:\n" + + " - 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."; + // alphanumeric and special characters + private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore + private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" + + ALPHANUMERIC_NO_UNDERSCORE + ")*"; + 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; + public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; + + public final String value; + + /** + * Constructs an {@code Email}. + * + * @param email A valid email address. + */ + public Email(String email) { + super(TYPE, email); + checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); + value = email; + } + + /** + * Returns if a given string is a valid email. + */ + public static boolean isValidEmail(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public Prefix getPrefix() { + return PREFIX_EMAIL; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; // short circuit if same object + } else if (o instanceof Email) { + Email email = (Email) o; + return value.equalsIgnoreCase(email.value); + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/attribute/Field.java b/src/main/java/seedu/address/model/attribute/Field.java new file mode 100644 index 00000000000..bf543bf13d6 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Field.java @@ -0,0 +1,110 @@ +package seedu.address.model.attribute; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's custom field in the address book. Guarantees: immutable; is valid as + * declared in {@link #isValidField(String)} + */ +public class Field { + + public static final String MESSAGE_CONSTRAINTS = "Custom fields can take in any values, and it should not be blank"; + + /* + * The first character of the field must not be a whitespace, otherwise " " (a blank string) becomes + * a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String name; + public final String value; + + /** + * Constructs a {@code Field}. + * + * @param name The name of the field. + */ + public Field(String name) { + requireNonNull(name); + checkArgument(isValidField(name), MESSAGE_CONSTRAINTS); + this.name = name; + this.value = null; + } + + /** + * Constructs a {@code Field}. + * + * @param name The name of the field. + * @param value The value of the field. + */ + public Field(String name, String value) { + requireNonNull(name); + checkArgument(isValidField(name), MESSAGE_CONSTRAINTS); + this.name = name; + requireNonNull(value); + checkArgument(isValidField(value), MESSAGE_CONSTRAINTS); + this.value = value; + } + + /** + * Sets the value of a custom Field by returning a new Field containing the field name and the new + * value to preserve immutability + * + * @param value The value to be set to the field + * @return A new Field instance containing the name and the specified value + */ + public Field setValue(String value) { + return new Field(name, value); + } + + /** + * Checks if the value of the Field has been set + * + * @return true if the value of the field is not null, false otherwise + */ + public boolean isValueSet() { + return value == null; + } + + /** + * Returns true if a given string is a valid name argument. + */ + public static boolean isValidField(String test) { + return test.matches(VALIDATION_REGEX); + } + + public boolean isNameMatch(String test) { + return test.equalsIgnoreCase(name); + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return "[" + name + "," + value + "]"; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Field) { + Field castedOther = (Field) other; + if (castedOther.value == null && value == null) { + return castedOther.isNameMatch(name); + } else if (castedOther.value == null || value == null) { + return false; + } + return name.equalsIgnoreCase(castedOther.name) && value.equals(castedOther.value); + } + return false; + } + + @Override + public int hashCode() { + return value == null ? name.hashCode() : value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/attribute/Name.java b/src/main/java/seedu/address/model/attribute/Name.java new file mode 100644 index 00000000000..32ee7fadd15 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Name.java @@ -0,0 +1,60 @@ +package seedu.address.model.attribute; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.commons.util.StringUtil.properCase; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.logic.parser.Prefix; + +/** + * Represents a Attribute's name in the address book. Guarantees: immutable; is valid as declared in + * {@link #isValidName(String)} + */ +public class Name extends AbstractAttribute<String> implements PrefixedAttribute { + + public static final String TYPE = "Name"; + public static final String MESSAGE_CONSTRAINTS = "Names should only contain alphanumeric " + + "characters and spaces and can include special characters like '-' and '_',\n but it should not be blank"; + + /* + * The first character of the address must not be a whitespace, otherwise " " (a blank string) + * becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[a-zA-Z][a-zA-Z0-9_\\- ]*"; + + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public Name(String name) { + super(TYPE, name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + fullName = properCase(name); + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public Prefix getPrefix() { + return PREFIX_NAME; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; // short circuit if same object + } else if (o instanceof Name) { + Name n = (Name) o; + return fullName.equals(n.fullName); + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/attribute/Phone.java b/src/main/java/seedu/address/model/attribute/Phone.java new file mode 100644 index 00000000000..cf200b8a606 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Phone.java @@ -0,0 +1,59 @@ +package seedu.address.model.attribute; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import seedu.address.logic.parser.Prefix; + +/** + * Represents a Person's phone number in the address book. Guarantees: immutable; is valid as + * declared in {@link #isValidPhone(String)} + */ +public class Phone extends AbstractAttribute<String> implements PrefixedAttribute { + + public static final String TYPE = "Phone"; + public static final String MESSAGE_CONSTRAINTS = + "Phone numbers can contain country code, region code and numbers.\n" + + "Optional Country code can begin with a optional '+' and followed by at most 4 digits\n" + + "Optional Area code can be at most 4 digits long\n" + + "Compulsary Phone number must be minimally 3 digits long\n"; + public static final String VALIDATION_REGEX = "(?:\\+?\\d{1,4} )?(?:\\d{1,4} )?\\d{3,}"; + public final String value; + + /** + * Constructs a {@code Phone}. + * + * @param phone A valid phone number. + */ + public Phone(String phone) { + super(TYPE, phone.replaceAll("\\s+", " ").trim()); + requireNonNull(phone); + checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); + value = phone; + } + + /** + * Returns true if a given string is a valid phone number. + */ + public static boolean isValidPhone(String test) { + test = test.replaceAll("\\s+", " ").trim(); + return test.matches(VALIDATION_REGEX); + } + + @Override + public Prefix getPrefix() { + return PREFIX_PHONE; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof Phone) { + Phone otherPhone = (Phone) o; + return value.equals(otherPhone.value); + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/attribute/PrefixedAttribute.java b/src/main/java/seedu/address/model/attribute/PrefixedAttribute.java new file mode 100644 index 00000000000..128363fa423 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/PrefixedAttribute.java @@ -0,0 +1,13 @@ +package seedu.address.model.attribute; + +import seedu.address.logic.parser.Prefix; + +/** + * An interface to mean that the attribute has a prefix + */ +public interface PrefixedAttribute { + /** + * Gets the prefix of this attribute + */ + Prefix getPrefix(); +} diff --git a/src/main/java/seedu/address/model/attribute/Progress.java b/src/main/java/seedu/address/model/attribute/Progress.java new file mode 100644 index 00000000000..8e22e0cba83 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/Progress.java @@ -0,0 +1,29 @@ +package seedu.address.model.attribute; + +/** + * A class that represents Progress as an attribute. + */ +public class Progress extends AbstractAttribute<String> { + public static final String TYPE = "Progress"; + public static final String MESSAGE_CONSTRAINTS = "Progress should be set to 25%, 50%, 75% or 100%."; + + /** + * Constructor to initiate progress. + * @param level + */ + public Progress(String level) { + super(TYPE, String.valueOf(level)); + } + + /** + * Method to check the input for setting progress. + * @param test + * @return true if input is valid. + */ + public static boolean isValidProgress(String test) { + if (test == "25%" || test == "50%" || test == "75%" || test == "100%") { + return true; + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/attribute/exceptions/AttributeException.java b/src/main/java/seedu/address/model/attribute/exceptions/AttributeException.java new file mode 100644 index 00000000000..07012a51ddc --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/exceptions/AttributeException.java @@ -0,0 +1,20 @@ +package seedu.address.model.attribute.exceptions; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Encapsulates an exception for checked exceptions during the evaluation of Attribute-related + * commands + */ +public class AttributeException extends CommandException { + + /** + * Constructs an AttributeException + * + * @param msg + */ + public AttributeException(String msg) { + super(msg); + } + +} diff --git a/src/main/java/seedu/address/model/attribute/exceptions/AttributeNotFoundException.java b/src/main/java/seedu/address/model/attribute/exceptions/AttributeNotFoundException.java new file mode 100644 index 00000000000..9b98341208c --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/exceptions/AttributeNotFoundException.java @@ -0,0 +1,19 @@ +package seedu.address.model.attribute.exceptions; + +/** + * Encapsulates an AttributeNotFoundException + */ +public class AttributeNotFoundException extends AttributeException { + + private static final String MESSAGE = "Attribute %s is not found"; + + /** + * Cosntructs an AttributeNotFoundException + * @param attributeName + */ + public AttributeNotFoundException(String attributeName) { + super(String.format(MESSAGE, attributeName)); + } + +} + diff --git a/src/main/java/seedu/address/model/attribute/exceptions/DuplicateAttributeException.java b/src/main/java/seedu/address/model/attribute/exceptions/DuplicateAttributeException.java new file mode 100644 index 00000000000..0a982ffc096 --- /dev/null +++ b/src/main/java/seedu/address/model/attribute/exceptions/DuplicateAttributeException.java @@ -0,0 +1,19 @@ +package seedu.address.model.attribute.exceptions; + +/** + * Encapsulates an DuplicateAttributeException + */ +public class DuplicateAttributeException extends AttributeException { + + private static final String MESSAGE = "Duplicate attribute found. You provided: %s, previously stored: %s"; + + /** + * Constructs a DuplicateAttributeException + * @param existingAttributeName + * @param newAttributeName + */ + public DuplicateAttributeException(String existingAttributeName, String newAttributeName) { + super(String.format(MESSAGE, newAttributeName, existingAttributeName)); + } + +} diff --git a/src/main/java/seedu/address/model/group/Group.java b/src/main/java/seedu/address/model/group/Group.java new file mode 100644 index 00000000000..f64745ee3a2 --- /dev/null +++ b/src/main/java/seedu/address/model/group/Group.java @@ -0,0 +1,43 @@ +package seedu.address.model.group; + +import static seedu.address.model.AccessDisplayFlags.GROUP; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import seedu.address.model.item.AbstractSingleItem; + +/** + * Represents a Group in the address book. + */ +public class Group extends AbstractSingleItem { + + public static final String VALIDATION_REGEX = "[a-zA-Z][a-zA-Z0-9_\\-]*"; + // public static final String VALIDATION_REGEX = "[a-zA-Z][a-zA-Z0-9]*"; + public static final String MESSAGE_CONSTRAINTS = "A group name should only consist " + + "of alphanumeric characters only and start with a letter.\n"; + + /** + * Constructor to create a group object + */ + public Group(String groupName) { + super(groupName, GROUP, GROUP); + assert isValidGroupName(groupName); + } + + /** + * Checks if the group name is valid. A group name is valid + * if the group name is fully alphanumeric. + * + * @param groupName for a specific team. + * @return true if the group name is valid, false otherwise. + */ + public static boolean isValidGroupName(String groupName) { + return groupName.matches(VALIDATION_REGEX); + } + + @Override + public UUID getUid() { + return UUID.nameUUIDFromBytes(("Group: " + getFullPath()).getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/seedu/address/model/group/Path.java b/src/main/java/seedu/address/model/group/Path.java new file mode 100644 index 00000000000..cf229cbe18a --- /dev/null +++ b/src/main/java/seedu/address/model/group/Path.java @@ -0,0 +1,44 @@ +package seedu.address.model.group; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Path to the nested group in the address book. + */ +public class Path { + + public static final String MESSAGE_CONSTRAINTS = "A path to a should be in " + + "the format of group[/nested-group/...], where [...] refers to optional " + + "arguments in the path. The path should adhere to the following " + + "constraints:\n" + + "1. Both group and nested-groups can contain all alphanumeric characters, " + + "hyphens and underscores only.\n" + + "2. Each of the group or nested groups are being split by a slash (/).\n"; + + private final String path; + + /** + * Constructs a path. Takes in a full path, typically denoted by + * the form group[/nested-group/...]. + * + * @param path that leads to an existing group. + */ + public Path(String path) { + requireNonNull(path); + this.path = path; + } + + /** + * Gets the path, denoted by the form group[/nested-group/...]. + * + * @return a String that stores the path to a group. + */ + public String getPath() { + return path; + } + + @Override + public String toString() { + return path; + } +} diff --git a/src/main/java/seedu/address/model/group/UniqueGroupList.java b/src/main/java/seedu/address/model/group/UniqueGroupList.java new file mode 100644 index 00000000000..5be23bb5906 --- /dev/null +++ b/src/main/java/seedu/address/model/group/UniqueGroupList.java @@ -0,0 +1,18 @@ +package seedu.address.model.group; + +import seedu.address.model.item.DisplayItemList; + +/** + * A list of groups that enforces uniqueness between its elements and does not + * allow nulls. + * A group is considered unique by comparing using + * {@code DisplayItem#weakEqual(displayItem))}. As such, adding and updating of + * persons uses Person#isSamePerson(Person) for equality so as to ensure that + * + * Supports a minimal set of list operations. + * + * @see Person#isSamePerson(Person) + */ +public class UniqueGroupList extends DisplayItemList<Group> { + +} diff --git a/src/main/java/seedu/address/model/group/exceptions/GroupOutOfBoundException.java b/src/main/java/seedu/address/model/group/exceptions/GroupOutOfBoundException.java new file mode 100644 index 00000000000..dda8bc9cad3 --- /dev/null +++ b/src/main/java/seedu/address/model/group/exceptions/GroupOutOfBoundException.java @@ -0,0 +1,16 @@ +package seedu.address.model.group.exceptions; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Encapsulates an OutOfBoundException for a Group + */ +public class GroupOutOfBoundException extends CommandException { + + public static final String ERR_MSG = "Group out of bounds. Length is only %d yet index %d supplied."; + + public GroupOutOfBoundException(int length, int index) { + super(String.format(ERR_MSG, length, index)); + } + +} diff --git a/src/main/java/seedu/address/model/item/AbstractDisplayItem.java b/src/main/java/seedu/address/model/item/AbstractDisplayItem.java new file mode 100644 index 00000000000..fe2f74d9acc --- /dev/null +++ b/src/main/java/seedu/address/model/item/AbstractDisplayItem.java @@ -0,0 +1,171 @@ +package seedu.address.model.item; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.AttributeList; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.tag.Tag; + +/** + * Abstract class to represent an item that can contain other items. + */ +public abstract class AbstractDisplayItem implements DisplayItem { + + protected Name name; + private int typeFlag; + private int parentTypeFlag; + private AttributeList attributes; + private Set<Tag> tags; + + protected AbstractDisplayItem(String name, int typeFlag, int parentTypeFlag) { + requireAllNonNull(name, typeFlag); + this.name = new Name(name); + this.typeFlag = typeFlag; + this.parentTypeFlag = parentTypeFlag; + attributes = new AttributeList(); + tags = new HashSet<>(); + } + + @Override + public void rename(String newName) { + assert Name.isValidName(newName); + this.name = new Name(newName); + } + + @Override + public Set<Tag> getTags() { + return Collections.unmodifiableSet(tags); + } + + @Override + public void addTags(String... descriptions) { + for (String description : descriptions) { + Tag toAdd = new Tag(description); + if (!tags.contains(toAdd)) { + tags.add(toAdd); + } + } + } + + @Override + public void deleteTag(String description) { + Tag toDelete = new Tag(description); + tags.removeIf(tag -> tag.equals(toDelete)); + } + + @Override + public Optional<Attribute<?>> getAttribute(String type) { + return getAttributes().stream() + .filter(attr -> attr.isNameMatch(type)) + .findFirst(); + } + + @Override + public void editAttribute(String attributeName, String attributeContent) throws AttributeException { + attributes.editAttribute(attributeName, attributeContent); + } + + @Override + public void addAttribute(Attribute<?> attribute) { + attributes.addAttribute(attribute); + } + + @Override + public void addAttribute(String attributeName, String attributeContent) throws AttributeException { + attributes.addAttribute(attributeName, attributeContent); + } + + @Override + public void setTags(Set<Tag> tags) { + this.tags = tags; + } + + protected boolean canBeChildOf(AbstractDisplayItem o) { + return (parentTypeFlag & o.typeFlag) > 0; + } + + protected abstract String getTitle(List<String> sb, AbstractDisplayItem o); + + @Override + public int getTypeFlag() { + return typeFlag; + } + + @Override + public void deleteAttribute(String type) throws AttributeException { + attributes.removeAttribute(type); + } + + @Override + public String toString() { + return name.toString(); + } + + @Override + public Name getName() { + return name; + } + + @Override + public boolean stronglyEqual(DisplayItem o) { + if (!weaklyEqual(o)) { + return false; + } + AbstractDisplayItem g = (AbstractDisplayItem) o; + return g.getParents().equals(getParents()) + && g.getAttributes().equals(getAttributes()) + && g.getTags().equals(getTags()); + } + + @Override + public boolean weaklyEqual(DisplayItem o) { + if (!(o instanceof AbstractDisplayItem)) { + return false; + } + return (o instanceof AbstractDisplayItem) + && ((AbstractDisplayItem) o).getFullPath().equals(getFullPath()) + && ((AbstractDisplayItem) o).typeFlag == typeFlag; + } + + @Override + public boolean isPartOfContext(DisplayItem o) { + if (o == null || getParents().contains(o)) { + return true; + } + for (DisplayItem parent : getParents()) { + if (parent.isPartOfContext(o)) { + return true; + } + } + return false; + } + + @Override + public List<Attribute<?>> getAttributes() { + return attributes.toList(); + } + + @Override + public List<Attribute<?>> getSavedAttributes() { + return getAttributes(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AbstractDisplayItem)) { + return false; + } + return stronglyEqual((AbstractDisplayItem) obj); + } +} diff --git a/src/main/java/seedu/address/model/item/AbstractSingleItem.java b/src/main/java/seedu/address/model/item/AbstractSingleItem.java new file mode 100644 index 00000000000..119adb7130f --- /dev/null +++ b/src/main/java/seedu/address/model/item/AbstractSingleItem.java @@ -0,0 +1,71 @@ +package seedu.address.model.item; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import seedu.address.model.item.exceptions.ItemCannotBeParentException; + +/** + * Abstract class to represent an item can only have a single parent. + */ +public abstract class AbstractSingleItem extends AbstractDisplayItem { + + protected AbstractSingleItem parent = null; + + protected AbstractSingleItem(String name, int typeFlag, int parentFlag) { + super(name, typeFlag, parentFlag); + parent = null; + } + + protected String getTitle(List<String> sb, AbstractDisplayItem o) { + sb.add(name.fullName); + if (parent == null || parent.equals(o)) { + Collections.reverse(sb); + return "/" + String.join("/", sb); + } + return parent.getTitle(sb, o); + } + + @Override + public String getFullPath() { + return getTitle(new ArrayList<String>(), null); + } + + // @@author mohamedsaf1 + @Override + public void setParent(DisplayItem o) { + if (o == null) { + parent = null; + return; + } + if (!(o instanceof AbstractSingleItem)) { + throw new ItemCannotBeParentException(o); + } + parent = (AbstractSingleItem) o; + } + + public AbstractSingleItem getParent() { + return parent; + } + + @Override + public Set<? extends DisplayItem> getParents() { + if (parent == null) { + return Set.of(); + } + return Set.of(parent); + } + + @Override + public void removeParent(DisplayItem o) { + parent = null; + } + // @@author + + @Override + public String toString() { + return getFullPath(); + } +} diff --git a/src/main/java/seedu/address/model/item/DisplayItem.java b/src/main/java/seedu/address/model/item/DisplayItem.java new file mode 100644 index 00000000000..d62c8ec2b73 --- /dev/null +++ b/src/main/java/seedu/address/model/item/DisplayItem.java @@ -0,0 +1,126 @@ +package seedu.address.model.item; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.tag.Tag; + +/** + * Represents a unique item in the addressbook, it can be either a accessable team or an entry + */ +public interface DisplayItem { + /** + * Returns the entry type of the displayable item to determine what type of item this is. + */ + int getTypeFlag(); + + /** + * Defines a stronger notions of equality between display items. + */ + boolean stronglyEqual(DisplayItem o); + + /** + * Defines a weaker notion of equality between display items. + */ + boolean weaklyEqual(DisplayItem o); + + /** + * Makes the current item to belong under {@code DisplayItem o} + */ + void setParent(DisplayItem o); + + /** + * Renames the current item with the name + */ + void rename(String name); + + /** + * Removes o as the parent of the current item {@code DisplayItem o} + */ + void removeParent(DisplayItem o); + + /** + * Returns the full path of the object. + */ + String getFullPath(); + + /** + * Gets a list of attributes applied to DisplayItem + */ + List<Attribute<?>> getAttributes(); + + /** + * Gets attributes that requires saving + */ + List<Attribute<?>> getSavedAttributes(); + + Optional<Attribute<?>> getAttribute(String type); + + /** + * Gets the name of the display item. + */ + Name getName(); + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} if modification + * is attempted. + */ + Set<Tag> getTags(); + + /** + * Adds a tag or multiple tags to this item + */ + void addTags(String... description); + + /** + * Deletes a tag from this item + */ + void deleteTag(String description); + + /** + * Deletes a tag from this item + */ + void setTags(Set<Tag> tags); + + /** + * Gets the list of parents of this displayItem. + */ + Set<? extends DisplayItem> getParents(); + + /** + * Adds an attribute to the current object. + */ + void addAttribute(Attribute<?> attribute); + + /** + * Adds an attribute with an attribute name and attribute content. + */ + void addAttribute(String attributeName, String attributeContent) throws AttributeException; + + /** + * Edits an existing attribute with an attribute name and attribute content. + */ + void editAttribute(String attributeName, String attributeContent) throws AttributeException; + + /** + * Removes an attribute to the current object. + */ + void deleteAttribute(String type) throws AttributeException; + + /** + * Returns true if {@code DisplayItem o} is a parent of this item + */ + boolean isPartOfContext(DisplayItem o); + + /** + * Returns unique uid for this displayItem. + */ + + UUID getUid(); + +} diff --git a/src/main/java/seedu/address/model/item/DisplayItemList.java b/src/main/java/seedu/address/model/item/DisplayItemList.java new file mode 100644 index 00000000000..44961e74782 --- /dev/null +++ b/src/main/java/seedu/address/model/item/DisplayItemList.java @@ -0,0 +1,143 @@ +package seedu.address.model.item; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.item.exceptions.DuplicateItemException; +import seedu.address.model.item.exceptions.ItemNotFoundException; + +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. A + * person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, + * adding and updating of persons uses Person#isSamePerson(Person) for equality so as to ensure that + * the person being added or updated is unique in terms of identity in the UniquePersonList. + * However, the removal of a person uses Person#equals(Object) so as to ensure that the person with + * exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Person#isSamePerson(Person) + */ +public class DisplayItemList<T extends DisplayItem> implements Iterable<T> { + + protected ObservableList<T> internalList = FXCollections.observableArrayList(); + private final ObservableList<T> internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(T toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::weaklyEqual); + } + + /** + * Returns the item in the list that is equal (but no necessarily the same object) as the given + * item. + * + * @param item The item to compare equality against. + * @return The item in the list which is equal to the given item + */ + public T get(T item) throws ItemNotFoundException { + requireNonNull(item); + List<T> matchingItems = internalList.stream().filter(item::weaklyEqual).collect(Collectors.toList()); + if (matchingItems.size() == 0) { + throw new ItemNotFoundException(); + } + return matchingItems.get(0); + } + + /** + * Adds a person to the list. The person must not already exist in the list. + */ + public void add(T toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateItemException(); + } + internalList.add(toAdd); + } + + public <U extends T> void setItems(List<U> items) { + requireAllNonNull(items); + if (!itemsAreUnique(items)) { + throw new DuplicateItemException(); + } + + internalList.setAll(items); + } + + public <U extends T> void setItems(DisplayItemList<U> replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Removes the equivalent item from the list. The item must exist in the list. + */ + public void remove(T toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ItemNotFoundException(); + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<T> asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * removes the element if predicate evals to true. + * + * @param predicate + */ + public void removeIf(Predicate<T> predicate) { + internalList.removeIf(predicate); + } + + @Override + public Iterator<T> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DisplayItemList // instanceof handles nulls + && internalList.equals(((DisplayItemList<?>) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + public void sort() { + internalList.sort((arg0, arg1) -> arg0.getFullPath().toLowerCase().compareTo(arg1.getFullPath().toLowerCase())); + } + + /** + * Returns true if {@code persons} contains only unique persons. + */ + private <U extends T> boolean itemsAreUnique(List<U> items) { + for (int i = 0; i < items.size() - 1; i++) { + for (int j = i + 1; j < items.size(); j++) { + if (items.get(i).weaklyEqual(items.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..622f66b9b31 --- /dev/null +++ b/src/main/java/seedu/address/model/item/NameContainsKeywordsPredicate.java @@ -0,0 +1,40 @@ +package seedu.address.model.item; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Item}'s {@code Name} matches any of the keywords given. + */ +public class NameContainsKeywordsPredicate<U extends DisplayItem> implements Predicate<U> { + private final List<String> keywords; + + public NameContainsKeywordsPredicate(List<String> keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(U item) { + return keywords.stream() + .map(k -> k.trim()) + .anyMatch(keyword -> { + return StringUtil.containsWordIgnoreCase(item.getName().fullName, keyword) + || item.getAttributes() + .stream() + .anyMatch( + attr -> StringUtil + .containsWordIgnoreCase(attr.getAttributeContent().toString(), 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/item/exceptions/DuplicateItemException.java b/src/main/java/seedu/address/model/item/exceptions/DuplicateItemException.java new file mode 100644 index 00000000000..d804378c4cf --- /dev/null +++ b/src/main/java/seedu/address/model/item/exceptions/DuplicateItemException.java @@ -0,0 +1,12 @@ +package seedu.address.model.item.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are + * considered duplicates if they have the same + * identity). + */ +public class DuplicateItemException extends RuntimeException { + public DuplicateItemException() { + super("Operation would result in duplicate items"); + } +} diff --git a/src/main/java/seedu/address/model/item/exceptions/ItemCannotBeParentException.java b/src/main/java/seedu/address/model/item/exceptions/ItemCannotBeParentException.java new file mode 100644 index 00000000000..13439afb0ab --- /dev/null +++ b/src/main/java/seedu/address/model/item/exceptions/ItemCannotBeParentException.java @@ -0,0 +1,12 @@ +package seedu.address.model.item.exceptions; + +import seedu.address.model.item.DisplayItem; + +/** + * Signals that the operation cannot be done as the displayitem cannot be a parent. + */ +public class ItemCannotBeParentException extends RuntimeException { + public ItemCannotBeParentException(DisplayItem item) { + super(String.format("Cannot be added to %s", item.toString())); + } +} diff --git a/src/main/java/seedu/address/model/item/exceptions/ItemNotFoundException.java b/src/main/java/seedu/address/model/item/exceptions/ItemNotFoundException.java new file mode 100644 index 00000000000..2c324751c41 --- /dev/null +++ b/src/main/java/seedu/address/model/item/exceptions/ItemNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.item.exceptions; + +/** + * Signals that the operation is unable to find the specified person. + */ +public class ItemNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take 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. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index f866e7133de..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "+_.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " - + "characters.\n" - + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " - + "separated by periods.\n" - + "The domain name must:\n" - + " - 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."; - // alphanumeric and special characters - private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore - private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" - + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - 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; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 79244d71cf7..00000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_CONSTRAINTS = - "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, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - - public final String fullName; - - /** - * Constructs a {@code Name}. - * - * @param name A valid name. - */ - public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; - } - - /** - * Returns true if a given string is a valid name. - */ - public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - 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 - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} 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<Person> { - private final List<String> keywords; - - public NameContainsKeywordsPredicate(List<String> 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/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..90365ace1a7 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -1,68 +1,41 @@ package seedu.address.model.person; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.model.AccessDisplayFlags.GROUP; +import static seedu.address.model.AccessDisplayFlags.PERSON; -import java.util.Collections; +import java.nio.charset.StandardCharsets; import java.util.HashSet; -import java.util.Objects; +import java.util.List; import java.util.Set; +import java.util.UUID; -import seedu.address.model.tag.Tag; +import seedu.address.model.item.AbstractDisplayItem; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.item.exceptions.ItemCannotBeParentException; /** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. + * 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 extends AbstractDisplayItem { - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set<Tag> tags = new HashSet<>(); + public static final String MESSAGE_INVALID_PERSON_NAME = + "Person name must begin with a letter, isalphanumeric and can only contains space and dash."; + private Set<AbstractSingleItem> parents = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set<Tag> tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set<Tag> getTags() { - return Collections.unmodifiableSet(tags); + public Person(String name) { + super(name, PERSON, GROUP); + requireAllNonNull(name); } /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. + * Returns true if both persons have the same name. This defines a weaker notion of equality between + * two persons. */ public boolean isSamePerson(Person otherPerson) { if (otherPerson == this) { @@ -73,51 +46,42 @@ public boolean isSamePerson(Person otherPerson) { && otherPerson.getName().equals(getName()); } - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ @Override - public boolean equals(Object other) { - if (other == this) { - return true; + public void setParent(DisplayItem o) throws ItemCannotBeParentException { + if (o == null) { + return; } - - if (!(other instanceof Person)) { - return false; + if (!(o instanceof AbstractSingleItem) || parents.contains(o)) { + throw new ItemCannotBeParentException(o); } - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + parents.add((AbstractSingleItem) o); } @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + public void removeParent(DisplayItem deletedParent) { + parents.removeIf(p -> (p.equals(deletedParent) || p.isPartOfContext(deletedParent))); } @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); + public String getFullPath() { + // person should not have a full path. + return name.fullName; + } - Set<Tag> tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } - return builder.toString(); + @Override + public Set<? extends DisplayItem> getParents() { + return parents; + } + + @Override + protected String getTitle(List<String> sb, AbstractDisplayItem o) { + // TODO Auto-generated method stub + return null; } + @Override + public UUID getUid() { + return UUID.nameUUIDFromBytes(("Person: " + getFullPath()).getBytes(StandardCharsets.UTF_8)); + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -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,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..8d4a4688aff 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -6,27 +6,23 @@ import java.util.Iterator; import java.util.List; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import seedu.address.model.item.DisplayItemList; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; /** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. + * A list of persons that enforces uniqueness between its elements and does not allow nulls. A + * person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, + * adding and updating of persons uses Person#isSamePerson(Person) for equality so as to ensure that + * the person being added or updated is unique in terms of identity in the UniquePersonList. + * However, the removal of a person uses Person#equals(Object) so as to ensure that the person with + * exactly the same fields will be removed. * * Supports a minimal set of list operations. * * @see Person#isSamePerson(Person) */ -public class UniquePersonList implements Iterable<Person> { - - private final ObservableList<Person> internalList = FXCollections.observableArrayList(); - private final ObservableList<Person> internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); +public class UniquePersonList extends DisplayItemList<Person> { /** * Returns true if the list contains an equivalent person as the given argument. @@ -37,9 +33,9 @@ public boolean contains(Person toCheck) { } /** - * Adds a person to the list. - * The person must not already exist in the list. + * Adds a person to the list. The person must not already exist in the list. */ + @Override public void add(Person toAdd) { requireNonNull(toAdd); if (contains(toAdd)) { @@ -49,9 +45,9 @@ public void add(Person toAdd) { } /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. + * Replaces the person {@code target} in the list with {@code editedPerson}. {@code target} must + * exist in the list. The person identity of {@code editedPerson} must not be the same as another + * existing person in the list. */ public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); @@ -69,8 +65,7 @@ public void setPerson(Person target, Person editedPerson) { } /** - * Removes the equivalent person from the list. - * The person must exist in the list. + * Removes the equivalent person from the list. The person must exist in the list. */ public void remove(Person toRemove) { requireNonNull(toRemove); @@ -85,8 +80,8 @@ public void setPersons(UniquePersonList replacement) { } /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of this list with {@code persons}. {@code persons} must not contain + * duplicate persons. */ public void setPersons(List<Person> persons) { requireAllNonNull(persons); @@ -97,13 +92,6 @@ public void setPersons(List<Person> persons) { internalList.setAll(persons); } - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList<Person> asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - @Override public Iterator<Person> iterator() { return internalList.iterator(); @@ -112,8 +100,8 @@ public Iterator<Person> iterator() { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); + || (other instanceof UniquePersonList // instanceof handles nulls + && internalList.equals(((UniquePersonList) other).internalList)); } @Override diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java index d7290f59442..4c7648a1c70 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java @@ -1,7 +1,8 @@ package seedu.address.model.person.exceptions; /** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * Signals that the operation will result in duplicate Persons (Persons are + * considered duplicates if they have the same * identity). */ public class DuplicatePersonException extends RuntimeException { diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonOutOfBoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonOutOfBoundException.java new file mode 100644 index 00000000000..1e1f6345044 --- /dev/null +++ b/src/main/java/seedu/address/model/person/exceptions/PersonOutOfBoundException.java @@ -0,0 +1,15 @@ +package seedu.address.model.person.exceptions; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Encapsulates an OutOfBoundException for Person + */ +public class PersonOutOfBoundException extends CommandException { + public static final String ERR_MSG = "Person out of bounds. Length is only %d yet index %d supplied."; + + public PersonOutOfBoundException(int length, int index) { + super(String.format(ERR_MSG, length, index)); + } + +} diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java new file mode 100644 index 00000000000..a805de88bb8 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Task.java @@ -0,0 +1,229 @@ +package seedu.address.model.task; + +import static seedu.address.model.AccessDisplayFlags.GROUP; +import static seedu.address.model.AccessDisplayFlags.PERSON; +import static seedu.address.model.AccessDisplayFlags.TASK; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Description; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.item.AbstractDisplayItem; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.item.exceptions.ItemCannotBeParentException; +import seedu.address.model.person.Person; + +/** + * Stores task details. + */ +public class Task extends AbstractSingleItem { + + private final Description description; + private final LocalDateTime completedTime; + private Set<Person> assignedParents = new HashSet<>(); + + /** + * Create a new task with no completed_time + * + * @param title The title of the task. + * @param description The description of the task. + */ + public Task(String title, String description) { + this(title, description, null); + } + + /** + * Create a new task with a completed_time. + * + * @param title The title of the task. + * @param description The description of the task. + * @param completedTime The completed_time of the task. + */ + public Task(String title, String description, LocalDateTime completedTime) { + super(title, TASK, GROUP | PERSON); + this.description = new Description(description); + this.completedTime = completedTime; + } + + /** + * Marks the task if it is unmarked + */ + public Task mark() { + if (this.completedTime != null) { + return this; + } + Task ret = new Task(name.fullName, description.getAttributeContent(), LocalDateTime.now()); + for (DisplayItem item : getParents()) { + ret.setParent(item); + } + for (Attribute<?> attr : getSavedAttributes()) { + ret.addAttribute(attr); + } + return ret; + } + + /** + * Unmarks the task if it is marked + */ + public Task unmark() { + if (this.completedTime == null) { + return this; + } + Task ret = new Task(name.fullName, description.getAttributeContent()); + for (DisplayItem item : getParents()) { + ret.setParent(item); + } + for (Attribute<?> attr : getSavedAttributes()) { + ret.addAttribute(attr); + } + return ret; + } + + /** + * Sets the completion date of task + */ + public Task setCompletionTime(LocalDateTime dt) { + if (this.completedTime == null) { + return this; + } + Task ret = new Task(name.fullName, description.getAttributeContent(), dt); + for (DisplayItem item : getParents()) { + ret.setParent(item); + } + return ret; + } + + public LocalDateTime getCompletedTime() { + return completedTime; + } + + public Description getDescription() { + return description; + } + + /** + * Returns true if both tasks have the same name and group. This defines a weaker notion of equality + * between two tasks. + */ + public boolean isSameTask(Task t) { + return getFullPath().equals(t.getFullPath()); + } + + /** + * Defines a stronger notions of equality between display items. + * + * @param o The other item to compare equality to + */ + @Override + public boolean stronglyEqual(DisplayItem o) { + if (!weaklyEqual(o)) { + return false; + } + Task task = (Task) o; + return Objects.equals(completedTime, task.completedTime) + && Objects.equals(description, task.description) + && Objects.equals(getAttributes(), task.getAttributes()); + } + + /** + * Defines a weaker notion of equality between display items. + * + * @param o The other item to compare equality to + */ + @Override + public boolean weaklyEqual(DisplayItem o) { + if (o instanceof Task) { + return isSameTask((Task) o); + } + return false; + } + + @Override + public List<Attribute<?>> getAttributes() { + List<Attribute<?>> ret = new ArrayList<>(); + ret.add(description); + ret.addAll(super.getAttributes()); + return ret; + } + + @Override + public void editAttribute(String attributeName, String attributeContent) throws AttributeException { + attributeName = attributeName.trim(); + if (description.isNameMatch(attributeName)) { + description.edit(attributeContent); + return; + } else if (attributeName.equalsIgnoreCase("Path")) { + throw new AttributeException("Path cannot be edited!"); + } + super.editAttribute(attributeName, attributeContent); + } + + @Override + public List<Attribute<?>> getSavedAttributes() { + return super.getAttributes(); + } + + /** + * Make the current item to belong under {@code DisplayItem o} + * + * @param o The new parent for the Task + */ + @Override + public void setParent(DisplayItem o) throws ItemCannotBeParentException { + if (o == null) { + parent = null; + return; + } + assert o instanceof AbstractDisplayItem; + + if (!canBeChildOf((AbstractDisplayItem) o)) { + throw new ItemCannotBeParentException(o); + } + + if (o instanceof AbstractSingleItem) { + setParentForSingleGrp((AbstractSingleItem) o); + } + + if (o instanceof Person) { + setContactParent((Person) o); + } + + } + + @Override + public Set<? extends DisplayItem> getParents() { + return Stream.concat(super.getParents().stream(), assignedParents.stream()).collect(Collectors.toSet()); + } + + private void setParentForSingleGrp(AbstractSingleItem o) { + super.setParent(o); + } + + private void setContactParent(Person o) { + if (o == null) { + return; + } + if (assignedParents.contains(o)) { + throw new ItemCannotBeParentException(o); + } + + assignedParents.add(o); + } + + @Override + public UUID getUid() { + return UUID.nameUUIDFromBytes(("Task: " + getFullPath()).getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/src/main/java/seedu/address/model/task/UniqueTaskList.java b/src/main/java/seedu/address/model/task/UniqueTaskList.java new file mode 100644 index 00000000000..b6bfb5f187d --- /dev/null +++ b/src/main/java/seedu/address/model/task/UniqueTaskList.java @@ -0,0 +1,122 @@ +package seedu.address.model.task; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import seedu.address.model.item.DisplayItemList; +import seedu.address.model.item.exceptions.DuplicateItemException; +import seedu.address.model.item.exceptions.ItemNotFoundException; + +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. A task is considered unique + * by comparing using {@code Task#isSameTask(Task)}. As such, adding and updating of tasks uses Task#isSameTask(Task) + * for equality so as to ensure that the task being added or updated is unique in terms of identity in the + * UniqueTaskList. However, the removal of a task uses Task#equals(Object) so as to ensure that the task with exactly + * the same fields will be removed. + * <p> + * Supports a minimal set of list operations. + * + * @see Task#isSameTask(Task) + */ +public class UniqueTaskList extends DisplayItemList<Task> { + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTask); + } + + /** + * Adds a task to the list. The task must not already exist in the list. + */ + public void add(Task toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateItemException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the task {@code target} in the list with {@code editedTask}. {@code target} must exist in the list. The + * task identity of {@code editedTask} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ItemNotFoundException(); + } + + if (!target.isSameTask(editedTask) && contains(editedTask)) { + throw new DuplicateItemException(); + } + + internalList.set(index, editedTask); + } + + /** + * Removes the equivalent task from the list. The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ItemNotFoundException(); + } + } + + public void setTasks(UniqueTaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tasks}. {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List<Task> tasks) { + requireAllNonNull(tasks); + if (!tasksAreUnique(tasks)) { + throw new DuplicateItemException(); + } + + internalList.setAll(tasks); + } + + @Override + public Iterator<Task> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code tasks} contains only unique tasks. + */ + private boolean tasksAreUnique(List<Task> tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).isSameTask(tasks.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskOutOfBoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskOutOfBoundException.java new file mode 100644 index 00000000000..0e16dcc345e --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskOutOfBoundException.java @@ -0,0 +1,16 @@ +package seedu.address.model.task.exceptions; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Encapsulates an OutOfBoundException for Task + */ +public class TaskOutOfBoundException extends CommandException { + + public static final String ERR_MSG = "Task out of bounds. Length is only %d yet index %d supplied."; + + public TaskOutOfBoundException(int length, int index) { + super(String.format(ERR_MSG, length, index)); + } + +} diff --git a/src/main/java/seedu/address/model/util/SampleData.json b/src/main/java/seedu/address/model/util/SampleData.json new file mode 100644 index 00000000000..36d29136f58 --- /dev/null +++ b/src/main/java/seedu/address/model/util/SampleData.json @@ -0,0 +1,601 @@ +{ + "persons": [ + { + "fields": { + "fieldList": [] + }, + "name": "Bob The Builder", + "uid": "5d1b473c-420d-3b1c-ab8a-7518748ae5a7", + "tags": [], + "attributes": [ + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + }, + { + "data": { + "type": "isImposter", + "content": "Imposter", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Plantkton", + "uid": "86c1660a-d6d6-3b1f-bc57-762ffbb9679b", + "tags": [], + "attributes": [ + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Spongebob", + "uid": "31d3e515-b7d6-3d54-95c0-a9a27c4d7987", + "tags": [], + "attributes": [ + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Patrick The Star", + "uid": "854cb165-d9cc-3a84-9583-a5f81673e48c", + "tags": [], + "attributes": [ + { + "data": { + "type": "hp", + "content": "5454545", + "display_format": 63, + "style_format": 288 + } + }, + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Sandy Cheeks", + "uid": "b467b6ae-fe5a-36f1-9274-a95d065a21ce", + "tags": [], + "attributes": [ + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Squidward", + "uid": "3e226616-1037-3f8b-bbe1-c9d9dc4e85db", + "tags": [], + "attributes": [ + { + "data": { + "type": "type", + "content": "Genius Programmer", + "display_format": 63, + "style_format": 288 + } + }, + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Mr Krabs", + "uid": "9682abe4-456b-378c-987d-23e984968f13", + "tags": [], + "attributes": [ + { + "data": { + "type": "place", + "content": "Bikini Bottoms", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Minion 1", + "uid": "80b1e5cd-a531-3a31-b8c8-5fed87e7d390", + "tags": [], + "attributes": [ + { + "data": { + "type": "type", + "content": "Plankton's Evil Minions", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Minion 2", + "uid": "44ac5933-590f-32b0-b819-cd0097aeb1c0", + "tags": [], + "attributes": [ + { + "data": { + "type": "type", + "content": "Plankton's Evil Minions", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Minion 3", + "uid": "52133639-288d-35b1-927a-66bca52c90e3", + "tags": [], + "attributes": [ + { + "data": { + "type": "type", + "content": "Plankton's Evil Minions", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "fields": { + "fieldList": [] + }, + "name": "Minion 4", + "uid": "ac861ff3-dbe9-3f7e-9482-40b593547d9a", + "tags": [], + "attributes": [ + { + "data": { + "type": "type", + "content": "Plankton's Evil Minions", + "display_format": 63, + "style_format": 288 + } + } + ] + } + ], + "groups": [ + { + "name": "Krusty_Crabs", + "uid": "f91547fe-a598-3268-8c30-284006315ab3", + "tags": [], + "attributes": [] + }, + { + "name": "Bug_Fixes", + "uid": "0c830a5d-a723-3155-b937-830ef6f087c3", + "tags": [], + "attributes": [] + }, + { + "name": "Secret-Operations", + "uid": "7dc570e3-7cbc-3d39-ac32-17391aceb260", + "tags": [], + "attributes": [ + { + "data": { + "type": "description", + "content": "DO NOT LOOK, NOTHING TO SEE HERE", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "name": "Sept", + "uid": "611bfc53-91fa-3c0d-8640-cea4bc5fd4dc", + "tags": [], + "attributes": [] + }, + { + "name": "Oct", + "uid": "4fc3f6c0-e106-3ed8-8c62-ecec89aa6771", + "tags": [], + "attributes": [] + }, + { + "name": "Squidward_birthday-surprise", + "uid": "6d3471fc-ece8-35f6-8d17-4cd29de4c74b", + "tags": [], + "attributes": [] + }, + { + "name": "Health_Inspection", + "uid": "4e656a81-67c6-3b20-b824-698fa9a2df23", + "tags": [], + "attributes": [ + { + "data": { + "type": "data", + "content": "by 2022/10/30", + "display_format": 63, + "style_format": 288 + } + } + ] + } + ], + "tasks": [ + { + "description": "Unable to view the description in GUI", + "localDateTime": "", + "name": "Description Not Working", + "uid": "5a35e6ac-33d0-32df-955b-12d59fb59fcb", + "tags": [], + "attributes": [ + { + "data": { + "type": "bug", + "content": "High Severity", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "description": "UG is missing seq commands details", + "localDateTime": "", + "name": "seq commands missing documentation", + "uid": "02e574e8-05ce-37ad-bd3d-5d040215bed5", + "tags": [], + "attributes": [ + { + "data": { + "type": "bug", + "content": "low severity", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "description": "User unable to use the replace command", + "localDateTime": "", + "name": "replace command not working", + "uid": "5c0b2324-cd6d-30db-badc-ff6bdaee529b", + "tags": [], + "attributes": [ + { + "data": { + "type": "bug", + "content": "High Severity", + "display_format": 63, + "style_format": 288 + } + } + ] + }, + { + "description": "Ensure tasks files are working", + "localDateTime": "", + "name": "Check tests files", + "uid": "bf81b6ae-7d2c-3158-8d36-892d65178b13", + "tags": [], + "attributes": [] + }, + { + "description": "Ensure user guide are up to date", + "localDateTime": "", + "name": "Check user guide", + "uid": "53e39006-f1f2-3fec-b3ed-14937585d0ba", + "tags": [], + "attributes": [] + }, + { + "description": "We need more fancier burgers!!!", + "localDateTime": "", + "name": "New Burger Recipes", + "uid": "b934d7ff-0f85-347b-805e-2bf55088a226", + "tags": [], + "attributes": [] + }, + { + "description": "Plankton need to know his secrets", + "localDateTime": "", + "name": "Steal Secret Recipes", + "uid": "9a0ac67f-e02b-30b6-af67-1b0e649151a8", + "tags": [], + "attributes": [] + }, + { + "description": "Plankton requires burger dominance", + "localDateTime": "", + "name": "Ruin the businness", + "uid": "74f4774d-9e64-3e31-a643-e6df74b12834", + "tags": [], + "attributes": [] + }, + { + "description": "Trust = benefits", + "localDateTime": "2022-10-30T15:57:51.199827900", + "name": "Gain trust", + "uid": "f9c2e1dd-9ac1-3ae7-8d37-270f9c563732", + "tags": [], + "attributes": [] + }, + { + "description": "Squidward birthday is on Oct 9", + "localDateTime": "", + "name": "Birthday checklist", + "uid": "4fb73f2f-3596-3025-b45e-950e2e56275d", + "tags": [], + "attributes": [] + }, + { + "description": "What does he like?", + "localDateTime": "2022-10-30T15:58:22.196729600", + "name": "Prepare gifts", + "uid": "33c49cd9-6373-350c-a005-e67c3f7a3a3f", + "tags": [], + "attributes": [] + }, + { + "description": "What kind of surprise do we need for him", + "localDateTime": "", + "name": "Prepare surprise", + "uid": "cbccb63f-2268-30d5-b1db-39689c76f8d0", + "tags": [], + "attributes": [] + }, + { + "description": "We need moar customers", + "localDateTime": "2022-10-30T16:02:48.645641900", + "name": "Advertise restaurant success", + "uid": "eca3ee0c-4513-33eb-93f5-57658adbd1d4", + "tags": [], + "attributes": [] + }, + { + "description": "It's looking old here", + "localDateTime": "2022-10-30T16:02:48.646641400", + "name": "Do renovation", + "uid": "1fd59d70-94c8-3157-8d57-82865e9fccdd", + "tags": [], + "attributes": [] + }, + { + "description": "we cannot fail this", + "localDateTime": "2022-10-30T16:02:48.648147200", + "name": "Prepare for health inspection", + "uid": "245efa69-e5bc-359b-81ea-7a3a7019f74f", + "tags": [], + "attributes": [] + }, + { + "description": "What events should we do next month?", + "localDateTime": "2022-10-30T16:02:48.649155", + "name": "Plans for Oct", + "uid": "84ffbd5e-e7fb-3069-91ec-d05564b23552", + "tags": [], + "attributes": [] + }, + { + "description": "!!!!! sweats", + "localDateTime": "", + "name": "Prepare for health inspection", + "uid": "4b914fa4-38be-3c9e-ab4b-00a2faf5546d", + "tags": [], + "attributes": [] + }, + { + "description": "yes", + "localDateTime": "2022-10-30T16:10:00.979435900", + "name": "Clean table", + "uid": "6440e851-84bb-3fb7-addf-131c7dc52bd0", + "tags": [], + "attributes": [] + }, + { + "description": "Make sure no dirt can be found", + "localDateTime": "2022-10-30T16:10:03.862598", + "name": "Sanitize Plates", + "uid": "fd319810-df1c-3e46-806f-daca5d676424", + "tags": [], + "attributes": [] + }, + { + "description": "This is important!", + "localDateTime": "", + "name": "Clean stoves", + "uid": "3044409c-f910-30cd-b6b4-6a263394dde9", + "tags": [], + "attributes": [] + }, + { + "description": "!!!!!!", + "localDateTime": "2022-10-30T16:10:02.169973900", + "name": "Hide all traces of secret recipe", + "uid": "12fd88fa-6ad7-30b2-bb4e-79da799545bf", + "tags": [], + "attributes": [] + } + ], + "itemRelationship": { + "f91547fe-a598-3268-8c30-284006315ab3": [], + "84ffbd5e-e7fb-3069-91ec-d05564b23552": [ + "611bfc53-91fa-3c0d-8640-cea4bc5fd4dc" + ], + "86c1660a-d6d6-3b1f-bc57-762ffbb9679b": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260", + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "80b1e5cd-a531-3a31-b8c8-5fed87e7d390": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ], + "5d1b473c-420d-3b1c-ab8a-7518748ae5a7": [], + "4e656a81-67c6-3b20-b824-698fa9a2df23": [ + "4fc3f6c0-e106-3ed8-8c62-ecec89aa6771" + ], + "fd319810-df1c-3e46-806f-daca5d676424": [ + "4e656a81-67c6-3b20-b824-698fa9a2df23" + ], + "1fd59d70-94c8-3157-8d57-82865e9fccdd": [ + "611bfc53-91fa-3c0d-8640-cea4bc5fd4dc" + ], + "4b914fa4-38be-3c9e-ab4b-00a2faf5546d": [ + "4fc3f6c0-e106-3ed8-8c62-ecec89aa6771" + ], + "bf81b6ae-7d2c-3158-8d36-892d65178b13": [ + "0c830a5d-a723-3155-b937-830ef6f087c3" + ], + "ac861ff3-dbe9-3f7e-9482-40b593547d9a": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ], + "6d3471fc-ece8-35f6-8d17-4cd29de4c74b": [ + "4fc3f6c0-e106-3ed8-8c62-ecec89aa6771" + ], + "33c49cd9-6373-350c-a005-e67c3f7a3a3f": [ + "6d3471fc-ece8-35f6-8d17-4cd29de4c74b" + ], + "3044409c-f910-30cd-b6b4-6a263394dde9": [ + "4e656a81-67c6-3b20-b824-698fa9a2df23" + ], + "245efa69-e5bc-359b-81ea-7a3a7019f74f": [ + "611bfc53-91fa-3c0d-8640-cea4bc5fd4dc" + ], + "12fd88fa-6ad7-30b2-bb4e-79da799545bf": [ + "4e656a81-67c6-3b20-b824-698fa9a2df23" + ], + "5c0b2324-cd6d-30db-badc-ff6bdaee529b": [ + "0c830a5d-a723-3155-b937-830ef6f087c3" + ], + "854cb165-d9cc-3a84-9583-a5f81673e48c": [], + "6440e851-84bb-3fb7-addf-131c7dc52bd0": [ + "4e656a81-67c6-3b20-b824-698fa9a2df23" + ], + "0c830a5d-a723-3155-b937-830ef6f087c3": [], + "3e226616-1037-3f8b-bbe1-c9d9dc4e85db": [ + "4e656a81-67c6-3b20-b824-698fa9a2df23", + "0c830a5d-a723-3155-b937-830ef6f087c3", + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "611bfc53-91fa-3c0d-8640-cea4bc5fd4dc": [ + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "cbccb63f-2268-30d5-b1db-39689c76f8d0": [ + "6d3471fc-ece8-35f6-8d17-4cd29de4c74b" + ], + "4fb73f2f-3596-3025-b45e-950e2e56275d": [ + "6d3471fc-ece8-35f6-8d17-4cd29de4c74b" + ], + "7dc570e3-7cbc-3d39-ac32-17391aceb260": [ + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "74f4774d-9e64-3e31-a643-e6df74b12834": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ], + "53e39006-f1f2-3fec-b3ed-14937585d0ba": [ + "0c830a5d-a723-3155-b937-830ef6f087c3" + ], + "f9c2e1dd-9ac1-3ae7-8d37-270f9c563732": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ], + "5a35e6ac-33d0-32df-955b-12d59fb59fcb": [ + "0c830a5d-a723-3155-b937-830ef6f087c3" + ], + "02e574e8-05ce-37ad-bd3d-5d040215bed5": [ + "0c830a5d-a723-3155-b937-830ef6f087c3" + ], + "44ac5933-590f-32b0-b819-cd0097aeb1c0": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ], + "b467b6ae-fe5a-36f1-9274-a95d065a21ce": [], + "52133639-288d-35b1-927a-66bca52c90e3": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ], + "4fc3f6c0-e106-3ed8-8c62-ecec89aa6771": [ + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "31d3e515-b7d6-3d54-95c0-a9a27c4d7987": [ + "6d3471fc-ece8-35f6-8d17-4cd29de4c74b", + "4e656a81-67c6-3b20-b824-698fa9a2df23", + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "eca3ee0c-4513-33eb-93f5-57658adbd1d4": [ + "611bfc53-91fa-3c0d-8640-cea4bc5fd4dc" + ], + "b934d7ff-0f85-347b-805e-2bf55088a226": [ + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "9682abe4-456b-378c-987d-23e984968f13": [ + "6d3471fc-ece8-35f6-8d17-4cd29de4c74b", + "4e656a81-67c6-3b20-b824-698fa9a2df23", + "f91547fe-a598-3268-8c30-284006315ab3" + ], + "9a0ac67f-e02b-30b6-af67-1b0e649151a8": [ + "7dc570e3-7cbc-3d39-ac32-17391aceb260" + ] + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..1ab39fb56dc 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,50 +1,149 @@ package seedu.address.model.util; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.attribute.Address; +import seedu.address.model.attribute.Email; +import seedu.address.model.attribute.Phone; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { + + // Hard coded for sample purposes. Checks greater than or equal to account for additional samples. + private static final int MAX_PERSON_ONE_INDEX = 6; + private static final int MAX_GROUP_ONE_INDEX = 4; + private static final int MAX_TASK_ONE_INDEX = 2; + + /** + * Creates {@code Person} data samples for sample use. + * + * @return static list of sample {@code Person} with sample data. + */ public static Person[] getSamplePersons() { + Person p1 = new Person("Alex Yeoh"); + p1.addAttribute(new Phone("87438807")); + p1.addAttribute(new Email("alexyeoh@example.com")); + p1.addAttribute(new Address("Blk 30 Geylang Street 29, #06-40")); + p1.addTags("friends"); + + Person p2 = new Person("Bernice Yu"); + p2.addAttribute(new Phone("99272758")); + p2.addAttribute(new Email("berniceyu@example.com")); + p2.addAttribute(new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18")); + p2.addTags("colleagues", "friends"); + + Person p3 = new Person("Charlotte Oliveiro"); + p3.addAttribute(new Phone("93210283")); + p3.addAttribute(new Email("charlotte@example.com")); + p3.addAttribute(new Address("Blk 11 Ang Mo Kio Street 74, #11-04")); + p3.addTags("family"); + + Person p4 = new Person("David Li"); + p4.addAttribute(new Phone("91031282")); + p4.addAttribute(new Email("lidavid@example.com")); + p4.addAttribute(new Address("Blk 436 Serangoon Gardens Street 26, #16-43")); + p4.addTags("family"); + + Person p5 = new Person("Irfan Ibrahim"); + p5.addAttribute(new Phone("92492021")); + p5.addAttribute(new Email("irfan@example.com")); + p5.addAttribute(new Address("Blk 47 Tampines Street 20, #17-35")); + p5.addTags("husband"); + + Person p6 = new Person("Roy Balakrishnan"); + p6.addAttribute(new Phone("92624417")); + p6.addAttribute(new Email("royb@example.com")); + p6.addAttribute(new Address("Blk 45 Aljunied Street 85, #11-31")); + p6.addTags("colleagues"); + 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")) + p1, p2, p3, p4, p5, p6 + }; + } + + /** + * Creates {@code Group} data samples for sample use. + * + * @return static list of sample {@code Group} with sample data. + */ + public static Group[] getSampleGroups() { + Group g1 = new Group("ABC_Company"); + g1.addAttribute(new Address("ABC building #06-06")); + g1.addAttribute(new Email("companyEmail@email.com")); + g1.addTags("Job", "Architecture"); + + Group g2 = new Group("Project_Alpha"); + g2.addAttribute(new Address("BCD building #07-07")); + g2.addAttribute(new Email("companyEmail2@gmail.com")); + g2.addTags("ProjectAlpha"); + g2.setParent(g1); + + Group g3 = new Group("Home_contacts"); + g3.addAttribute(new Address("Blk 59 Choa Chu Kang North 5, #04-04")); + g3.addAttribute(new Phone("63636363")); + g3.addTags("Home"); + + Group g4 = new Group("Children"); + g4.addTags("Children"); + g4.setParent(g3); + + return new Group[] { + g1, g2, g3, g4 + }; + } + + /** + * Creates {@code Task} data samples for sample use. + * + * @return static list of sample {@code Task} with sample data. + */ + public static Task[] getSampleTask() { + Task t1 = new Task("Update bugged feature", "Feature is bugged"); + t1.setCompletionTime(LocalDateTime.of(1, 3, 3, 1, 1, 3)); + t1.addTags("Bug", "Feature"); + + Task t2 = new Task("Do the laundry", "Bring laundry to a laundromat."); + t2.addTags("Urgent", "Chores"); + t2.addAttribute(new Phone("83839393")); + t2.addAttribute(new Address("Maryland Rd 59, #01-02")); + + return new Task[] { + t1, t2 }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); + + Person[] persons = getSamplePersons(); + Group[] groups = getSampleGroups(); + Task[] tasks = getSampleTask(); + + addParenting(persons, groups, tasks); + + for (Group sampleGroup : getSampleGroups()) { + sampleAb.addTeam(sampleGroup); + } + for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + + for (Task sampleTask : getSampleTask()) { + sampleAb.addTask(sampleTask); + } + return sampleAb; } @@ -57,4 +156,27 @@ public static Set<Tag> getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Adds parenting between the persons (p), groups (g) and tasks (t). + * + * The example parenting scheme will use (in one-indexed notation): + * p6 -> g2 + * p3 -> g4 + * p4 -> g4 + * p5 -> g3 + * t1 -> g2 + * t2 -> p3 + */ + private static void addParenting(Person[] persons, Group[] groups, Task[] tasks) { + + assert persons.length >= MAX_PERSON_ONE_INDEX && groups.length >= MAX_GROUP_ONE_INDEX + && tasks.length >= MAX_TASK_ONE_INDEX; + + persons[5].setParent(groups[1]); + persons[2].setParent(groups[3]); + persons[3].setParent(groups[3]); + persons[4].setParent(groups[2]); + tasks[0].setParent(groups[1]); + tasks[1].setParent(persons[2]); + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAbstractAttribute.java b/src/main/java/seedu/address/storage/JsonAdaptedAbstractAttribute.java new file mode 100644 index 00000000000..c28f0550e17 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedAbstractAttribute.java @@ -0,0 +1,76 @@ +package seedu.address.storage; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.attribute.AbstractAttribute; +import seedu.address.model.attribute.Attribute; + +/** + * Jackson-friendly version of {@link Attribute}. + */ +class JsonAdaptedAbstractAttribute { + + public static final String CORRUPTED_FIELD_MESSAGE_FORMAT = "Attribute data is corrupted!"; + + private final Map<String, Object> data = new HashMap<>(); + + /** + * Constructs a {@code JsonAdaptedAbstractAttribute} with the given attribute details. + */ + @JsonCreator + public JsonAdaptedAbstractAttribute(@JsonProperty("data") Map<String, Object> data) { + if (data != null) { + this.data.putAll(data); + } + } + + /** + * Converts a given {@code Attribute} into this class for Jackson use. + */ + public JsonAdaptedAbstractAttribute(Attribute<?> attribute) { + if (attribute != null) { + data.putAll(attribute.toSaveableData()); + } + } + + public static boolean isSaveableDataFormat(Map<String, Object> data) { + return data.containsKey(AbstractAttribute.SAVE_KEY_TYPE_NAME) + && data.containsKey(AbstractAttribute.SAVE_KEY_VALUE) + && data.containsKey(AbstractAttribute.SAVE_KEY_DISPLAY_FORMAT) + && data.containsKey(AbstractAttribute.SAVE_KEY_STYLE_FORMAT); + } + + /** + * Converts this Jackson-friendly adapted attribute object into the model's {@code Attribute} + * object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted + * attribute. + */ + public Attribute<?> toModelType() throws IllegalValueException { + assert data != null; + if (!isSaveableDataFormat(data)) { + throw new IllegalValueException(CORRUPTED_FIELD_MESSAGE_FORMAT); + } + + final String modelTypeName = (String) data.get(AbstractAttribute.SAVE_KEY_TYPE_NAME); + final Object modelValue = data.get(AbstractAttribute.SAVE_KEY_VALUE); + final int modelDisplayFormat = (int) data.get(AbstractAttribute.SAVE_KEY_DISPLAY_FORMAT); + final int modelStyleFormat = (int) data.get(AbstractAttribute.SAVE_KEY_STYLE_FORMAT); + + try { + Attribute<?> modelAttribute = + ParserUtil.parseAttribute(modelTypeName, modelValue, modelDisplayFormat, modelStyleFormat); + return modelAttribute; + } catch (ParseException e) { + throw new IllegalValueException(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAbstractDisplayItem.java b/src/main/java/seedu/address/storage/JsonAdaptedAbstractDisplayItem.java new file mode 100644 index 00000000000..f1942ed6fe6 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedAbstractDisplayItem.java @@ -0,0 +1,48 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.model.item.AbstractDisplayItem; + +/** + * Jackson-friendly version of {@link AbstractDisplayItem}. + */ +abstract class JsonAdaptedAbstractDisplayItem { + + private final String name; + private final String uid; + private final List<JsonAdaptedAbstractAttribute> attributes = new ArrayList<>(); + private final List<JsonAdaptedTag> tags = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedAbstractDisplayItem} with the given displayItem details. + */ + protected JsonAdaptedAbstractDisplayItem(String name, String uid, List<JsonAdaptedAbstractAttribute> attributes, + List<JsonAdaptedTag> tags) { + this.name = name; + this.uid = uid; + if (attributes != null) { + this.attributes.addAll(attributes); + } + if (tags != null) { + this.tags.addAll(tags); + } + } + + protected String getName() { + return name; + } + + protected String getUid() { + return uid; + } + + protected List<JsonAdaptedAbstractAttribute> getAttributes() { + return attributes; + } + + protected List<JsonAdaptedTag> getTags() { + return tags; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAddressBookParser.java b/src/main/java/seedu/address/storage/JsonAdaptedAddressBookParser.java new file mode 100644 index 00000000000..4b5f91fc56f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedAddressBookParser.java @@ -0,0 +1,59 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.AddressBookParser; + +/** + * Jackson-friendly version of {@link AddressBookParser}. + */ +public class JsonAdaptedAddressBookParser { + + private final Map<String, String> alias = new HashMap<>(); + private final Map<String, JsonAdaptedCustomCommandBuilder> macros = new HashMap<>(); + + /** + * Constructs a {@code JsonAdaptedAddressBookParser} with the given addressBookParser details. + */ + @JsonCreator + public JsonAdaptedAddressBookParser(@JsonProperty("alias") Map<String, String> alias, + @JsonProperty("macros") Map<String, JsonAdaptedCustomCommandBuilder> macros) { + requireAllNonNull(alias, macros); + this.alias.putAll(alias); + this.macros.putAll(macros); + } + + /** + * Converts a given {@code CustomCommandBuilder} into this class for Jackson use. + */ + public JsonAdaptedAddressBookParser(AddressBookParser source) { + requireNonNull(source); + alias.putAll(source.getAliasMapper()); + source.getBonusMapper().forEach((macro, customCommand) + -> macros.put(macro, new JsonAdaptedCustomCommandBuilder(customCommand))); + } + + /** + * Converts this Jackson-friendly adapted AddressBookParser object into the model's + * {@code AddressBookParser} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted addressBookParser. + */ + public void toModelType() throws IllegalValueException { + AddressBookParser addressBookParser = AddressBookParser.get(); + assert addressBookParser != null; + + alias.forEach((beforeAlias, newAlias) -> addressBookParser.addAlias(beforeAlias, newAlias)); + for (Map.Entry<String, JsonAdaptedCustomCommandBuilder> macro : macros.entrySet()) { + addressBookParser.addCommand(macro.getValue().toModelType()); + } + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedCustomCommandBuilder.java b/src/main/java/seedu/address/storage/JsonAdaptedCustomCommandBuilder.java new file mode 100644 index 00000000000..e643b4aa5ab --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedCustomCommandBuilder.java @@ -0,0 +1,54 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; +import java.util.stream.Stream; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CustomCommandBuilder; +import seedu.address.logic.parser.AddressBookParser; + +/** + * Jackson-friendly version of {@link CustomCommandBuilder}. + */ +public class JsonAdaptedCustomCommandBuilder { + + private final String commandMacroName; + private final String commandMacroReplace; + + /** + * Constructs a {@code JsonAdaptedCustomCommandBuilder} with the given customCommandBuilder details. + */ + @JsonCreator + public JsonAdaptedCustomCommandBuilder(@JsonProperty("commandMacroName") String commandMacroName, + @JsonProperty("commandMacroReplace") String commandMacroReplace) { + this.commandMacroName = commandMacroName; + this.commandMacroReplace = commandMacroReplace; + } + + /** + * Converts a given {@code CustomCommandBuilder} into this class for Jackson use. + */ + public JsonAdaptedCustomCommandBuilder(CustomCommandBuilder source) { + requireNonNull(source); + commandMacroName = source.getRepr(); + commandMacroReplace = source.getCommandData(); + } + + /** + * Converts this Jackson-friendly adapted CustomCommandBuilder object into the model's + * {@code CustomCommandBuilder} object. + */ + public CustomCommandBuilder toModelType() throws IllegalValueException { + if (!AddressBookParser.isValidName(commandMacroName) + || Stream.of(commandMacroName, commandMacroReplace).anyMatch(Objects::isNull) + || commandMacroReplace.isBlank()) { + throw new IllegalValueException(CustomCommandBuilder.MESSAGE_CONSTRAINTS); + } + return new CustomCommandBuilder(commandMacroName, commandMacroReplace); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java new file mode 100644 index 00000000000..9766096c53b --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java @@ -0,0 +1,83 @@ +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.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.group.Group; +import seedu.address.model.tag.Tag; + +/** + * Jackson-friendly version of {@link Group}. + */ +class JsonAdaptedGroup extends JsonAdaptedAbstractDisplayItem { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Group's %s field is missing!"; + + /** + * Constructs a {@code JsonAdaptedGroup} with the given group details. + */ + @JsonCreator + public JsonAdaptedGroup(@JsonProperty("name") String name, @JsonProperty("uid") String uid, + @JsonProperty("tags") List<JsonAdaptedTag> tags, + @JsonProperty("attributes") List<JsonAdaptedAbstractAttribute> attributes) { + super(name, uid, attributes, tags); + } + + /** + * Converts a given {@code Group} into this class for Jackson use. + */ + public JsonAdaptedGroup(Group source) { + super(source.getName().fullName, source.getUid().toString(), + source.getSavedAttributes().stream() + .map(JsonAdaptedAbstractAttribute::new) + .collect(Collectors.toList()), + source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted group object into the model's + * {@code Group} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person. + */ + public Group toModelType() throws IllegalValueException { + final List<Tag> groupTags = new ArrayList<>(); + final List<Attribute> modelAttributes = new ArrayList<>(); + + // Exception handling is not supported in Java streams. + for (JsonAdaptedTag tag : getTags()) { + groupTags.add(tag.toModelType()); + } + + for (JsonAdaptedAbstractAttribute attribute : getAttributes()) { + modelAttributes.add(attribute.toModelType()); + } + + String name = getName(); + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Group.isValidGroupName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + + final Set<Tag> modelTags = new HashSet<>(groupTags); + + Group group = new Group(name); + group.setTags(modelTags); + modelAttributes.forEach(attribute -> group.addAttribute(attribute)); + return group; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..d5d024bfa6c 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -10,51 +10,37 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Name; import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; /** * Jackson-friendly version of {@link Person}. */ -class JsonAdaptedPerson { +class JsonAdaptedPerson extends JsonAdaptedAbstractDisplayItem { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - private final String name; - private final String phone; - private final String email; - private final String address; - private final List<JsonAdaptedTag> tagged = new ArrayList<>(); - /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List<JsonAdaptedTag> tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } + public JsonAdaptedPerson(@JsonProperty("name") String name, + @JsonProperty("uid") String uid, @JsonProperty("tags") List<JsonAdaptedTag> tags, + @JsonProperty("attributes") List<JsonAdaptedAbstractAttribute> attributes) { + super(name, uid, attributes, tags); } /** * Converts a given {@code Person} into this class for Jackson use. */ public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() + super(source.getName().fullName, source.getUid().toString(), + source.getSavedAttributes().stream() + .map(JsonAdaptedAbstractAttribute::new) + .collect(Collectors.toList()), + source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); } @@ -66,44 +52,32 @@ public JsonAdaptedPerson(Person source) { */ public Person toModelType() throws IllegalValueException { final List<Tag> personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { + final List<Attribute> modelAttributes = new ArrayList<>(); + + // Exception handling is not supported in Java streams. + for (JsonAdaptedTag tag : getTags()) { personTags.add(tag.toModelType()); } + for (JsonAdaptedAbstractAttribute attribute : getAttributes()) { + modelAttributes.add(attribute.toModelType()); + } + + String name = getName(); if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } if (!Name.isValidName(name)) { throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); + // dummy fields final Set<Tag> modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + + Person p = new Person(name); + p.setTags(modelTags); + modelAttributes.stream().forEach(attribute -> p.addAttribute(attribute)); + return p; } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..c1f85d64d67 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -1,5 +1,7 @@ package seedu.address.storage; +import static java.util.Objects.requireNonNull; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; @@ -25,6 +27,7 @@ public JsonAdaptedTag(String tagName) { * Converts a given {@code Tag} into this class for Jackson use. */ public JsonAdaptedTag(Tag source) { + requireNonNull(source); tagName = source.tagName; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..87ae1ea7a99 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,95 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +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.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Task; + +/** + * Jackson-friendly version of {@link Task}. + */ +class JsonAdaptedTask extends JsonAdaptedAbstractDisplayItem { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + public static final String INVALID_FIELD_MESSAGE_FORMAT = "Task's %s field is invalid!"; + + private final String description; + private final String localDateTime; + + @JsonCreator + public JsonAdaptedTask(@JsonProperty("description") String description, + @JsonProperty("localDateTime") String localDateTime, @JsonProperty("name") String name, + @JsonProperty("uid") String uid, @JsonProperty("tags") List<JsonAdaptedTag> tags, + @JsonProperty("attributes") List<JsonAdaptedAbstractAttribute> attributes) { + super(name, uid, attributes, tags); + this.description = description; + this.localDateTime = localDateTime; + } + + public JsonAdaptedTask(Task source) { + super(source.getName().fullName, source.getUid().toString(), + source.getSavedAttributes().stream() + .map(JsonAdaptedAbstractAttribute::new) + .collect(Collectors.toList()), + source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + description = source.getDescription().getAttributeContent(); + LocalDateTime completedTime = source.getCompletedTime(); + localDateTime = completedTime == null ? "" : completedTime.toString(); + } + + public Task toModelType() throws IllegalValueException { + final List<Tag> taskTags = new ArrayList<>(); + final List<Attribute> modelAttributes = new ArrayList<>(); + for (JsonAdaptedTag tag : getTags()) { + taskTags.add(tag.toModelType()); + } + + for (JsonAdaptedAbstractAttribute attribute : getAttributes()) { + modelAttributes.add(attribute.toModelType()); + } + + String name = getName(); + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + + final Name modelName = new Name(name); + final Set<Tag> modelTags = new HashSet<>(taskTags); + + LocalDateTime modelLocalDateTime = null; + if (localDateTime == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, LocalDateTime.class.getSimpleName())); + } + try { + if (!localDateTime.isEmpty()) { + modelLocalDateTime = LocalDateTime.parse(localDateTime); + } + } catch (DateTimeParseException pe) { + throw new IllegalValueException(String.format(INVALID_FIELD_MESSAGE_FORMAT, + LocalDateTime.class.getSimpleName())); + } + + Task task = new Task(modelName.getAttributeContent(), description, modelLocalDateTime); + task.setTags(modelTags); + modelAttributes.forEach(attribute -> task.addAttribute(attribute)); + return task; + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..1bb9dfb5a36 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -1,17 +1,25 @@ package seedu.address.storage; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; +import javafx.collections.ObservableList; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.AddressBookParser; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.item.DisplayItem; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * An Immutable AddressBook that is serializable to JSON format. @@ -20,15 +28,37 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_GROUP = "Groups list contains duplicate group(s)."; + public static final String MESSAGE_DUPLICATE_TASK = "Tasks list contains duplicate task(s)."; + + public static final String MESSAGE_INVALID_GROUP_PARENT_COUNT = "Wrong number of group parent."; + public static final String MESSAGE_NONEXISTING_UID_PARENT_PAIR = + "Uid (%s) does not have a corresponding parent associated."; private final List<JsonAdaptedPerson> persons = new ArrayList<>(); + private final List<JsonAdaptedGroup> groups = new ArrayList<>(); + private final List<JsonAdaptedTask> tasks = new ArrayList<>(); + + // Contains the parent child relationship for persons, groups and tasks by their Uuids. + private final Map<String, List<String>> itemRelationship = new HashMap<>(); + + // Contains information from the singleton AddressBookParser. + private final JsonAdaptedAddressBookParser jsonAdaptedAddressBookParser; /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons, groups and tasks. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List<JsonAdaptedPerson> persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List<JsonAdaptedPerson> persons, + @JsonProperty("groups") List<JsonAdaptedGroup> groups, + @JsonProperty("tasks") List<JsonAdaptedTask> tasks, + @JsonProperty("itemRelationship") Map<String, List<String>> itemRelationship, + @JsonProperty("addressBookParser") JsonAdaptedAddressBookParser jsonAdaptedAddressBookParser) { this.persons.addAll(persons); + this.groups.addAll(groups); + this.tasks.addAll(tasks); + this.itemRelationship.putAll(itemRelationship); + this.jsonAdaptedAddressBookParser = jsonAdaptedAddressBookParser; } /** @@ -37,7 +67,26 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List<JsonAdaptedPers * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. */ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); + ObservableList<Person> personList = source.getPersonList(); + ObservableList<Group> groupList = source.getTeamsList(); + ObservableList<Task> taskList = source.getTasksList(); + + persons.addAll(personList.stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); + groups.addAll(groupList.stream().map(JsonAdaptedGroup::new).collect(Collectors.toList())); + tasks.addAll(taskList.stream().map(JsonAdaptedTask::new).collect(Collectors.toList())); + + personList.forEach(person -> itemRelationship.put(person.getUid().toString(), + person.getParents().stream().map(parent -> parent.getUid().toString()).collect(Collectors.toList()))); + + groupList.forEach(group -> itemRelationship.put( + group.getUid().toString(), + group.getParents().stream().map(parent -> parent.getUid().toString()).collect(Collectors.toList()))); + + taskList.forEach(task -> itemRelationship.put( + task.getUid().toString(), + task.getParents().stream().map(parent -> parent.getUid().toString()).collect(Collectors.toList()))); + + jsonAdaptedAddressBookParser = new JsonAdaptedAddressBookParser(AddressBookParser.get()); } /** @@ -47,14 +96,87 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { */ public AddressBook toModelType() throws IllegalValueException { AddressBook addressBook = new AddressBook(); + Map<String, Group> builtGroup = new HashMap<>(); + Map<String, Person> builtPerson = new HashMap<>(); + Map<String, Task> builtTask = new HashMap<>(); + + for (JsonAdaptedGroup jsonAdaptedGroup : groups) { + builtGroup.put(jsonAdaptedGroup.getUid(), jsonAdaptedGroup.toModelType()); + } + + for (JsonAdaptedPerson jsonAdaptedPerson : persons) { + builtPerson.put(jsonAdaptedPerson.getUid(), jsonAdaptedPerson.toModelType()); + } + + for (JsonAdaptedTask jsonAdaptedTask : tasks) { + builtTask.put(jsonAdaptedTask.getUid(), jsonAdaptedTask.toModelType()); + } + + // Build groups + for (Map.Entry<String, Group> pair : builtGroup.entrySet()) { + Group group = pair.getValue(); + List<String> parentUid = itemRelationship.get(pair.getKey()); + if (parentUid.size() > 1) { + throw new IllegalValueException(MESSAGE_INVALID_GROUP_PARENT_COUNT); + } + + if (parentUid.size() == 1) { + group.setParent(builtGroup.get(parentUid.get(0))); + } + + if (addressBook.hasGroup(group)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_GROUP); + } + + addressBook.addTeam(group); + } + + Map<String, AbstractSingleItem> builtSingleItem = new HashMap<>(builtTask); + builtSingleItem.putAll(builtGroup); + for (JsonAdaptedPerson jsonAdaptedPerson : persons) { Person person = jsonAdaptedPerson.toModelType(); + List<String> parentUidList = itemRelationship.get(jsonAdaptedPerson.getUid()); + for (String parentUid : parentUidList) { + if (!builtSingleItem.containsKey(parentUid)) { + throw new IllegalValueException(MESSAGE_NONEXISTING_UID_PARENT_PAIR); + } + + person.setParent(builtSingleItem.get(parentUid)); + } + if (addressBook.hasPerson(person)) { throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); } + addressBook.addPerson(person); } + + Map<String, DisplayItem> builtDisplayItem = new HashMap<>(builtGroup); + builtDisplayItem.putAll(builtTask); + builtDisplayItem.putAll(builtPerson); + + for (JsonAdaptedTask jsonAdaptedTask : tasks) { + Task task = jsonAdaptedTask.toModelType(); + List<String> parentUidList = itemRelationship.get(jsonAdaptedTask.getUid()); + for (String parentUid : parentUidList) { + if (!builtDisplayItem.containsKey(parentUid)) { + throw new IllegalValueException(MESSAGE_NONEXISTING_UID_PARENT_PAIR); + } + + task.setParent(builtDisplayItem.get(parentUid)); + } + + if (addressBook.hasTask(task)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_TASK); + } + addressBook.addTask(task); + } + + if (jsonAdaptedAddressBookParser != null) { + jsonAdaptedAddressBookParser.toModelType(); + } + return addressBook; } - } diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index beda8bd9f11..f593f8f27f6 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -28,5 +28,4 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { @Override void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 6cfa0162164..e44a77b9096 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -21,7 +21,8 @@ public class StorageManager implements Storage { private UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and + * {@code UserPrefStorage}. */ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { this.addressBookStorage = addressBookStorage; diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..385fddd5c2f 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -27,7 +27,8 @@ public class CommandBox extends UiPart<Region> { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; - // calls #setStyleToDefault() whenever there is a change to the text of the command box. + // calls #setStyleToDefault() whenever there is a change to the text of the + // command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } diff --git a/src/main/java/seedu/address/ui/GroupCard.java b/src/main/java/seedu/address/ui/GroupCard.java new file mode 100644 index 00000000000..8993b19ee09 --- /dev/null +++ b/src/main/java/seedu/address/ui/GroupCard.java @@ -0,0 +1,67 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.group.Group; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class GroupCard extends UiPart<Region> { + + private static final String FXML = "GroupListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved + * keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The + * issue on AddressBook level 4</a> + */ + + public final Group grp; + + @FXML + private HBox cardPane; + @FXML + private VBox parentContainer; + @FXML + private Label name; + @FXML + private Label id; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to + * display. + */ + public GroupCard(Group grp, int displayedIndex) { + super(FXML); + this.grp = grp; + id.setText(displayedIndex + ". "); + name.setText(grp.getFullPath()); + grp.getAttributes().forEach(attr -> parentContainer.getChildren().add(attr.getJavaFxRepresentation())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupCard)) { + return false; + } + + // state check + GroupCard card = (GroupCard) other; + return id.getText().equals(card.id.getText()) + && grp.equals(card.grp); + } +} diff --git a/src/main/java/seedu/address/ui/GroupListPanel.java b/src/main/java/seedu/address/ui/GroupListPanel.java new file mode 100644 index 00000000000..df7197786ae --- /dev/null +++ b/src/main/java/seedu/address/ui/GroupListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +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.group.Group; + +/** + * Panel containing the list of teams. + */ +public class GroupListPanel extends UiPart<Region> { + private static final String FXML = "TeamListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(GroupListPanel.class); + + @FXML + private ListView<Group> teamListView; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public GroupListPanel(ObservableList<Group> teamList) { + super(FXML); + teamListView.setItems(teamList); + teamListView.setCellFactory(listView -> new TeamListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using + * a {@code PersonCard}. + */ + class TeamListViewCell extends ListCell<Group> { + @Override + protected void updateItem(Group group, boolean empty) { + super.updateItem(group, empty); + + if (empty || group == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new GroupCard(group, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..b5eba4fb184 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<Stage> { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-t11-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..ddb14f209fd 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -16,6 +16,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.item.AbstractSingleItem; /** * The Main Window. Provides the basic application layout containing @@ -27,13 +28,18 @@ public class MainWindow extends UiPart<Stage> { private final Logger logger = LogsCenter.getLogger(getClass()); + private AbstractSingleItem prev = null; + private Stage primaryStage; private Logic logic; // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private GroupListPanel groupListPanel; + private TaskListPanel taskListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private StatusBarFooter statusBarFooter; @FXML private StackPane commandBoxPlaceholder; @@ -44,6 +50,12 @@ public class MainWindow extends UiPart<Stage> { @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane folderListPanelPlaceholder; + + @FXML + private StackPane taskListPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @@ -78,6 +90,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -113,16 +126,30 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + groupListPanel = new GroupListPanel(logic.getFilteredGroupList()); + folderListPanelPlaceholder.getChildren().add(groupListPanel.getRoot()); + + taskListPanel = new TaskListPanel(logic.getFilteredTaskList()); + taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); } + private void handleContextChange(AbstractSingleItem o) { + if (prev == o) { + return; + } + prev = o; + statusBarFooter.updateFooter(o); + } + /** * Sets the default size based on {@code guiSettings}. */ @@ -186,6 +213,8 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + handleContextChange(logic.getContainer()); + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..6d74cebe841 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -7,6 +7,7 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import seedu.address.model.person.Person; /** @@ -17,11 +18,13 @@ public class PersonCard extends UiPart<Region> { private static final String FXML = "PersonListCard.fxml"; /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * Note: Certain keywords such as "location" and "resources" are reserved + * keywords in JavaFX. * As a consequence, UI elements' variable names cannot be set to such keywords * or an exception will be thrown by JavaFX during runtime. * - * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The + * issue on AddressBook level 4</a> */ public final Person person; @@ -33,25 +36,24 @@ public class PersonCard extends UiPart<Region> { @FXML private Label id; @FXML - private Label phone; + private VBox fields; @FXML - private Label address; - @FXML - private Label email; + private VBox parentContainer; @FXML private FlowPane tags; /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. + * Creates a {@code PersonCode} with the given {@code Person} and index to + * display. */ public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); + person.getAttributes().stream() + .filter(attr -> attr.isDisplayable()) + .forEach(attr -> parentContainer.getChildren().add(attr.getJavaFxRepresentation())); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index b577f829423..fe6b6706aff 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -6,6 +6,7 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.Region; +import seedu.address.model.item.AbstractSingleItem; /** * A ui for the status bar that is displayed at the footer of the application. @@ -17,12 +18,27 @@ public class StatusBarFooter extends UiPart<Region> { @FXML private Label saveLocationStatus; + @FXML + private Label currGroupStatus; + /** * Creates a {@code StatusBarFooter} with the given {@code Path}. */ public StatusBarFooter(Path saveLocation) { super(FXML); - saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); + saveLocationStatus.setText(String.format("Save Location: %s", Paths.get(".").resolve(saveLocation).toString())); + updateFooter(null); + } + + /** + * Updates the footer so it represent the current working container + */ + public void updateFooter(AbstractSingleItem o) { + if (o == null) { + currGroupStatus.setText("/"); + return; + } + currGroupStatus.setText(o.getFullPath()); } } diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 00000000000..cd206a958f7 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,103 @@ +package seedu.address.ui; + +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.List; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.task.Task; + +/** + * An UI component that displays information of a {@code Tasks}. + */ +public class TaskCard extends UiPart<Region> { + + private static final String FXML = "TaskListCard.fxml"; + private static final String INCOMPLETE_TAG = "-fx-text-fill: white;" + + "-fx-background-color: #e84118;" + + "-fx-padding: 1 3 1 3;" + + "-fx-border-radius: 2;" + + "-fx-background-radius: 2;" + + "-fx-font-size: 11;"; + private static final String COMPLETE_TAG = "-fx-text-fill: white;" + + "-fx-background-color: #4cd137;" + + "-fx-padding: 1 3 1 3;" + + "-fx-border-radius: 2;" + + "-fx-background-radius: 2;" + + "-fx-font-size: 11;"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved + * keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The + * issue on AddressBook level 4</a> + */ + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private VBox parentContainer; + @FXML + private Label datetime; + @FXML + private Label path; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to + * display. + */ + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + name.setText(task.getName().fullName); + List<Attribute<?>> attrs = task.getAttributes(); + for (int i = 0; i < attrs.size(); i++) { + if (attrs.get(i).isDisplayable()) { + parentContainer.getChildren().add(attrs.get(i).getJavaFxRepresentation()); + } + } + path.setText(String.format("Path: " + task.getFullPath())); + if (task.getCompletedTime() != null) { + datetime.setText( + "Completed on: " + task.getCompletedTime() + .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM))); + datetime.setStyle(COMPLETE_TAG); + } else { + datetime.setText("Incomplete!"); + datetime.setStyle(INCOMPLETE_TAG); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java new file mode 100644 index 00000000000..0f28db5d513 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +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.task.Task; + +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart<Region> { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView<Task> taskListView; + + /** + * Creates a {@code TaskListPanel} with the given {@code ObservableList}. + */ + public TaskListPanel(ObservableList<Task> taskList) { + super(FXML); + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using + * a {@code PersonCard}. + */ + class TaskListViewCell extends ListCell<Task> { + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskCard(task, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index fc820e01a9c..b3a0eb24d90 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -9,7 +9,8 @@ import seedu.address.MainApp; /** - * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. + * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status + * bars, etc. * It contains a scene graph with a root node of type {@code T}. */ public abstract class UiPart<T> { @@ -28,7 +29,9 @@ public UiPart(URL fxmlFileUrl) { } /** - * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * Constructs a UiPart using the specified FXML file within + * {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -44,7 +47,9 @@ public UiPart(URL fxmlFileUrl, T root) { } /** - * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * Constructs a UiPart with the specified FXML file within + * {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { @@ -60,8 +65,9 @@ public T getRoot() { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. - * @param root Specifies the root of the object hierarchy. + * @param root Specifies the root of the object hierarchy. */ private void loadFxmlFile(URL location, T root) { requireNonNull(location); @@ -76,7 +82,8 @@ private void loadFxmlFile(URL location, T root) { } /** - * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. + * Returns the FXML file URL for the specified FXML file name within + * {@link #FXML_FILE_FOLDER}. */ private static URL getFxmlFileUrl(String fxmlFileName) { requireNonNull(fxmlFileName); diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..3d07d623f5a 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -132,6 +132,24 @@ -fx-text-fill: #010504; } +#fields { + -fx-font-family: "Segoe UI"; + -fx-text-fill: #010504; + -fx-font-size: 11px; +} + +#fields .paragraph Text { + -fx-font-family: "Segoe UI"; + -fx-text-fill: #010504; + -fx-font-size: 11px; +} + +#fields .label { + -fx-font-family: "Segoe UI"; + -fx-text-fill: #ffffff; + -fx-font-size: 13px; +} + .stack-pane { -fx-background-color: derive(#1d1d1d, 20%); } diff --git a/src/main/resources/view/GroupListCard.fxml b/src/main/resources/view/GroupListCard.fxml new file mode 100644 index 00000000000..8c8464291e7 --- /dev/null +++ b/src/main/resources/view/GroupListCard.fxml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <VBox fx:id="parentContainer" alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <HBox alignment="CENTER_LEFT" spacing="5"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="name" styleClass="cell_big_label" text="\$first" /> + </HBox> + </VBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> + </GridPane> +</HBox> diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..8b27d2a3f85 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,13 +6,13 @@ <?import javafx.scene.control.Menu?> <?import javafx.scene.control.MenuBar?> <?import javafx.scene.control.MenuItem?> -<?import javafx.scene.control.SplitPane?> <?import javafx.scene.image.Image?> +<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.VBox?> +<?import javafx.stage.Stage?> -<fx:root type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" - title="Address App" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> +<fx:root minHeight="600" minWidth="450" onCloseRequest="#handleExit" title="Contactmation" type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1"> <icons> <Image url="@/images/address_book_32.png" /> </icons> @@ -33,25 +33,47 @@ </Menu> </MenuBar> - <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> + <StackPane fx:id="commandBoxPlaceholder" styleClass="pane-with-border" VBox.vgrow="NEVER"> <padding> - <Insets top="5" right="10" bottom="5" left="10" /> + <Insets bottom="5" left="10" right="10" top="5" /> </padding> </StackPane> - <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" - minHeight="100" prefHeight="100" maxHeight="100"> + <StackPane fx:id="resultDisplayPlaceholder" maxHeight="100" minHeight="100" prefHeight="100" styleClass="pane-with-border" VBox.vgrow="NEVER"> <padding> - <Insets top="5" right="10" bottom="5" left="10" /> + <Insets bottom="5" left="10" right="10" top="5" /> </padding> </StackPane> + <HBox VBox.vgrow="ALWAYS"> + <children> + <VBox fx:id="folderList" minWidth="340.0" prefWidth="340.0" styleClass="pane-with-border" HBox.hgrow="ALWAYS"> + <padding> + <Insets bottom="10" left="10" right="10" top="10" /> + </padding> + <children> + <StackPane fx:id="folderListPanelPlaceholder" VBox.vgrow="ALWAYS" /> + </children> + </VBox> - <VBox fx:id="personList" styleClass="pane-with-border" minWidth="340" prefWidth="340" VBox.vgrow="ALWAYS"> - <padding> - <Insets top="10" right="10" bottom="10" left="10" /> - </padding> - <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS"/> - </VBox> + <VBox fx:id="personList" minWidth="340.0" prefWidth="340.0" styleClass="pane-with-border" HBox.hgrow="ALWAYS"> + <padding> + <Insets bottom="10" left="10" right="10" top="10" /> + </padding> + <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS" /> + </VBox> + <VBox fx:id="taskList" minWidth="340.0" prefWidth="340.0" styleClass="pane-with-border" HBox.hgrow="ALWAYS"> + <padding> + <Insets bottom="10" left="10" right="10" top="10" /> + </padding> + <children> + <StackPane fx:id="taskListPanelPlaceholder" VBox.vgrow="ALWAYS" /> + </children> + </VBox> + </children> + <VBox.margin> + <Insets /> + </VBox.margin> + </HBox> <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER" /> </VBox> diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..ae2d2b505d9 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,32 @@ <?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.VBox?> -<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"> <GridPane HBox.hgrow="ALWAYS"> <columnConstraints> <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> </columnConstraints> - <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <VBox fx:id="parentContainer" alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> <padding> - <Insets top="5" right="5" bottom="5" left="15" /> + <Insets bottom="5" left="15" right="5" top="5" /> </padding> - <HBox spacing="5" alignment="CENTER_LEFT"> + <HBox alignment="CENTER_LEFT" spacing="5"> <Label fx:id="id" styleClass="cell_big_label"> <minWidth> <!-- Ensures that the label text is never truncated --> <Region fx:constant="USE_PREF_SIZE" /> </minWidth> </Label> - <Label fx:id="name" text="\$first" styleClass="cell_big_label" /> + <Label fx:id="name" styleClass="cell_big_label" text="\$first" /> </HBox> <FlowPane fx:id="tags" /> - <Label fx:id="phone" styleClass="cell_small_label" text="\$phone" /> - <Label fx:id="address" styleClass="cell_small_label" text="\$address" /> - <Label fx:id="email" styleClass="cell_small_label" text="\$email" /> + <VBox fx:id="fields" styleClass="cell_small_label" /> </VBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> </GridPane> </HBox> diff --git a/src/main/resources/view/StatusBarFooter.fxml b/src/main/resources/view/StatusBarFooter.fxml index 149f62bd29c..557c557a1fa 100644 --- a/src/main/resources/view/StatusBarFooter.fxml +++ b/src/main/resources/view/StatusBarFooter.fxml @@ -3,10 +3,16 @@ <?import javafx.scene.control.Label?> <?import javafx.scene.layout.ColumnConstraints?> <?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.RowConstraints?> -<GridPane styleClass="status-bar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> +<GridPane gridLinesVisible="true" styleClass="status-bar" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1"> <columnConstraints> - <ColumnConstraints hgrow="SOMETIMES" minWidth="10" /> + <ColumnConstraints hgrow="ALWAYS" maxWidth="74.85712208066668" minWidth="10.0" percentWidth="50.0" prefWidth="53.71431187220982" /> + <ColumnConstraints hgrow="ALWAYS" maxWidth="93.14288377761841" minWidth="10.0" percentWidth="50.0" prefWidth="57.14283098493304" /> </columnConstraints> - <Label fx:id="saveLocationStatus" /> + <Label fx:id="currGroupStatus" /> + <Label fx:id="saveLocationStatus" GridPane.columnIndex="1" /> + <rowConstraints> + <RowConstraints /> + </rowConstraints> </GridPane> diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 00000000000..d08d0c3528e --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <VBox fx:id="parentContainer" alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <HBox alignment="CENTER_LEFT" spacing="5"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="name" styleClass="cell_big_label" text="\$first" /> + + </HBox> + <Label fx:id="datetime" styleClass="cell_small_label" text="\$datetime" /> + <Label fx:id="path" styleClass="cell_small_label" text="\$location" /> + </VBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> + </GridPane> +</HBox> diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 00000000000..9c4917370a7 --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="taskListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/main/resources/view/TeamListPanel.fxml b/src/main/resources/view/TeamListPanel.fxml new file mode 100644 index 00000000000..874462cee79 --- /dev/null +++ b/src/main/resources/view/TeamListPanel.fxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="teamListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidGroupAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidGroupAddressBook.json new file mode 100644 index 00000000000..457115b8ac7 --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidGroupAddressBook.json @@ -0,0 +1,37 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "External-Storage", + "uid" : "921eacb2-f171-3862-9ac5-271cbff03ca3", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Wrong name: Internal_Sto2$$rage", + "uid" : "da78ceee-3544-30f6-95ac-ed3e600b20d9", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "tasks" : [ ], + "itemRelationship" : { + "da78ceee-3544-30f6-95ac-ed3e600b20d9" : [ ], + "921eacb2-f171-3862-9ac5-271cbff03ca3" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json index 6a4d2b7181c..0e2b8d21f83 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json @@ -1,13 +1,65 @@ { - "persons": [ { - "name": "Valid Person", - "phone": "9482424", - "email": "hans@example.com", - "address": "4th street" + "persons" : [ { + "name" : "Person with invalid name field: Ha!ns Mu@ster", + "uid" : "a2af5cb4-0db5-3638-9f21-2b9efa99d166", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "93938282", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "hans@example.com", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "4th street", + "display_format" : 31, + "style_format" : 288 + } + } ] }, { - "name": "Person With Invalid Phone Field", - "phone": "948asdf2424", - "email": "hans@example.com", - "address": "4th street" - } ] + "name" : "Will Cake", + "uid" : "5853d883-81f0-3f4c-978a-3c8d41a37e2c", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "93930404", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "will@gmail.com", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "19th avenue", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "groups" : [ ], + "tasks" : [ ], + "itemRelationship" : { + "a2af5cb4-0db5-3638-9f21-2b9efa99d166" : [ ], + "5853d883-81f0-3f4c-978a-3c8d41a37e2c" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidTaskAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidTaskAddressBook.json new file mode 100644 index 00000000000..9bb3191eb64 --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidTaskAddressBook.json @@ -0,0 +1,33 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "t1", + "uid" : "ca762678-c236-3043-b841-2bc071624f8f", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ { + "description" : "12am with friends", + "localDateTime" : "", + "name" : "Stargazing", + "uid" : "f11c0f7a-f044-3b1e-aa5e-3ff59f09d2c6", + "tags" : [ ], + "attributes" : [ ] + }, { + "description" : "In the morning, and hang it out", + "localDateTime" : "", + "name" : "Wash l#$!aundry", + "uid" : "4ef85a0c-b8d3-3b0d-83f9-d881273fb8ce", + "tags" : [ ], + "attributes" : [ ] + } ], + "itemRelationship" : { + "f11c0f7a-f044-3b1e-aa5e-3ff59f09d2c6" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "4ef85a0c-b8d3-3b0d-83f9-d881273fb8ce" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "ca762678-c236-3043-b841-2bc071624f8f" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidGroupAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidGroupAddressBook.json new file mode 100644 index 00000000000..d73798eadbe --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidGroupAddressBook.json @@ -0,0 +1,37 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "Wrong name: @$^!External-Storage", + "uid" : "921eacb2-f171-3862-9ac5-271cbff03ca3", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Wrong name: Internal_Sto2$$rage", + "uid" : "da78ceee-3544-30f6-95ac-ed3e600b20d9", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "tasks" : [ ], + "itemRelationship" : { + "da78ceee-3544-30f6-95ac-ed3e600b20d9" : [ ], + "921eacb2-f171-3862-9ac5-271cbff03ca3" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json index ccd21f7d1a9..2b5d8f400b1 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json @@ -1,8 +1,38 @@ { - "persons": [ { - "name": "Person with invalid name field: Ha!ns Mu@ster", - "phone": "9482424", - "email": "hans@example.com", - "address": "4th street" - } ] + "persons" : [ { + "name" : "Person with invalid name field: Ha!ns Mu@ster", + "uid" : "a2af5cb4-0db5-3638-9f21-2b9efa99d166", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "93938282", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "hans@example.com", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "4th street", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "groups" : [ ], + "tasks" : [ ], + "itemRelationship" : { + "a2af5cb4-0db5-3638-9f21-2b9efa99d166" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidTaskAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidTaskAddressBook.json new file mode 100644 index 00000000000..dcce47f58b3 --- /dev/null +++ b/src/test/data/JsonAddressBookStorageTest/invalidTaskAddressBook.json @@ -0,0 +1,33 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "t1", + "uid" : "ca762678-c236-3043-b841-2bc071624f8f", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ { + "description" : "12am with friends", + "localDateTime" : "", + "name" : "Starg@$%@azing", + "uid" : "f11c0f7a-f044-3b1e-aa5e-3ff59f09d2c6", + "tags" : [ ], + "attributes" : [ ] + }, { + "description" : "In the morning, and hang it out", + "localDateTime" : "", + "name" : "Wash l#$!aundry", + "uid" : "4ef85a0c-b8d3-3b0d-83f9-d881273fb8ce", + "tags" : [ ], + "attributes" : [ ] + } ], + "itemRelationship" : { + "f11c0f7a-f044-3b1e-aa5e-3ff59f09d2c6" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "4ef85a0c-b8d3-3b0d-83f9-d881273fb8ce" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "ca762678-c236-3043-b841-2bc071624f8f" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateGroupAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateGroupAddressBook.json new file mode 100644 index 00000000000..942e9c428f1 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateGroupAddressBook.json @@ -0,0 +1,59 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "Automation", + "uid" : "96c7e59a-2951-3c3e-9ccf-65175925c021", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Alpha", + "uid" : "7ab6512c-2b11-3271-b57e-01268273c4be", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_B", + "uid" : "90836d25-6687-38e0-bc73-a556c87f0525", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_B", + "uid" : "81d52a6d-6d12-3500-b6ae-287b9982ef44", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_B", + "uid" : "8a5d7b1f-ddf8-338f-8915-a546f415d9b6", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Omega", + "uid" : "486394ea-153e-30e1-9127-41828704c42d", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Beta", + "uid" : "db2bc468-f8f5-3781-b15a-fe299ed87905", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Gamma", + "uid" : "e609a102-15a9-3ef8-8845-6f4b12355b8e", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ ], + "itemRelationship" : { + "486394ea-153e-30e1-9127-41828704c42d" : [ "7ab6512c-2b11-3271-b57e-01268273c4be" ], + "7ab6512c-2b11-3271-b57e-01268273c4be" : [ ], + "db2bc468-f8f5-3781-b15a-fe299ed87905" : [ ], + "81d52a6d-6d12-3500-b6ae-287b9982ef44" : [ "90836d25-6687-38e0-bc73-a556c87f0525" ], + "8a5d7b1f-ddf8-338f-8915-a546f415d9b6" : [ "90836d25-6687-38e0-bc73-a556c87f0525" ], + "90836d25-6687-38e0-bc73-a556c87f0525" : [ "7ab6512c-2b11-3271-b57e-01268273c4be" ], + "96c7e59a-2951-3c3e-9ccf-65175925c021" : [ ], + "e609a102-15a9-3ef8-8845-6f4b12355b8e" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json index 48831cc7674..cc731c13e4d 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json @@ -1,14 +1,217 @@ { - "persons": [ { - "name": "Alice Pauline", - "phone": "94351253", - "email": "alice@example.com", - "address": "123, Jurong West Ave 6, #08-111", - "tagged": [ "friends" ] - }, { - "name": "Alice Pauline", - "phone": "94351253", - "email": "pauline@example.com", - "address": "4th street" - } ] + "persons" : [ { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Alice Pauline", + "uid" : "6a407006-9331-3372-a72f-fb26358890f7", + "tags" : [ "friends" ], + "attributes" : [ { + "data" : { + "type" : "Address", + "content" : "123, Jurong West Ave 6, #08-111", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "alice@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Phone", + "content" : "94351253", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Benson Meier", + "uid" : "1c39a824-f416-3eb8-881e-f007e45b2006", + "tags" : [ "owesMoney", "friends" ], + "attributes" : [ { + "data" : { + "type" : "Address", + "content" : "311, Clementi Ave 2, #02-25", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "johnd@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Phone", + "content" : "98765432", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Carl Kurz", + "uid" : "c579e74e-64af-3a5a-91bd-ec8042734baa", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "95352563", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "heinz@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "wall street", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Daniel Meier", + "uid" : "ec313cbe-3237-3521-a920-093d010c830e", + "tags" : [ "friends" ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "87652533", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "cornelia@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "10th street", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Fiona Kunz", + "uid" : "c42334cb-c054-3a81-885b-cf9c01896ec1", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "94822240", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "werner@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "michegan ave", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Fiona Kunz", + "uid" : "8bdc4591-a4a8-3123-af9d-67a17f65a207", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "94824270", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "lydia@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "little tokyo", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "George Best", + "uid" : "e18b1774-c755-3701-875f-c7e32483ebe7", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "94824420", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "anna@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "4th street", + "display_format" : 63, + "style_format" : 288 + } + } ] + } ], + "groups" : [ ], + "tasks" : [ ], + "itemRelationship" : { + "6a407006-9331-3372-a72f-fb26358890f7" : [ ], + "8bdc4591-a4a8-3123-af9d-67a17f65a207" : [ ], + "1c39a824-f416-3eb8-881e-f007e45b2006" : [ ], + "ec313cbe-3237-3521-a920-093d010c830e" : [ ], + "c42334cb-c054-3a81-885b-cf9c01896ec1" : [ ], + "c579e74e-64af-3a5a-91bd-ec8042734baa" : [ ], + "e18b1774-c755-3701-875f-c7e32483ebe7" : [ ] + } } diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicateTaskAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicateTaskAddressBook.json new file mode 100644 index 00000000000..8ff4d6f5c43 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/duplicateTaskAddressBook.json @@ -0,0 +1,62 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "t1", + "uid" : "ca762678-c236-3043-b841-2bc071624f8f", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ { + "description" : "Bug related to the Alpha command", + "localDateTime" : "2022-11-04T22:07:53.947688600", + "name" : "Fix bug", + "uid" : "0dce1d16-a2e7-37eb-977f-efeaaeaa4f4c", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Logic", + "content" : "Check null value in command", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "description" : "Buy printer from XYZ street", + "localDateTime" : "", + "name" : "Buy printer", + "uid" : "4243b59a-e08f-349f-9e59-07e1481efece", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "description" : "Buy printer from XYZ street", + "localDateTime" : "", + "name" : "Buy printer", + "uid" : "4243b59a-e08f-349f-9e59-07e1481efece", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "itemRelationship" : { + "8cc6fe70-d453-376d-9b7c-59a2c777277e" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "0dce1d16-a2e7-37eb-977f-efeaaeaa4f4c" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "ca762678-c236-3043-b841-2bc071624f8f" : [ ], + "4243b59a-e08f-349f-9e59-07e1481efece" : [ "ca762678-c236-3043-b841-2bc071624f8f" ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidGroupAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidGroupAddressBook.json new file mode 100644 index 00000000000..06ed9f85260 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/invalidGroupAddressBook.json @@ -0,0 +1,81 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "Team_Omega", + "uid" : "486394ea-153e-30e1-9127-41828704c42d", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Urgent", + "content" : "Do paperwork", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Team_A", + "uid" : "6194e27d-8565-3d60-8b3f-3a2419664f0d", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Meetup", + "content" : "Saturday", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Team_Alpha", + "uid" : "7ab6512c-2b11-3271-b57e-01268273c4be", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Work", + "content" : "Internship", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Leave_number", + "content" : "3", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Tea$^!m_B", + "uid" : "7674b450-2144-314b-8521-ad83b7ca12ab", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Beta", + "uid" : "db2bc468-f8f5-3781-b15a-fe299ed87905", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Automation", + "uid" : "96c7e59a-2951-3c3e-9ccf-65175925c021", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Gamma", + "uid" : "e609a102-15a9-3ef8-8845-6f4b12355b8e", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ ], + "itemRelationship" : { + "486394ea-153e-30e1-9127-41828704c42d" : [ "7ab6512c-2b11-3271-b57e-01268273c4be" ], + "6194e27d-8565-3d60-8b3f-3a2419664f0d" : [ "7ab6512c-2b11-3271-b57e-01268273c4be" ], + "7ab6512c-2b11-3271-b57e-01268273c4be" : [ ], + "7674b450-2144-314b-8521-ad83b7ca12ab" : [ "6194e27d-8565-3d60-8b3f-3a2419664f0d" ], + "db2bc468-f8f5-3781-b15a-fe299ed87905" : [ ], + "96c7e59a-2951-3c3e-9ccf-65175925c021" : [ ], + "e609a102-15a9-3ef8-8845-6f4b12355b8e" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json index ad3f135ae42..8c93a7380f3 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json @@ -1,8 +1,217 @@ { - "persons": [ { - "name": "Hans Muster", - "phone": "9482424", - "email": "invalid@email!3e", - "address": "4th street" - } ] + "persons" : [ { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Alice Pauline", + "uid" : "6a407006-9331-3372-a72f-fb26358890f7", + "tags" : [ "friends" ], + "attributes" : [ { + "data" : { + "type" : "Address", + "content" : "123, Jurong West Ave 6, #08-111", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "alice$@ @example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Phone", + "content" : "94351253", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Benson Meier", + "uid" : "1c39a824-f416-3eb8-881e-f007e45b2006", + "tags" : [ "owesMoney", "friends" ], + "attributes" : [ { + "data" : { + "type" : "Address", + "content" : "311, Clementi Ave 2, #02-25", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "johnd@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Phone", + "content" : "98765432", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Carl Kurz", + "uid" : "c579e74e-64af-3a5a-91bd-ec8042734baa", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "95352563", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "heinz@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "wall street", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Daniel Meier", + "uid" : "ec313cbe-3237-3521-a920-093d010c830e", + "tags" : [ "friends" ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "87652533", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "cornelia@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "10th street", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Elle Meyer", + "uid" : "c42334cb-c054-3a81-885b-cf9c01896ec1", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "9482224", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "werner@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "michegan ave", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "Fiona Kunz", + "uid" : "8bdc4591-a4a8-3123-af9d-67a17f65a207", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "9482427", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "lydia@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "little tokyo", + "display_format" : 63, + "style_format" : 288 + } + } ] + }, { + "fields" : { + "fieldList" : [ ] + }, + "name" : "George Best", + "uid" : "e18b1774-c755-3701-875f-c7e32483ebe7", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "9482442", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "anna@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "4th street", + "display_format" : 63, + "style_format" : 288 + } + } ] + } ], + "groups" : [ ], + "tasks" : [ ], + "itemRelationship" : { + "6a407006-9331-3372-a72f-fb26358890f7" : [ ], + "8bdc4591-a4a8-3123-af9d-67a17f65a207" : [ ], + "1c39a824-f416-3eb8-881e-f007e45b2006" : [ ], + "ec313cbe-3237-3521-a920-093d010c830e" : [ ], + "c42334cb-c054-3a81-885b-cf9c01896ec1" : [ ], + "c579e74e-64af-3a5a-91bd-ec8042734baa" : [ ], + "e18b1774-c755-3701-875f-c7e32483ebe7" : [ ] + } } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidTaskAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidTaskAddressBook.json new file mode 100644 index 00000000000..0af51cb3b0f --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/invalidTaskAddressBook.json @@ -0,0 +1,55 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "t1", + "uid" : "ca762678-c236-3043-b841-2bc071624f8f", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ { + "description" : "Bug related to the Alpha command", + "localDateTime" : "2022-11-04T22:07:53.947688600", + "name" : "Fix bug", + "uid" : "0dce1d16-a2e7-37eb-977f-efeaaeaa4f4c", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Logic", + "content" : "Check null value in command", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "description" : "Administrative tasks by management", + "localDateTime" : "", + "name" : "Do paperwork", + "uid" : "8cc6fe70-d453-376d-9b7c-59a2c777277e", + "tags" : [ ], + "attributes" : [ ] + }, { + "description" : "Buy printer from XYZ street", + "localDateTime" : "39581", + "name" : "Buy printer", + "uid" : "4243b59a-e08f-349f-9e59-07e1481efece", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "itemRelationship" : { + "8cc6fe70-d453-376d-9b7c-59a2c777277e" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "0dce1d16-a2e7-37eb-977f-efeaaeaa4f4c" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "ca762678-c236-3043-b841-2bc071624f8f" : [ ], + "4243b59a-e08f-349f-9e59-07e1481efece" : [ "ca762678-c236-3043-b841-2bc071624f8f" ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalGroupsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalGroupsAddressBook.json new file mode 100644 index 00000000000..2f39166df82 --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/typicalGroupsAddressBook.json @@ -0,0 +1,81 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "Team_Omega", + "uid" : "486394ea-153e-30e1-9127-41828704c42d", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Urgent", + "content" : "Do paperwork", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Team_A", + "uid" : "6194e27d-8565-3d60-8b3f-3a2419664f0d", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Meetup", + "content" : "Saturday", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Team_Alpha", + "uid" : "7ab6512c-2b11-3271-b57e-01268273c4be", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Work", + "content" : "Internship", + "display_format" : 31, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Leave_number", + "content" : "3", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "name" : "Team_B", + "uid" : "7674b450-2144-314b-8521-ad83b7ca12ab", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Beta", + "uid" : "db2bc468-f8f5-3781-b15a-fe299ed87905", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Automation", + "uid" : "96c7e59a-2951-3c3e-9ccf-65175925c021", + "tags" : [ ], + "attributes" : [ ] + }, { + "name" : "Team_Gamma", + "uid" : "e609a102-15a9-3ef8-8845-6f4b12355b8e", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ ], + "itemRelationship" : { + "486394ea-153e-30e1-9127-41828704c42d" : [ "7ab6512c-2b11-3271-b57e-01268273c4be" ], + "6194e27d-8565-3d60-8b3f-3a2419664f0d" : [ "7ab6512c-2b11-3271-b57e-01268273c4be" ], + "7ab6512c-2b11-3271-b57e-01268273c4be" : [ ], + "7674b450-2144-314b-8521-ad83b7ca12ab" : [ "6194e27d-8565-3d60-8b3f-3a2419664f0d" ], + "db2bc468-f8f5-3781-b15a-fe299ed87905" : [ ], + "96c7e59a-2951-3c3e-9ccf-65175925c021" : [ ], + "e609a102-15a9-3ef8-8845-6f4b12355b8e" : [ ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json index f10eddee12e..dfb4417d2c4 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json @@ -1,46 +1,217 @@ { - "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()", "persons" : [ { + "fields" : { + "fieldList" : [ ] + }, "name" : "Alice Pauline", - "phone" : "94351253", - "email" : "alice@example.com", - "address" : "123, Jurong West Ave 6, #08-111", - "tagged" : [ "friends" ] + "uid" : "6a407006-9331-3372-a72f-fb26358890f7", + "tags" : [ "friends" ], + "attributes" : [ { + "data" : { + "type" : "Address", + "content" : "123, Jurong West Ave 6, #08-111", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "alice@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Phone", + "content" : "94351253", + "display_format" : 63, + "style_format" : 288 + } + } ] }, { + "fields" : { + "fieldList" : [ ] + }, "name" : "Benson Meier", - "phone" : "98765432", - "email" : "johnd@example.com", - "address" : "311, Clementi Ave 2, #02-25", - "tagged" : [ "owesMoney", "friends" ] + "uid" : "1c39a824-f416-3eb8-881e-f007e45b2006", + "tags" : [ "owesMoney", "friends" ], + "attributes" : [ { + "data" : { + "type" : "Address", + "content" : "311, Clementi Ave 2, #02-25", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "johnd@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Phone", + "content" : "98765432", + "display_format" : 63, + "style_format" : 288 + } + } ] }, { + "fields" : { + "fieldList" : [ ] + }, "name" : "Carl Kurz", - "phone" : "95352563", - "email" : "heinz@example.com", - "address" : "wall street", - "tagged" : [ ] + "uid" : "c579e74e-64af-3a5a-91bd-ec8042734baa", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "95352563", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "heinz@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "wall street", + "display_format" : 63, + "style_format" : 288 + } + } ] }, { + "fields" : { + "fieldList" : [ ] + }, "name" : "Daniel Meier", - "phone" : "87652533", - "email" : "cornelia@example.com", - "address" : "10th street", - "tagged" : [ "friends" ] + "uid" : "ec313cbe-3237-3521-a920-093d010c830e", + "tags" : [ "friends" ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "87652533", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "cornelia@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "10th street", + "display_format" : 63, + "style_format" : 288 + } + } ] }, { + "fields" : { + "fieldList" : [ ] + }, "name" : "Elle Meyer", - "phone" : "9482224", - "email" : "werner@example.com", - "address" : "michegan ave", - "tagged" : [ ] + "uid" : "c42334cb-c054-3a81-885b-cf9c01896ec1", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "94822240", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "werner@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "michegan ave", + "display_format" : 63, + "style_format" : 288 + } + } ] }, { + "fields" : { + "fieldList" : [ ] + }, "name" : "Fiona Kunz", - "phone" : "9482427", - "email" : "lydia@example.com", - "address" : "little tokyo", - "tagged" : [ ] + "uid" : "8bdc4591-a4a8-3123-af9d-67a17f65a207", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "94824270", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "lydia@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "little tokyo", + "display_format" : 63, + "style_format" : 288 + } + } ] }, { + "fields" : { + "fieldList" : [ ] + }, "name" : "George Best", - "phone" : "9482442", - "email" : "anna@example.com", - "address" : "4th street", - "tagged" : [ ] - } ] + "uid" : "e18b1774-c755-3701-875f-c7e32483ebe7", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Phone", + "content" : "94824420", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Email", + "content" : "anna@example.com", + "display_format" : 63, + "style_format" : 288 + } + }, { + "data" : { + "type" : "Address", + "content" : "4th street", + "display_format" : 63, + "style_format" : 288 + } + } ] + } ], + "groups" : [ ], + "tasks" : [ ], + "itemRelationship" : { + "6a407006-9331-3372-a72f-fb26358890f7" : [ ], + "8bdc4591-a4a8-3123-af9d-67a17f65a207" : [ ], + "1c39a824-f416-3eb8-881e-f007e45b2006" : [ ], + "ec313cbe-3237-3521-a920-093d010c830e" : [ ], + "c42334cb-c054-3a81-885b-cf9c01896ec1" : [ ], + "c579e74e-64af-3a5a-91bd-ec8042734baa" : [ ], + "e18b1774-c755-3701-875f-c7e32483ebe7" : [ ] + } } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalTasksAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalTasksAddressBook.json new file mode 100644 index 00000000000..2b85d89b00e --- /dev/null +++ b/src/test/data/JsonSerializableAddressBookTest/typicalTasksAddressBook.json @@ -0,0 +1,55 @@ +{ + "persons" : [ ], + "groups" : [ { + "name" : "t1", + "uid" : "ca762678-c236-3043-b841-2bc071624f8f", + "tags" : [ ], + "attributes" : [ ] + } ], + "tasks" : [ { + "description" : "Bug related to the Alpha command", + "localDateTime" : "2022-11-04T22:07:53.947688600", + "name" : "Fix bug", + "uid" : "0dce1d16-a2e7-37eb-977f-efeaaeaa4f4c", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Logic", + "content" : "Check null value in command", + "display_format" : 31, + "style_format" : 288 + } + } ] + }, { + "description" : "Administrative tasks by management", + "localDateTime" : "", + "name" : "Do paperwork", + "uid" : "8cc6fe70-d453-376d-9b7c-59a2c777277e", + "tags" : [ ], + "attributes" : [ ] + }, { + "description" : "Buy printer from XYZ street", + "localDateTime" : "", + "name" : "Buy printer", + "uid" : "4243b59a-e08f-349f-9e59-07e1481efece", + "tags" : [ ], + "attributes" : [ { + "data" : { + "type" : "Priority", + "content" : "High", + "display_format" : 31, + "style_format" : 288 + } + } ] + } ], + "itemRelationship" : { + "8cc6fe70-d453-376d-9b7c-59a2c777277e" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "0dce1d16-a2e7-37eb-977f-efeaaeaa4f4c" : [ "ca762678-c236-3043-b841-2bc071624f8f" ], + "ca762678-c236-3043-b841-2bc071624f8f" : [ ], + "4243b59a-e08f-349f-9e59-07e1481efece" : [ "ca762678-c236-3043-b841-2bc071624f8f" ] + }, + "jsonAdaptedAddressBookParser" : { + "alias" : { }, + "macros" : { } + } +} diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..0d164c04fa8 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -1,7 +1,6 @@ package seedu.address.logic; 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; @@ -17,10 +16,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.persons.AddPersonCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ModelManager; @@ -43,8 +42,8 @@ public class LogicManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage( + temporaryFolder.resolve("addressBook.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); logic = new LogicManager(model, storage); @@ -58,8 +57,9 @@ public void execute_invalidCommandFormat_throwsParseException() { @Test public void execute_commandExecutionError_throwsCommandException() { - String deleteCommand = "delete 9"; - assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + String deleteCommand = "person delete 9"; + assertCommandException(deleteCommand, String.format( + "Person out of bounds. Length is only %d yet index 9 supplied.", model.getFilteredPersonList().size())); } @Test @@ -71,15 +71,17 @@ public void execute_validCommand_success() throws Exception { @Test public void execute_storageThrowsIoException_throwsCommandException() { // Setup LogicManager with JsonAddressBookIoExceptionThrowingStub - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json")); - JsonUserPrefsStorage userPrefsStorage = - new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json")); + JsonAddressBookStorage addressBookStorage = new JsonAddressBookIoExceptionThrowingStub( + temporaryFolder.resolve("ioExceptionAddressBook.json")); + JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage( + temporaryFolder.resolve("ioExceptionUserPrefs.json")); StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); logic = new LogicManager(model, storage); // Execute add command - String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + String addCommand = + AddPersonCommand.getFullCommand(AddPersonCommand.SUBCOMMAND_WORD) + NAME_DESC_AMY + PHONE_DESC_AMY + + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; Person expectedPerson = new PersonBuilder(AMY).withTags().build(); ModelManager expectedModel = new ModelManager(); @@ -94,21 +96,23 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException } /** - * Executes the command and confirms that - * - no exceptions are thrown <br> + * Executes the command and confirms that - no exceptions are thrown <br> * - the feedback message is equal to {@code expectedMessage} <br> * - the internal model manager state is the same as that in {@code expectedModel} <br> + * * @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); } /** - * Executes the command, confirms that a ParseException is thrown and that the result message is correct. + * Executes the command, confirms that a ParseException is thrown and that the result message is + * correct. + * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertParseException(String inputCommand, String expectedMessage) { @@ -116,7 +120,9 @@ private void assertParseException(String inputCommand, String expectedMessage) { } /** - * Executes the command, confirms that a CommandException is thrown and that the result message is correct. + * Executes the command, confirms that a CommandException is thrown and that the result message is + * correct. + * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandException(String inputCommand, String expectedMessage) { @@ -124,24 +130,26 @@ private void assertCommandException(String inputCommand, String expectedMessage) } /** - * Executes the command, confirms that the exception is thrown and that the result message is correct. + * Executes the command, confirms that the exception is thrown and that the result message is + * correct. + * * @see #assertCommandFailure(String, Class, String, Model) */ private void assertCommandFailure(String inputCommand, Class<? extends Throwable> expectedException, - String expectedMessage) { + String expectedMessage) { Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } /** - * Executes the command and confirms that - * - the {@code expectedException} is thrown <br> + * Executes the command and confirms that - the {@code expectedException} is thrown <br> * - the resulting error message is equal to {@code expectedMessage} <br> * - the internal model manager state is the same as that in {@code expectedModel} <br> + * * @see #assertCommandSuccess(String, String, Model) */ private void assertCommandFailure(String inputCommand, Class<? extends Throwable> 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 deleted file mode 100644 index 5865713d5dd..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static org.junit.jupiter.api.Assertions.assertEquals; -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.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandTest { - - @Test - public void constructor_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new AddCommand(null)); - } - - @Test - public void execute_personAcceptedByModel_addSuccessful() throws Exception { - ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); - Person validPerson = new PersonBuilder().build(); - - CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); - - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); - assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person validPerson = new PersonBuilder().build(); - AddCommand addCommand = new AddCommand(validPerson); - ModelStub modelStub = new ModelStubWithPerson(validPerson); - - assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); - } - - @Test - public void equals() { - Person alice = new PersonBuilder().withName("Alice").build(); - Person bob = new PersonBuilder().withName("Bob").build(); - AddCommand addAliceCommand = new AddCommand(alice); - AddCommand addBobCommand = new AddCommand(bob); - - // same object -> returns true - assertTrue(addAliceCommand.equals(addAliceCommand)); - - // same values -> returns true - AddCommand addAliceCommandCopy = new AddCommand(alice); - assertTrue(addAliceCommand.equals(addAliceCommandCopy)); - - // different types -> returns false - assertFalse(addAliceCommand.equals(1)); - - // null -> returns false - assertFalse(addAliceCommand.equals(null)); - - // different person -> returns false - assertFalse(addAliceCommand.equals(addBobCommand)); - } - - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - throw new AssertionError("This method should not be called."); - } - - @Override - public GuiSettings getGuiSettings() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - throw new AssertionError("This method should not be called."); - } - - @Override - public Path getAddressBookFilePath() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void addPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBook(ReadOnlyAddressBook newData) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - throw new AssertionError("This method should not be called."); - } - - @Override - public boolean hasPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void deletePerson(Person target) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ObservableList<Person> getFilteredPersonList() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void updateFilteredPersonList(Predicate<Person> predicate) { - throw new AssertionError("This method should not be called."); - } - } - - /** - * A Model stub that contains a single person. - */ - private class ModelStubWithPerson extends ModelStub { - private final Person person; - - ModelStubWithPerson(Person person) { - requireNonNull(person); - this.person = person; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return this.person.isSamePerson(person); - } - } - - /** - * A Model stub that always accept the person being added. - */ - private class ModelStubAcceptingPersonAdded extends ModelStub { - final ArrayList<Person> personsAdded = new ArrayList<>(); - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return personsAdded.stream().anyMatch(person::isSamePerson); - } - - @Override - public void addPerson(Person person) { - requireNonNull(person); - personsAdded.add(person); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return new AddressBook(); - } - } - -} diff --git a/src/test/java/seedu/address/logic/commands/AddFieldCommandTest.java b/src/test/java/seedu/address/logic/commands/AddFieldCommandTest.java new file mode 100644 index 00000000000..8f189f2d561 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddFieldCommandTest.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands; + +//import static org.junit.jupiter.api.Assertions.assertTrue; +//import static seedu.address.logic.commands.fields.AddFieldCommand.MESSAGE_DUPLICATE; +import static seedu.address.logic.commands.fields.AddFieldCommand.NO_INPUT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.fields.AddFieldCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +//import seedu.address.model.item.DisplayItem; + + +public class AddFieldCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + /*@Test + public void execute_validField_success() throws CommandException { + AddFieldCommand addFieldCommand = new AddFieldCommand(INDEX_SECOND, "u", "p", "test"); + addFieldCommand.execute(model); + DisplayItem itemStub = model.getFromFilteredPerson(INDEX_SECOND); + assertTrue(itemStub.getAttribute("p").isPresent()); + }*/ + + @Test + public void execute_invalidFType_throwsCommandException() throws CommandException { + AddFieldCommand addFieldCommand = new AddFieldCommand(INDEX_SECOND, "a", "p", "test"); + assertThrows(CommandException.class, NO_INPUT, () -> addFieldCommand.execute(model)); + } + + /*@Test + public void execute_duplicateField_throwsCommandException() throws CommandException { + AddFieldCommand addFieldCommand = new AddFieldCommand(INDEX_SECOND, "u", "p", "test"); + DisplayItem itemStub = model.getFromFilteredPerson(INDEX_SECOND); + itemStub.addAttribute("u", "test"); + assertThrows(CommandException.class, MESSAGE_DUPLICATE, () -> addFieldCommand.execute(model)); + }*/ +} diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java index 4f3eb46e9ef..1ce6aeef982 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java @@ -8,6 +8,15 @@ import org.junit.jupiter.api.Test; public class CommandResultTest { + + @Test + public void createCommandObject_rest() { + CommandResult commandResultStub = new CommandResult("test", true, false, "result"); + assertTrue(commandResultStub.isShowHelp()); + assertFalse(commandResultStub.isExit()); + assertEquals(commandResultStub.getResult(), java.util.Optional.ofNullable("result")); + } + @Test public void equals() { CommandResult commandResult = new CommandResult("feedback"); diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..991f1e8ee4b 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -17,9 +17,8 @@ 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.item.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; /** * Contains helper methods for testing commands. @@ -28,14 +27,16 @@ public class CommandTestUtil { public static final String VALID_NAME_AMY = "Amy Bee"; public static final String VALID_NAME_BOB = "Bob Choo"; - public static final String VALID_PHONE_AMY = "11111111"; - public static final String VALID_PHONE_BOB = "22222222"; + public static final String VALID_PHONE_AMY = "91234567"; + public static final String VALID_PHONE_BOB = "81234567"; public static final String VALID_EMAIL_AMY = "amy@example.com"; 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_GROUP_NAME = "group1"; + public static final String VALID_PATH = "group1/group2/group3"; public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -53,21 +54,23 @@ public class CommandTestUtil { 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_GROUP_NAME = "group 1"; + public static final String INVALID_PATH = "invalid \\ path"; public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; - public static final EditCommand.EditPersonDescriptor DESC_AMY; - public static final EditCommand.EditPersonDescriptor DESC_BOB; + // public static final EditCommand.EditPersonDescriptor DESC_AMY; + // 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(); - } + // 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(); + // } /** * Executes the given {@code command}, confirms that <br> @@ -75,7 +78,7 @@ public class CommandTestUtil { * - the {@code actualModel} matches {@code expectedModel} */ public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, - Model expectedModel) { + Model expectedModel) { try { CommandResult result = command.execute(actualModel); assertEquals(expectedCommandResult, result); @@ -86,11 +89,11 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm } /** - * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} - * that takes a string {@code expectedMessage}. + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} that + * takes a string {@code expectedMessage}. */ public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, - Model expectedModel) { + Model expectedModel) { CommandResult expectedCommandResult = new CommandResult(expectedMessage); assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); } @@ -99,7 +102,8 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri * Executes the given {@code command}, confirms that <br> * - a {@code CommandException} is thrown <br> * - the CommandException message matches {@code expectedMessage} <br> - * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged + * - the address book, filtered person list and selected person in {@code actualModel} remain + * unchanged */ public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { // we are unable to defensively copy the model for comparison later, so we can @@ -111,9 +115,10 @@ 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. + * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in + * the {@code model}'s address book. */ public static void showPersonAtIndex(Model model, Index targetIndex) { assertTrue(targetIndex.getZeroBased() < model.getFilteredPersonList().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..0d593007071 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -5,33 +5,40 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; 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.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import org.junit.jupiter.api.Test; -import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Getter; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.CmdBuilder; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; +import seedu.address.model.item.DisplayItem; import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonOutOfBoundException; +import seedu.address.testutil.TypicalPersons; /** - * Contains integration tests (interaction with the Model) and unit tests for - * {@code DeleteCommand}. + * Contains integration tests (interaction with the Model) and unit tests for {@code DeleteCommand}. */ public class DeleteCommandTest { + private static final Getter<Person> P_GETTER = (m, i) -> m.getFromFilteredPerson(i); + private static final Changer<Person> P_DELETER = (m, item) -> m.deletePerson(item); + private static final java.util.function.Predicate<Object> P_TESTER = o -> o instanceof Person; - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(TypicalPersons.getTypicalAddressBook(), new UserPrefs()); @Test public void execute_validIndexUnfilteredList_success() { - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand<Person> deleteCommand = CmdBuilder.makeDelPerson(INDEX_FIRST); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_SUCCESS, personToDelete); ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); expectedModel.deletePerson(personToDelete); @@ -42,19 +49,22 @@ public void execute_validIndexUnfilteredList_success() { @Test public void execute_invalidIndexUnfilteredList_throwsCommandException() { Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); + DeleteCommand<Person> deleteCommand = CmdBuilder.makeDelPerson(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCommand, + model, + String.format(PersonOutOfBoundException.ERR_MSG, model.getFilteredPersonList().size(), + outOfBoundIndex.getOneBased())); } @Test public void execute_validIndexFilteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand<Person> deleteCommand = CmdBuilder.makeDelPerson(INDEX_FIRST); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_SUCCESS, personToDelete); Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); expectedModel.deletePerson(personToDelete); @@ -65,27 +75,28 @@ public void execute_validIndexFilteredList_success() { @Test public void execute_invalidIndexFilteredList_throwsCommandException() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + Index outOfBoundIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); + DeleteCommand<Person> deleteCommand = CmdBuilder.makeDelPerson(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCommand, model, String.format(PersonOutOfBoundException.ERR_MSG, + model.getFilteredPersonList().size(), outOfBoundIndex.getOneBased())); } @Test - public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); + public void equals() throws CommandException { + DeleteCommand<Person> deleteFirstCommand = CmdBuilder.makeDelPerson(INDEX_FIRST); + DeleteCommand<Person> deleteSecondCommand = CmdBuilder.makeDelPerson(INDEX_SECOND); // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCommand<Person> deleteFirstCommandCopy = CmdBuilder.makeDelPerson(INDEX_FIRST); assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); // different types -> returns false @@ -96,8 +107,23 @@ public void equals() { // different person -> returns false assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + + deleteFirstCommand.setInput(P_GETTER.apply(model, INDEX_FIRST)); + deleteFirstCommandCopy.setInput(P_GETTER.apply(model, INDEX_FIRST)); + + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); } + @Test + public void setInput_test() throws CommandException { + DisplayItem dataStub = model.getFromFilteredPerson(Index.fromZeroBased(1)); + + DeleteCommand delCommandStub = new DeleteCommand(Index.fromZeroBased(1), P_GETTER, P_DELETER, P_TESTER); + + delCommandStub.setInput(dataStub); + + assertTrue(delCommandStub.equals(delCommandStub)); + } /** * Updates {@code model}'s filtered list to show no one. */ @@ -106,4 +132,6 @@ private void showNoPerson(Model model) { assertTrue(model.getFilteredPersonList().isEmpty()); } + + } diff --git a/src/test/java/seedu/address/logic/commands/DeleteFieldCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteFieldCommandTest.java new file mode 100644 index 00000000000..7ead0231d85 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteFieldCommandTest.java @@ -0,0 +1,37 @@ +package seedu.address.logic.commands; + +//import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.logic.commands.fields.AddFieldCommand.NO_INPUT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +//import seedu.address.logic.commands.fields.AddFieldCommand; +import seedu.address.logic.commands.fields.DeleteFieldCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + + +public class DeleteFieldCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + /*@Test + public void execute_validField_success() throws CommandException { + AddFieldCommand addFieldCommand = new AddFieldCommand(INDEX_SECOND, "u", "p", "test"); + addFieldCommand.execute(model); + DeleteFieldCommand deleteFieldCommand = new DeleteFieldCommand(INDEX_SECOND, "u", "p"); + deleteFieldCommand.execute(model); + seedu.address.model.item.DisplayItem itemStub = model.getFromFilteredPerson(INDEX_SECOND); + assertFalse(itemStub.getAttribute("p").isPresent()); + }*/ + + @Test + public void execute_invalidFType_throwsCommandException() throws CommandException { + DeleteFieldCommand deleteFieldCommand = new DeleteFieldCommand(INDEX_SECOND, "a", "p"); + assertThrows(CommandException.class, NO_INPUT, () -> deleteFieldCommand.execute(model)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java deleted file mode 100644 index 214c6c2507b..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ /dev/null @@ -1,173 +0,0 @@ -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.DESC_AMY; -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; -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 org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) and unit tests for EditCommand. - */ -public class EditCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); - - 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_someFieldsSpecifiedUnfilteredList_success() { - Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); - 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(); - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); - EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(lastPerson, editedPerson); - - 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()); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder(personInList).build()); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_invalidPersonIndexUnfilteredList_failure() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - /** - * Edit filtered list where index is larger than size of filtered list, - * but smaller than size of address book - */ - @Test - public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - EditCommand editCommand = new EditCommand(outOfBoundIndex, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY); - - // same values -> returns true - EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor); - assertTrue(standardCommand.equals(commandWithSameValues)); - - // same object -> returns true - assertTrue(standardCommand.equals(standardCommand)); - - // null -> returns false - assertFalse(standardCommand.equals(null)); - - // different types -> returns false - assertFalse(standardCommand.equals(new ClearCommand())); - - // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); - - // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/EditFieldCommandTest.java b/src/test/java/seedu/address/logic/commands/EditFieldCommandTest.java new file mode 100644 index 00000000000..f12b5d64749 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/EditFieldCommandTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.commands; + +//import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.logic.commands.fields.AddFieldCommand.NO_INPUT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +//import seedu.address.logic.commands.fields.AddFieldCommand; +import seedu.address.logic.commands.fields.EditFieldCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +//import seedu.address.model.item.DisplayItem; + + +public class EditFieldCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + /*@Test + public void execute_validField_success() throws CommandException { + AddFieldCommand addFieldCommand = new AddFieldCommand(INDEX_FIRST, "u", "p", "test"); + addFieldCommand.execute(model); + DisplayItem itemStub = model.getFromFilteredPerson(INDEX_FIRST); + EditFieldCommand editFieldCommand = new EditFieldCommand(INDEX_FIRST, "u", "p", "test123"); + editFieldCommand.execute(model); + DisplayItem itemStubEdited = model.getFromFilteredPerson(INDEX_FIRST); + assertFalse(itemStubEdited.getAttribute("p").equals(itemStub)); + }*/ + + @Test + public void invalidFType_throwsCommandException() { + EditFieldCommand editFieldCommand = new EditFieldCommand(INDEX_FIRST, "a", "p", "test"); + assertThrows(CommandException.class, NO_INPUT, () -> editFieldCommand.execute(model)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java deleted file mode 100644 index e0288792e72..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ /dev/null @@ -1,58 +0,0 @@ -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.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -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_HUSBAND; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.testutil.EditPersonDescriptorBuilder; - -public class EditPersonDescriptorTest { - - @Test - public void equals() { - // same values -> returns true - EditPersonDescriptor descriptorWithSameValues = new EditPersonDescriptor(DESC_AMY); - assertTrue(DESC_AMY.equals(descriptorWithSameValues)); - - // same object -> returns true - assertTrue(DESC_AMY.equals(DESC_AMY)); - - // null -> returns false - assertFalse(DESC_AMY.equals(null)); - - // different types -> returns false - assertFalse(DESC_AMY.equals(5)); - - // different values -> returns false - assertFalse(DESC_AMY.equals(DESC_BOB)); - - // different name -> returns false - EditPersonDescriptor editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // different phone -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // different email -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); - assertFalse(DESC_AMY.equals(editedAmy)); - - // 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/FieldPrefixesTest.java b/src/test/java/seedu/address/logic/commands/FieldPrefixesTest.java new file mode 100644 index 00000000000..636b2088d2d --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FieldPrefixesTest.java @@ -0,0 +1,48 @@ +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.parser.CliSyntax.PREFIX_ADDRESS; +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.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.parser.FieldPrefixes; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class FieldPrefixesTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void prefix_test() throws ParseException { + FieldPrefixes fieldPrefixesStub = new FieldPrefixes(); + Prefix prefixStub = new Prefix("/v"); + + fieldPrefixesStub.addPrefix(prefixStub, "Vaccination", model); + assertTrue(fieldPrefixesStub.contains(prefixStub)); + + fieldPrefixesStub.removePrefix(prefixStub); + assertFalse(fieldPrefixesStub.contains(prefixStub)); + } + + @Test + public void matchesDefaultPrefixes_test() { + FieldPrefixes fieldPrefixesStub = new FieldPrefixes(); + + assertTrue(fieldPrefixesStub.matchesDefaultPrefixes(PREFIX_ADDRESS)); + assertTrue(fieldPrefixesStub.matchesDefaultPrefixes(PREFIX_NAME)); + assertTrue(fieldPrefixesStub.matchesDefaultPrefixes(PREFIX_EMAIL)); + assertTrue(fieldPrefixesStub.matchesDefaultPrefixes(PREFIX_PHONE)); + assertTrue(fieldPrefixesStub.matchesDefaultPrefixes(PREFIX_TAG)); + + assertFalse(fieldPrefixesStub.matchesDefaultPrefixes(new Prefix("v/"))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandParserTest.java b/src/test/java/seedu/address/logic/commands/FindCommandParserTest.java new file mode 100644 index 00000000000..7d51a7365df --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindCommandParserTest.java @@ -0,0 +1,37 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Retriever; +import seedu.address.logic.parser.FindCommandParser; +import seedu.address.logic.parser.Parser; +import seedu.address.model.item.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; + +public class FindCommandParserTest { + + private Changer<Predicate<Person>> changerStub = ((model, item) -> model.updateFilteredPersonList(item)); + private Retriever<Integer> getSizeStub = ((model) -> model.getFilteredPersonList().size()); + + private final Parser<FindCommand<Person>> parser = new FindCommandParser<>(changerStub, getSizeStub); + + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + FindCommand<Person> expectedFindCommand = new FindCommand<>( + new NameContainsKeywordsPredicate<>(Arrays.asList("Alice", "Bob")), changerStub, getSizeStub); + + assertParseSuccess(parser, "Alice Bob", expectedFindCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 9b15db28bbb..9d48bab9ed7 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; 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; @@ -12,13 +11,19 @@ import java.util.Arrays; import java.util.Collections; +import java.util.function.Predicate; import org.junit.jupiter.api.Test; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Retriever; +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.person.NameContainsKeywordsPredicate; +import seedu.address.model.item.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. @@ -27,21 +32,24 @@ public class FindCommandTest { private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Changer<Predicate<Person>> changerStub = ((model, item) -> model.updateFilteredPersonList(item)); + private Retriever<Integer> getSizeStub = ((model) -> model.getFilteredPersonList().size()); + @Test public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); + NameContainsKeywordsPredicate<Person> firstPredicate = new NameContainsKeywordsPredicate<>( + Collections.singletonList("first")); + NameContainsKeywordsPredicate<Person> secondPredicate = new NameContainsKeywordsPredicate<>( + Collections.singletonList("second")); - FindCommand findFirstCommand = new FindCommand(firstPredicate); - FindCommand findSecondCommand = new FindCommand(secondPredicate); + FindCommand<Person> findFirstCommand = new FindCommand<>(firstPredicate, changerStub, getSizeStub); + FindCommand<Person> findSecondCommand = new FindCommand<>(secondPredicate, changerStub, getSizeStub); // same object -> returns true assertTrue(findFirstCommand.equals(findFirstCommand)); // same values -> returns true - FindCommand findFirstCommandCopy = new FindCommand(firstPredicate); + FindCommand<Person> findFirstCommandCopy = new FindCommand<>(firstPredicate, changerStub, getSizeStub); assertTrue(findFirstCommand.equals(findFirstCommandCopy)); // different types -> returns false @@ -56,9 +64,9 @@ public void equals() { @Test public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); - FindCommand command = new FindCommand(predicate); + String expectedMessage = String.format(FindCommand.SUCCESS_MESSAGE, 0); + NameContainsKeywordsPredicate<Person> predicate = preparePredicate(" "); + FindCommand<Person> command = new FindCommand<>(predicate, changerStub, getSizeStub); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); assertEquals(Collections.emptyList(), model.getFilteredPersonList()); @@ -66,18 +74,29 @@ public void execute_zeroKeywords_noPersonFound() { @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); + String expectedMessage = String.format(FindCommand.SUCCESS_MESSAGE, 3); + NameContainsKeywordsPredicate<Person> predicate = preparePredicate("Kurz Elle Kunz"); + FindCommand<Person> command = new FindCommand<>(predicate, changerStub, getSizeStub); expectedModel.updateFilteredPersonList(predicate); assertCommandSuccess(command, model, expectedMessage, expectedModel); assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); } + @Test + public void setInput_test() throws CommandException { + Object dataStub = model.getFromFilteredPerson(Index.fromZeroBased(1)); + String input = String.valueOf(dataStub.toString().split("\\s+")); + NameContainsKeywordsPredicate predicateStub = new NameContainsKeywordsPredicate<>(Arrays.asList(input)); + FindCommand<Person> findCommandStub = new FindCommand<>(predicateStub, changerStub, + getSizeStub); + FindCommand<Person> findCommandStubCopy = (FindCommand<Person>) findCommandStub.setInput(dataStub); + + assertTrue(findCommandStub.equals(findCommandStubCopy)); + } /** * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + private NameContainsKeywordsPredicate<Person> preparePredicate(String userInput) { + return new NameContainsKeywordsPredicate<Person>(Arrays.asList(userInput.split("\\s+"))); } } diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java index 435ff1f7275..9674b1f7d7b 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java @@ -2,7 +2,7 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.jupiter.api.BeforeEach; @@ -33,7 +33,7 @@ public void execute_listIsNotFiltered_showsSameList() { @Test public void execute_listIsFiltered_showsEverything() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/MarkTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/MarkTaskCommandTest.java new file mode 100644 index 00000000000..75ded02fd2f --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/MarkTaskCommandTest.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.tasks.MarkTaskCommand.ALREADY_MARKED; +import static seedu.address.logic.commands.tasks.MarkTaskCommand.MESSAGE_USAGE; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.tasks.MarkTaskCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.task.Task; +import seedu.address.testutil.TypicalTasks; + +public class MarkTaskCommandTest { + + private Model model = new ModelManager(TypicalTasks.getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexFilteredList_success() throws CommandException { + Task taskToMark = model.getFromFilteredTasks(INDEX_FIRST); + MarkTaskCommand markTaskCommand = new MarkTaskCommand(INDEX_FIRST); + markTaskCommand.execute(model); + ModelManager expectedModel = new ModelManager(TypicalTasks.getTypicalAddressBook(), new UserPrefs()); + Task taskToMarkExpected = expectedModel.getFromFilteredTasks(INDEX_FIRST); + expectedModel.setTask(taskToMarkExpected, taskToMarkExpected.mark()); + assertTrue(taskToMark.equals(taskToMarkExpected)); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() throws CommandException { + MarkTaskCommand markTaskCommand = new MarkTaskCommand(null); + assertThrows(CommandException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE), () -> + markTaskCommand.execute(model)); + } + + @Test + public void execute_duplicateMarkTask_throwsCommandException() throws CommandException { + MarkTaskCommand markTaskCommand = new MarkTaskCommand(INDEX_FIRST); + Task taskToMark = model.getFromFilteredTasks(INDEX_FIRST); + model.setTask(taskToMark, taskToMark.mark()); + assertThrows(CommandException.class, String.format(ALREADY_MARKED, taskToMark), () -> + markTaskCommand.execute(model)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/persons/AddCommandIntegrationTest.java similarity index 79% rename from src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java rename to src/test/java/seedu/address/logic/commands/persons/AddCommandIntegrationTest.java index cb8714bb055..6600327a810 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ b/src/test/java/seedu/address/logic/commands/persons/AddCommandIntegrationTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.persons; import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; @@ -28,18 +28,17 @@ public void setUp() { @Test public void execute_newPerson_success() { Person validPerson = new PersonBuilder().build(); - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); expectedModel.addPerson(validPerson); - assertCommandSuccess(new AddCommand(validPerson), model, - String.format(AddCommand.MESSAGE_SUCCESS, validPerson), expectedModel); + assertCommandSuccess(new AddPersonCommand(validPerson), model, + String.format(AddPersonCommand.MESSAGE_SUCCESS, validPerson), expectedModel); } @Test public void execute_duplicatePerson_throwsCommandException() { Person personInList = model.getAddressBook().getPersonList().get(0); - assertCommandFailure(new AddCommand(personInList), model, AddCommand.MESSAGE_DUPLICATE_PERSON); + assertCommandFailure(new AddPersonCommand(personInList), model, AddPersonCommand.MESSAGE_DUPLICATE_PERSON); } } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index d9659205b57..f6a78745eaa 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -5,7 +5,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.List; @@ -13,31 +13,31 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; 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.persons.AddPersonCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.group.Group; +import seedu.address.model.item.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.model.task.Task; import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; +/** Test to test the singleton address book parser */ public class AddressBookParserTest { - private final AddressBookParser parser = new AddressBookParser(); + private final AddressBookParser parser = AddressBookParser.get(); @Test public void parseCommand_add() throws Exception { Person person = new PersonBuilder().build(); - AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); - assertEquals(new AddCommand(person), command); + AddPersonCommand command = (AddPersonCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); + assertEquals(new AddPersonCommand(person), command); } @Test @@ -46,22 +46,33 @@ public void parseCommand_clear() throws Exception { assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); } + @SuppressWarnings("unchecked") @Test - public void parseCommand_delete() throws Exception { - DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); + public void parsePersonCommand_delete() throws Exception { + DeleteCommand<Person> command = (DeleteCommand<Person>) parser.parseCommand( + CmdBuilder.P_DELETE + " " + INDEX_FIRST.getOneBased()); + assertEquals(CmdBuilder.makeDelPerson(INDEX_FIRST), command); } + @SuppressWarnings("unchecked") @Test - public void parseCommand_edit() throws Exception { - Person person = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); - EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + public void parseTaskCommand_delete() throws Exception { + DeleteCommand<Task> command = (DeleteCommand<Task>) parser.parseCommand( + CmdBuilder.T_DELETE + " " + + INDEX_FIRST.getOneBased()); + assertEquals(CmdBuilder.makeDelTask(INDEX_FIRST), command); } + @SuppressWarnings("unchecked") + @Test + public void parseTeamCommand_delete() throws Exception { + DeleteCommand<Group> command = (DeleteCommand<Group>) parser.parseCommand( + CmdBuilder.G_DELETE + " " + + INDEX_FIRST.getOneBased()); + assertEquals(CmdBuilder.makeDelGrp(INDEX_FIRST), command); + } + + @Test public void parseCommand_exit() throws Exception { assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); @@ -71,9 +82,9 @@ public void parseCommand_exit() throws Exception { @Test public void parseCommand_find() throws Exception { List<String> keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + FindCommand<?> command = (FindCommand<?>) parser.parseCommand("person find " + + keywords.stream().collect(Collectors.joining(" "))); + assertEquals(CmdBuilder.makeFindPerson(new NameContainsKeywordsPredicate<Person>(keywords)), command); } @Test @@ -90,12 +101,16 @@ public void parseCommand_list() throws Exception { @Test public void parseCommand_unrecognisedInput_throwsParseException() { - assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () - -> parser.parseCommand("")); + assertThrows(ParseException.class, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () -> { + parser.parseCommand(""); + }); } @Test public void parseCommand_unknownCommand_throwsParseException() { assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); } + + } diff --git a/src/test/java/seedu/address/logic/parser/CmdBuilder.java b/src/test/java/seedu/address/logic/parser/CmdBuilder.java new file mode 100644 index 00000000000..e9434c51047 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/CmdBuilder.java @@ -0,0 +1,143 @@ +package seedu.address.logic.parser; + +import java.util.function.Predicate; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.FunctionalInterfaces.Changer; +import seedu.address.commons.util.FunctionalInterfaces.Getter; +import seedu.address.commons.util.FunctionalInterfaces.Retriever; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.tasks.MarkTaskCommand; +import seedu.address.model.group.Group; +import seedu.address.model.item.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.task.Task; + +/** + * Class to build commands that are harder to construct + */ +public class CmdBuilder { + + public static final String P_DELETE = "person delete"; + public static final String T_DELETE = "task delete"; + public static final String G_DELETE = "team delete"; + + private static final Changer<Predicate<Person>> P_CHANGER = ((model, item) -> model.updateFilteredPersonList(item)); + private static final Retriever<Integer> P_SIZE = ((model) -> model.getFilteredPersonList().size()); + + private static final Changer<Predicate<Task>> T_CHANGER = ((model, item) -> model.updateFilteredTaskList(item)); + private static final Retriever<Integer> T_SIZE = ((model) -> model.getFilteredTaskList().size()); + + + private static final Changer<Predicate<Group>> G_CHANGER = ((model, item) -> model.updateFilteredTeamList(item)); + private static final Retriever<Integer> G_SIZE = ((model) -> model.getFilteredTeamList().size()); + + private static final Getter<Person> P_GETTER = (m, i) -> m.getFromFilteredPerson(i); + private static final Changer<Person> P_DELETER = (m, item) -> m.deletePerson(item); + private static final Predicate<Object> P_TESTER = o -> o instanceof Person; + + private static final Getter<Task> T_GETTER = (m, i) -> m.getFromFilteredTasks(i); + private static final Changer<Task> T_DELETER = (m, item) -> m.deleteTask(item); + private static final Predicate<Object> T_TESTER = o -> o instanceof Task; + + private static final Getter<Group> G_GETTER = (m, i) -> m.getFromFilteredTeams(i); + private static final Changer<Group> G_DELETER = (m, item) -> m.deleteTeam(item); + private static final Predicate<Object> G_TESTER = o -> o instanceof Task; + + // ================= delete =============================================== + /** + * Returns a delete command for person + */ + public static final DeleteCommand<Person> makeDelPerson(Index index) { + return new DeleteCommand<>(index, P_GETTER, P_DELETER, P_TESTER); + } + + /** + * Returns a delete command for task + */ + public static final DeleteCommand<Task> makeDelTask(Index index) { + return new DeleteCommand<>(index, T_GETTER, T_DELETER, T_TESTER); + } + + /** + * Returns a delete command for group + */ + public static final DeleteCommand<Group> makeDelGrp(Index index) { + return new DeleteCommand<>(index, G_GETTER, G_DELETER, G_TESTER); + } + + // ===========================delete parser =============================== + + /** + * Returns a Parser for delete command for person + */ + public static final Parser<DeleteCommand<Person>> makeDelParserPerson() { + return DeleteCommand.parser(P_GETTER, P_DELETER, P_TESTER); + } + + /** + * Returns a Parser for delete command for task + */ + public static final Parser<DeleteCommand<Task>> makeDelParserTask() { + return DeleteCommand.parser(T_GETTER, T_DELETER, T_TESTER); + } + + /** + * Returns a Parser for delete command for group + */ + public static final Parser<DeleteCommand<Group>> makeDelParserGroup() { + return DeleteCommand.parser(G_GETTER, G_DELETER, G_TESTER); + } + + // ================= find =============================================== + /** + * Returns a find command for person + */ + public static final FindCommand<Person> makeFindPerson(NameContainsKeywordsPredicate<Person> predicate) { + return new FindCommand<>(predicate, P_CHANGER, P_SIZE); + } + + /** + * Returns a find command for task + */ + public static final FindCommand<Task> makeFindTask(NameContainsKeywordsPredicate<Task> predicate) { + return new FindCommand<>(predicate, T_CHANGER, T_SIZE); + } + + /** + * Returns a find command for group + */ + public static final FindCommand<Group> makeFindGrp(NameContainsKeywordsPredicate<Group> predicate) { + return new FindCommand<>(predicate, G_CHANGER, G_SIZE); + } + + // ===========================find parser =============================== + + /** + * Returns a Parser for find command for person + */ + public static final FindCommandParser<Person> makeFindParserPerson() { + return new FindCommandParser<>(P_CHANGER, P_SIZE); + } + + /** + * Returns a Parser for find command for task + */ + public static final FindCommandParser<Task> makeFindParserTask() { + return new FindCommandParser<>(T_CHANGER, T_SIZE); + } + + /** + * Returns a Parser for find command for group + */ + public static final FindCommandParser<Group> makeFindParserGroup() { + return new FindCommandParser<>(G_CHANGER, G_SIZE); + } + + // =========================== task =============================== + + public static final MarkTaskCommand makeMarkTask(Index index) { + return new MarkTaskCommand(index); + } +} diff --git a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java index 9bf1ccf1cef..08e09fb9a9c 100644 --- a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java +++ b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java @@ -11,11 +11,11 @@ public class CommandParserTestUtil { /** - * Asserts that the parsing of {@code userInput} by {@code parser} is successful and the command created - * equals to {@code expectedCommand}. + * Asserts that the parsing of {@code userInput} by {@code parser} is successful and the command + * created equals to {@code expectedCommand}. */ public static void assertParseSuccess(Parser<? extends Command> parser, String userInput, - Command expectedCommand) { + Command expectedCommand) { try { Command command = parser.parse(userInput); assertEquals(expectedCommand, command); @@ -25,8 +25,8 @@ public static void assertParseSuccess(Parser<? extends Command> parser, String u } /** - * Asserts that the parsing of {@code userInput} by {@code parser} is unsuccessful and the error message - * equals to {@code expectedMessage}. + * Asserts that the parsing of {@code userInput} by {@code parser} is unsuccessful and the error + * message equals to {@code expectedMessage}. */ public static void assertParseFailure(Parser<? extends Command> parser, String userInput, String expectedMessage) { try { diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java index 27eaec84450..708440b4317 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java @@ -3,30 +3,54 @@ 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_FIRST; import org.junit.jupiter.api.Test; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.model.group.Group; +import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** - * As we are only doing white-box testing, our test cases do not cover path variations - * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the - * same path through the DeleteCommand, and therefore we test only one of them. - * The path variation for those two cases occur inside the ParserUtil, and - * therefore should be covered by the ParserUtilTest. + * As we are only doing white-box testing, our test cases do not cover path variations outside of + * the DeleteCommand code. For example, inputs "1" and "1 abc" take the same path through the + * DeleteCommand, and therefore we test only one of them. The path variation for those two cases + * occur inside the ParserUtil, and therefore should be covered by the ParserUtilTest. */ public class DeleteCommandParserTest { - private DeleteCommandParser parser = new DeleteCommandParser(); + private Parser<DeleteCommand<Person>> parserP = CmdBuilder.makeDelParserPerson(); + private Parser<DeleteCommand<Group>> parserG = CmdBuilder.makeDelParserGroup(); + private Parser<DeleteCommand<Task>> parserT = CmdBuilder.makeDelParserTask(); @Test - public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + public void parsePerson_validArgs_returnsDeleteCommand() { + assertParseSuccess(parserP, "1", CmdBuilder.makeDelPerson(INDEX_FIRST)); } @Test - public void parse_invalidArgs_throwsParseException() { - assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + public void parsePerson_invalidArgs_throwsParseException() { + assertParseFailure(parserP, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + + @Test + public void parseTask_validArgs_returnsDeleteCommand() { + assertParseSuccess(parserT, "1", CmdBuilder.makeDelTask(INDEX_FIRST)); + } + + @Test + public void parseTask_invalidArgs_throwsParseException() { + assertParseFailure(parserT, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + + @Test + public void parseGrp_validArgs_returnsDeleteCommand() { + assertParseSuccess(parserG, "1", CmdBuilder.makeDelGrp(INDEX_FIRST)); + } + + @Test + public void parseGrp_invalidArgs_throwsParseException() { + assertParseFailure(parserG, "a", 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 deleted file mode 100644 index 2ff31522486..00000000000 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ /dev/null @@ -1,211 +0,0 @@ -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.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; -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.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -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 MESSAGE_INVALID_FORMAT = - String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); - - private EditCommandParser parser = new EditCommandParser(); - - @Test - public void parse_missingParts_failure() { - // no index specified - assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); - - // no field specified - assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); - - // no index and no field specified - assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); - } - - @Test - public void parse_invalidPreamble_failure() { - // negative index - assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); - - // zero index - assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); - - // invalid arguments being parsed as preamble - assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); - - // invalid prefix being parsed as preamble - assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); - } - - @Test - public void parse_invalidValue_failure() { - assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name - 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 - // 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); - } - - @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; - - 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(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_someFieldsSpecified_success() { - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - 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/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java deleted file mode 100644 index 70f4f0e79c4..00000000000 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ /dev/null @@ -1,34 +0,0 @@ -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 { - - private FindCommandParser parser = new FindCommandParser(); - - @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); - } - -} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..e8f1b44fad6 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.Collections; @@ -14,10 +14,13 @@ import org.junit.jupiter.api.Test; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.attribute.Address; +import seedu.address.model.attribute.Email; +import seedu.address.model.attribute.Field; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.Phone; +import seedu.address.model.group.Group; +import seedu.address.model.group.Path; import seedu.address.model.tag.Tag; public class ParserUtilTest { @@ -26,13 +29,17 @@ public class ParserUtilTest { 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_TEAM_NAME = "invalid team"; + private static final String INVALID_PATH_NAME = "invalid team\\"; private static final String VALID_NAME = "Rachel Walker"; - private static final String VALID_PHONE = "123456"; + private static final String VALID_PHONE = "91234560"; 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_TEAM_NAME = "validTeamName"; + private static final String VALID_PATH = "team/team1/team2"; private static final String WHITESPACE = " \t\r\n"; @@ -43,17 +50,18 @@ public void parseIndex_invalidInput_throwsParseException() { @Test public void parseIndex_outOfRangeInput_throwsParseException() { - assertThrows(ParseException.class, MESSAGE_INVALID_INDEX, () - -> ParserUtil.parseIndex(Long.toString(Integer.MAX_VALUE + 1))); + assertThrows(ParseException.class, MESSAGE_INVALID_INDEX, () -> { + ParserUtil.parseIndex(Long.toString(Integer.MAX_VALUE + 1)); + }); } @Test public void parseIndex_validInput_success() throws Exception { // No whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex("1")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex("1")); // Leading and trailing whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 ")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex(" 1 ")); } @Test @@ -193,4 +201,43 @@ public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { assertEquals(expectedTagSet, actualTagSet); } + + @Test + public void parsePath_validPath_returnsPath() throws Exception { + Path actualPath = ParserUtil.parsePath(VALID_PATH); + Path expectedPath = new Path(VALID_PATH); + + assertEquals(actualPath.toString(), expectedPath.toString()); + } + + @Test + public void parsePath_invalidPathTrailing_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parsePath(INVALID_PATH_NAME)); + } + + @Test + public void parseTeam_validTeamName_returnsGroup() throws Exception { + Group actualGroup = ParserUtil.parseGroup(VALID_TEAM_NAME); + Group expectedGroup = new Group(VALID_TEAM_NAME); + + assertEquals(actualGroup, expectedGroup); + } + + @Test + public void parseGroup_invalidTeamNameHasWhitespace_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseGroup(INVALID_TEAM_NAME)); + } + + @Test + public void parseField_validFieldName_returnsField() throws ParseException { + Field expectedField = ParserUtil.parseField("fieldStub"); + Field actualField = new Field("fieldStub"); + + assertEquals(expectedField, actualField); + } + + @Test + public void parseField_invalidFieldName_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseField(" ")); + } } diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/person/AddPersonCommandParserTest.java similarity index 57% rename from src/test/java/seedu/address/logic/parser/AddCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/person/AddPersonCommandParserTest.java index 5cf487d7ebb..ba134d4f721 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/person/AddPersonCommandParserTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.person; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; @@ -18,10 +18,6 @@ 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; @@ -31,17 +27,18 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.logic.commands.persons.AddPersonCommand; +import seedu.address.logic.parser.persons.AddPersonCommandParser; +import seedu.address.model.attribute.Address; +import seedu.address.model.attribute.Email; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.Phone; 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 { - private AddCommandParser parser = new AddCommandParser(); +public class AddPersonCommandParserTest { + private AddPersonCommandParser parser = new AddPersonCommandParser(); @Test public void parse_allFieldsPresent_success() { @@ -49,29 +46,29 @@ public void parse_allFieldsPresent_success() { // 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)); + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddPersonCommand(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)); + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddPersonCommand(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)); + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddPersonCommand(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)); + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddPersonCommand(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)); + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddPersonCommand(expectedPerson)); // multiple tags - all accepted Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); + .build(); assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddPersonCommand(expectedPersonMultipleTags)); } @Test @@ -79,63 +76,40 @@ 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, - new AddCommand(expectedPerson)); - } - - @Test - public void parse_compulsoryFieldMissing_failure() { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); - - // missing name prefix - assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing phone prefix - assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); - - // all prefixes missing - assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, - expectedMessage); + new AddPersonCommand(expectedPerson)); } @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 + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + 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 + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, 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 + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, 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); + 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); + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS); // two invalid values, only first invalid value reported - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, - Name.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, Name.MESSAGE_CONSTRAINTS); // 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, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonCommand.MESSAGE_USAGE)); } } diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 87782528ecd..0b971b2e419 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -18,8 +18,10 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.task.Task; import seedu.address.testutil.PersonBuilder; public class AddressBookTest { @@ -29,6 +31,8 @@ public class AddressBookTest { @Test public void constructor() { assertEquals(Collections.emptyList(), addressBook.getPersonList()); + assertEquals(Collections.emptyList(), addressBook.getTeamsList()); + assertEquals(Collections.emptyList(), addressBook.getTasksList()); } @Test @@ -84,7 +88,8 @@ public void getPersonList_modifyList_throwsUnsupportedOperationException() { } /** - * A stub ReadOnlyAddressBook whose persons list can violate interface constraints. + * A stub ReadOnlyAddressBook whose persons list can violate interface + * constraints. */ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList<Person> persons = FXCollections.observableArrayList(); @@ -97,6 +102,16 @@ private static class AddressBookStub implements ReadOnlyAddressBook { public ObservableList<Person> getPersonList() { return persons; } + + @Override + public ObservableList<Group> getTeamsList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList<Task> getTasksList() { + throw new AssertionError("This method should not be called."); + } } } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..5a4f31aaacc 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.item.NameContainsKeywordsPredicate; import seedu.address.testutil.AddressBookBuilder; public class ModelManagerTest { diff --git a/src/test/java/seedu/address/model/attribute/AbstractAttributeTest.java b/src/test/java/seedu/address/model/attribute/AbstractAttributeTest.java new file mode 100644 index 00000000000..3cd42851083 --- /dev/null +++ b/src/test/java/seedu/address/model/attribute/AbstractAttributeTest.java @@ -0,0 +1,233 @@ +package seedu.address.model.attribute; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.model.AccessDisplayFlags.DEFAULT; +import static seedu.address.model.AccessDisplayFlags.DEFAULT_STYLE; +import static seedu.address.model.AccessDisplayFlags.DISPLAY_OK; +import static seedu.address.model.AccessDisplayFlags.HIDE_TYPE; +import static seedu.address.model.AccessDisplayFlags.LEFT_JUSTIFY; +import static seedu.address.model.AccessDisplayFlags.MENU_OK; +import static seedu.address.model.AccessDisplayFlags.RIGHT_JUSTIFY; +import static seedu.address.model.attribute.AbstractAttribute.SAVE_KEY_DISPLAY_FORMAT; +import static seedu.address.model.attribute.AbstractAttribute.SAVE_KEY_STYLE_FORMAT; +import static seedu.address.model.attribute.AbstractAttribute.SAVE_KEY_TYPE_NAME; +import static seedu.address.model.attribute.AbstractAttribute.SAVE_KEY_VALUE; +import static seedu.address.testutil.TypicalAttributes.AGE; +import static seedu.address.testutil.TypicalAttributes.POSITION; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +class AbstractAttributeTest { + + @Test + void constructor_validInputs_success() { + Attribute<Integer> age = new AbstractAttribute<Integer>("Age", 5) { }; + assertNotNull(age); + } + + @Test + void constructor_nullTypeName_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + new AbstractAttribute<Integer>(null, 5) { }); + } + + @Test + void isNameMatch_sameCase_returnsTrue() { + assertTrue(POSITION.isNameMatch("Position")); + } + + @Test + void isNameMatch_differentCase_returnsTrue() { + assertTrue(POSITION.isNameMatch("poSItION")); + } + + @Test + void isNameMatch_differentString_returnsFalse() { + assertFalse(POSITION.isNameMatch("Positions")); + } + + @Test + void isAllFlagMatch_containsFlag_returnsTrue() { + assertTrue(AGE.isAllFlagMatch(DEFAULT)); + } + + @Test + void isAllFlagMatch_doesNotContainFlag_returnsFalse() { + assertFalse(AGE.isAllFlagMatch(HIDE_TYPE)); + } + + @Test + void isAnyFlagMatch_containsFlag_returnsTrue() { + assertTrue(AGE.isAnyFlagMatch(DEFAULT)); + } + + @Test + void isAnyFlagMatch_doesNotContainFlag_returnsFalse() { + assertFalse(AGE.isAnyFlagMatch(HIDE_TYPE)); + } + + @Test + void isAnyStyleMatch_containsFlag_returnsTrue() { + assertTrue(AGE.isAnyStyleMatch(LEFT_JUSTIFY)); + } + + @Test + void isAnyStyleMatch_doesNotContainFlag_returnsFalse() { + assertFalse(AGE.isAnyStyleMatch(RIGHT_JUSTIFY)); + } + + @Test + void isAllStyleMatch_containsFlag_returnsTrue() { + assertTrue(AGE.isAllStyleMatch(DEFAULT_STYLE)); + } + + @Test + void isAllStyleMatch_doesNotContainFlag_returnsFalse() { + assertFalse(AGE.isAllStyleMatch(RIGHT_JUSTIFY)); + } + + @Test + void getAttributeContent_success() { + assertEquals(AGE.getAttributeContent(), 20); + } + + @Test + void getAttributeType_success() { + assertEquals(AGE.getAttributeType(), "Age"); + } + + @Test + void isVisibleInMenu_hasMenuOkFlag_returnsTrue() { + AbstractAttribute<String> attr = + new AbstractAttribute<>("Telegram", "bunz", MENU_OK, DEFAULT_STYLE) { }; + assertTrue(attr.isVisibleInMenu()); + } + + @Test + void isVisibleInMenu_doesNotHaveMenuOkFlag_returnsFalse() { + AbstractAttribute<String> attr = + new AbstractAttribute<>("Telegram", "bunz", DISPLAY_OK, DEFAULT_STYLE) { }; + assertFalse(attr.isVisibleInMenu()); + } + + @Test + void isDisplayable_hasDisplayOkFlag_returnsTrue() { + AbstractAttribute<String> attr = + new AbstractAttribute<>("Telegram", "bunz", DISPLAY_OK, DEFAULT_STYLE) { }; + assertTrue(attr.isDisplayable()); + } + + @Test + void isDisplayable_doesNotHaveDisplayOkFlag_returnsFalse() { + AbstractAttribute<String> attr = + new AbstractAttribute<>("Telegram", "bunz", HIDE_TYPE, DEFAULT_STYLE) { }; + assertFalse(attr.isDisplayable()); + } + + @Test + void isSameType_sameTypeSameValue_returnsTrue() { + assertTrue(AGE.isSameType(AGE)); + assertTrue(POSITION.isSameType(POSITION)); + } + + @Test + void isSameType_sameTypeDifferentValue_returnsTrue() { + assertTrue(AGE.isSameType(new AbstractAttribute<>("Age", 100) { })); + assertTrue(POSITION.isSameType(new AbstractAttribute<>("Position", 100) { })); + } + + @Test + void isSameType_differentType_returnsFalse() { + assertFalse(AGE.isSameType(new AbstractAttribute<>("Year", 100) { })); + assertFalse(POSITION.isSameType(new AbstractAttribute<>("Rank", "CEO") { })); + } + + @Test + void equals_instanceOfAbstractAttribute() { + // Same object -> return true + assertTrue(AGE.equals(AGE)); + assertTrue(POSITION.equals(POSITION)); + + // Same type same value -> return true + assertTrue(AGE.equals(new AbstractAttribute<>("Age", 20) { })); + assertTrue(POSITION.equals(new AbstractAttribute<>("Position", "CEO") { })); + + // Same type Different Value -> return false + assertFalse(AGE.equals(new AbstractAttribute<>("Age", 100) { })); + assertFalse(POSITION.equals(new AbstractAttribute<>("Position", "President") { })); + + // Different type same value -> return false + assertFalse(AGE.equals(new AbstractAttribute<>("Year", 20) { })); + assertFalse(POSITION.equals(new AbstractAttribute<>("Rank", "CEO") { })); + } + + @Test + void equals_notAbstractAttributeInstance_returnsFalse() { + assertFalse(AGE.equals(5)); + } + + @Test + void toString_noHideType_success() { + assertEquals(POSITION.toString(), "Position: CEO"); + } + + @Test + void toString_hideType_success() { + Attribute<String> attr = + new AbstractAttribute<String>("String", "value", HIDE_TYPE, DEFAULT_STYLE) { }; + assertEquals(attr.toString(), "value"); + } + + @Test + void toString_nullValue_success() { + Attribute<String> attr = new AbstractAttribute<String>("String", null) { }; + assertEquals(attr.toString(), ""); + } + + @Test + void hashCode_success() { + assertEquals(AGE.hashCode(), 66036); + assertEquals(POSITION.hashCode(), 812513371); + } + + @Test + void toSaveableData_success() { + Map<String, Object> toMatch = new HashMap<>(); + toMatch.put(SAVE_KEY_TYPE_NAME, "Age"); + toMatch.put(SAVE_KEY_VALUE, 20); + toMatch.put(SAVE_KEY_DISPLAY_FORMAT, DEFAULT); + toMatch.put(SAVE_KEY_STYLE_FORMAT, DEFAULT_STYLE); + assertEquals(AGE.toSaveableData(), toMatch); + } + + @Test + void getFormatCss_inMenu_success() { + // Font Size Normal, Left-justify + AbstractAttribute<?> attribute = (AbstractAttribute<?>) POSITION; + assertEquals(attribute.getFormatCss(true), + "-fx-font: normal 10.000000pt 'Segoe UI'; -fx-text-alignment: left;"); + } + + @Test + void getFormatCss_notInMenu_success() { + // Bold, Underline, Dropshadow, Center-Justify, Font size Big + AbstractAttribute<String> attr = + new AbstractAttribute<>("Position", "Director", DEFAULT, 0b01001010101) { }; + assertEquals(attr.getFormatCss(false), + "-fx-font: normal bold 32.000000pt 'Segoe UI'; -fx-underline: true; -fx-effect: dropshadow(three-pass" + + "-box, rgba(0, 0, 0, 0.8), 10, 0, 0, 0); -fx-text-alignment: center;"); + } + + @Test + void testGetFormatCss() { + AbstractAttribute<?> attribute = (AbstractAttribute<?>) POSITION; + assertEquals(attribute.getFormatCss(true), attribute.getFormatCss()); + } +} diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/attribute/AddressTest.java similarity index 96% rename from src/test/java/seedu/address/model/person/AddressTest.java rename to src/test/java/seedu/address/model/attribute/AddressTest.java index dcd3be87b3a..62542075e8b 100644 --- a/src/test/java/seedu/address/model/person/AddressTest.java +++ b/src/test/java/seedu/address/model/attribute/AddressTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.attribute; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/seedu/address/model/attribute/AttributeListTest.java b/src/test/java/seedu/address/model/attribute/AttributeListTest.java new file mode 100644 index 00000000000..8f06029feda --- /dev/null +++ b/src/test/java/seedu/address/model/attribute/AttributeListTest.java @@ -0,0 +1,307 @@ +package seedu.address.model.attribute; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.model.AccessDisplayFlags.DEFAULT; +import static seedu.address.model.AccessDisplayFlags.DEFAULT_STYLE; +import static seedu.address.testutil.TypicalAttributes.AGE; +import static seedu.address.testutil.TypicalAttributes.POSITION; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.attribute.exceptions.AttributeException; + +class AttributeListTest { + + private Attribute<ArrayList<?>> objectAttribute = + new AbstractAttribute<>("Object", new ArrayList<Object>()) { }; + + + @Test + void createAttributeInstance() { + AttributeList attributeList = new AttributeList(); + + // Valid arguments + assertEquals(attributeList.createAttributeInstance("Position", "CEO"), POSITION); + assertEquals(attributeList.createAttributeInstance("Age", 20), AGE); + + List<Object> sampleList = new ArrayList<>(); + assertEquals(attributeList.createAttributeInstance("objectTest", sampleList), + new AbstractAttribute<>("Objecttest", sampleList) { }); + } + + @Test + void createAttributeInstance_withSettingAndStyle_success() { + AttributeList attributeList = new AttributeList(); + assertEquals(attributeList.createAttributeInstance("Position", "CEO", DEFAULT, DEFAULT_STYLE), + POSITION); + } + + @Test + void addAttribute_stringAttribute_success() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(POSITION); + assertNotNull(attributeList.findAttribute("Position")); + } + + @Test + void addAttribute_integerAttribute_success() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(AGE); + assertNotNull(attributeList.findAttribute("Age")); + } + + @Test + void addAttribute_objectAttribute_success() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(objectAttribute); + assertNotNull(attributeList.findAttribute("Object")); + } + + @Test + void addAttribute_stringValue_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Department", "Marketing"); + assertNotNull(attributeList.findAttribute("Department")); + } + + @Test + void addAttribute_integerValue_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Age", 23); + assertNotNull(attributeList.findAttribute("Age")); + } + + @Test + void addAttribute_existingAttribute_throwsAttributeException() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Age", 23); + assertThrows(AttributeException.class, () -> attributeList.addAttribute("Age", 24)); + } + + @Test + void addAttribute_attributeNameOnly_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Birthday"); + assertNotNull(attributeList.findAttribute("Birthday")); + } + + @Test + void addAttribute_withSettingAndStyle_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Age", 20, DEFAULT, DEFAULT_STYLE); + assertEquals(attributeList.findAttribute("Age"), AGE); + } + + @Test + void addAttribute_existingAttributeWithSettingAndStyle_throwsAttributeException() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(AGE); + assertThrows(AttributeException.class, () -> + attributeList.addAttribute("Age", 5, DEFAULT, DEFAULT_STYLE)); + } + + @Test + void findAttribute_nonExistingAttribute_returnsNull() { + AttributeList attributeList = new AttributeList(); + assertNull(attributeList.findAttribute("Specialisation")); + } + + @Test + void findAttribute_existingAttribute_returnsCorrectAttribute() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(POSITION); + assertEquals(attributeList.findAttribute("Position"), POSITION); + } + + @Test + void findAttribute_existingAttributeNonCaseSensitive_returnsCorrectAttribute() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(POSITION); + assertEquals(attributeList.findAttribute("poSItIOn"), POSITION); + } + + @Test + void editAttribute_validInputs_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Position", "Marketing"); + attributeList.editAttribute("Position", "CEO"); + assertEquals(attributeList.findAttribute("Position"), POSITION); + } + + @Test + void editAttribute_nonExistingAttribute_throwsAttributeException() { + AttributeList attributeList = new AttributeList(); + assertThrows(AttributeException.class, () -> + attributeList.editAttribute("Position", "President")); + } + + @Test + void removeAttribute_existingAttribute_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("Age", 45); + attributeList.removeAttribute("Age"); + assertNull(attributeList.findAttribute("Age")); + } + + @Test + void removeAttribute_existingAttributeWithAttributeInstance_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(AGE); + attributeList.removeAttribute(AGE); + assertNull(attributeList.findAttribute("Age")); + } + + @Test + void removeAttribute_nonExistingAttribute_throwsAttributeException() { + AttributeList attributeList = new AttributeList(); + assertThrows(AttributeException.class, () -> attributeList.removeAttribute("anything")); + } + + @Test + void removeField_existingAttribute_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute("test", "string"); + attributeList.removeField("test"); + assertNull(attributeList.findAttribute("test")); + } + + @Test + void removeField_nonExistingAttribute_doesNothing() { + AttributeList attributeList = new AttributeList(); + attributeList.removeField("anything"); + assertEquals(attributeList, new AttributeList()); + } + + @Test + void updateAttribute_existingAttribute_success() throws AttributeException { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(POSITION); + Attribute<String> updatedAttribute = new AbstractAttribute<>("newAttribute", "newField") { }; + attributeList.updateAttribute(POSITION, updatedAttribute); + assertEquals(attributeList.findAttribute("newAttribute"), updatedAttribute); + } + + @Test + void updateAttribute_nonExistingAttribute_throwsAttributeException() { + AttributeList attributeList = new AttributeList(); + assertThrows(AttributeException.class, () -> attributeList.updateAttribute(POSITION, AGE)); + } + + @Test + void retrieveFieldValue_existingAttribute_success() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(POSITION); + assertEquals(attributeList.retrieveFieldValue("Position"), "CEO"); + } + + @Test + void retrieveFieldValue_existingNonStringAttribute_success() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(objectAttribute); + assertEquals(attributeList.retrieveFieldValue("Object"), new ArrayList<Object>()); + } + + @Test + void retrieveFieldValue_nonExistingAttribute_returnsNull() { + AttributeList attributelist = new AttributeList(); + assertNull(attributelist.retrieveFieldValue("AGE")); + } + + @Test + void addAll_validListOfAttributes_success() { + AttributeList attributeList = new AttributeList(); + AttributeList toMatch = new AttributeList(); + List<Attribute<?>> attributes = new ArrayList<>(); + attributes.add(POSITION); + attributes.add(AGE); + attributes.add(objectAttribute); + toMatch.addAttribute(POSITION); + toMatch.addAttribute(AGE); + toMatch.addAttribute(objectAttribute); + attributeList.addAll(attributes); + assertEquals(attributeList, toMatch); + } + + @Test + void addAll_validAttributeList_success() { + AttributeList attributeList = new AttributeList(); + AttributeList toMatch = new AttributeList(); + toMatch.addAttribute(POSITION); + toMatch.addAttribute(AGE); + toMatch.addAttribute(objectAttribute); + attributeList.addAll(toMatch); + assertEquals(attributeList, toMatch); + } + + @Test + void addAll_emptyListOfAttributes_success() { + AttributeList attributeList = new AttributeList(); + List<Attribute<?>> attributes = new ArrayList<>(); + attributeList.addAll(attributes); + assertEquals(attributeList, new AttributeList()); + } + + @Test + void addAll_emptyAttributeList_success() { + AttributeList attributeList = new AttributeList(); + AttributeList toMatch = new AttributeList(); + attributeList.addAll(toMatch); + assertEquals(attributeList, toMatch); + } + + @Test + void toList() { + AttributeList attributeList = new AttributeList(); + List<Attribute<?>> attributes = new ArrayList<>(); + attributes.add(POSITION); + attributes.add(AGE); + attributes.add(objectAttribute); + attributeList.addAttribute(POSITION); + attributeList.addAttribute(AGE); + attributeList.addAttribute(objectAttribute); + assertEquals(attributeList.toList(), attributes); + } + + @Test + void isEmpty_emptyAttributeList_returnsTrue() { + AttributeList attributeList = new AttributeList(); + assertTrue(attributeList.isEmpty()); + } + + @Test + void isEmpty_nonEmptyAttributeList_returnsFalse() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(POSITION); + assertFalse(attributeList.isEmpty()); + } + + @Test + void toString_success() { + AttributeList attributeList = new AttributeList(); + attributeList.addAttribute(AGE); + attributeList.addAttribute(POSITION); + assertEquals(attributeList.toString(), "Age: 20Position: CEO"); + } + + @Test + void equals_sameObject_returnTrue() { + AttributeList attributeList = new AttributeList(); + assertTrue(attributeList.equals(attributeList)); + } + + @Test + void equals_differentInstances_returnFalse() { + AttributeList attributeList = new AttributeList(); + assertFalse(attributeList.equals("Bunny")); + } + +} + diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/attribute/EmailTest.java similarity index 98% rename from src/test/java/seedu/address/model/person/EmailTest.java rename to src/test/java/seedu/address/model/attribute/EmailTest.java index bbcc6c8c98e..417db5eb99d 100644 --- a/src/test/java/seedu/address/model/person/EmailTest.java +++ b/src/test/java/seedu/address/model/attribute/EmailTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.attribute; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; + public class EmailTest { @Test diff --git a/src/test/java/seedu/address/model/attribute/FieldTest.java b/src/test/java/seedu/address/model/attribute/FieldTest.java new file mode 100644 index 00000000000..e66d855ba81 --- /dev/null +++ b/src/test/java/seedu/address/model/attribute/FieldTest.java @@ -0,0 +1,122 @@ +package seedu.address.model.attribute; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class FieldTest { + + private static final Field DEFAULT_FIELD = new Field("Github", "johndoe123"); + private static final Field DEFAULT_EMPTY_FIELD = new Field("Telegram"); + + @Test + void constructor_validInputs_success() { + Field field = new Field("Github", "johndoe123"); + assertEquals(field, DEFAULT_FIELD); + } + + @Test + void constructor_nameOnly_success() { + Field field = new Field("Telegram"); + assertEquals(field, DEFAULT_EMPTY_FIELD); + } + + @Test + void constructor_nullValue_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Field("Github", null)); + } + + @Test + void constructor_nullName_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Field(null, "johndoe123")); + assertThrows(NullPointerException.class, () -> new Field(null)); + } + + @Test + void setValue_validInput_success() { + assertEquals(DEFAULT_FIELD.setValue("abc"), new Field("Github", "abc")); + } + + @Test + void setValue_nullValue_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> DEFAULT_FIELD.setValue(null)); + } + + @Test + void isValueSet_emptyValue_returnsTrue() { + assertTrue(DEFAULT_EMPTY_FIELD.isValueSet()); + } + + @Test + void isValueSet_nonEmptyValue_returnsFalse() { + assertFalse(DEFAULT_FIELD.isValueSet()); + } + + @Test + void isValidField_validName_returnsTrue() { + assertTrue(Field.isValidField("abcd")); + } + + @Test + void isValidField_invalidName_returnsFalse() { + assertFalse(Field.isValidField(" abcd")); + } + + @Test + void isNameMatch_sameName_returnsTrue() { + assertTrue(DEFAULT_FIELD.isNameMatch("Github")); + } + + @Test + void isNameMatch_differentName_returnsFalse() { + assertFalse(DEFAULT_FIELD.isNameMatch("Hubgit")); + } + + @Test + void isNameMatch_sameNameDifferentCase_returnsTrue() { + assertTrue(DEFAULT_FIELD.isNameMatch("giTHUB")); + } + + @Test + void getValue_success() { + assertEquals(DEFAULT_FIELD.getValue(), "johndoe123"); + } + + @Test + void toString_success() { + assertEquals(DEFAULT_FIELD.toString(), "[Github,johndoe123]"); + assertEquals(DEFAULT_EMPTY_FIELD.toString(), "[Telegram,null]"); + } + + @Test + void testEquals() { + // Same object -> returns true + assertTrue(DEFAULT_FIELD.equals(DEFAULT_FIELD)); + + // Same type and value -> returns true + assertTrue(DEFAULT_FIELD.equals(new Field("Github", "johndoe123"))); + + // Case-insensitive same type and value -> returns true + assertTrue(DEFAULT_FIELD.equals(new Field("gitHUB", "johndoe123"))); + + // Same type different value -> returns false + assertFalse(DEFAULT_FIELD.equals(new Field("Github", "johndoe"))); + assertFalse(DEFAULT_FIELD.equals(DEFAULT_EMPTY_FIELD)); + + // Different type same value -> returns false + assertFalse(DEFAULT_FIELD.equals(new Field("Githubb", "johndoe123"))); + + // Same type same value, but case-sensitive value -> returns false + assertFalse(DEFAULT_FIELD.equals(new Field("Github", "Johndoe123"))); + + } + + @Test + void testHashCode() { + assertEquals(DEFAULT_FIELD.hashCode(), 258744195); + assertEquals(DEFAULT_EMPTY_FIELD.hashCode(), -1295823583); + } +} diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/attribute/NameTest.java similarity index 88% rename from src/test/java/seedu/address/model/person/NameTest.java rename to src/test/java/seedu/address/model/attribute/NameTest.java index c9801392874..cd5e553ab75 100644 --- a/src/test/java/seedu/address/model/person/NameTest.java +++ b/src/test/java/seedu/address/model/attribute/NameTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.attribute; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -29,10 +29,11 @@ public void isValidName() { assertFalse(Name.isValidName(" ")); // spaces only assertFalse(Name.isValidName("^")); // only non-alphanumeric characters assertFalse(Name.isValidName("peter*")); // contains non-alphanumeric characters + assertFalse(Name.isValidName("12345")); // must begin with a letter // valid name + assertTrue(Name.isValidName("person-test")); // - and _ allowed assertTrue(Name.isValidName("peter jack")); // alphabets only - assertTrue(Name.isValidName("12345")); // numbers only assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters assertTrue(Name.isValidName("Capital Tan")); // with capital letters assertTrue(Name.isValidName("David Roger Jackson Ray Jr 2nd")); // long names diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/attribute/PhoneTest.java similarity index 55% rename from src/test/java/seedu/address/model/person/PhoneTest.java rename to src/test/java/seedu/address/model/attribute/PhoneTest.java index 8dd52766a5f..ec7c3fd1c05 100644 --- a/src/test/java/seedu/address/model/person/PhoneTest.java +++ b/src/test/java/seedu/address/model/attribute/PhoneTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.attribute; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -30,11 +30,22 @@ public void isValidPhone() { assertFalse(Phone.isValidPhone("91")); // less than 3 numbers assertFalse(Phone.isValidPhone("phone")); // non-numeric assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits - assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits + assertFalse(Phone.isValidPhone("+54553 123123")); // country code max 4 digit + assertFalse(Phone.isValidPhone("+543 12312 123123")); // area code max 4 digit // valid phone numbers - assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers - assertTrue(Phone.isValidPhone("93121534")); + assertTrue(Phone.isValidPhone("65 123123")); // optional + for contry code + assertTrue(Phone.isValidPhone("+543 123123")); // additional spacings + assertTrue(Phone.isValidPhone("111")); // short number + assertTrue(Phone.isValidPhone("+65 83010000")); // with country code + assertTrue(Phone.isValidPhone("+1 133 10230002")); // with country code and area code + assertTrue(Phone.isValidPhone("+1113 3133 102")); // with country code and area code + assertTrue(Phone.isValidPhone("+1113 3133 102123123123123")); // with country code and area code + assertTrue(Phone.isValidPhone("11115869")); assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers + assertTrue(Phone.isValidPhone("9312 1534")); // spaces within digits + assertTrue(Phone.isValidPhone("81115869")); + assertTrue(Phone.isValidPhone("93121534")); + assertTrue(Phone.isValidPhone("63159560")); } } diff --git a/src/test/java/seedu/address/model/item/AbstractDisplayItemTest.java b/src/test/java/seedu/address/model/item/AbstractDisplayItemTest.java new file mode 100644 index 00000000000..6ccef89d187 --- /dev/null +++ b/src/test/java/seedu/address/model/item/AbstractDisplayItemTest.java @@ -0,0 +1,166 @@ +package seedu.address.model.item; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalAttributes.AGE; +import static seedu.address.testutil.TypicalAttributes.POSITION; +import static seedu.address.testutil.TypicalPersons.ALICE; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.PersonBuilder; + +class AbstractDisplayItemTest { + private Set<Tag> tags; + + Person buildDefaultPerson(String name, String... tags) { + return new PersonBuilder().withName(name) + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("94351253") + .withTags(tags).build(); + } + + @Test + void rename_validName_success() { + AbstractDisplayItem alice = buildDefaultPerson("Alice"); + alice.rename("Bob"); + assertEquals(alice.getName().fullName, "Bob"); + } + + @Test + void getTags() { + tags = new HashSet<>(); + tags.add(new Tag("friends")); + assertEquals(buildDefaultPerson("Charlie", "friends").getTags(), tags); + } + + @Test + void addTags() { + tags = new HashSet<>(); + tags.add(new Tag("friends")); + tags.add(new Tag("colleagues")); + AbstractDisplayItem donny = buildDefaultPerson("Donny", "friends"); + donny.addTags("colleagues"); + assertEquals(donny.getTags(), tags); + } + + @Test + void deleteTag_existingTag_success() { + AbstractDisplayItem echo = buildDefaultPerson("Echo", "friends"); + echo.deleteTag("friends"); + assertTrue(echo.getTags().isEmpty()); + } + + @Test + void deleteTag_tagNotFound_doesNothing() { + Set<Tag> prevTags = ALICE.getTags(); + ALICE.deleteTag("doctor"); + assertEquals(ALICE.getTags(), prevTags); + } + + @Test + void getAttribute_attributeNotFound_success() { + AbstractDisplayItem george = buildDefaultPerson("George", "friends"); + assertFalse(george.getAttribute("dummy").isPresent()); + } + + @Test + void getAttribute_attributeFound_success() throws AttributeException { + AbstractDisplayItem dummy = new PersonBuilder(ALICE).withAttribute("Github", "dummy123").build(); + assertEquals(dummy.getAttribute("Github").get().toString(), "Github: dummy123"); + } + + @Test + void editAttribute_existingAttribute_success() throws AttributeException { + AbstractDisplayItem dummy = new PersonBuilder(ALICE).withAttribute("Github", "dummy123").build(); + dummy.editAttribute("Github", "dummy321"); + assertEquals(dummy.getAttribute("Github").get().toString(), "Github: dummy321"); + } + + @Test + void addAttribute_newAttributeInstance_success() { + AbstractDisplayItem dummy = buildDefaultPerson("dummy", "friends"); + dummy.addAttribute(AGE); + assertEquals(dummy.getAttribute("Age").get().toString(), "Age: 20"); + } + + @Test + void addAttribute_stringTypeAndValue_success() throws AttributeException { + AbstractDisplayItem dummy = buildDefaultPerson("dummy", "friends"); + dummy.addAttribute("Position", "CEO"); + assertEquals(dummy.getAttribute("Position").get(), POSITION); + } + + @Test + void addAttribute_existingAttribute_throwsAttributeException() throws AttributeException { + AbstractDisplayItem dummy = buildDefaultPerson("dummy", "friends"); + dummy.addAttribute("Position", "CEO"); + assertThrows(AttributeException.class, () -> dummy.addAttribute("Position", "President")); + } + + @Test + void setTags() { + AbstractDisplayItem dummy = buildDefaultPerson("dummy"); + Set<Tag> tags = new HashSet<Tag>(); + tags.add(new Tag("president")); + tags.add(new Tag("boss")); + dummy.setTags(tags); + assertEquals(dummy.getTags(), tags); + } + + @Test + void canBeChildOf() { + } + + @Test + void getTitle() { + } + + @Test + void getTypeFlag() { + } + + @Test + void deleteAttribute() { + } + + @Test + void testToString() { + } + + @Test + void getName() { + } + + @Test + void stronglyEqual() { + } + + @Test + void weaklyEqual() { + } + + @Test + void isPartOfContext() { + } + + @Test + void getAttributes() { + } + + @Test + void getSavedAttributes() { + } + + @Test + void testEquals() { + } +} diff --git a/src/test/java/seedu/address/model/item/AbstractSingleItemTest.java b/src/test/java/seedu/address/model/item/AbstractSingleItemTest.java new file mode 100644 index 00000000000..ed6ff97f9ba --- /dev/null +++ b/src/test/java/seedu/address/model/item/AbstractSingleItemTest.java @@ -0,0 +1,114 @@ +package seedu.address.model.item; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.model.AccessDisplayFlags.GROUP; +import static seedu.address.model.AccessDisplayFlags.TASK; +import static seedu.address.testutil.TypicalPersons.ALICE; + +import java.util.ArrayList; +import java.util.Set; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.item.exceptions.ItemCannotBeParentException; +import seedu.address.testutil.PersonBuilder; + +class AbstractSingleItemTest { + + private static final AbstractSingleItem SAMPLE_GROUP = new AbstractSingleItem("CS2103", GROUP, GROUP) { + @Override + public UUID getUid() { + return null; + } + }; + + private static final AbstractSingleItem SAMPLE_TASK = new AbstractSingleItem("Testing", TASK, TASK) { + @Override + public UUID getUid() { + return null; + } + }; + + private static AbstractSingleItem otherGroup = new AbstractSingleItem("Group2", GROUP, GROUP) { + @Override + public UUID getUid() { + return null; + } + }; + + @Test + void instanceOfAbstractDisplayItem_returnsTrue() { + assertTrue(SAMPLE_GROUP instanceof AbstractDisplayItem); + } + + @Test + void constructor_validInputs_success() { + AbstractSingleItem item = new AbstractSingleItem("ABC", TASK, TASK) { + @Override + public UUID getUid() { + return null; + } + }; + assertNotNull(item); + } + + @Test + void getTitle_success() { + ArrayList<String> sb = new ArrayList<>(); + sb.add("contactmation.json"); + assertEquals(otherGroup.getTitle(sb, null), "/Group2/contactmation.json"); + } + + @Test + void getFullPath_success() { + assertEquals(otherGroup.getFullPath(), "/Group2"); + } + + @Test + void setParent_inputIsAbstractSingleItem_success() { + SAMPLE_GROUP.setParent(otherGroup); + assertEquals(SAMPLE_GROUP.getParent(), otherGroup); + SAMPLE_GROUP.setParent(null); + } + + @Test + void setParent_inputIsNull_success() { + SAMPLE_TASK.setParent(null); + assertNull(SAMPLE_TASK.getParent()); + } + + @Test + void setParent_inputNotAbstractSingleItem_throwsItemCannotBeParentException() { + assertThrows(ItemCannotBeParentException.class, () -> SAMPLE_GROUP.setParent(new PersonBuilder(ALICE).build())); + } + + @Test + void getParent_success() { + assertNull(SAMPLE_GROUP.getParent()); + } + + @Test + void getParents_success() { + assertEquals(otherGroup.getParents(), Set.of()); + SAMPLE_GROUP.setParent(otherGroup); + assertEquals(SAMPLE_GROUP.getParents(), Set.of(otherGroup)); + SAMPLE_GROUP.setParent(null); + } + + @Test + void removeParent() { + SAMPLE_GROUP.setParent(otherGroup); + SAMPLE_GROUP.removeParent(otherGroup); + assertNull(SAMPLE_GROUP.getParent()); + } + + @Test + void testToString() { + assertEquals(otherGroup.toString(), "/Group2"); + } +} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java index f136664e017..88aab822648 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; +import seedu.address.model.item.NameContainsKeywordsPredicate; import seedu.address.testutil.PersonBuilder; public class NameContainsKeywordsPredicateTest { @@ -18,14 +19,17 @@ public void equals() { List<String> firstPredicateKeywordList = Collections.singletonList("first"); List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); - NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList); + NameContainsKeywordsPredicate<Person> firstPredicate = + new NameContainsKeywordsPredicate<Person>(firstPredicateKeywordList); + NameContainsKeywordsPredicate<Person> secondPredicate = + new NameContainsKeywordsPredicate<Person>(secondPredicateKeywordList); // same object -> returns true assertTrue(firstPredicate.equals(firstPredicate)); // same values -> returns true - NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList); + NameContainsKeywordsPredicate<Person> firstPredicateCopy = + new NameContainsKeywordsPredicate<Person>(firstPredicateKeywordList); assertTrue(firstPredicate.equals(firstPredicateCopy)); // different types -> returns false @@ -41,35 +45,37 @@ public void equals() { @Test public void test_nameContainsKeywords_returnsTrue() { // One keyword - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); + NameContainsKeywordsPredicate<Person> predicate = + new NameContainsKeywordsPredicate<>(Collections.singletonList("Alice")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Multiple keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); + predicate = new NameContainsKeywordsPredicate<>(Arrays.asList("Alice", "Bob")); assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); + 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")); + predicate = new NameContainsKeywordsPredicate<>(Arrays.asList("aLIce", "bOB")); assertTrue(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")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice").withPhone("80000000") + .withEmail("alice@email.com").withAddress("Main Street").build())); } @Test public void test_nameDoesNotContainKeywords_returnsFalse() { - // Zero keywords - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); - // Non-matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); + NameContainsKeywordsPredicate<Person> predicate = new NameContainsKeywordsPredicate<>(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")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") - .withEmail("alice@email.com").withAddress("Main Street").build())); + // Zero keywords + predicate = new NameContainsKeywordsPredicate<>(Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").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..62fe6bb33c1 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/address/model/person/PersonTest.java @@ -40,14 +40,14 @@ public void isSamePerson() { editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); assertFalse(ALICE.isSamePerson(editedAlice)); - // name differs in case, all other attributes same -> returns false + // name differs in case, all other attributes same -> returns true Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); - assertFalse(BOB.isSamePerson(editedBob)); + assertTrue(BOB.isSamePerson(editedBob)); - // name has trailing spaces, all other attributes same -> returns false + // name has trailing spaces, all other attributes same -> returns true String nameWithTrailingSpaces = VALID_NAME_BOB + " "; editedBob = new PersonBuilder(BOB).withName(nameWithTrailingSpaces).build(); - assertFalse(BOB.isSamePerson(editedBob)); + assertTrue(BOB.isSamePerson(editedBob)); } @Test diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java index 1cc5fe9e0fe..efa97f4422f 100644 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java @@ -164,7 +164,8 @@ public void setPersons_listWithDuplicatePersons_throwsDuplicatePersonException() @Test public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () - -> uniquePersonList.asUnmodifiableObservableList().remove(0)); + assertThrows(UnsupportedOperationException.class, () -> { + uniquePersonList.asUnmodifiableObservableList().remove(0); + }); } } diff --git a/src/test/java/seedu/address/storage/JsonAdaptedAbstractAttributeTest.java b/src/test/java/seedu/address/storage/JsonAdaptedAbstractAttributeTest.java new file mode 100644 index 00000000000..6e7668f4287 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedAbstractAttributeTest.java @@ -0,0 +1,52 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.storage.JsonAdaptedAbstractAttribute.CORRUPTED_FIELD_MESSAGE_FORMAT; +import static seedu.address.storage.JsonAdaptedAbstractAttribute.isSaveableDataFormat; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.DisplayItemSampleData.INVALID_SAVE_ATTRIBUTE; +import static seedu.address.testutil.TypicalAttributes.PHONE; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; + +public class JsonAdaptedAbstractAttributeTest { + + private static final Map<String, Object> INVALID_ATTRIBUTE = INVALID_SAVE_ATTRIBUTE; + + private static final Map<String, Object> VALID_ATTRIBUTE = PHONE.toSaveableData(); + + @Test + public void toModelType_validAttributeDetails_returnsAttribute() throws Exception { + + JsonAdaptedAbstractAttribute attribute = new JsonAdaptedAbstractAttribute(VALID_ATTRIBUTE); + assertEquals(PHONE, attribute.toModelType()); + } + + @Test + public void toModelType_invalidAttributeDetails_throwsIllegalValueException() { + JsonAdaptedAbstractAttribute attribute = new JsonAdaptedAbstractAttribute(INVALID_ATTRIBUTE); + assertThrows(IllegalValueException.class, CORRUPTED_FIELD_MESSAGE_FORMAT, attribute::toModelType); + } + + @Test + public void toModelType_nullAttributeDetails_throwsIllegalValueException() { + JsonAdaptedAbstractAttribute attribute = new JsonAdaptedAbstractAttribute((Map<String, Object>) null); + assertThrows(IllegalValueException.class, CORRUPTED_FIELD_MESSAGE_FORMAT, attribute::toModelType); + } + + @Test + public void isSaveableDataFormat_validAttributeDataFormat_returnsTrue() { + assertTrue(isSaveableDataFormat(VALID_ATTRIBUTE)); + } + + @Test + public void isSaveableDataFormat_invalidAttributeDataFormat_returnsFalse() { + assertFalse(isSaveableDataFormat(INVALID_ATTRIBUTE)); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedCustomCommandBuilderTest.java b/src/test/java/seedu/address/storage/JsonAdaptedCustomCommandBuilderTest.java new file mode 100644 index 00000000000..aff514ecbea --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedCustomCommandBuilderTest.java @@ -0,0 +1,58 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalCustomCommandBuilders.DEFAULT_INFO_COMMAND; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CustomCommandBuilder; + +public class JsonAdaptedCustomCommandBuilderTest { + + private static final String INVALID_MACRO_NAME = "@$%!"; + private static final String INVALID_MACRO_REPLACE = ""; + + private static final String VALID_MACRO_NAME = DEFAULT_INFO_COMMAND.getRepr(); + private static final String VALID_MACRO_REPLACE = DEFAULT_INFO_COMMAND.getCommandData(); + + @Test + public void toModelType_validCustomCommandBuilderDetails_returnsCustomCommandBuilder() throws Exception { + JsonAdaptedCustomCommandBuilder customCommandBuilder = new JsonAdaptedCustomCommandBuilder(VALID_MACRO_NAME, + VALID_MACRO_REPLACE); + assertEquals(DEFAULT_INFO_COMMAND, customCommandBuilder.toModelType()); + } + + @Test + public void toModelType_invalidMacroName_throwsIllegalArgumentException() { + JsonAdaptedCustomCommandBuilder customCommandBuilder = new JsonAdaptedCustomCommandBuilder(INVALID_MACRO_NAME, + VALID_MACRO_REPLACE); + String expectedMessage = CustomCommandBuilder.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, customCommandBuilder::toModelType); + } + + @Test + public void toModelType_nullMacroName_throwsIllegalArgumentException() { + JsonAdaptedCustomCommandBuilder customCommandBuilder = new JsonAdaptedCustomCommandBuilder(null, + VALID_MACRO_REPLACE); + String expectedMessage = CustomCommandBuilder.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, customCommandBuilder::toModelType); + } + + @Test + public void toModelType_invalidMacroReplace_throwsIllegalArgumentException() throws Exception { + JsonAdaptedCustomCommandBuilder customCommandBuilder = new JsonAdaptedCustomCommandBuilder(VALID_MACRO_NAME, + INVALID_MACRO_REPLACE); + String expectedMessage = CustomCommandBuilder.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, customCommandBuilder::toModelType); + } + + @Test + public void toModelType_nullMacroReplace_throwsIllegalArgumentException() { + JsonAdaptedCustomCommandBuilder customCommandBuilder = new JsonAdaptedCustomCommandBuilder( + VALID_MACRO_NAME, null); + String expectedMessage = CustomCommandBuilder.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, customCommandBuilder::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedGroupTest.java b/src/test/java/seedu/address/storage/JsonAdaptedGroupTest.java new file mode 100644 index 00000000000..17a570f9390 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedGroupTest.java @@ -0,0 +1,47 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedGroup.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.DisplayItemSampleData.INVALID_NAME_RACHEL; +import static seedu.address.testutil.TypicalGroups.TEAM_ALPHA; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.attribute.Name; + +public class JsonAdaptedGroupTest { + + private static final String VALID_UID = TEAM_ALPHA.getUid().toString(); + private static final List<JsonAdaptedTag> VALID_TAGS = TEAM_ALPHA.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList());; + private static final List<JsonAdaptedAbstractAttribute> VALID_ATTRIBUTES = TEAM_ALPHA + .getAttributes().stream().map(JsonAdaptedAbstractAttribute::new).collect(Collectors.toList()); + + private static final String INVALID_NAME = INVALID_NAME_RACHEL; + + @Test + public void toModelType_validGroupDetails_returnsGroup() throws Exception { + JsonAdaptedGroup group = new JsonAdaptedGroup(TEAM_ALPHA); + assertEquals(TEAM_ALPHA, group.toModelType()); + } + + @Test + public void toModelType_invalidName_throwsIllegalValueException() { + JsonAdaptedGroup group = new JsonAdaptedGroup(INVALID_NAME, VALID_UID, VALID_TAGS, VALID_ATTRIBUTES); + String expectedMessage = Name.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, group::toModelType); + } + + @Test + public void toModelType_nullName_throwsIllegalValueException() { + JsonAdaptedGroup group = new JsonAdaptedGroup(null, VALID_UID, VALID_TAGS, VALID_ATTRIBUTES); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, group::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java index 83b11331cdb..43d518c7b62 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java @@ -3,108 +3,50 @@ 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.DisplayItemSampleData.INVALID_NAME_RACHEL; import static seedu.address.testutil.TypicalPersons.BENSON; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.attribute.Name; public class JsonAdaptedPersonTest { - private static final String INVALID_NAME = "R@chel"; - 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_NAME = INVALID_NAME_RACHEL; - 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 String VALID_UID = BENSON.getUid().toString(); private static final List<JsonAdaptedTag> VALID_TAGS = BENSON.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList()); + private static final List<JsonAdaptedAbstractAttribute> VALID_ATTRIBUTES = BENSON.getAttributes() + .stream() + .map(JsonAdaptedAbstractAttribute::new) + .collect(Collectors.toList()); @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { + JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON); assertEquals(BENSON, person.toModelType()); } @Test public void toModelType_invalidName_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + + JsonAdaptedPerson person = new JsonAdaptedPerson(INVALID_NAME, VALID_UID, VALID_TAGS, + VALID_ATTRIBUTES); 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_UID, VALID_TAGS, + VALID_ATTRIBUTES); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } - - @Test - public void toModelType_invalidPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - 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); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); - 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); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); - 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); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidTags_throwsIllegalValueException() { - List<JsonAdaptedTag> invalidTags = new ArrayList<>(VALID_TAGS); - invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); - assertThrows(IllegalValueException.class, person::toModelType); - } - } diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTagTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTagTest.java new file mode 100644 index 00000000000..59d84799682 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTagTest.java @@ -0,0 +1,29 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.tag.Tag; + +public class JsonAdaptedTagTest { + + private static final String INVALID_TAG = "#TAGGING"; + private static final String VALID_TAG = VALID_TAG_FRIEND; + + @Test + public void toModelType_validTagDetails_returnsTag() throws Exception { + JsonAdaptedTag tag = new JsonAdaptedTag(VALID_TAG); + assertEquals(new Tag(VALID_TAG), tag.toModelType()); + } + + @Test + public void toModelType_invalidTagName_throwsIllegalValueException() { + JsonAdaptedTag tag = new JsonAdaptedTag(INVALID_TAG); + String expectedMessage = Tag.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, tag::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java new file mode 100644 index 00000000000..8d33cea41d2 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTaskTest.java @@ -0,0 +1,81 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedTask.INVALID_FIELD_MESSAGE_FORMAT; +import static seedu.address.storage.JsonAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.DisplayItemSampleData.INVALID_NAME_RACHEL; +import static seedu.address.testutil.TypicalTasks.FIX_BUG; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.attribute.Name; +import seedu.address.model.task.Task; + +public class JsonAdaptedTaskTest { + + private static final String VALID_NAME = FIX_BUG.getName().fullName; + private static final String VALID_DESCRIPTION = FIX_BUG.getDescription().getAttributeContent(); + private static final String VALID_DATETIME = FIX_BUG.getCompletedTime().toString(); + private static final String VALID_UID = FIX_BUG.getUid().toString(); + private static final List<JsonAdaptedTag> VALID_TAGS = FIX_BUG.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList()); + private static final List<JsonAdaptedAbstractAttribute> VALID_ATTRIBUTES = FIX_BUG + .getAttributes().stream().map(JsonAdaptedAbstractAttribute::new).collect(Collectors.toList()); + + private static final String INVALID_NAME = INVALID_NAME_RACHEL; + private static final String INVALID_DATETIME = "34871"; + + @Test + public void toModelType_validTaskDetails_returnsTask() throws Exception { + JsonAdaptedTask task = new JsonAdaptedTask(FIX_BUG); + Task toModelTask = task.toModelType(); + + // Parenting is done when constructing the JsonSerializableAddressBook, and not considered here. + assertEquals(FIX_BUG.getName(), toModelTask.getName()); + assertEquals(FIX_BUG.getAttributes(), toModelTask.getAttributes()); + assertEquals(FIX_BUG.getTags(), toModelTask.getTags()); + assertEquals(FIX_BUG.getDescription(), toModelTask.getDescription()); + assertEquals(FIX_BUG.getCompletedTime(), toModelTask.getCompletedTime()); + } + + @Test + public void toModelType_invalidDateTime_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, INVALID_DATETIME, + VALID_NAME, VALID_UID, VALID_TAGS, VALID_ATTRIBUTES); + String expectedMessage = String.format(INVALID_FIELD_MESSAGE_FORMAT, + LocalDateTime.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDateTime_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, null, + VALID_NAME, VALID_UID, VALID_TAGS, VALID_ATTRIBUTES); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, + LocalDateTime.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidName_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_DATETIME, + INVALID_NAME, VALID_UID, VALID_TAGS, VALID_ATTRIBUTES); + String expectedMessage = Name.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullName_throwsIllegalValueException() { + JsonAdaptedTask task = new JsonAdaptedTask(VALID_DESCRIPTION, VALID_DATETIME, + null, VALID_UID, VALID_TAGS, VALID_ATTRIBUTES); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java index ac3c3af9566..f49b5995378 100644 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java @@ -36,8 +36,8 @@ private java.util.Optional<ReadOnlyAddressBook> readAddressBook(String filePath) private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { return prefsFileInTestDataFolder != null - ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) - : null; + ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) + : null; } @Test @@ -60,6 +60,26 @@ public void readAddressBook_invalidAndValidPersonAddressBook_throwDataConversion assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidPersonAddressBook.json")); } + @Test + public void readAddressBook_invalidGroupAddressBook_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readAddressBook("invalidGroupAddressBook.json")); + } + + @Test + public void readAddressBook_invalidAndValidGroupAddressBook_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidGroupAddressBook.json")); + } + + @Test + public void readAddressBook_invalidTaskAddressBook_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readAddressBook("invalidTaskAddressBook.json")); + } + + @Test + public void readAddressBook_invalidAndValidTaskAddressBook_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidTaskAddressBook.json")); + } + @Test public void readAndSaveAddressBook_allInOrder_success() throws Exception { Path filePath = testFolder.resolve("TempAddressBook.json"); @@ -83,7 +103,6 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception { jsonAddressBookStorage.saveAddressBook(original); // file path not specified readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified assertEquals(original, new AddressBook(readBack)); - } @Test @@ -97,7 +116,7 @@ public void saveAddressBook_nullAddressBook_throwsNullPointerException() { private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { try { new JsonAddressBookStorage(Paths.get(filePath)) - .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); + .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); } catch (IOException ioe) { throw new AssertionError("There should not be an error writing to the file.", ioe); } diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java index 188c9058d20..66660edce97 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java @@ -11,24 +11,56 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.JsonUtil; import seedu.address.model.AddressBook; +import seedu.address.testutil.TypicalGroups; import seedu.address.testutil.TypicalPersons; +import seedu.address.testutil.TypicalTasks; public class JsonSerializableAddressBookTest { private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); + private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); + private static final Path TYPICAL_GROUPS_FILE = TEST_DATA_FOLDER.resolve("typicalGroupsAddressBook.json"); + private static final Path INVALID_GROUP_FILE = TEST_DATA_FOLDER.resolve("invalidGroupAddressBook.json"); + private static final Path DUPLICATE_GROUP_FILE = TEST_DATA_FOLDER.resolve("duplicateGroupAddressBook.json"); + + private static final Path TYPICAL_TASKS_FILE = TEST_DATA_FOLDER.resolve("typicalTasksAddressBook.json"); + private static final Path INVALID_TASK_FILE = TEST_DATA_FOLDER.resolve("invalidTaskAddressBook.json"); + private static final Path DUPLICATE_TASK_FILE = TEST_DATA_FOLDER.resolve("duplicateTaskAddressBook.json"); + @Test public void toModelType_typicalPersonsFile_success() throws Exception { JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, JsonSerializableAddressBook.class).get(); AddressBook addressBookFromFile = dataFromFile.toModelType(); AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); + assertEquals(addressBookFromFile, typicalPersonsAddressBook); } + @Test + public void toModelType_typicalGroupsFile_success() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_GROUPS_FILE, + JsonSerializableAddressBook.class).get(); + AddressBook addressBookFromFile = dataFromFile.toModelType(); + AddressBook typicalGroupsAddressBook = TypicalGroups.getTypicalAddressBook(); + + assertEquals(addressBookFromFile, typicalGroupsAddressBook); + } + + @Test + public void toModelType_typicalTasksFile_success() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_TASKS_FILE, + JsonSerializableAddressBook.class).get(); + AddressBook addressBookFromFile = dataFromFile.toModelType(); + AddressBook typicalTasksAddressBook = TypicalTasks.getTypicalAddressBook(); + + assertEquals(addressBookFromFile, typicalTasksAddressBook); + } + @Test public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, @@ -36,6 +68,20 @@ public void toModelType_invalidPersonFile_throwsIllegalValueException() throws E assertThrows(IllegalValueException.class, dataFromFile::toModelType); } + @Test + public void toModelType_invalidGroupFile_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_GROUP_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_invalidTaskFile_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_TASK_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + @Test public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, @@ -44,4 +90,19 @@ public void toModelType_duplicatePersons_throwsIllegalValueException() throws Ex dataFromFile::toModelType); } + @Test + public void toModelType_duplicateGroups_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_GROUP_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_GROUP, + dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicateTasks_throwsIllegalValueException() throws Exception { + JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_TASK_FILE, + JsonSerializableAddressBook.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_TASK, + dataFromFile::toModelType); + } } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 99a16548970..1678f3e0f59 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.nio.file.Path; @@ -14,6 +13,9 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.UserPrefs; +import seedu.address.testutil.TypicalGroups; +import seedu.address.testutil.TypicalPersons; +import seedu.address.testutil.TypicalTasks; public class StorageManagerTest { @@ -37,8 +39,8 @@ private Path getTempFilePath(String fileName) { public void prefsReadSave() throws Exception { /* * Note: This is an integration test that verifies the StorageManager is properly wired to the - * {@link JsonUserPrefsStorage} class. - * More extensive testing of UserPref saving/reading is done in {@link JsonUserPrefsStorageTest} class. + * {@link JsonUserPrefsStorage} class. More extensive testing of UserPref saving/reading is done in + * {@link JsonUserPrefsStorageTest} class. */ UserPrefs original = new UserPrefs(); original.setGuiSettings(new GuiSettings(300, 600, 4, 6)); @@ -48,13 +50,39 @@ public void prefsReadSave() throws Exception { } @Test - public void addressBookReadSave() throws Exception { + public void addressBookReadSave_personEntries() throws Exception { /* * Note: This is an integration test that verifies the StorageManager is properly wired to the - * {@link JsonAddressBookStorage} class. - * More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class. + * {@link JsonAddressBookStorage} class. More extensive testing of UserPref saving/reading is done + * in {@link JsonAddressBookStorageTest} class. */ - AddressBook original = getTypicalAddressBook(); + AddressBook original = TypicalPersons.getTypicalAddressBook(); + storageManager.saveAddressBook(original); + ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); + assertEquals(original, new AddressBook(retrieved)); + } + + @Test + public void addressBookReadSave_groupEntries() throws Exception { + /* + * Note: This is an integration test that verifies the StorageManager is properly wired to the + * {@link JsonAddressBookStorage} class. More extensive testing of UserPref saving/reading is done + * in {@link JsonAddressBookStorageTest} class. + */ + AddressBook original = TypicalGroups.getTypicalAddressBook(); + storageManager.saveAddressBook(original); + ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); + assertEquals(original, new AddressBook(retrieved)); + } + + @Test + public void addressBookReadSave_taskEntries() throws Exception { + /* + * Note: This is an integration test that verifies the StorageManager is properly wired to the + * {@link JsonAddressBookStorage} class. More extensive testing of UserPref saving/reading is done + * in {@link JsonAddressBookStorageTest} class. + */ + AddressBook original = TypicalTasks.getTypicalAddressBook(); storageManager.saveAddressBook(original); ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); assertEquals(original, new AddressBook(retrieved)); @@ -65,4 +93,9 @@ public void getAddressBookFilePath() { assertNotNull(storageManager.getAddressBookFilePath()); } + @Test + public void getUserPrefsFilePath() { + assertNotNull(storageManager.getUserPrefsFilePath()); + } + } diff --git a/src/test/java/seedu/address/testutil/AbstractDisplayItemBuilder.java b/src/test/java/seedu/address/testutil/AbstractDisplayItemBuilder.java new file mode 100644 index 00000000000..e5899e67d8a --- /dev/null +++ b/src/test/java/seedu/address/testutil/AbstractDisplayItemBuilder.java @@ -0,0 +1,89 @@ +package seedu.address.testutil; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.address.model.attribute.AbstractAttribute; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.item.AbstractDisplayItem; +import seedu.address.model.tag.Tag; +import seedu.address.model.util.SampleDataUtil; + +abstract class AbstractDisplayItemBuilder { + + protected Name name; + protected List<Attribute<?>> attributes = new ArrayList<>(); + protected Set<Tag> tags = new HashSet<>(); + + protected AbstractDisplayItemBuilder(Name name, List<Attribute<?>> attributes, + Set<Tag> tags) { + requireAllNonNull(name, attributes, tags); + this.name = name; + this.attributes.addAll(attributes); + this.tags.addAll(tags); + } + + /** + * Sets the {@code Name} of the {@code AbstractDisplayItem} that is being built. + */ + public abstract AbstractDisplayItemBuilder withName(String name); + + /** + * Parses the {@code tags} into a {@code Set<Tag>} and set it to the {@code AbstractDisplayItem} that we are + * building. + */ + public abstract AbstractDisplayItemBuilder withTags(String... tags); + + /** + * Returns a {@code AbstractDisplayItem} with all specified attributes in builder. + */ + public abstract AbstractDisplayItem build(); + + /** + * Adds a custom attribute to the {@code AbstractDisplayItemBuilder}. + */ + public abstract AbstractDisplayItemBuilder withAttribute(Attribute<?> attribute); + + /** + * Adds a custom attribute. Refer to {@link #withAttribute(Attribute)} for more information. + */ + public abstract <U> AbstractDisplayItemBuilder withAttribute(String name, U data); + + /** + * Sets the name for this {@code AbstractDisplayItem}. + */ + protected void setName(String name) { + this.name = new Name(name); + } + + /** + * Sets the tags for this {@code AbstractDisplayItem}. + */ + protected void setTags(String... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + } + + /** + * Adds a custom attribute. Refer to {@link #withAttribute(Attribute)} for more information. + */ + protected void addAttribute(Attribute<?> attribute) { + List<Attribute<?>> existing = this.attributes.stream() + .filter(a -> a.isNameMatch(attribute.getAttributeType())) + .collect(Collectors.toList()); + this.attributes.removeAll(existing); + this.attributes.add(attribute); + } + + /** + * Adds a custom attribute. Refer to {@link #withAttribute(Attribute)} for more information. + */ + protected <U> void addAttribute(String name, U data) { + this.attributes.add(new AbstractAttribute<U>(name, data) {}); + } +} diff --git a/src/test/java/seedu/address/testutil/AbstractSingleItemBuilder.java b/src/test/java/seedu/address/testutil/AbstractSingleItemBuilder.java new file mode 100644 index 00000000000..8da8de8a628 --- /dev/null +++ b/src/test/java/seedu/address/testutil/AbstractSingleItemBuilder.java @@ -0,0 +1,19 @@ +package seedu.address.testutil; + +import java.util.List; +import java.util.Set; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.tag.Tag; + +abstract class AbstractSingleItemBuilder extends AbstractDisplayItemBuilder { + + protected AbstractSingleItem parent; + + protected AbstractSingleItemBuilder(Name name, List<Attribute<?>> attributes, + Set<Tag> tags) { + super(name, attributes, tags); + } +} diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index d53799fd110..2fa11abf4d0 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -1,7 +1,9 @@ package seedu.address.testutil; import seedu.address.model.AddressBook; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; +import seedu.address.model.task.Task; /** * A utility class to help with building Addressbook objects. @@ -28,6 +30,22 @@ public AddressBookBuilder withPerson(Person person) { return this; } + /** + * Adds a new {@code Group} to the {@code AddressBook} that we are building. + */ + public AddressBookBuilder withGroup(Group group) { + addressBook.addTeam(group); + return this; + } + + /** + * Adds a new {@code Task} to the {@code AddressBook} that we are building. + */ + public AddressBookBuilder withTask(Task task) { + addressBook.addTask(task); + return this; + } + public AddressBook build() { return addressBook; } diff --git a/src/test/java/seedu/address/testutil/DisplayItemSampleData.java b/src/test/java/seedu/address/testutil/DisplayItemSampleData.java new file mode 100644 index 00000000000..dfe480d98ca --- /dev/null +++ b/src/test/java/seedu/address/testutil/DisplayItemSampleData.java @@ -0,0 +1,30 @@ +package seedu.address.testutil; + +import java.util.Map; + +/** + * A provides sample data for DisplayItem objects. + */ +public class DisplayItemSampleData { + + public static final String VALID_NAME_ALICE = "Alice Pauline"; + public static final String VALID_ADDRESS_ALICE = "123, Jurong West Ave 6, #08-111"; + public static final String VALID_EMAIL_ALICE = "alice@example.com"; + public static final String VALID_PHONE_ALICE = "94351253"; + public static final String VALID_TAG_FRIENDS = "friends"; + public static final String VALID_NAME_BENSON = "Benson Meier"; + public static final String VALID_ADDRESS_BENSON = "311, Clementi Ave 2, #02-25"; + public static final String VALID_EMAIL_BENSON = "johnd@example.com"; + public static final String VALID_PHONE_BENSON = "98765432"; + public static final String VALID_TAG_OWES_MONEY = "owesMoney"; + + public static final String INVALID_NAME_RACHEL = "R@chel"; + public static final String INVALID_TAG_HASH = "#tag"; + public static final String INVALID_TAG_SPECIAL = "%)*^"; + public static final Map<String, Object> INVALID_SAVE_ATTRIBUTE = Map.of( + "test", "none", + "content", "nothing", + "display_format", "23", + "style_format", "35"); + +} diff --git a/src/test/java/seedu/address/testutil/DisplayItemStubs.java b/src/test/java/seedu/address/testutil/DisplayItemStubs.java new file mode 100644 index 00000000000..3015a641705 --- /dev/null +++ b/src/test/java/seedu/address/testutil/DisplayItemStubs.java @@ -0,0 +1,300 @@ +package seedu.address.testutil; + +import static seedu.address.model.AccessDisplayFlags.GROUP; +import static seedu.address.model.AccessDisplayFlags.PERSON; +import static seedu.address.model.AccessDisplayFlags.TASK; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.tag.Tag; + +/** + * A utility class to help with stubs DisplayItems for group, person and task + */ +public class DisplayItemStubs { + public static final DisplayItem BASIC_GROUP_STUB = new DisplayItem() { + + @Override + public int getTypeFlag() { + return GROUP; + } + + @Override + public boolean stronglyEqual(DisplayItem o) { + return false; + } + + @Override + public boolean weaklyEqual(DisplayItem o) { + return false; + } + + @Override + public void setParent(DisplayItem o) {} + + @Override + public void rename(String name) {} + + @Override + public void removeParent(DisplayItem o) {} + + @Override + public String getFullPath() { + return "Group_STUB/"; + } + + @Override + public List<Attribute<?>> getAttributes() { + return List.<Attribute<?>>of(); + } + + @Override + public List<Attribute<?>> getSavedAttributes() { + return List.<Attribute<?>>of(); + } + + @Override + public Optional<Attribute<?>> getAttribute(String type) { + return Optional.empty(); + } + + @Override + public Name getName() { + return new Name("Group_STUB"); + } + + @Override + public Set<Tag> getTags() { + return Set.of(); + } + + @Override + public void addTags(String... description) {} + + @Override + public void deleteTag(String description) {} + + @Override + public void setTags(Set<Tag> tags) {} + + @Override + public Set<? extends DisplayItem> getParents() { + return null; + } + + @Override + public void addAttribute(Attribute<?> attribute) {} + + @Override + public void addAttribute(String attributeName, String attributeContent) throws AttributeException {} + + @Override + public void editAttribute(String attributeName, String attributeContent) throws AttributeException {} + + @Override + public void deleteAttribute(String type) throws AttributeException {} + + @Override + public boolean isPartOfContext(DisplayItem o) { + return false; + } + + @Override + public UUID getUid() { + return UUID.nameUUIDFromBytes("GROUPSTUB".getBytes()); + } + }; + + public static final DisplayItem BASIC_PERSON_STUB = new DisplayItem() { + + @Override + public int getTypeFlag() { + return PERSON; + } + + @Override + public boolean stronglyEqual(DisplayItem o) { + return false; + } + + @Override + public boolean weaklyEqual(DisplayItem o) { + return false; + } + + @Override + public void setParent(DisplayItem o) {} + + @Override + public void rename(String name) {} + + @Override + public void removeParent(DisplayItem o) {} + + @Override + public String getFullPath() { + return "PERSON_STUB"; + } + + @Override + public List<Attribute<?>> getAttributes() { + return List.<Attribute<?>>of(); + } + + @Override + public List<Attribute<?>> getSavedAttributes() { + return List.<Attribute<?>>of(); + } + + @Override + public Optional<Attribute<?>> getAttribute(String type) { + return Optional.empty(); + } + + @Override + public Name getName() { + return new Name("PERSON_STUB"); + } + + @Override + public Set<Tag> getTags() { + return Set.of(); + } + + @Override + public void addTags(String... description) {} + + @Override + public void deleteTag(String description) {} + + @Override + public void setTags(Set<Tag> tags) {} + + @Override + public Set<? extends DisplayItem> getParents() { + return null; + } + + @Override + public void addAttribute(Attribute<?> attribute) {} + + @Override + public void addAttribute(String attributeName, String attributeContent) throws AttributeException {} + + @Override + public void editAttribute(String attributeName, String attributeContent) throws AttributeException {} + + @Override + public void deleteAttribute(String type) throws AttributeException {} + + @Override + public boolean isPartOfContext(DisplayItem o) { + return false; + } + + @Override + public UUID getUid() { + return UUID.nameUUIDFromBytes("PERSONSTUB".getBytes()); + } + }; + + public static final DisplayItem BASIC_TASK_STUB = new DisplayItem() { + + @Override + public int getTypeFlag() { + return TASK; + } + + @Override + public boolean stronglyEqual(DisplayItem o) { + return false; + } + + @Override + public boolean weaklyEqual(DisplayItem o) { + return false; + } + + @Override + public void setParent(DisplayItem o) {} + + @Override + public void rename(String name) {} + + @Override + public void removeParent(DisplayItem o) {} + + @Override + public String getFullPath() { + return "TASK_STUB"; + } + + @Override + public List<Attribute<?>> getAttributes() { + return List.<Attribute<?>>of(); + } + + @Override + public List<Attribute<?>> getSavedAttributes() { + return List.<Attribute<?>>of(); + } + + @Override + public Optional<Attribute<?>> getAttribute(String type) { + return Optional.empty(); + } + + @Override + public Name getName() { + return new Name("TASK_STUB"); + } + + @Override + public Set<Tag> getTags() { + return Set.of(); + } + + @Override + public void addTags(String... description) {} + + @Override + public void deleteTag(String description) {} + + @Override + public void setTags(Set<Tag> tags) {} + + @Override + public Set<? extends DisplayItem> getParents() { + return null; + } + + @Override + public void addAttribute(Attribute<?> attribute) {} + + @Override + public void addAttribute(String attributeName, String attributeContent) throws AttributeException {} + + @Override + public void editAttribute(String attributeName, String attributeContent) throws AttributeException {} + + @Override + public void deleteAttribute(String type) throws AttributeException {} + + @Override + public boolean isPartOfContext(DisplayItem o) { + return false; + } + + @Override + public UUID getUid() { + return UUID.nameUUIDFromBytes("TASKSTUB".getBytes()); + } + }; +} diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java deleted file mode 100644 index 4584bd5044e..00000000000 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.testutil; - -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -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. - */ -public class EditPersonDescriptorBuilder { - - private EditPersonDescriptor descriptor; - - public EditPersonDescriptorBuilder() { - descriptor = new EditPersonDescriptor(); - } - - public EditPersonDescriptorBuilder(EditPersonDescriptor descriptor) { - this.descriptor = new EditPersonDescriptor(descriptor); - } - - /** - * Returns an {@code EditPersonDescriptor} with fields containing {@code person}'s details - */ - public EditPersonDescriptorBuilder(Person person) { - descriptor = new EditPersonDescriptor(); - descriptor.setName(person.getName()); - descriptor.setPhone(person.getPhone()); - descriptor.setEmail(person.getEmail()); - descriptor.setAddress(person.getAddress()); - descriptor.setTags(person.getTags()); - } - - /** - * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withName(String name) { - descriptor.setName(new Name(name)); - return this; - } - - /** - * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withPhone(String phone) { - descriptor.setPhone(new Phone(phone)); - return this; - } - - /** - * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withEmail(String email) { - descriptor.setEmail(new Email(email)); - return this; - } - - /** - * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withAddress(String address) { - descriptor.setAddress(new Address(address)); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set<Tag>} and set it to the {@code EditPersonDescriptor} - * that we are building. - */ - public EditPersonDescriptorBuilder withTags(String... tags) { - Set<Tag> tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); - descriptor.setTags(tagSet); - return this; - } - - public EditPersonDescriptor build() { - return descriptor; - } -} diff --git a/src/test/java/seedu/address/testutil/GroupBuilder.java b/src/test/java/seedu/address/testutil/GroupBuilder.java new file mode 100644 index 00000000000..e2d6b28a146 --- /dev/null +++ b/src/test/java/seedu/address/testutil/GroupBuilder.java @@ -0,0 +1,82 @@ +package seedu.address.testutil; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashSet; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Name; +import seedu.address.model.group.Group; +import seedu.address.model.item.AbstractSingleItem; + +/** + * A utility class to help with building Group objects. + */ +public class GroupBuilder extends AbstractSingleItemBuilder { + + public static final String DEFAULT_NAME = "Team_Alpha"; + + /** + * Creates a {@code GroupBuilder} with the default details. + */ + public GroupBuilder() { + super(new Name(DEFAULT_NAME), new ArrayList<>(), new HashSet<>()); + } + + /** + * Initializes the {@code GroupBuilder} with the data of {@code groupToCopy}. + */ + public GroupBuilder(Group groupToCopy) { + super(groupToCopy.getName(), new ArrayList<>(groupToCopy.getAttributes()), + new HashSet<>(groupToCopy.getTags())); + } + + /** + * Sets the parent of the {@code GroupBuilder} that is being built. + */ + public GroupBuilder withParent(AbstractSingleItem item) { + requireNonNull(item); + this.parent = item; + return this; + } + + @Override + public GroupBuilder withName(String name) { + super.setName(name); + return this; + } + + @Override + public GroupBuilder withTags(String... tags) { + super.setTags(tags); + return this; + } + + @Override + public GroupBuilder withAttribute(Attribute<?> attribute) { + super.addAttribute(attribute); + return this; + } + + @Override + public <U> GroupBuilder withAttribute(String name, U data) { + super.addAttribute(name, data); + return this; + } + + @Override + public Group build() { + Group group = new Group(name.fullName); + + if (parent != null) { + group.setParent(parent); + } + group.setTags(tags); + + for (Attribute<?> attr : attributes) { + group.addAttribute(attr); + } + return group; + } +} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index 6be381d39ba..7e2ffab96e5 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -1,96 +1,122 @@ package seedu.address.testutil; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.attribute.Address; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Email; +import seedu.address.model.attribute.Name; +import seedu.address.model.attribute.Phone; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.item.DisplayItem; 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. */ -public class PersonBuilder { +public class PersonBuilder extends AbstractDisplayItemBuilder { public static final String DEFAULT_NAME = "Amy Bee"; - 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"; - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set<Tag> tags; + private final Set<DisplayItem> parents; /** * Creates a {@code PersonBuilder} with the default details. */ public PersonBuilder() { - name = new Name(DEFAULT_NAME); - phone = new Phone(DEFAULT_PHONE); - email = new Email(DEFAULT_EMAIL); - address = new Address(DEFAULT_ADDRESS); - tags = new HashSet<>(); + super(new Name(DEFAULT_NAME), new ArrayList<>(), new HashSet<>()); + parents = new HashSet<>(); } /** * Initializes the PersonBuilder with the data of {@code personToCopy}. */ public PersonBuilder(Person personToCopy) { - name = personToCopy.getName(); - phone = personToCopy.getPhone(); - email = personToCopy.getEmail(); - address = personToCopy.getAddress(); - tags = new HashSet<>(personToCopy.getTags()); + super(personToCopy.getName(), new ArrayList<>(personToCopy.getAttributes()), + new HashSet<>(personToCopy.getTags())); + parents = new HashSet<>(personToCopy.getParents()); } - /** - * Sets the {@code Name} of the {@code Person} that we are building. - */ + @Override public PersonBuilder withName(String name) { - this.name = new Name(name); + super.setName(name); + return this; + } + + @Override + public PersonBuilder withTags(String... tags) { + super.setTags(tags); + return this; + } + + @Override + public PersonBuilder withAttribute(Attribute<?> attribute) { + super.addAttribute(attribute); + return this; + } + + @Override + public <U> PersonBuilder withAttribute(String name, U data) { + super.addAttribute(name, data); return this; } /** - * Parses the {@code tags} into a {@code Set<Tag>} and set it to the {@code Person} that we are building. + * Adds address attribute to person. + * + * @param string address + * @return a {@code PersonBuilder} with the additional address attribute. */ - public PersonBuilder withTags(String ... tags) { - this.tags = SampleDataUtil.getTagSet(tags); + public PersonBuilder withAddress(String string) { + withAttribute(new Address(string)); return this; } /** - * Sets the {@code Address} of the {@code Person} that we are building. + * Adds email attribute to person. + * + * @param string email + * @return a {@code PersonBuilder} with the additional email attribute. */ - public PersonBuilder withAddress(String address) { - this.address = new Address(address); + public PersonBuilder withEmail(String string) { + withAttribute(new Email(string)); return this; } /** - * Sets the {@code Phone} of the {@code Person} that we are building. + * Adds phone attribute to person. + * + * @param string phone number. + * @return a {@code PersonBuilder} with the additional phone attribute. */ - public PersonBuilder withPhone(String phone) { - this.phone = new Phone(phone); + public PersonBuilder withPhone(String string) { + withAttribute(new Phone(string)); return this; } /** - * Sets the {@code Email} of the {@code Person} that we are building. + * Adds a new parent to the {@code PersonBuilder} that is being built. */ - public PersonBuilder withEmail(String email) { - this.email = new Email(email); + public PersonBuilder withParent(AbstractSingleItem item) { + this.parents.add(item); return this; } + @Override public Person build() { - return new Person(name, phone, email, address, tags); - } + Person p = new Person(name.fullName); + p.setTags(tags); + for (Attribute<?> attr : attributes) { + p.addAttribute(attr); + } + + for (DisplayItem parent : parents) { + p.setParent(parent); + } + + return p; + } } diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 90849945183..c3210a8e954 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -1,17 +1,11 @@ package seedu.address.testutil; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -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 seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.persons.AddPersonCommand; +import seedu.address.model.attribute.PrefixedAttribute; import seedu.address.model.person.Person; -import seedu.address.model.tag.Tag; /** * A utility class for Person. @@ -22,7 +16,7 @@ public class PersonUtil { * Returns an add command string for adding the {@code person}. */ public static String getAddCommand(Person person) { - return AddCommand.COMMAND_WORD + " " + getPersonDetails(person); + return AddPersonCommand.getFullCommand(AddPersonCommand.SUBCOMMAND_WORD) + " " + getPersonDetails(person); } /** @@ -31,32 +25,15 @@ public static String getAddCommand(Person person) { public static String getPersonDetails(Person person) { StringBuilder sb = new StringBuilder(); sb.append(PREFIX_NAME + person.getName().fullName + " "); - 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 + " ") - ); + person.getAttributes().stream().forEach( + attr -> { + if (attr instanceof PrefixedAttribute) { + sb.append(((PrefixedAttribute) attr).getPrefix()); + sb.append(attr.getAttributeContent() + " "); + } + }); + person.getTags().stream().forEach(s -> sb.append(PREFIX_TAG + s.tagName + " ")); return sb.toString(); - } - /** - * Returns the part of command string for the given {@code EditPersonDescriptor}'s details. - */ - public static String getEditPersonDescriptorDetails(EditPersonDescriptor descriptor) { - StringBuilder sb = new StringBuilder(); - descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); - 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<Tag> tags = descriptor.getTags().get(); - if (tags.isEmpty()) { - sb.append(PREFIX_TAG); - } else { - tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); - } - } - return sb.toString(); } } diff --git a/src/test/java/seedu/address/testutil/TaskBuilder.java b/src/test/java/seedu/address/testutil/TaskBuilder.java new file mode 100644 index 00000000000..a2ed747b1d8 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TaskBuilder.java @@ -0,0 +1,125 @@ +package seedu.address.testutil; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Description; +import seedu.address.model.attribute.Name; +import seedu.address.model.item.AbstractDisplayItem; +import seedu.address.model.item.AbstractSingleItem; +import seedu.address.model.item.DisplayItem; +import seedu.address.model.person.Person; +import seedu.address.model.task.Task; +import seedu.address.model.util.SampleDataUtil; + +/** + * A utility class to help with building Task objects. + */ +public class TaskBuilder extends AbstractSingleItemBuilder { + + public static final String DEFAULT_NAME = "Make your bed"; + public static final String DEFAULT_DESCRIPTION = "Making your bed is important!"; + + private Description description; + private LocalDateTime completedTime; + private Set<Person> assignedParents; + + /** + * Creates a {@code TaskBuilder} with the default details. + */ + public TaskBuilder() { + super(new Name(DEFAULT_NAME), new ArrayList<>(), new HashSet<>()); + this.description = new Description(DEFAULT_DESCRIPTION); + this.assignedParents = new HashSet<>(); + } + + /** + * Initializes the TaskBuilder with the data of {@code taskToCopy}. + */ + public TaskBuilder(Task taskToCopy) { + super(taskToCopy.getName(), new ArrayList<>(taskToCopy.getAttributes()), + new HashSet<>(taskToCopy.getTags())); + this.description = taskToCopy.getDescription(); + this.completedTime = taskToCopy.getCompletedTime(); + taskToCopy.getParents().forEach(parent -> withParent(parent)); + } + + /** + * Sets the parent of the {@code TaskBuilder} that is being built. + */ + public TaskBuilder withParent(DisplayItem parent) { + requireNonNull(parent); + assert parent instanceof AbstractDisplayItem; + if (parent instanceof AbstractSingleItem) { + this.parent = (AbstractSingleItem) parent; + } + + if (parent instanceof Person) { + assignedParents.add((Person) parent); + } + return this; + } + + @Override + public TaskBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + @Override + public TaskBuilder withTags(String... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + return this; + } + + @Override + public TaskBuilder withAttribute(Attribute<?> attribute) { + super.addAttribute(attribute); + return this; + } + + @Override + public <U> TaskBuilder withAttribute(String name, U data) { + super.addAttribute(name, data); + return this; + } + + /** + * Sets the description of the {@code TaskBuilder} that is being built. + */ + public TaskBuilder withDescription(String description) { + this.description = new Description(description); + return this; + } + + /** + * Sets the completed time of the {@code TaskBuilder} that is being built. + */ + public TaskBuilder withCompletedTime(LocalDateTime completedTime) { + this.completedTime = completedTime; + return this; + } + + @Override + public Task build() { + Task task = new Task(name.fullName, description.getAttributeContent(), completedTime); + for (Person parent : assignedParents) { + task.setParent(parent); + } + + if (parent != null) { + task.setParent(parent); + } + + for (Attribute<?> attr : attributes) { + task.addAttribute(attr); + } + + return task; + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalAttributes.java b/src/test/java/seedu/address/testutil/TypicalAttributes.java new file mode 100644 index 00000000000..6801de37e41 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalAttributes.java @@ -0,0 +1,19 @@ +package seedu.address.testutil; + +import seedu.address.model.attribute.AbstractAttribute; +import seedu.address.model.attribute.Attribute; +import seedu.address.model.attribute.Email; +import seedu.address.model.attribute.Phone; + +/** + * A utility class containing a list of {@code Attribute} objects to be used in + * tests. + */ +public class TypicalAttributes { + + public static final Attribute<?> PHONE = new Phone("95319531"); + public static final Attribute<?> EMAIL = new Email("example@gmail.com"); + public static final Attribute<?> AGE = new AbstractAttribute<>("Age", 20) { }; + public static final Attribute<?> POSITION = new AbstractAttribute<>("Position", "CEO") { }; + +} diff --git a/src/test/java/seedu/address/testutil/TypicalCustomCommandBuilders.java b/src/test/java/seedu/address/testutil/TypicalCustomCommandBuilders.java new file mode 100644 index 00000000000..bc0c200e8dd --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalCustomCommandBuilders.java @@ -0,0 +1,15 @@ +package seedu.address.testutil; + +import seedu.address.logic.commands.CustomCommandBuilder; + +/** + * A utility class containing a list of {@code CustomCommandBuilder} objects to be used in tests. + */ +public class TypicalCustomCommandBuilders { + + public static final CustomCommandBuilder DEFAULT_INFO_COMMAND = new CustomCommandBuilder("addDefaultInfo", + "seq field add Performance 50 ; field add mcLeft 14 ; field add role Employee"); + public static final CustomCommandBuilder CLOSE_CONTACT_COMMAND = new CustomCommandBuilder("setCloseContact", + "if [[contains unvaccinated]] ;; [[ field add closeContact 14 days]] ;; " + + "[[ field add closeContact 7 days]]"); +} diff --git a/src/test/java/seedu/address/testutil/TypicalGroups.java b/src/test/java/seedu/address/testutil/TypicalGroups.java new file mode 100644 index 00000000000..3695fe971fd --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalGroups.java @@ -0,0 +1,43 @@ +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.group.Group; + +/** + * A utility class containing a list of {@code Group} objects to be used in + * tests. + */ +public class TypicalGroups { + + public static final Group TEAM_ALPHA = new GroupBuilder().withName("Team_Alpha") + .withAttribute("Work", "Internship").withAttribute("Leave_number", "3") + .build(); + public static final Group TEAM_BETA = new GroupBuilder().withName("Team_Beta").build(); + public static final Group TEAM_OMEGA = new GroupBuilder().withName("Team_Omega") + .withAttribute("Urgent", "Do paperwork").withParent(TEAM_ALPHA).build(); + public static final Group TEAM_GAMMA = new GroupBuilder().withName("Team_Gamma").build(); + public static final Group TEAM_A = new GroupBuilder().withName("Team_A") + .withAttribute("Meetup", "Saturday").withParent(TEAM_ALPHA).build(); + public static final Group TEAM_B = new GroupBuilder().withName("Team_B").withParent(TEAM_A).build(); + public static final Group AUTOMATION = new GroupBuilder().withName("Automation").build(); + + /** + * Returns an {@code AddressBook} with all the typical groups. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (Group group : getTypicalGroups()) { + ab.addTeam(group); + } + return ab; + } + + public static List<Group> getTypicalGroups() { + return new ArrayList<>(Arrays.asList( + TEAM_OMEGA, TEAM_A, TEAM_ALPHA, TEAM_B, TEAM_BETA, AUTOMATION, TEAM_GAMMA)); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e613937657..134e60a4c20 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -6,7 +6,7 @@ * A utility class containing a list of {@code Index} objects to be used in tests. */ 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 = Index.fromOneBased(1); + public static final Index INDEX_SECOND = Index.fromOneBased(2); + public static final Index INDEX_THIRD = 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..bcca1164b04 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -10,6 +10,16 @@ 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.testutil.DisplayItemSampleData.VALID_ADDRESS_ALICE; +import static seedu.address.testutil.DisplayItemSampleData.VALID_ADDRESS_BENSON; +import static seedu.address.testutil.DisplayItemSampleData.VALID_EMAIL_ALICE; +import static seedu.address.testutil.DisplayItemSampleData.VALID_EMAIL_BENSON; +import static seedu.address.testutil.DisplayItemSampleData.VALID_NAME_ALICE; +import static seedu.address.testutil.DisplayItemSampleData.VALID_NAME_BENSON; +import static seedu.address.testutil.DisplayItemSampleData.VALID_PHONE_ALICE; +import static seedu.address.testutil.DisplayItemSampleData.VALID_PHONE_BENSON; +import static seedu.address.testutil.DisplayItemSampleData.VALID_TAG_FRIENDS; +import static seedu.address.testutil.DisplayItemSampleData.VALID_TAG_OWES_MONEY; import java.util.ArrayList; import java.util.Arrays; @@ -19,45 +29,48 @@ import seedu.address.model.person.Person; /** - * A utility class containing a list of {@code Person} objects to be used in tests. + * A utility class containing a list of {@code Person} objects to be used in + * tests. */ 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") - .withPhone("94351253") - .withTags("friends").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 ALICE = new PersonBuilder().withName(VALID_NAME_ALICE) + .withAddress(VALID_ADDRESS_ALICE).withEmail(VALID_EMAIL_ALICE) + .withPhone(VALID_PHONE_ALICE) + .withTags(VALID_TAG_FRIENDS).build(); + public static final Person BENSON = new PersonBuilder().withName(VALID_NAME_BENSON) + .withAddress(VALID_ADDRESS_BENSON) + .withEmail(VALID_EMAIL_BENSON).withPhone(VALID_PHONE_BENSON) + .withTags(VALID_TAG_OWES_MONEY, VALID_TAG_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") + public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("94822240") .withEmail("werner@example.com").withAddress("michegan ave").build(); - public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") + public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("94824270") .withEmail("lydia@example.com").withAddress("little tokyo").build(); - public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") + public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("94824420") .withEmail("anna@example.com").withAddress("4th street").build(); // Manually added - public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") + public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("84824240") .withEmail("stefan@example.com").withAddress("little india").build(); - public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") + public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("84821319") .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) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) .build(); public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER - private TypicalPersons() {} // prevents instantiation + private TypicalPersons() { + } // prevents instantiation /** * Returns an {@code AddressBook} with all the typical persons. @@ -73,4 +86,5 @@ public static AddressBook getTypicalAddressBook() { public static List<Person> getTypicalPersons() { return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); } + } diff --git a/src/test/java/seedu/address/testutil/TypicalTasks.java b/src/test/java/seedu/address/testutil/TypicalTasks.java new file mode 100644 index 00000000000..be16e56451f --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalTasks.java @@ -0,0 +1,45 @@ +package seedu.address.testutil; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.group.Group; +import seedu.address.model.task.Task; + +/** + * A utility class containing a list of {@code Task} objects to be used in + * tests. + */ +public class TypicalTasks { + + private static final Group WRAPPER_GROUP = new GroupBuilder().withName("t1").build(); + + public static final Task FIX_BUG = new TaskBuilder().withName("Fix bug") + .withDescription("Bug related to the Alpha command") + .withCompletedTime(LocalDateTime.of(2022, 11, 4, 22, 7, 53, 947688600)) + .withAttribute("Logic", "Check null value in command").withParent(WRAPPER_GROUP).build(); + public static final Task DO_PAPERWORK = new TaskBuilder().withName("Do paperwork") + .withDescription("Administrative tasks by management").withParent(WRAPPER_GROUP).build(); + public static final Task BUY_PRINTER = new TaskBuilder().withName("Buy printer") + .withDescription("Buy printer from XYZ street") + .withAttribute("Priority", "High").withParent(WRAPPER_GROUP).build(); + + /** + * Returns an {@code AddressBook} with all the typical tasks. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + ab.addTeam(WRAPPER_GROUP); + for (Task task : getTypicalTasks()) { + ab.addTask(task); + } + return ab; + } + + public static List<Task> getTypicalTasks() { + return new ArrayList<>(Arrays.asList(FIX_BUG, DO_PAPERWORK, BUY_PRINTER)); + } +} diff --git a/unused/AbstractContainerItem b/unused/AbstractContainerItem new file mode 100644 index 00000000000..145fed15d9a --- /dev/null +++ b/unused/AbstractContainerItem @@ -0,0 +1,118 @@ +package seedu.address.model.item; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import seedu.address.model.item.exceptions.ItemCannotBeParentException; +import seedu.address.model.attribute.Name; +import seedu.address.model.tag.UniqueID; + +/** + * Abstract class to represent an item that can contain other items. + */ +public abstract class AbstractContainerItem extends DisplayItemList<DisplayItem> implements DisplayItem { + + protected AbstractContainerItem parent = null; + protected Name name; + protected String fullPath = null; + protected UniqueID uuid; + + protected AbstractContainerItem(Name name, AbstractContainerItem parent) { + this.name = name; + this.parent = parent; + } + + @Override + public void add(DisplayItem toAdd) { + requireNonNull(toAdd); + toAdd.setParent(this); + super.add(toAdd); + } + + @Override + public <T extends DisplayItem> void setItems(List<T> items) { + requireAllNonNull(items); + // guards + for (int i = 0; i < items.size(); i++) { + if (items.get(i).equals(this)) { + throw new ItemCannotBeParentException(this); + } + } + + for (int i = 0; i < items.size(); i++) { + items.get(i).setParent(this); + } + + super.setItems(items); + } + + @Override + public <T extends DisplayItem> void setItems(DisplayItemList<T> replacement) { + requireAllNonNull(replacement); + setItems(replacement.internalList); + } + + private String getTitle(List<String> sb, AbstractContainerItem o) { + sb.add(toString()); + if (parent == null || parent.equals(o)) { + Collections.reverse(sb); + return "/" + String.join("/", sb); + } + return parent.getTitle(sb, o); + } + + protected void regenerateFullPathName() { + fullPath = getTitle(new ArrayList<String>(), null); + } + + public String getFullPathName() { + if (fullPath != null) { + regenerateFullPathName(); + } + return getTitle(new ArrayList<String>(), null); + } + + public String getRelativePathName(AbstractContainerItem o) { + return getTitle(new ArrayList<String>(), o); + } + + @Override + public void setParent(DisplayItem o) { + if (o == null) { + parent = null; + return; + } + if (!(o instanceof AbstractContainerItem)) { + throw new ItemCannotBeParentException(o); + } + parent = (AbstractContainerItem) o; + regenerateFullPathName(); + } + + public AbstractContainerItem getParent() { + return parent; + } + + @Override + public String toString() { + return name.toString(); + } + + @Override + public boolean isPartOfContext(DisplayItem o) { + AbstractContainerItem temp = parent; + while (parent != null) { + if (parent.equals(o)) { + return true; + } + + temp = temp.getParent(); + } + + return o == null; + } +} diff --git a/unused/AddAttributeCommandParser.java b/unused/AddAttributeCommandParser.java new file mode 100644 index 00000000000..66274eeed1c --- /dev/null +++ b/unused/AddAttributeCommandParser.java @@ -0,0 +1,72 @@ +// @@author jasonchristopher21 +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.attributes.AddAttributeCommand; +import seedu.address.logic.commands.attributes.AddGroupAttributeCommand; +import seedu.address.logic.commands.attributes.AddPersonAttributeCommand; +import seedu.address.logic.commands.attributes.AddTaskAttributeCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new {@code AddAttributeCommand} object + */ +public class AddAttributeCommandParser implements Parser<AddAttributeCommand> { + + /** + * Regex for the basic format of AddAttributeCommand, should contain a type + * (user/group/task), its respective + * ID, the name of the attribute and the content of the attribute. + */ + private static final Pattern ADD_ATTRIBUTE_COMMAND_FORMAT = Pattern + .compile("(?<type>[ugt])/(?<id>\\w+)\\s+(?<attributeName>\\w+)\\s+(?<attributeContent>.+)"); + + /** + * Parses the given {@code String} of arguments in the context of the + * {@code AddAttributeCommand} + * and returns a {@code AddFieldCommand} object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddAttributeCommand parse(String args) throws ParseException { + + final Matcher matcher = ADD_ATTRIBUTE_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddAttributeCommand.MESSAGE_USAGE)); + } + + Index index = null; + + try { + index = ParserUtil.parseIndex(matcher.group("id").trim()); // TODO: change this to UUID implementation + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddAttributeCommand.MESSAGE_USAGE), pe); + } + + String type = matcher.group("type"); + + String attributeName = matcher.group("attributeName").trim(); + String attributeContent = matcher.group("attributeContent").trim(); + + if (type.equals("u")) { + return new AddPersonAttributeCommand(index, attributeName, attributeContent); + } else if (type.equals("g")) { + return new AddGroupAttributeCommand(index, attributeName, attributeContent); + } else if (type.equals("t")) { + return new AddTaskAttributeCommand(index, attributeName, attributeContent); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddAttributeCommand.MESSAGE_USAGE)); + } + + } + +} diff --git a/unused/AddGroupCommand.java b/unused/AddGroupCommand.java new file mode 100644 index 00000000000..feafab89997 --- /dev/null +++ b/unused/AddGroupCommand.java @@ -0,0 +1,54 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.item.AbstractContainerItem; + +/** + * Add a group to the address book. + */ +public class AddGroupCommand extends Command { + + public static final String COMMAND_WORD = "mkgroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Add a new group in the current " + + "group scope if the group name does not currently exist. The group name should only " + + "be alphanumeric and have hyphens and/or underscores only.\n" + + "Parameters: group_name/group_within_group_name\n" + + "Example: " + COMMAND_WORD + " group_1/group_a"; + + public static final String MESSAGE_SUCCESS = "New group added: %1$s"; + public static final String MESSAGE_DUPLICATE_GROUP = "This group already exists in the address book"; + + private final Group toAdd; + + /** + * Creates a AddGroupCommand to add the specified {@Code group} + * + * @param group that is being added to the AddressBook. + */ + public AddGroupCommand(Group group) { + requireNonNull(group); + toAdd = group; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasTeam(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_GROUP); + } + + AbstractContainerItem currentContext = model.getContextContainer(); + + if (currentContext != null) { + toAdd.setParent(currentContext); + } + model.addTeam(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } +} diff --git a/unused/AddGroupCommandParser.java b/unused/AddGroupCommandParser.java new file mode 100644 index 00000000000..73c933fbacb --- /dev/null +++ b/unused/AddGroupCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.AddGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.Group; + +/** + * Parses input arguments and creates a new AddGroupCommand object + */ +public class AddGroupCommandParser implements Parser<AddGroupCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the + * AddGroupCommand. + * + * @param args refer to the subsequent arguments after the initial command word. + * @return an AddGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGroupCommand parse(String args) throws ParseException { + try { + requireNonNull(args); + String trimmedArgs = args.trim(); + Group newGroup = ParserUtil.parseGroup(trimmedArgs); + return new AddGroupCommand(newGroup); + } catch (ParseException pe) { + throw new ParseException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE), + pe); + } + } +} diff --git a/unused/AddGroupCommandParserTest.java b/unused/AddGroupCommandParserTest.java new file mode 100644 index 00000000000..d0b4e673ca7 --- /dev/null +++ b/unused/AddGroupCommandParserTest.java @@ -0,0 +1,19 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_GROUP_NAME; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.AddGroupCommand; + +public class AddGroupCommandParserTest { + private AddGroupCommandParser parser = new AddGroupCommandParser(); + + @Test + public void parse_groupFormat_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE); + assertParseFailure(parser, INVALID_GROUP_NAME, expectedMessage); + } +} diff --git a/unused/AddGroupCommandTest.java b/unused/AddGroupCommandTest.java new file mode 100644 index 00000000000..380a0b67fe2 --- /dev/null +++ b/unused/AddGroupCommandTest.java @@ -0,0 +1,13 @@ +package seedu.address.logic.commands; + +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class AddGroupCommandTest { + + @Test + public void constructor_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddGroupCommand(null)); + } +} diff --git a/unused/ChangeGroupCommand.java b/unused/ChangeGroupCommand.java new file mode 100644 index 00000000000..126e86bc39c --- /dev/null +++ b/unused/ChangeGroupCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Path; + +/** + * Change the scope of the group to a different group. + */ +public class ChangeGroupCommand extends Command { + + public static final String COMMAND_WORD = "cg"; // "cg" stands for change group + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Change to a currently " + + "existing group. Group names should be alphanumeric and may contain hyphens and " + + "underscores.\n" + + "Parameters: group_name/group_within_group_name\n" + + "Example: " + COMMAND_WORD + " group_1/group_a"; + + public static final String MESSAGE_SUCCESS = "Changed group to: %1$s"; + + public static final String MESSAGE_NO_GROUP_FOUND = "This group scope does not currently " + + "exist in the address book"; + + public static final String MESSAGE_IN_CURRENT_GROUP = "You are currently in the group " + + "scope specified."; + + private final Path path; + + /** + * Creates a ChangeGroupCommand to change scope of group to the specified + * {@Code Group} + */ + public ChangeGroupCommand(Path path) { + this.path = path; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String newPath = path.getPath(); + // if (model.isInSamePath(newPath)) { + // throw new CommandException(MESSAGE_IN_CURRENT_GROUP); + // } + + // if (!model.canChangeContext(newPath)) { + // throw new CommandException(MESSAGE_NO_GROUP_FOUND); + // } + + // model.changeContext(newPath); + return new CommandResult(String.format(MESSAGE_SUCCESS, newPath)); + } +} diff --git a/unused/DeleteCommand.java b/unused/DeleteCommand.java new file mode 100644 index 00000000000..b00c2c71ab3 --- /dev/null +++ b/unused/DeleteCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands.persons; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class DeleteCommand extends PersonCommand { + + public static final String SUBCOMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + + ": Deletes the person identified by the index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + getFullCommand(SUBCOMMAND_WORD) + " 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + + final Index targetIndex; + + public DeleteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (person == null && targetIndex == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + if (person == null) { + person = model.getFromFilteredPerson(targetIndex); + } + + model.deletePerson(person); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, person)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DeleteCommand)) { + return false; + } + if (this == other) { + return true; + } + DeleteCommand c = (DeleteCommand) other; + if (targetIndex == null) { + if (c.targetIndex != null) { + return false; + } + } else if (!targetIndex.equals(c.targetIndex)) { + return false; + } + + if (person == null) { + if (c.person != null) { + return false; + } + } else if (!person.equals(c.person)) { + return false; + } + + return true; + } +} diff --git a/unused/DeleteCommandParser.java b/unused/DeleteCommandParser.java new file mode 100644 index 00000000000..957a91f05c0 --- /dev/null +++ b/unused/DeleteCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser.persons; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.persons.DeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteCommandParser implements Parser<DeleteCommand> { + + private static final String NUMBER = "\\s*[\\-+]?[0-9]+\\s*"; + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand and returns a + * DeleteCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCommand parse(String args) throws ParseException { + if (args == null || args.trim().equals("")) { + return new DeleteCommand(null); + } else if (!args.matches(NUMBER)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(args); + return new DeleteCommand(index); + } + +} diff --git a/unused/DeleteGroupCommand.java b/unused/DeleteGroupCommand.java new file mode 100644 index 00000000000..be679ff6d60 --- /dev/null +++ b/unused/DeleteGroupCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands; + +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.group.Group; + +import java.util.List; + +import static java.util.Objects.requireNonNull; + +public class DeleteGroupCommand extends Command { + + public static final String COMMAND_WORD = "rmgroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the group identified by the index number used in the displayed group list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Deleted Group: %1$s"; + + private final Index targetIndex; + + public DeleteGroupCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Group> lastShownList = model.getFilteredTeamList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Group groupToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTeam(groupToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, groupToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCommand // instanceof handles nulls + && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + } +} diff --git a/unused/DeleteTaskCommand.java b/unused/DeleteTaskCommand.java new file mode 100644 index 00000000000..85c9b4e8da5 --- /dev/null +++ b/unused/DeleteTaskCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Deletes a task from Contactmation. + * + * @author connlim + * @author mohamedsaf1 + */ +public class DeleteTaskCommand extends TaskCommand { + public static final String SUBCOMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + ": Delete the selected task\n" + + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1\n"; + + public static final String DELETE_SUCCESS = " task %s is deleted%n"; + + private final Index targetIndex; + + public DeleteTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (targetIndex == null && task == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + if (task == null) { + task = model.getFromFilteredTasks(targetIndex); + } + + model.deleteTask(task); + return new CommandResult(String.format(DELETE_SUCCESS, task)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DeleteTaskCommand)) { + return false; + } + if (this == other) { + return true; + } + DeleteTaskCommand c = (DeleteTaskCommand) other; + if (targetIndex == null) { + if (c.targetIndex != null) { + return false; + } + } else if (!targetIndex.equals(c.targetIndex)) { + return false; + } + + if (task == null) { + if (c.task != null) { + return false; + } + } else if (!task.equals(c.task)) { + return false; + } + + return true; + } + +} diff --git a/unused/DeleteTaskCommandParser.java b/unused/DeleteTaskCommandParser.java new file mode 100644 index 00000000000..568e4836dbe --- /dev/null +++ b/unused/DeleteTaskCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.tasks; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.tasks.DeleteTaskCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author mohamedsaf1 + +/** + * Parses input arguments and creates a new RmTaskCommand object + */ +public class DeleteTaskCommandParser implements Parser<DeleteTaskCommand> { + + private static final String NUMBER = "\\s*[\\-+]?[0-9]+\\s*"; + + @Override + public DeleteTaskCommand parse(String args) throws ParseException { + if (args.trim().equals("")) { + return new DeleteTaskCommand(null); + } else if (!args.matches(NUMBER)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(args); + return new DeleteTaskCommand(index); + } +} diff --git a/unused/DeleteTeamCommand.java b/unused/DeleteTeamCommand.java new file mode 100644 index 00000000000..c068b0b2fd5 --- /dev/null +++ b/unused/DeleteTeamCommand.java @@ -0,0 +1,88 @@ +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +// @@author mohamedsaf1 +/** + * Deletes a team from Contactmation + */ +public class DeleteTeamCommand extends TeamCommand { + public static final String SUBCOMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + " " + SUBCOMMAND_WORD + + ": Delete the team with the specified index\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " " + SUBCOMMAND_WORD + " 1\n"; + + public static final String SWITCH_SUCCESS = " Deleted %s%n"; + + private Group toDelete = null; + + private final Index targetIndex; + + public DeleteTeamCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (toDelete == null && targetIndex == null) { + throw new CommandException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + if (toDelete == null) { + toDelete = model.getFromFilteredTeams(targetIndex); + } + + model.deleteTeam(toDelete); + return new CommandResult(String.format(SWITCH_SUCCESS, toDelete)); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof Group)) { + toDelete = null; + return this; + } + this.toDelete = (Group) additionalData; + return this; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DeleteTeamCommand)) { + return false; + } + if (this == other) { + return true; + } + DeleteTeamCommand c = (DeleteTeamCommand) other; + if (targetIndex == null) { + if (c.targetIndex != null) { + return false; + } + } else if (!targetIndex.equals(c.targetIndex)) { + return false; + } + + if (toDelete == null) { + if (c.toDelete != null) { + return false; + } + } else if (!toDelete.equals(c.toDelete)) { + return false; + } + + return true; + } +} diff --git a/unused/DeleteTeamCommandParser.java b/unused/DeleteTeamCommandParser.java new file mode 100644 index 00000000000..62374ba23b9 --- /dev/null +++ b/unused/DeleteTeamCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser.teams; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.teams.DeleteTeamCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +// @@author mohamedsaf1 +/** + * Parses input arguments and creates a new DeleteTeamCommand object + */ +public class DeleteTeamCommandParser implements Parser<DeleteTeamCommand> { + + private static final String NUMBER = "\\s*[\\-+]?[0-9]+\\s*"; + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand and returns a + * DeleteCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTeamCommand parse(String args) throws ParseException { + if (args == null || args.trim().length() == 0) { + return new DeleteTeamCommand(null); + } else if (!args.matches(NUMBER)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTeamCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(args); + return new DeleteTeamCommand(index); + } + +} diff --git a/unused/DuplicateCheck.java b/unused/DuplicateCheck.java new file mode 100644 index 00000000000..f5773c518cd --- /dev/null +++ b/unused/DuplicateCheck.java @@ -0,0 +1,68 @@ +/** + * Duplicate checking algorithm for v1.4. Removed due to feature freeze: cannot fix feature flaw. + */ +public class DuplicateCheck { + + /** + * Checks if an AbstractDisplayItem has the same email attribute + * as the current AbstractDisplayItem instance. + * + * @param displayItem An other AbstractDisplayItem instance. + * @return true if both DisplayItem instances have the same Email instance, false otherwise. + */ + public boolean isSameEmail(AbstractDisplayItem displayItem) { + Attribute<?> currentEmail = attributes.findAttribute("Email"); + Attribute<?> otherEmail = displayItem.attributes.findAttribute("Email"); + if (currentEmail == otherEmail) { + return true; + } else if (currentEmail == null || otherEmail == null) { + return false; + } + return currentEmail.equals(otherEmail); + } + + /** + * Checks if this AbstractDisplayItem instance and another AbstractDisplayItem instance + * do not have an Email attribute. + * + * @param displayItem Another AbstractDisplayItem instance. + * @return true if both AbstractDisplayItem instances have no Email attribute, false otherwise. + */ + public boolean isBothEmailNull(AbstractDisplayItem displayItem) { + Attribute<?> currentEmail = attributes.findAttribute("Email"); + Attribute<?> otherEmail = displayItem.attributes.findAttribute("Email"); + return currentEmail == null && otherEmail == null; + } + + /** + * Checks if an AbstractDisplayItem has the same phone attribute + * as the current AbstractDisplayItem instance. + * + * @param displayItem An other AbstractDisplayItem instance. + * @return true if both DisplayItem instances have the same Phone instance, false otherwise. + */ + public boolean isSamePhone(AbstractDisplayItem displayItem) { + Attribute<?> currentPhone = attributes.findAttribute("Phone"); + Attribute<?> otherPhone = displayItem.attributes.findAttribute("Phone"); + if (currentPhone == otherPhone) { + return true; + } else if (currentPhone == null || otherPhone == null) { + return false; + } + return currentPhone.equals(otherPhone); + } + + /** + * Checks if this AbstractDisplayItem instance and another AbstractDisplayItem instance + * do not have a Phone attribute. + * + * @param displayItem Another AbstractDisplayItem instance. + * @return true if both AbstractDisplayItem instances have no Phone attribute, false otherwise. + */ + public boolean isBothPhoneNull(AbstractDisplayItem displayItem) { + Attribute<?> currentPhone = attributes.findAttribute("Phone"); + Attribute<?> otherPhone = displayItem.attributes.findAttribute("Phone"); + return currentPhone == null && otherPhone == null; + } + +} diff --git a/unused/EditAttributeCommandParser.java b/unused/EditAttributeCommandParser.java new file mode 100644 index 00000000000..94cac2dd24a --- /dev/null +++ b/unused/EditAttributeCommandParser.java @@ -0,0 +1,70 @@ +// @@author jasonchristopher21 +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.attributes.EditAttributeCommand; +import seedu.address.logic.commands.attributes.EditGroupAttributeCommand; +import seedu.address.logic.commands.attributes.EditPersonAttributeCommand; +import seedu.address.logic.commands.attributes.EditTaskAttributeCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new {@code EditAttributeCommand} object + */ +public class EditAttributeCommandParser implements Parser<EditAttributeCommand> { + + /** + * Regex for the basic format of EditAttributeCommand, should contain a type (user/group/task), its + * respective ID, the name of the attribute and the content of the attribute. + */ + private static final Pattern ADD_ATTRIBUTE_COMMAND_FORMAT = Pattern + .compile("(?<type>[ugt])/(?<id>\\w+)\\s+(?<attributeName>\\w+)\\s+(?<attributeContent>.+)"); + + /** + * Parses the given {@code String} of arguments in the context of the {@code EditAttributeCommand} + * and returns a {@code EditAttributeCommand} object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditAttributeCommand parse(String args) throws ParseException { + + final Matcher matcher = ADD_ATTRIBUTE_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditAttributeCommand.MESSAGE_USAGE)); + } + + Index index = null; + + try { + index = ParserUtil.parseIndex(matcher.group("id").trim()); // TODO: change this to UUID implementation + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditAttributeCommand.MESSAGE_USAGE), pe); + } + + String type = matcher.group("type"); + + String attributeName = matcher.group("attributeName").trim(); + String attributeContent = matcher.group("attributeContent").trim(); + + if (type.equals("u")) { + return new EditPersonAttributeCommand(index, attributeName, attributeContent); + } else if (type.equals("g")) { + return new EditGroupAttributeCommand(index, attributeName, attributeContent); + } else if (type.equals("t")) { + return new EditTaskAttributeCommand(index, attributeName, attributeContent); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditAttributeCommand.MESSAGE_USAGE)); + } + + } + +} diff --git a/unused/EntryType.java b/unused/EntryType.java new file mode 100644 index 00000000000..a180783d3ac --- /dev/null +++ b/unused/EntryType.java @@ -0,0 +1,22 @@ +package seedu.address.model.item; + +//@@author Eclipse-Dominator + +/** + * Enum to represent the which kind of fxml card to use to represent the item. + */ +public enum EntryType { + USER(false), + TEAM(true), + TASK(true); + + private boolean isOpenable; + + EntryType(boolean isOpenable) { + this.isOpenable = isOpenable; + } + + public boolean getIsOpenable() { + return this.isOpenable; + } +} diff --git a/unused/Fields.java b/unused/Fields.java new file mode 100644 index 00000000000..a2c40967d36 --- /dev/null +++ b/unused/Fields.java @@ -0,0 +1,140 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.model.attribute.Field; + +/** + * Represents a Person's custom fields pairings in the address book. + * + * Unused due to implementation of Attributes + */ +public class Fields { + + // A list of fields + private List<Field> fields; + + /** + * Constructs a new Fields instance. + */ + public Fields() { + fields = new ArrayList<>(); + } + + /** + * Adds a field to the list of fields. + * + * @param field A Field instance to be added to the list. + */ + public void addField(Field field) { + fields.add(field); + } + + /** + * Adds a field to the list of fields by a given field name. + * + * @param fieldName the name of the Field instance to be added to the list. + */ + public void addField(String fieldName) { + Field field = new Field(fieldName); + fields.add(field); + } + + /** + * Removes a field from the list of fields. + * + * @param field A field to be removed from the list. + * @return true if the Field was removed successfully, false otherwise. + */ + public boolean removeField(Field field) { + return fields.remove(field); + } + + /** + * Removes a field from the list of fields. + * + * @param fieldName The name of the field to be removed from the list. + */ + public void removeField(String fieldName) { + List<Field> fieldsToRemove = fields.stream() + .filter(field -> field.isNameMatch(fieldName)) + .collect(Collectors.toList()); + fields.removeAll(fieldsToRemove); + } + + /** + * Updates the Field object with a new Field object. + * + * @param oldField The old Field object from the Person. + * @param newField The new Field object to be updated. + */ + public void updateField(Field oldField, Field newField) { + int index = fields.indexOf(oldField); + fields.set(index, newField); + } + + /** + * Retrieves the value of a field given by a specified name. + * + * @param name The name of the field to be searched. + * @return the value of the field queried. + */ + public String retrieveFieldValue(String name) { + for (Field field : fields) { + if (field.isNameMatch(name)) { + return field.getValue(); + } + } + return "Value not found"; + } + + /** + * Adds all items from a given list of fields to the list stored in the + * Fields object. + * + * @param fields A list of fields to add. + */ + public void addAll(List<Field> fields) { + this.fields.addAll(fields); + } + + /** + * Adds all items from a given Fields instance. + * + * @param fields A Fields object containing field information to be added from. + */ + public void addAll(Fields fields) { + if (fields != null && !fields.isEmpty()) { + this.fields.addAll(fields.toList()); + } + } + + /** + * Returns a List representation of the {@code Fields} instance. + * + * @return a List containing the {@code Field} instances. + */ + public List<Field> toList() { + return fields; + } + + /** + * Checks if the {@code Fields} is empty. + * + * @return true if there are no {@code Field} instances stored in this + * {@code Fields} + * object, false otherwise. + */ + public boolean isEmpty() { + return fields.isEmpty(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + fields.forEach(builder::append); + return builder.toString(); + } +} diff --git a/unused/FindTaskCommand.java b/unused/FindTaskCommand.java new file mode 100644 index 00000000000..ea567dfd354 --- /dev/null +++ b/unused/FindTaskCommand.java @@ -0,0 +1,87 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.item.NameContainsKeywordsPredicate; +import seedu.address.model.task.Task; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindTaskCommand extends TaskCommand { + + public static final String SUBCOMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + + ": Finds all persons whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + getFullCommand(SUBCOMMAND_WORD) + " alice bob charlie"; + + private NameContainsKeywordsPredicate<Task> predicate; + + public FindTaskCommand(NameContainsKeywordsPredicate<Task> predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredTaskList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTaskCommand + && predicate.equals(((FindTaskCommand) other).predicate)); // state check + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof String)) { + return this; + } + predicate = new NameContainsKeywordsPredicate<>(Arrays.asList(additionalData.toString().split("\\s+"))); + return this; + } + + /** + * Returns a Parser that 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 + */ + public static Parser<FindTaskCommand> parser() { + return new Parser<FindTaskCommand>() { + /** + * 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 + */ + public FindTaskCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + return new FindTaskCommand(null); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindTaskCommand(new NameContainsKeywordsPredicate<Task>(Arrays.asList(nameKeywords))); + } + }; + } +} diff --git a/unused/FindTeamCommand.java b/unused/FindTeamCommand.java new file mode 100644 index 00000000000..b7daa94c91b --- /dev/null +++ b/unused/FindTeamCommand.java @@ -0,0 +1,87 @@ +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.item.NameContainsKeywordsPredicate; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindTeamCommand extends TeamCommand { + + public static final String SUBCOMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = getFullCommand(SUBCOMMAND_WORD) + + ": Finds all persons whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + getFullCommand(SUBCOMMAND_WORD) + " alice bob charlie"; + + private NameContainsKeywordsPredicate<Group> predicate; + + public FindTeamCommand(NameContainsKeywordsPredicate<Group> predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTeamList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredTeamList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTeamCommand + && predicate.equals(((FindTeamCommand) other).predicate)); // state check + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + if (additionalData == null || !(additionalData instanceof String)) { + return this; + } + predicate = new NameContainsKeywordsPredicate<>(Arrays.asList(additionalData.toString().split("\\s+"))); + return this; + } + + /** + * Returns a Parser that 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 + */ + public static Parser<FindTeamCommand> parser() { + return new Parser<FindTeamCommand>() { + /** + * 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 + */ + public FindTeamCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + return new FindTeamCommand(null); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindTeamCommand(new NameContainsKeywordsPredicate<Group>(Arrays.asList(nameKeywords))); + } + }; + } +} diff --git a/unused/ForEachPersonCommand.java b/unused/ForEachPersonCommand.java new file mode 100644 index 00000000000..abe605436c2 --- /dev/null +++ b/unused/ForEachPersonCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands.persons; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Marks a task as complete + */ +public class ForEachPersonCommand extends PersonCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "foreach"; + + public static final String MESSAGE_USAGE = PersonCommand.getFullCommand(SUBCOMMAND_WORD) + + "for each person in the current list, execute subsequent commands with that person as context\n" + + "e.g. " + getFullCommand(SUBCOMMAND_WORD) + "task delete"; + + private static final String ON_COMPLETE = "Completed person loop! (failed: %d/%d executions)"; + + private final Command nextCmd; + + /** + * Constructor for foreach person command. + */ + public ForEachPersonCommand(String nextCmd) throws ParseException { + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error: \n" + ps.getMessage()); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Person> lastShownList = new ArrayList<>(model.getFilteredPersonList()); + int[] skipped = {0, lastShownList.size()}; + lastShownList.forEach(t -> { + try { + nextCmd.setInput(t); + nextCmd.execute(model); + } catch (CommandException e) { + skipped[0]++; + } + }); + return new CommandResult(String.format(ON_COMPLETE, skipped[0], skipped[1])); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/unused/ForEachPersonCommandParser.java b/unused/ForEachPersonCommandParser.java new file mode 100644 index 00000000000..beecbb266c8 --- /dev/null +++ b/unused/ForEachPersonCommandParser.java @@ -0,0 +1,17 @@ +package seedu.address.logic.parser.persons; + +import seedu.address.logic.commands.persons.ForEachPersonCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser for foreach command on person + */ +public class ForEachPersonCommandParser implements Parser<ForEachPersonCommand> { + + @Override + public ForEachPersonCommand parse(String userInput) throws ParseException { + return new ForEachPersonCommand(userInput.trim()); + } + +} diff --git a/unused/ForEachTaskCommand.java b/unused/ForEachTaskCommand.java new file mode 100644 index 00000000000..5e044f8fd79 --- /dev/null +++ b/unused/ForEachTaskCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + +/** + * Marks a task as complete + */ +public class ForEachTaskCommand extends TaskCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "foreach"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + "for each task in the current list, execute subsequent commands with that task as context\n" + + "e.g. " + getFullCommand(SUBCOMMAND_WORD) + "task delete"; + + private static final String ON_COMPLETE = "Completed task loop! (failed: %d/%d executions)"; + + private final Command nextCmd; + + /** + * Constructor for task foreach command + */ + public ForEachTaskCommand(String nextCmd) throws ParseException { + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error: \n" + ps.getMessage()); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Task> lastShownList = new ArrayList<>(model.getFilteredTaskList()); + int[] skipped = {0, lastShownList.size()}; + lastShownList.forEach(t -> { + try { + nextCmd.setInput(t); + nextCmd.execute(model); + } catch (CommandException e) { + skipped[0]++; + } + }); + return new CommandResult(String.format(ON_COMPLETE, skipped[0], skipped[1])); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/unused/ForEachTaskCommandParser.java b/unused/ForEachTaskCommandParser.java new file mode 100644 index 00000000000..156402f5804 --- /dev/null +++ b/unused/ForEachTaskCommandParser.java @@ -0,0 +1,17 @@ +package seedu.address.logic.parser.tasks; + +import seedu.address.logic.commands.tasks.ForEachTaskCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input for ForEachTask Command + */ +public class ForEachTaskCommandParser implements Parser<ForEachTaskCommand> { + + @Override + public ForEachTaskCommand parse(String userInput) throws ParseException { + return new ForEachTaskCommand(userInput.trim()); + } + +} diff --git a/unused/ForEachTeamCommand.java b/unused/ForEachTeamCommand.java new file mode 100644 index 00000000000..0ddd9ce229a --- /dev/null +++ b/unused/ForEachTeamCommand.java @@ -0,0 +1,65 @@ +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +/** + * Marks a task as complete + */ +public class ForEachTeamCommand extends TeamCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "foreach"; + + public static final String MESSAGE_USAGE = TeamCommand.getFullCommand(SUBCOMMAND_WORD) + + "for each task in the current list, execute subsequent commands with that task as context\n" + + "e.g. " + getFullCommand(SUBCOMMAND_WORD) + "task delete"; + + private static final String ON_COMPLETE = "Completed task loop! (failed: %d/%d executions)"; + + private final Command nextCmd; + + /** + * Creates a ForEachTeam Command + * + * @param nextCmd next command to be executed + * @throws ParseException thrown when next command fails to parse properly + */ + public ForEachTeamCommand(String nextCmd) throws ParseException { + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error"); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Group> lastShownList = new ArrayList<>(model.getFilteredTeamList()); + int[] skipped = {0, lastShownList.size()}; + lastShownList.forEach(t -> { + try { + nextCmd.setInput(t); + nextCmd.execute(model); + } catch (CommandException e) { + skipped[0]++; + } + }); + return new CommandResult(String.format(ON_COMPLETE, skipped[0], skipped[1])); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/unused/ForEachTeamCommandParser.java b/unused/ForEachTeamCommandParser.java new file mode 100644 index 00000000000..eea5634fde5 --- /dev/null +++ b/unused/ForEachTeamCommandParser.java @@ -0,0 +1,17 @@ +package seedu.address.logic.parser.teams; + +import seedu.address.logic.commands.teams.ForEachTeamCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input for ForEachTeam Command + */ +public class ForEachTeamCommandParser implements Parser<ForEachTeamCommand> { + + @Override + public ForEachTeamCommand parse(String userInput) throws ParseException { + return new ForEachTeamCommand(userInput.trim()); + } + +} diff --git a/unused/GroupCommand.java b/unused/GroupCommand.java new file mode 100644 index 00000000000..5960a810863 --- /dev/null +++ b/unused/GroupCommand.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands.groups; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +/** + * Add a team to the address book. + */ +public class GroupCommand extends Command { + public static final String COMMAND_WORD = "team"; + + public static final String MESSAGE_SUCCESS = "New team added: %1$s!"; + public static final String MESSAGE_DUPLICATE_TEAM = "This team already exists in the address book"; + + private final Group toAdd; + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public GroupCommand(Group team) { + requireNonNull(team); + toAdd = team; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasTeam(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TEAM); + } + + model.addTeam(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GroupCommand // instanceof handles nulls + && toAdd.equals(((GroupCommand) other).toAdd)); + } +} diff --git a/unused/JsonAdaptedField.java b/unused/JsonAdaptedField.java new file mode 100644 index 00000000000..d130eacfe20 --- /dev/null +++ b/unused/JsonAdaptedField.java @@ -0,0 +1,46 @@ +// @@author autumn-sonata + +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.attribute.Field; + +/** + * Jackson-friendly version of {@link Field}. + */ +class JsonAdaptedField { + + private final String name; + private final String value; + + /** + * Constructs a {@code JsonAdaptedField} with the given field details. + */ + @JsonCreator + public JsonAdaptedField(@JsonProperty("name") String name, @JsonProperty("value") String value) { + this.name = name == null ? "" : name; + this.value = value == null ? "" : value; + } + + public JsonAdaptedField(Field field) { + assert field != null; + name = field.name; + value = field.value; + } + + /** + * Converts this Jackson-friendly adapted field object into the model's {@code Field} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted field. + */ + public Field toModelType() throws IllegalValueException { + if (!Field.isValidName(name) || !Field.isValidField(value)) { + throw new IllegalValueException(Field.MESSAGE_CONSTRAINTS); + } + + return new Field(name, value); + } +} diff --git a/unused/ProgressCommand.java b/unused/ProgressCommand.java new file mode 100644 index 00000000000..826a7d8480b --- /dev/null +++ b/unused/ProgressCommand.java @@ -0,0 +1,61 @@ +// @@author mohamedsaf1 +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.Progress; +import seedu.address.model.task.Task; + +/** + * Class that represents command for Progress. + */ +public class ProgressCommand extends TaskCommand { + public static final String SUBCOMMAND_WORD = "progress"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + ": Sets the progress of the task\n" + + "Parameters: INDEX (must be a positive integer) and LEVEL (25%, 50%, 75% or 100%)\n" + + "Example: " + COMMAND_WORD + " 1" + " 25%\n"; + public static final String COMPLETE_SUCCESS = " progress for task %s is set.%n"; + public static final String ALREADY_SET = " progess for task %s has already been set!%n"; + + private final Index targetIndex; + private final Progress level; + + /** + * Constructor for progress command + * + * @param targetIndex + * @param level + */ + public ProgressCommand(Index targetIndex, Progress level) { + this.targetIndex = targetIndex; + this.level = level; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List<Task> lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Task task = lastShownList.get(targetIndex.getZeroBased()); + Task editedTask = task.setProgress(String.valueOf(level)); + if (editedTask == task) { + throw new CommandException(ALREADY_SET); + } + model.setTask(task, task.setProgress(String.valueOf(level))); + return new CommandResult(String.format(COMPLETE_SUCCESS, task)); + } +} diff --git a/unused/ProgressCommandParser.java b/unused/ProgressCommandParser.java new file mode 100644 index 00000000000..d8c5dd9301b --- /dev/null +++ b/unused/ProgressCommandParser.java @@ -0,0 +1,34 @@ +// @@author mohamedsaf1 +package seedu.address.logic.parser.tasks; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.tasks.MarkTaskCommand; +import seedu.address.logic.commands.tasks.ProgressCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.attribute.Progress; + +/** + * A class that represents parsing a progress command. + */ +public class ProgressCommandParser implements Parser<ProgressCommand> { + /** + * Method to parse the arguments to set progress for tasks. + * + * @throws ParseException + */ + @Override + public ProgressCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + Progress level = ParserUtil.parseProgress(args); + return new ProgressCommand(index, level); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkTaskCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/unused/RemoveAttributeCommandParser.java b/unused/RemoveAttributeCommandParser.java new file mode 100644 index 00000000000..1d66301978a --- /dev/null +++ b/unused/RemoveAttributeCommandParser.java @@ -0,0 +1,71 @@ +// @@author jasonchristopher21 +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.attributes.RemoveAttributeCommand; +import seedu.address.logic.commands.attributes.RemoveGroupAttributeCommand; +import seedu.address.logic.commands.attributes.RemovePersonAttributeCommand; +import seedu.address.logic.commands.attributes.RemoveTaskAttributeCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new {@code RemoveAttributeCommand} + * object + */ +public class RemoveAttributeCommandParser implements Parser<RemoveAttributeCommand> { + + /** + * Regex for the basic format of RemoveAttributeCommand, should contain a type + * (user/group/task), its respective + * ID, the name of the attribute and the content of the attribute. + */ + private static final Pattern REMOVE_ATTRIBUTE_COMMAND_FORMAT = Pattern + .compile("(?<type>[ugt])/(?<id>\\w+)\\s+(?<attributeName>\\w+)"); + + /** + * Parses the given {@code String} of arguments in the context of the + * {@code RemoveAttributeCommand} + * and returns a {@code RemoveAttributeCommand} object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveAttributeCommand parse(String args) throws ParseException { + + final Matcher matcher = REMOVE_ATTRIBUTE_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveAttributeCommand.MESSAGE_USAGE)); + } + + Index index = null; + + try { + index = ParserUtil.parseIndex(matcher.group("id").trim()); // TODO: change this to UUID implementation + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveAttributeCommand.MESSAGE_USAGE), pe); + } + + String type = matcher.group("type"); + + String attributeName = matcher.group("attributeName").trim(); + + if (type.equals("u")) { + return new RemovePersonAttributeCommand(index, attributeName); + } else if (type.equals("g")) { + return new RemoveGroupAttributeCommand(index, attributeName); + } else if (type.equals("t")) { + return new RemoveTaskAttributeCommand(index, attributeName); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveAttributeCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/unused/RemoveFieldCommand.java b/unused/RemoveFieldCommand.java new file mode 100644 index 00000000000..190ce438710 --- /dev/null +++ b/unused/RemoveFieldCommand.java @@ -0,0 +1,52 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FIELD; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Removes a field by a given field name from the addressbook. + */ +public class RemoveFieldCommand extends PureCommand { + + public static final String COMMAND_WORD = "rmfield"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes a custom field from the address book. " + + "Parameters: FIELD"; + + public static final String MESSAGE_SUCCESS = "Field removed: %1$s"; + + private final String fieldName; + + /** + * Constructs a RemoveFieldCommand instance. + * + * @param fieldName The name of the field to be removed. + */ + public RemoveFieldCommand(String fieldName) { + requireNonNull(fieldName); + this.fieldName = fieldName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + PREFIX_FIELD.removeField(fieldName, model); + } catch (ParseException err) { + throw new CommandException(err.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, fieldName)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveFieldCommand // instanceof handles nulls + && fieldName.equals(((RemoveFieldCommand) other).fieldName)); // state check + } +} diff --git a/unused/SelectPersonCommand.java b/unused/SelectPersonCommand.java new file mode 100644 index 00000000000..14a5e903dcb --- /dev/null +++ b/unused/SelectPersonCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands.persons; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Marks a task as complete + */ +public class SelectPersonCommand extends PersonCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = PersonCommand.getFullCommand(SUBCOMMAND_WORD) + + "selects a person and execute subsequent commands with that person as context\n" + + "e.g. " + getFullCommand(SUBCOMMAND_WORD) + "1 contains Description"; + + private final Index targetIndex; + private final Command nextCmd; + + /** + * Constructor to create a select person command + */ + public SelectPersonCommand(Index targetIndex, String nextCmd) throws ParseException { + this.targetIndex = targetIndex; + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error: \n" + ps.getMessage()); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + nextCmd.setInput(model.getFromFilteredPerson(targetIndex)); + return nextCmd.execute(model); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/unused/SelectPersonCommandParser.java b/unused/SelectPersonCommandParser.java new file mode 100644 index 00000000000..bb29a08938d --- /dev/null +++ b/unused/SelectPersonCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.persons; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.persons.SelectPersonCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input for SelectPerson Command + */ +public class SelectPersonCommandParser implements Parser<SelectPersonCommand> { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<index>[0-9]+)\\s+(?<commands>.*)"); + + @Override + public SelectPersonCommand parse(String args) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectPersonCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(matcher.group("index")); + return new SelectPersonCommand(index, matcher.group("commands")); + } +} diff --git a/unused/SelectTaskCommand.java b/unused/SelectTaskCommand.java new file mode 100644 index 00000000000..605f84a37d1 --- /dev/null +++ b/unused/SelectTaskCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands.tasks; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Marks a task as complete + */ +public class SelectTaskCommand extends TaskCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = TaskCommand.getFullCommand(SUBCOMMAND_WORD) + + "selects a task and execute subsequent commands with that task as context\n" + + "e.g. " + getFullCommand(SUBCOMMAND_WORD) + "1 contains description"; + + private final Index targetIndex; + private final Command nextCmd; + + /** + * Constructor to select a task + */ + public SelectTaskCommand(Index targetIndex, String nextCmd) throws ParseException { + this.targetIndex = targetIndex; + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException ps) { + throw new ParseException("Syntax Error: \n" + ps.getMessage()); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + nextCmd.setInput(model.getFromFilteredTasks(targetIndex)); + return nextCmd.execute(model); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/unused/SelectTaskCommandParser.java b/unused/SelectTaskCommandParser.java new file mode 100644 index 00000000000..02c8c11a79b --- /dev/null +++ b/unused/SelectTaskCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser.tasks; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.tasks.SelectTaskCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input for SelectTask Command + */ +public class SelectTaskCommandParser implements Parser<SelectTaskCommand> { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<index>[0-9]+)\\s+(?<commands>.*)"); + + @Override + public SelectTaskCommand parse(String args) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectTaskCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(matcher.group("index")); + return new SelectTaskCommand(index, matcher.group("commands")); + } +} diff --git a/unused/SelectTeamCommand.java b/unused/SelectTeamCommand.java new file mode 100644 index 00000000000..f0b3b7e5e72 --- /dev/null +++ b/unused/SelectTeamCommand.java @@ -0,0 +1,52 @@ +package seedu.address.logic.commands.teams; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.PureCommandInterface; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +// @@author connlim + +/** + * Marks a task as complete + */ +public class SelectTeamCommand extends TeamCommand implements PureCommandInterface { + public static final String SUBCOMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = TeamCommand.getFullCommand(SUBCOMMAND_WORD) + + "selects a team and execute subsequent commands with that team as context\n" + + "e.g. " + getFullCommand(SUBCOMMAND_WORD) + " 1 contains description"; + + private final Index targetIndex; + private final Command nextCmd; + + /** + * Constructor to select a team. + */ + public SelectTeamCommand(Index targetIndex, String nextCmd) throws ParseException { + this.targetIndex = targetIndex; + try { + this.nextCmd = AddressBookParser.get().parseCommand(nextCmd); + } catch (ParseException e) { + throw new ParseException("Syntax error parsing select"); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + nextCmd.setInput(model.getFromFilteredTeams(targetIndex)); + return nextCmd.execute(model); + } + + @Override + public Command setInput(Object additionalData) throws CommandException { + return this; + } +} diff --git a/unused/SelectTeamCommandParser.java b/unused/SelectTeamCommandParser.java new file mode 100644 index 00000000000..a02b0db35e7 --- /dev/null +++ b/unused/SelectTeamCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.teams; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.teams.SelectTeamCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser to parse user input for SelectTeam Command + */ +public class SelectTeamCommandParser implements Parser<SelectTeamCommand> { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<index>[0-9]+)\\s+(?<commands>.*)"); + + @Override + public SelectTeamCommand parse(String args) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectTeamCommand.MESSAGE_USAGE)); + } + Index index = ParserUtil.parseIndex(matcher.group("index")); + return new SelectTeamCommand(index, matcher.group("commands")); + } +} diff --git a/unused/UniqueID.java b/unused/UniqueID.java new file mode 100644 index 00000000000..32b2ddafd8d --- /dev/null +++ b/unused/UniqueID.java @@ -0,0 +1,55 @@ +// @@author mohamedsaf1 +package seedu.address.model.tag; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.item.AbstractDisplayItem; + +/** + * A class for an unique ID for each item. + */ +public class UniqueID { + public static final String MESSAGE_CONSTRAINTS = "UUID must be unique."; + public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + + private String id; + + /** + * A constructor to create an UUID object. + * + * @param id is the ID generated when instantiating an item object. + */ + public UniqueID(String id) { + this.id = id; + requireNonNull(id); + checkArgument(isValidUniqueID(id), MESSAGE_CONSTRAINTS); + } + + public static boolean isValidUniqueID(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueID // instanceof handles nulls + && id.equals(((UniqueID) other).id)); // state check + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + id + ']'; + } + + public void generateUniqueID(AbstractDisplayItem item) { + this.id = String.valueOf(item); + } +} diff --git a/unused/UnusedChunks.java b/unused/UnusedChunks.java new file mode 100644 index 00000000000..e4913f62113 --- /dev/null +++ b/unused/UnusedChunks.java @@ -0,0 +1,58 @@ +// @@author jasonchristopher21 + +@Override +public void addAttribute(String attributeName, String attributeContent) throws AttributeException { + requireAllNonNull(attributeName, attributeContent); + attributes.addAttribute(attributeName, attributeContent); +} + +@Override +public void editAttribute(String attributeName, String attributeContent) throws AttributeException { + requireAllNonNull(attributeName, attributeContent); + attributes.editAttribute(attributeName, attributeContent); +} + + +/** + * Retrieves the Fields instance of the Person. + * + * @return the Fields instance of the Person. + */ +public AttributeList getFields() { + return this.attributes; +} + +/** + * Adds a Field to the Fields of the Person. + * + * @param fieldName the field name to be added. + */ +public void addField(String fieldName) throws AttributeException { + attributes.addAttribute(fieldName); +} + +/** + * Removes a field from the Fields of the Person + * + * @param fieldName the field name to be removed. + */ +public void removeField(String fieldName) { + attributes.removeField(fieldName); +} + +// @@author mohamedsaf1 + + /** + * Parses a {@code String level} into a {@code Progress}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code level} is invalid. + */ + public static Progress parseProgress(String level) throws ParseException { + requireNonNull(level); + String trimmedProgress = level.trim(); + if (!Progress.isValidProgress(trimmedProgress)) { + throw new ParseException(Progress.MESSAGE_CONSTRAINTS); + } + return new Progress(level); + } diff --git a/unused/attributes/AddAttributeCommand.java b/unused/attributes/AddAttributeCommand.java new file mode 100644 index 00000000000..e5df06bdbbd --- /dev/null +++ b/unused/attributes/AddAttributeCommand.java @@ -0,0 +1,41 @@ +// @@author jasonchristopher21 + +package seedu.address.logic.commands.attributes; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import seedu.address.logic.commands.Command; + +/** + * Adds an attribute to a person. + */ +public abstract class AddAttributeCommand extends Command { + + public static final String COMMAND_WORD = "addfield"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a custom field to the address book. " + + "Parameters: CUSTOM_PREFIX FIELD"; + + protected final String attributeName; + protected final String attributeContent; + + /** + * Constructs a new AddAttributeCommand instance. + * + * @param attributeName The name of the attribute to be added. + * @param attributeContent The content of the attribute to be added. + */ + public AddAttributeCommand(String attributeName, String attributeContent) { + requireAllNonNull(attributeName, attributeContent); + this.attributeName = attributeName; + this.attributeContent = attributeContent; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddAttributeCommand // instanceof handles nulls + && attributeName.equals(((AddAttributeCommand) other).attributeName) + && attributeContent.equals(((AddAttributeCommand) other).attributeContent)); + } +} diff --git a/unused/attributes/AddGroupAttributeCommand.java b/unused/attributes/AddGroupAttributeCommand.java new file mode 100644 index 00000000000..4d2a4a8cbd2 --- /dev/null +++ b/unused/attributes/AddGroupAttributeCommand.java @@ -0,0 +1,55 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.group.Group; +import seedu.address.model.group.exceptions.GroupOutOfBoundException; + +/** + * Adds a group attribute to the address book. + */ +public class AddGroupAttributeCommand extends AddAttributeCommand { + + public static final String MESSAGE_SUCCESS = "New field added: %s, with value: %s"; + + private final Index groupIndex; // change this to UUID later + + /** + * Constructs an AddGroupAttributeCommand instance. + * + * @param groupIndex index of the group. + * @param attributeName the name of the attribute to be added. + * @param attributeContent the content of the attribute to be added. + */ + public AddGroupAttributeCommand(Index groupIndex, String attributeName, String attributeContent) { + super(attributeName, attributeContent); + requireNonNull(groupIndex); + this.groupIndex = groupIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Group group = model.getFromFilteredTeams(groupIndex); + group.addAttribute(attributeName, attributeContent); + } catch (GroupOutOfBoundException | AttributeException ae) { + throw new CommandException(ae.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName, attributeContent)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof AddGroupAttributeCommand + && groupIndex.equals(((AddGroupAttributeCommand) other).groupIndex))); + } +} diff --git a/unused/attributes/AddPersonAttributeCommand.java b/unused/attributes/AddPersonAttributeCommand.java new file mode 100644 index 00000000000..6dd892620c8 --- /dev/null +++ b/unused/attributes/AddPersonAttributeCommand.java @@ -0,0 +1,55 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonOutOfBoundException; + +/** + * Adds a person attribute to the address book. + */ +public class AddPersonAttributeCommand extends AddAttributeCommand { + + public static final String MESSAGE_SUCCESS = "New field added: %s, with value: %s"; + + private final Index personIndex; // change this to UUID later + + /** + * Constructs an AddPersonAttributeCommand instance. + * + * @param personIndex index of the person. + * @param attributeName the name of the attribute to be added. + * @param attributeContent the content of the attribute to be added. + */ + public AddPersonAttributeCommand(Index personIndex, String attributeName, String attributeContent) { + super(attributeName, attributeContent); + requireNonNull(personIndex); + this.personIndex = personIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Person person = model.getFromFilteredPerson(personIndex); + person.addAttribute(attributeName, attributeContent); + } catch (PersonOutOfBoundException | AttributeException e) { + throw new CommandException(e.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName, attributeContent)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof AddPersonAttributeCommand + && personIndex.equals(((AddPersonAttributeCommand) other).personIndex))); + } +} diff --git a/unused/attributes/AddTaskAttributeCommand.java b/unused/attributes/AddTaskAttributeCommand.java new file mode 100644 index 00000000000..0eaf30defce --- /dev/null +++ b/unused/attributes/AddTaskAttributeCommand.java @@ -0,0 +1,55 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.TaskOutOfBoundException; + +/** + * Adds a task attribute to the address book. + */ +public class AddTaskAttributeCommand extends AddAttributeCommand { + + public static final String MESSAGE_SUCCESS = "New field added: %s, with value: %s"; + + private final Index taskIndex; // change this to UUID later + + /** + * Constructs an AddTaskAttributeCommand instance. + * + * @param taskIndex index of the task. + * @param attributeName the name of the attribute to be added. + * @param attributeContent the content of the attribute to be added. + */ + public AddTaskAttributeCommand(Index taskIndex, String attributeName, String attributeContent) { + super(attributeName, attributeContent); + requireNonNull(taskIndex); + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Task task = model.getFromFilteredTasks(taskIndex); + task.addAttribute(attributeName, attributeContent); + } catch (TaskOutOfBoundException | AttributeException ae) { + throw new CommandException(ae.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName, attributeContent)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof AddTaskAttributeCommand + && taskIndex.equals(((AddTaskAttributeCommand) other).taskIndex))); + } +} diff --git a/unused/attributes/EditAttributeCommand.java b/unused/attributes/EditAttributeCommand.java new file mode 100644 index 00000000000..fe084ffc976 --- /dev/null +++ b/unused/attributes/EditAttributeCommand.java @@ -0,0 +1,40 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import seedu.address.logic.commands.Command; + +/** + * Edits an existing attribute in the AddressBook. + */ +public abstract class EditAttributeCommand extends Command { + public static final String COMMAND_WORD = "editfield"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits a custom field of a Person/Task/Team in the address book. " + + "Parameters: CUSTOM_PREFIX FIELD"; + + protected final String attributeName; + protected final String attributeContent; + + /** + * Constructs a new EditAttributeCommand instance. + * + * @param attributeName The name of the attribute to be added. + * @param attributeContent The content of the attribute to be added. + */ + public EditAttributeCommand(String attributeName, String attributeContent) { + requireAllNonNull(attributeName, attributeContent); + this.attributeName = attributeName; + this.attributeContent = attributeContent; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EditAttributeCommand // instanceof handles nulls + && attributeName.equals(((EditAttributeCommand) other).attributeName) + && attributeContent.equals(((EditAttributeCommand) other).attributeContent)); + } +} diff --git a/unused/attributes/EditGroupAttributeCommand.java b/unused/attributes/EditGroupAttributeCommand.java new file mode 100644 index 00000000000..0275e09a008 --- /dev/null +++ b/unused/attributes/EditGroupAttributeCommand.java @@ -0,0 +1,55 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.group.Group; +import seedu.address.model.group.exceptions.GroupOutOfBoundException; + +/** + * Adds a group attribute to the address book. + */ +public class EditGroupAttributeCommand extends EditAttributeCommand { + + public static final String MESSAGE_SUCCESS = "New field added: %s, with value: %s"; + + private final Index groupIndex; // change this to UUID later + + /** + * Constructs an EditGroupAttributeCommand instance. + * + * @param groupIndex index of the group. + * @param attributeName the name of the attribute to be added. + * @param attributeContent the content of the attribute to be added. + */ + public EditGroupAttributeCommand(Index groupIndex, String attributeName, String attributeContent) { + super(attributeName, attributeContent); + requireNonNull(groupIndex); + this.groupIndex = groupIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Group group = model.getFromFilteredTeams(groupIndex); + group.editAttribute(attributeName, attributeContent); + } catch (GroupOutOfBoundException | AttributeException ae) { + throw new CommandException(ae.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName, attributeContent)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof EditGroupAttributeCommand + && groupIndex.equals(((EditGroupAttributeCommand) other).groupIndex))); + } +} diff --git a/unused/attributes/EditPersonAttributeCommand.java b/unused/attributes/EditPersonAttributeCommand.java new file mode 100644 index 00000000000..0250f0321a6 --- /dev/null +++ b/unused/attributes/EditPersonAttributeCommand.java @@ -0,0 +1,55 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonOutOfBoundException; + +/** + * Adds a person attribute to the address book. + */ +public class EditPersonAttributeCommand extends EditAttributeCommand { + + public static final String MESSAGE_SUCCESS = "New field added: %s, with value: %s"; + + private final Index personIndex; // change this to UUID later + + /** + * Constructs an AddPersonAttributeCommand instance. + * + * @param personIndex index of the person. + * @param attributeName the name of the attribute to be added. + * @param attributeContent the content of the attribute to be added. + */ + public EditPersonAttributeCommand(Index personIndex, String attributeName, String attributeContent) { + super(attributeName, attributeContent); + requireNonNull(personIndex); + this.personIndex = personIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Person person = model.getFromFilteredPerson(personIndex); + person.editAttribute(attributeName, attributeContent); + } catch (PersonOutOfBoundException | AttributeException e) { + throw new CommandException(e.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName, attributeContent)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof EditPersonAttributeCommand + && personIndex.equals(((EditPersonAttributeCommand) other).personIndex))); + } +} diff --git a/unused/attributes/EditTaskAttributeCommand.java b/unused/attributes/EditTaskAttributeCommand.java new file mode 100644 index 00000000000..165eba67e6a --- /dev/null +++ b/unused/attributes/EditTaskAttributeCommand.java @@ -0,0 +1,55 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.TaskOutOfBoundException; + +/** + * Adds a task attribute to the address book. + */ +public class EditTaskAttributeCommand extends EditAttributeCommand { + + public static final String MESSAGE_SUCCESS = "New field added: %s, with value: %s"; + + private final Index taskIndex; // change this to UUID later + + /** + * Constructs an EditTaskAttributeCommand instance. + * + * @param taskIndex index of the task. + * @param attributeName the name of the attribute to be added. + * @param attributeContent the content of the attribute to be added. + */ + public EditTaskAttributeCommand(Index taskIndex, String attributeName, String attributeContent) { + super(attributeName, attributeContent); + requireNonNull(taskIndex); + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Task task = model.getFromFilteredTasks(taskIndex); + task.editAttribute(attributeName, attributeContent); + } catch (TaskOutOfBoundException | AttributeException ae) { + throw new CommandException(ae.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName, attributeContent)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof EditTaskAttributeCommand + && taskIndex.equals(((EditTaskAttributeCommand) other).taskIndex))); + } +} diff --git a/unused/attributes/RemoveAttributeCommand.java b/unused/attributes/RemoveAttributeCommand.java new file mode 100644 index 00000000000..e90d96af5f5 --- /dev/null +++ b/unused/attributes/RemoveAttributeCommand.java @@ -0,0 +1,36 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; + +/** + * Removes an attribute from the AddressBook. + */ +public abstract class RemoveAttributeCommand extends Command { + + public static final String COMMAND_WORD = "rmfield"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes a custom field from the address book. " + + "Parameters: CUSTOM_PREFIX FIELD"; + + protected final String attributeName; + + /** + * Constructs a new RemoveAttributeCommand instance. + * + * @param attributeName The name of the attribute to be removed. + */ + public RemoveAttributeCommand(String attributeName) { + requireNonNull(attributeName); + this.attributeName = attributeName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveAttributeCommand // instanceof handles nulls + && attributeName.equals(((RemoveAttributeCommand) other).attributeName)); + } +} diff --git a/unused/attributes/RemoveGroupAttributeCommand.java b/unused/attributes/RemoveGroupAttributeCommand.java new file mode 100644 index 00000000000..15d4b4e56a1 --- /dev/null +++ b/unused/attributes/RemoveGroupAttributeCommand.java @@ -0,0 +1,54 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.group.Group; +import seedu.address.model.group.exceptions.GroupOutOfBoundException; + +/** + * Removes an existing attribute from a Person in the AddressBook. + */ +public class RemoveGroupAttributeCommand extends RemoveAttributeCommand { + + public static final String MESSAGE_SUCCESS = "Field removed successfully: %s"; + + private final Index groupIndex; // change this to UUID later + + /** + * Constructs an RemoveGroupAttributeCommand instance. + * + * @param groupIndex index of the person. + * @param attributeName the name of the attribute to be added. + */ + public RemoveGroupAttributeCommand(Index groupIndex, String attributeName) { + super(attributeName); + requireNonNull(groupIndex); + this.groupIndex = groupIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Group group = model.getFromFilteredTeams(groupIndex); + group.removeAttribute(attributeName); + } catch (GroupOutOfBoundException | AttributeException e) { + throw new CommandException(e.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof RemoveGroupAttributeCommand + && groupIndex.equals(((RemoveGroupAttributeCommand) other).groupIndex))); + } +} diff --git a/unused/attributes/RemovePersonAttributeCommand.java b/unused/attributes/RemovePersonAttributeCommand.java new file mode 100644 index 00000000000..5f0e1b143c6 --- /dev/null +++ b/unused/attributes/RemovePersonAttributeCommand.java @@ -0,0 +1,54 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonOutOfBoundException; + +/** + * Removes an existing attribute from a Person in the AddressBook. + */ +public class RemovePersonAttributeCommand extends RemoveAttributeCommand { + + public static final String MESSAGE_SUCCESS = "Field removed successfully: %s"; + + private final Index personIndex; // change this to UUID later + + /** + * Constructs an AddPersonAttributeCommand instance. + * + * @param personIndex index of the person. + * @param attributeName the name of the attribute to be added. + */ + public RemovePersonAttributeCommand(Index personIndex, String attributeName) { + super(attributeName); + requireNonNull(personIndex); + this.personIndex = personIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Person person = model.getFromFilteredPerson(personIndex); + person.removeAttribute(attributeName); + } catch (PersonOutOfBoundException | AttributeException e) { + throw new CommandException(e.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof RemovePersonAttributeCommand + && personIndex.equals(((RemovePersonAttributeCommand) other).personIndex))); + } +} diff --git a/unused/attributes/RemoveTaskAttributeCommand.java b/unused/attributes/RemoveTaskAttributeCommand.java new file mode 100644 index 00000000000..11051748b4d --- /dev/null +++ b/unused/attributes/RemoveTaskAttributeCommand.java @@ -0,0 +1,54 @@ +// @@author jasonchristopher21 +package seedu.address.logic.commands.attributes; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attribute.exceptions.AttributeException; +import seedu.address.model.task.Task; +import seedu.address.model.task.exceptions.TaskOutOfBoundException; + +/** + * Adds a task attribute to the address book. + */ +public class RemoveTaskAttributeCommand extends RemoveAttributeCommand { + + public static final String MESSAGE_SUCCESS = "Field removed successfully: %s"; + + private final Index taskIndex; // change this to UUID later + + /** + * Constructs an EditTaskAttributeCommand instance. + * + * @param taskIndex index of the task. + * @param attributeName the name of the attribute to be added. + */ + public RemoveTaskAttributeCommand(Index taskIndex, String attributeName) { + super(attributeName); + requireNonNull(taskIndex); + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + Task task = model.getFromFilteredTasks(taskIndex); + task.removeAttribute(attributeName); + } catch (TaskOutOfBoundException | AttributeException ae) { + throw new CommandException(ae.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, attributeName)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (super.equals(other) + && (other instanceof RemoveTaskAttributeCommand + && taskIndex.equals(((RemoveTaskAttributeCommand) other).taskIndex))); + } +}