diff --git a/.gitignore b/.gitignore index 71c9194e8bd..90490c36f5b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ src/main/resources/docs/ /config.json /preferences.json /*.log.* +**/*.log # Test sandbox files src/test/data/sandbox/ diff --git a/README.md b/README.md index 13f5c77403f..4d840c1912f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -[![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/AY2223S1-CS2103T-F12-1/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S1-CS2103T-F12-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. +# Cobb - Property Address Book +Cobb is an optimised object-oriented command-line entry management application that aims to make database management for Real Estate Agents easier and more accessible. +* The project simulates an ongoing software project for a desktop application used for managing contact details and property information. * 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. +* For detailed documentation of this project, see the **[Cobb Product Website](https://ay2223s1-cs2103t-f12-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..838f7d1c4b1 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,10 @@ test { finalizedBy jacocoTestReport } +run { + enableAssertions = true +} + task coverage(type: JacocoReport) { sourceDirectories.from files(sourceSets.main.allSource.srcDirs) classDirectories.from files(sourceSets.main.output) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index d618671b832..c31b53cf66c 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -400,7 +400,7 @@ - + diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..aeb2bb8d129 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,56 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can reach us at the email `cobb[at]comp.nus.edu.sg` ## Project team -### John Doe +### Qi Zhi - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/riccqi)] +[[portfolio](team/riccqi.md)] -* Role: Project Advisor +* Role: Integration -### Jane Doe +### Chrysline Lim - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/chryslinelim)] +[[portfolio](team/chryslinelim.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Documentation -### Johnny Doe +### Chng Ian - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github]](http://github.com/chngchngchng) +[[portfolio]](team/chngchngchng.md) * Role: Developer -* Responsibilities: Data +* Responsibilities: CLI Commands, Integration -### Jean Doe +## Zsigmond Poh - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/zsiggg)] +[[portfolio](team/zsiggg.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Refactoring of previous AB3 code, management of issues on GitHub, +implementing new features -### James Doe +### Chen Hung-Yu - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/jchilling)] +[[portfolio](team/jchilling.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Data diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..4b18e5af6e1 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,17 +2,38 @@ layout: page title: Developer Guide --- + +## **Introduction** + +Cobb is a JavaFX application that helps property agents manage their database of buyers and properties using a command-line interface. + +### Purpose of Guide + +This guide is primarily targeted towards developers looking to understand or extend the functionalities of Cobb, +and software testers looking to test Cobb's features. You are also welcome to read this if you understand UML diagrams +and have a general understanding of how a software application works under the hood. + +### Scope of Guide + +This guide first gives a high-level architecture overview of Cobb, before explaining the main components that make up +the application. It then explains some notable features and the design considerations behind their implementation. To +better understand what the application does from the user's standpoint, you might want to dive into the +[Use cases](#Use cases) section first, or simply run the application and try it for yourself before reading the rest +of the guide. + +-------------------------------------------------------------------------------------------------------------------- +## **Table of Contents** * 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} +Based off on [AddressBook-Level3](https://github.com/se-edu/addressbook-level3).
+Libraries used: [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5), [JavaFX](https://openjfx.io), +[PlantUML](https://plantuml.com). -------------------------------------------------------------------------------------------------------------------- - ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). @@ -20,11 +41,7 @@ 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. -
+[Back to top](#table-of-contents) ### Architecture @@ -36,7 +53,10 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called +[`Main`](https://github.com/AY2223S1-CS2103T-F12-1/tp/blob/master/src/main/java/seedu/address/Main.java) +and [`MainApp`](https://github.com/AY2223S1-CS2103T-F12-1/tp/blob/master/src/main/java/seedu/address/MainApp.java). +It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -52,57 +72,69 @@ The rest of the App consists of four components. **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 `deletebuyer 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. +* 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. +For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality +using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given +component through its interface rather than the concrete class (reason: to prevent outside component's being coupled +to the implementation of a component), as illustrated in the (partial) class diagram below. The sections below give more details of each component. ### UI component +[Back to top](#table-of-contents) + -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) +**API**: +[`Ui.java`](https://github.com/AY2223S1-CS2103T-F12-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 consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `BuyerListPanel`, `PropertyListPanel`, `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-F12-1/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-F12-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`. +* depends on some classes in the `Model` component, as it displays `Buyer` and `Property` objects residing in `Model`. ### Logic component +[Back to top](#table-of-contents) -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : +[`Logic.java`](https://github.com/AY2223S1-CS2103T-F12-1/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) 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 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. When `Logic` is called upon to execute a command, it uses the `CobbParser` class to parse the user command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddBuyerCommand`) +which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to add a buyer). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned 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("deletebuyer 1")` +API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `deletebuyer 1` Command](images/DeleteSequenceDiagram.png) -
: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. +
:information_source: **Note:** The lifeline for `DeleteBuyerCommandParser` +should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: @@ -110,138 +142,567 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha 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. +* When called upon to parse a user command, the `CobbParser` class creates an `XYZCommandParser` (`XYZ` is a +placeholder for the specific command name e.g. `AddBuyerCommandParser`) which uses the other classes shown above to +parse the user command and create a `XYZCommand` object (e.g. `AddBuyerCommand`) which the `CobbParser` returns back +as a `Command` object. +* All `XYZCommandParser` classes (e.g. `AddBuyerCommandParser`, `DeletePropertyCommandParser`, ...) 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) - - +[Back to top](#table-of-contents) +<<<<<<< HEAD +**API** : [`Model.java`](https://github.com/AY2223S1-CS2103T-F12-1/tp/blob/master/src/main/java/seedu/address/model/Model.java) +[!ModelClassDiagram](images/ModelClassDiagram.png) The `Model` component, -* 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) - -
: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.
- - - +* stores the buyer book data i.e., all `Buyer` objects (which are contained in a `UniqueBuyerList` object). +* stores the property book data i.e, all `Property` objects (which are contained in a `UniquePropertyList` object). +* stores the currently 'selected' `Buyer` and `Property` objects (e.g. results of a search query) as separate +_filtered_ lists which are exposed to outsiders as unmodifiable `ObservableList` and `ObservableList` +respectively that can be 'observed' e.g. the UI can be bound to these lists so that the UI automatically updates when +the data in the lists change. +* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a +`ReadOnlyUserPref` object. +* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they +should make sense on their own without depending on other components) + +
:information_source: **Note:** An alternative (arguably, a more OOP) +model is given below. A generic Cobb class is created as the parent for BuyerBook and PropertyBook since the two +children classes have a lot of methods with identical purposes.
+ +[!BetterModelClassDiagram](images/BetterModelClassDiagram.png)
### Storage component +[Back to top](#table-of-contents) -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : +[`Storage.java`](https://github.com/AY2223S1-CS2103T-F12-1/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) - + 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`) +* can save buyer book data, property book data, and user preference data in JSON format, and read them back into +corresponding objects. +* inherits from `BuyerBookStorage`, `PropertyBookStorage`, `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`) ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.commons` package. -------------------------------------------------------------------------------------------------------------------- ## **Implementation** +[Back to top](#table-of-contents) This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature - -#### Proposed Implementation +### Navigate previous commands with arrow keys + +#### Motivation + +Currently, once a command is executed successfully, there is no way for the user to get it back easily. +However, a user who frequently uses command line interfaces (CLIs) might expect the use of arrow keys to bring +back previous commands, as a way to quickly input multiple similar commands at once. + +#### Implementation +`CommandBox` in the `commandbox` package represents the GUI component where the user enters commands. +`CommandRetriever` is a static nested class within `CommandBox`, used to contain and retrieve the history of +successful commands. It implements the following public methods: +- `CommandRetriever#addCommand(String command, TextField textfield)` - Adds a successful command to the command history, +and clears the TextField +- `CommandRetriever#getPreviousCommand(TextField textfield)` - Displays the previous command in the Textfield, if it +exists in the history +- `CommandRetriever#getNextCommand(TextField textfield)` - Displays the next command in the Textfield, if it +exists in the history + +These methods are called by `CommandBox#handleCommandEntered()` and `CommandBox#handleKeyReleased(KeyEvent e)`. + +Here is the class diagram for `CommandRetriever`. + +![CommandRetrieverClassDiagram](images/CommandRetrieverClassDiagram.png) + +`CommandRetriever` only keeps track of the commands executed successfully, as invalid commands are highlighted red +and do not disappear from the TextField. Thus, there is no need to store these invalid commands since the user can +already edit the invalid command in the current implementation without having to retype it. Storing invalid commands +in `CommandRetriever` would only clutter up the history, especially if the user inputted numerous invalid commands. + +Given below is an example usage scenario and how the arrow key changes the `CommandBox` text field at each step. +A sequence diagram is also provided below. + +![CommandRetrieverSequenceDiagram](images/CommandRetrieverSequenceDiagram.png) + +Step 1. The user launches the application for the first time. `commandHistory` is initialised as an empty +`ArrayList`, and index is initialised as 0. + +Step 2. The user executes a command, `listbuyers` by pressing the Enter key. `CommandBox#handleCommand()` is fired, +getting the text from the text field. Since it is a valid command, it is executed successfully. `listbuyers` is added +to the `commandHistory` list, and index is set to `commandHistory.size()`. + +Step 3. The user types a command halfway, but does not press the Enter key. He/she now wishes to use the previous +command to type the command. + +Step 4. The user presses and releases the Up arrow. `CommandBox#handleKeyReleased()` is fired, which sets the text field +to display the `(index - 1)`th element in `commmandHistory`. Because the current command is one that has not been +executed, it is saved in the field `currentCommand`. + +Step 5. The user presses and releases the Up arrow again. `CommandBox#handleKeyReleased()` is fired, but since there are +no more previous commands, nothing happens. + +Step 6. The user presses and releases the Down arrow. `CommandBox#handleKeyReleased()` is fired. Since this is the last +element in `commandHistory`, the text field is set to display the string `currentCommand`. This would be the user's +unexecuted command from Step 3. + +### Indexing existing buyers and properties in the database +[Back to top](#table-of-contents) + +Many of the existing features that are currently implemented requires Cobb to index existing entries in the database. +For example, `deletebuyer 1` would perform the `deletebuyer` operation on the buyer at index `1` of the buyer list. + +If the index provided is not a positive integer or not a valid number, Cobb will throw an error requesting the user to +provide a valid input. Similarly, if the index provided is valid but exceeds the number of elements currently in the list, +Cobb will be able to identify that there is a bounds mismatch and inform the user to provide a valid input within bounds. + +Internally, both of the lists of `Buyers` and `Properties` are stored using an `ObservableList`, which is an array-like +data structure provided by JavaFX which fires off reports about all of its changes to associated listeners. This means that +any changes to the structure or objects in the `ObservableList` will be recorded by its listeners, causing the updated +list to be displayed correctly on the user's screen. + +#### Design Considerations +**Aspect: How entries are indexed in a list** +* **Alternative 1 (current choice)**: Entries are indexed by their relative positions in the current `ObservableList`. + If the list is filtered or sorted, then the entries' relative positions will change according to this new version of the + list. + * Pros: + * Users will be able to quickly ascertain the index of an entry in the list simply by finding the entry in the list. + * Indices of visible entries in the `ObservableList` will always be in the range `[1,n]` inclusive, where n + is the number of entries currently visible in the list. This gives the indices order and structure. + * No index field needs to be created for `Buyer` and `Property` objects. + * Cons: + * The relative index of an entry will change depending on the current structure of the `ObservableList`. This + means that a property that has index `1` might not have the same index after the property list is filtered. + +* **Alternative 2**: Entries in a list are indexed by an internal fixed `uuid` parameter that is automatically generated + upon its creation. + * Pros: + * Users will still be able to identify the index of the entry by first looking for the entry in the list, and then + looking at the value of its `uuid` parameter. + * The index of the entry in the list will not change if the structure of the list is changed, e.g. through filtering + or sorting operations. + * Cons: + * `Buyer` and `Property` class will be bloated with one extra parameter. + * Care needs to be taken to ensure that no IDs are clashing with each other, which might lead to implementation issues. + * Users might be able to execute commands on entries in the list that are not currently visible, which might lead + to confusion. + +### Internal implementations of Buyer and Property +[Back to top](#table-of-contents) + +Cobb allows functionality that performs operations on two distinct types of entities stored in the database: Potential property +buyers, as well as properties that are for sale. To allow for this functionality, two classes were created to represent each type +of entity: `Buyer` and `Property`, respectively. + +The structure of a `Buyer` object can be viewed in the class diagram below. + +![BuyerClassDiagram](images/BuyerClassDiagram.png) + +From the diagram, it can be seen that a `Buyer` object consists of the following attributes: +- `name`, representing the name of the buyer. +- `phone`, representing the phone number of the buyer. +- `address`, representing the address of the buyer. +- `email`, representing the email address of the buyer. +- `priority`, representing the priority of the buyer - High, Normal or Low. +- `priceRange`, representing the price range of properties that a buyer is willing to consider. +- `characteristics`, representing the characteristics of a property that a buyer is looking for. +- `entryTime`, representing the creation time of the buyer (created and accessed internally). + +On the other hand, the structure of a `Property` object can be viewed in the class diagram below. + +![PropertyClassDiagram](images/PropertyClassDiagramNew.png) + +From the diagram, it can be seen that a `Property` object consists of the following attributes: +- `propertyName`, representing the name of the property. +- `address`, representing the address of the property. +- `description`, representing a short description of the property. +- `owner`, representing the Owner of the property. An owner has an `ownerName` and an `ownerPhone`. +- `price`, representing the price of the property. +- `characteristics`, representing the characteristics that a property possesses. +- `propertyEntryTime`, representing the creation time of the property (created and accessed internally). + +#### Design Considerations + +**Aspect: How `Buyer` and `Property` objects are stored internally** + +* **Alternative 1 (current choice)**: Individual classes are created for each of the attributes related to `Buyer` and `Property` + objects. + * Pros: + * Reduces piling up of functionality within the `Buyer` and `Property` classes by abstracting out behaviour related to each + individual attribute to its own component class. + * Adheres more to the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single-responsibility_principle) + through aforementioned abstraction of functionality. + * Makes the code neater and easier to follow. + * Makes individual components of code easier to test and easier to re-use. + * Cons: + * Increases coupling of different classes. + * Increases the number of classes in the codebase. + + +* **Alternative 2**: All individual attributes are stored directly in the `Property` or `Buyer` classes, with all related + functionality also implemented within them. This would result in `Buyer` and `Property` objects similar to that in the + UML diagram below. + ![Alternative Property and Buyer implementation](images/AlternativeBuyerAndProperty.png) + * Pros: + * No extra classes are required, as all attributes are stored directly in the component classes. + * All functionality is executed by the singular component class, so there is no confusion. + * Cons: + * Very bloated component classes with a lot of functionality. + * Difficult to abstract and test each functionality of the class separately. + +### Creating a buyer: `addbuyer` +[Back to top](#table-of-contents) + +The structure for executing an `addbuyer` command follows the flow as mentioned in the “Logic component” section of this guide. + +The `Buyer` class represents a buyer with buyer-specific fields. `PriceRange`, `Characteristics`, and `Priority` +denote his budget, requirements for the property, and buyer priority respectively. + +These three fields are all optional:
+- When the user chooses not to indicate a buyer’s price range or desired characteristics, the `priceRange` and `desiredCharacteristics` field of a buyer may be null. Hence, they have both been implemented using `Optional`.
+- When the user chooses not to indicate a buyer priority, the buyer's priority will be set to the default priority as `NORMAL`. +- When the user creates a buyer, the time of creation is also automatically stored as an `LocalDateTime`. + +This is the class diagram of a `Buyer`. + +![BuyerClassDiagram](images/BuyerClassDiagram.png) + +Following the execution of this sample `addbuyer` command: +`addbuyer -n Jane -ph 89991237 -e jane@gmail.com -a Bishan Street 12 -r 2000-5000`, the following object diagram represents +the internal state of the newly created `Buyer`. -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: +![BuyerObjectDiagram](images/AddBuyerObjectDiagram-Final_state.png) -* `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. +Note that the following objects are created by default: +1. A new `LocalDateTime` object. +2. A new `Priority` object, representing *NORMAL* priority. +3. A new `Optional` object. In this context, since a price range was specified in the command, this `Optional` contains a `PriceRange` object. +4. A new `Optional` object containing a null pointer, as no characteristics flag was specified in the command above. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +The `Optional` object has been omitted from the object diagram for brevity. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +#### Design Considerations +The buyer list should not allow for creation of duplicate buyers. This means that some condition(s) defining equality +between two separate `Buyer` objects should be specified. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Originally, a buyer's `Name` was used to distinguish a singular buyer from others. However, this was changed +in later iterations as it was realised that there could very well exist two unique buyers with similar names. -![UndoRedoState0](images/UndoRedoState0.png) +Instead, the `Phone` and `Email` of the `Buyer` are used to distinguish a buyer, as there cannot be two buyers that have +the exact same phone numbers and email addresses. -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +The entry time was added towards later parts of development to help facilitate a more flexible implementation of the `sortbuyers` command. +Early discussions allowed a user to provide the date and time manually using a `-d` flag, but this feature was scrapped as +it was found to provide little to no value. -![UndoRedoState1](images/UndoRedoState1.png) +The activity diagram for the creation of a buyer can be seen below. -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +![Add buyer activity diagram](images/AddBuyerActivityDiagram.png) -![UndoRedoState2](images/UndoRedoState2.png) +### Creating a property: `addprop` +[Back to top](#table-of-contents) -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +The structure for executing an `addprop` command follows the flow as mentioned in the "Logic component" section of this guide. -
- -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +The `Property` class represents a property with property-specific fields. `Price` and `Characteristics` classes denote the price and feature of the property respectively. -![UndoRedoState3](images/UndoRedoState3.png) +The `price` field is mandatory while the `characteristics` field is optional. When the user chooses not to indicate a property's characteristics, the `characteristics` field of a property may be null. Hence, it has been implemented using `Optional`. +When the user creates a property, the entry time is also automatically stored as an `LocalDateTime`. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +This is the class diagram of a `Property`. -
+![PropertyClassDiagram](images/PropertyClassDiagramNew.png) -The following sequence diagram shows how the undo operation works: +Following the execution of this sample `addprop` command: +`addprop -n Peak Residences -a 333 Thompson Road -p 1000000 -d long property description -o Bob -ph 91234567`, the following +object diagram represents the internal state of the newly created `Property`. -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +![PropertyObjectDiagram](images/AddPropertyObjectDiagram-Final_state.png) -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +Note that the following objects are created by default: +1. A new `LocalDateTime` object. +2. A new `Optional` object containing a null pointer, if no `-c` flag was specified in the command. +
:exclamation: **Note:** +The `Optional` object has been omitted form the object diagram for brevity.
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. - -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +#### Design Considerations +The property list should not allow for creation of duplicate properties. This means that some condition(s) defining equality +between two separate `Property` objects should be specified. -
+Originally, `PropertyName` and `Price` were used to distinguish a singular property from others. However, this was changed +in later iterations as it was realised that there could very well exist two entirely separate properties with identical names and prices. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Instead, the `Address` of the `Property` is used to distinguish a property, as there cannot be two properties existing at the exact +same address. -![UndoRedoState4](images/UndoRedoState4.png) +The entry time was added towards later parts of development to help facilitate a more flexible implementation of the `sortprops` command. +Early discussions allowed a user to provide the date and time manually using a `-d` flag, but this feature was scrapped as +it was found to provide little to no value. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +The activity diagram for the creation of a property can be seen below. -![UndoRedoState5](images/UndoRedoState5.png) +![Add property activity diagram](images/AddPropertyActivityDiagram.png) -The following activity diagram summarizes what happens when a user executes a new command: +### Editing of buyers and properties +[Back to top](#table-of-contents) - +#### Motivation +The user may want to edit the details of a buyer or property after adding it to the application. For example, the user may want to change the budget range of a buyer after adding it to Cobb. +Or, the user may want to change the price of a property after adding it to Cobb. -#### Design considerations: +#### Implementation +The `EditBuyerCommand` and `EditPropertyCommand` classes extends the `Command` class. They are used to edit the details of a buyer or property, respectively. +Both commands allow the user to change any of the fields of a buyer or property. The commands expect at least one flag to be edited, otherwise an error message will be displayed. +When the edit command is inputted, the `EditBuyerCommandParser` and `EditPropertyCommandParser` classes are used to parse the user input and create the respective `EditBuyerCommand` and `EditPropertyCommand` objects. +When these created command objects are executed by the `LogicManager`, the `EditBuyerCommand#execute(Model model)` or `EditPropertyCommand#execute(Model model)` methods are called. These methods will edit the buyer or property in the model, and return a `CommandResult` object. -**Aspect: How undo & redo executes:** +
:exclamation: **Note:** +To be more concise, we will be referring to both buyers and properties as entities in this section from here onwards. +
-* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +During this execution process, the existing entity is first retrieved from the model. The fields of the entities are then edited according to what flags were passed in by the user during the edit commands. +A new buyer or property is then created with the edited fields, and any fields that have not been edited will be copied over from the original entity. The new entity is then added to the model, and the original entity is removed from the model. +The new buyer or property is then added into the model, replacing the old one. The new entity will then be displayed to the user, and a success message is displayed. + +The following sequence diagram shows how the `EditBuyerCommand` is executed. + + +#### Design Considerations +**Aspect: How the edit commands should relate to each other:** + +* **Alternative 1 (current choice):** `EditBuyerCommand` and `EditPropertyCommand` are separate, and both inherit from the `Command` class. + * Pros: + * Both the `Buyer` and `Property` classes have different fields that are exclusive to each other. + * This reduces complexity of the system, and unexpected behaviours. + * The inheritance of the `Command` class allows us to keep to the Command design pattern, to easily add more types of edit commands in the future, without having to change the existing code. + * Cons: + * More boilerplate code for each of the classes, which increases the size of the codebase. +* **Alternative 2:** A single `EditCommand` class is used to edit both buyer and property. + * Cons: + * Unnecessary complexity is introduced into the system. + +**Aspect: How the edited entities should interact with the model:** +* We also decided for the edit commands to create a new entity, instead of editing the existing one. This allows us to not include any setters in the `Buyer` and `Property` classes, which make the objects immutable, so there is less likelihood of unexpected changes to the object. +By creating a new entity every time the user edits, we can easily add the new buyer or property into the model, and remove the old one. This also allows us to easily undo the edit command in the future, by simply adding the old entity back into the model. + +### Owner specification within a property +[Back to top](#table-of-contents) + +#### Motivation +In real estate, a property being listed by a property agent is usually owned by a property owner. +However, the agent may not be the owner of the property. Hence, we decided to allow the user to specify the owner of a property, +with essential details such as their name and phone number, and have them represented as part of the `Property` class. + +#### Implementation +To identify the owner of the property, we decided to include an `Owner` object within a `Property`. This `Owner` class contains two fields: `name` and `phone`. + +The `name` and `phone` fields in the `Owner` class are compulsory, to make sure that each property being sold has a relevant contact buyer. +The fields are also validated the same way as when creating a new `Buyer` object. + +To support retrieving the `Owner` of a `Property`, we added the following methods: +- `Property#getOwner()` - Returns the `Owner` object of the property. +- `Property#getOwnerName()` - Retrieves the name of the owner of the property. +- `Property#getOwnerPhone()` - Retrieves the phone number of the owner of the property. + +This is the class diagram showing the full `Property` class diagram, with the `Owner` class included: +![FullPropertyClassDiagram](images/OwnerClassDiagram.png) + +The `Owner` class enacts the Composition relationship, as the `Property` class contains the `Owner` object. Hence, if the property is deleted, it's associated owner will also be deleted. +The tradeoffs for this approach is examined below: + +#### Design Considerations + +**Aspect: How the owner class associates with the property class** + +* **Alternative 1 (current choice):** Owner class is coupled together with the property class. + * Pros: + * The `Owner` class is only used in the `Property` class, so it makes sense to couple them together. + * You do not need to create an owner object separately using another command. + * This reduces complexity of the system, and unexpected behaviours. + * Cons: + * This creates a 1-to-1 relationship between the owner and the property. + * Each owner is coupled tightly with the property, and cannot be used for other properties. + +* **Alternative 2:** Users will have to create an `Owner` object separately, and link it to the property manually. + * Pros: + * This allows for a many-to-many relationship between the owners and properties. + * This allows for better OOP design, as owners will be treated as a separate, first-class entity, similar to + `Buyer`. + * Cons: + * Increases complexity for a possibly limited use case of linking an owner to multiple properties. + * This may lead to unexpected behaviours, such as whether properties linked to an owner should be deleted when + the owner is deleted. + +### Filtering buyers and properties +[Back to top](#table-of-contents) + +In order to filter `Buyers` and `Properties`, a `Predicate` needs to be passed into the `ObservableList` that stores +references to these objects and displays them on the user's screen. These predicates can differ in the conditions that are +being tested, consequently, they might give different outputs when applied to a given list. + + +#### Design Considerations +In order to allow for multiple-condition filtering, that is, the concatenation of multiple filter predicates, an abstract +`AbstractFilterXYZPredicate` class was created to employ polymorphic behaviour, where XYZ represents the entity type that +we are working with, for example `AbstractFilterBuyersPredicate` or `AbstractFilterPropsPredicate`. + +As `Property` has a single specific `Price`, it is much less useful to filter the list using one price value as it is +unlikely to match any property. Instead, we decided to filter by a price range instead, where any property whose price +falls within this range would be displayed. + +Additionally, since users +are only allowed to filter using certain conditions as defined in the behaviour of the `filter` commands, concrete classes +extending this abstract predicate class were implemented for each condition. For example: + +Users can filter `Properties` by their `PropertyName`, `Price`, `Characteristics` or `OwnerName`. As a result, the following +concrete predicate classes were implemented: +1. `PropertyNameContainsSubstringPredicate` +2. `FilterPropsByPricePredicate` +3. `FilterPropsContainingAllCharacteristicsPredicate` +4. `FilterPropsContainingAnyCharacteristicsPredicate` +5. `FilterPropsByOwnerNamePredicate` + +Based on command parameters passed in by the user, these predicates are constructed and concatenated together to form a single +`Predicate`, which is then used to filter the `ObservableArrayList` directly. More specifics regarding concatenation behaviour +can be found in the [filter-specific design considerations](#filter-specific-design-considerations) section located below. + +The class diagram below represents the overall structure of the predicates for `Buyers` and `Properties`.
+[!FilterPredicatesClassDiagram](images/FilterPredicatesClassDiagram.png) + +#### Filter-specific design considerations +1. Filtering `Properties` by their prices takes in a `priceRange` instead of just a `Price` as it makes more sense for + agents to want to identify properties that fit within a certain price range instead of a fixed price. +2. For both `filterBuyers` and `filterProps`, the default concatenated predicate will be a logical **AND** of all individual + predicates, that is, all predicates need to be satisfied in order for the entry to pass through the filter. +3. For both `filterBuyers` and `filterProps`, passing in the `-fuzzy` flag will change the final concatenated predicate to be + a logical **OR** of all individual predicates, that is, only one of the predicates needs to be satisfied in order + for the entry to pass through the filter. +4. If the `-c` flag is specified, that is, desired characteristics are supplied as filter conditions, the default behaviour + is for Cobb to filter out entries that contain **ALL** of the given characteristics. The `-fuzzy` flag changes this behaviour + to filter out entries that contain *at least one* of the given characteristics. +5. Filtering entries by name - that is, providing the `-n` flag to the filter command, will filter all entries whose names + contain the parameter provided to `-n` as a *substring*. + +### Sorting buyers and properties +[Back to top](#table-of-contents) + +To sort `Buyers` and `Properties`, the `ObservableList` that stores references to these objects and displays them on the user's screen +is modified directly to a sorted version. These changes are propagated directly to the `FilteredList`, enabling users to sort +a previously filtered list. As the `FilteredList` is based on the `ObservableList`, users can also sort the list first, +then filter it. This results in users being able to build sort and filter functions on top of each other to more powerfully +manipulate the list based on their needs. + +A `Comparator` is used to sort the `ObservableList`. Different comparators with different conditions are used to sort the +list by different criteria. The following are the `Comparators` used to allow for the corresponding sorting functions: + +**`Buyer`: `BuyerComparator`** +1. `BuyerNameComparator`: sort by buyer's name +2. `PriceRangeComparator`: sort by buyer's price range +3. `PriorityComparator`: sort by buyer's priority + +**`Property`: `PropertyComparator`** +1. `PropertyNameComparator`: sort by property's name +2. `PriceComparator`: sort by property's price
+ +**Both:** +1. `TimeComparator`: sort by entry's time of creation + +A `BuyerComparator` compares two `Buyer` entities by using the `Comparator` stored in it on the corresponding `Buyer` fields. +For example, if a `BuyerComparator` contains a `BuyerNameComparator`, the two `Buyer`s are compared by their `Name`s using the `BuyerNameComparator`. +As we allow sorting only by one criterion at a time, a `BuyerComparator` will only contain one field `Comparator`. + +The UML diagrams below represent the overall structure of the `Comparator`s used. + + + + +Below is a Sequence Diagram showing how a `sortbuyer -n ASC` command is executed through the model to modify the original `ObservableList`. + +![SortBuyersSequenceDiagram](images/SortSequenceDiagram.png) + +#### Design Considerations +Similar to the `FilteredList` abstraction provided by JavaFX, we considered using a `SortedList` to present the list in a +sorted version without modifying the underlying data structure `ObservableList`. This is +to preserve the chronological order in which users enter the entries so that it can still be displayed with the `list` command. + +However, this meant that we needed to have both `FilteredList` and `SortedList` stored and vary which one is displayed to users +on entering a command. As such, the last-shown list changes depending on the last command entered. To keep track of this, +we used a flag which would be updated everytime a `filter` or `sort` command was used. All `Command`s that referred to an +entry on the displayed list were adapted to take relative indices from the last-shown list indicated by the flag. The `Model` +component also needed access to the `UI` component's `BuyerPanelList` and `PropertyPanelList` in order to display +the corresponding `FilteredList` or `SortedList` based on changes to the flag. To reduce coupling, we would have had to +apply the Observer pattern. -* **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. +We decided against this as the complicated design made it more bug-prone. In addition, it did not allow for +stacking of `filter` and `sort` functions, that is, a user is unable to filter on top of a sorted list or vice versa as the +`FilteredList` and `SortedList` are independent and separate from each other. + +Hence, the above-mentioned design was used. We included chronological sorting as well using `TimeComparator` +so that the user is able to return to the original state of the list, +since sorting by name, for example, would permanently modify the `ObservableList`. + +### Matching properties to a buyer, and vice versa +[Back to top](#table-of-contents) + +`matchprop` and `matchbuyer` are convenience features, allowing the users to find suitable matches between buyers +and properties easily. Without this, users would have to manually input the conditions from an identified buyer or +property and make use of the `filter` command. `matchprop` allows users to filter the buyer list with the conditions +set by a specific property. `matchbuyer` is similar, filtering the property list with the conditions set by a specific +buyer. + +#### Specifications + +For `matchprop`, a `Buyer` is considered a match if its `PriceRange` contains the property's `Price` (inclusive), +and contains at least 1 common characteristic with the property. +For `matchbuyer`, a `Property` is considered a match if its `Price` is within the buyer's `PriceRange` (inclusive), +and contains at least 1 common characteristic with the buyer. + +It is also possible to specify `-strict`, which would require a matching object to have _all_ (instead of at least 1) +the characteristics of the target object. This was added due to the possibility that the user gets too many matches, +and wishes to narrow down the results easily. Without this, the user would have to revert to the `filter` command +and manually input more specific conditions. -_{more aspects and alternatives to be added}_ +Here is a Sequence Diagram of how `matchbuyer 1` is handled, assuming 1 is a valid index and the `Buyer` at index 1 +contains both a `PriceRange` and `Characteristics`. -### \[Proposed\] Data archiving +:information_source: **Note:** These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing. -
### Launch and shutdown @@ -340,38 +994,198 @@ testers are expected to do more *exploratory* testing. 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. + 2. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. -1. Saving window preferences +2. Saving window preferences 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 2. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +### Adding a buyer +1. Test case: `addbuyer -n Tim -ph 87321237 -e tim@gmail.com -a S648234`
+ Expected: New buyer should be added into the list with relevant details. "New buyer added" message should be displayed + with details of the buyer that was added. + +2. Test case: `addbuyer -n Jane -a Street`
+ Expected: "Invalid command format" error message should be displayed, with information regarding the syntax of the `addbuyer` command + and a correct example of the command. + +### Adding a property +1. Test case: `addprop -n Peak Residences -a 333 Thompson Road -p 1000000 -d long property description -o Bob -ph 91234567 -c Toa Payoh; Bright`
+ Expected: New property should be added into the list with relevant details. "New property added" message should be displayed + with details of the property that was added. + +2. Test case: `addprop -n Peak Residences -a 333 Street`
+ Expected: "Invalid command format" error message should be displayed, with information regarding the syntax of the `addprop` command + and a correct example of the command. + +### Deleting a buyer + +1. Test case: `deletebuyer 1`
+ Expected: First buyer is deleted from the list. "Deleted Buyer" message should be displayed on the screen + with details of the deleted contact. + +2. Test case: `deletebuyer 0`
+ Expected: No buyer is deleted. Error details shown in the status message. + +3. Other incorrect delete commands to try: `deletebuyer`, `deletebuyer x`, `...`, `deletebuyer test` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Deleting a property + +1. Test case: `deleteprop 1`
+ Expected: First property is deleted from the list. "Deleted Property" message should be displayed on the screen + with details of the deleted contact. + +2. Test case: `deleteprop 0`
+ Expected: No property is deleted. Error details shown in the status message. + +3. Other incorrect delete commands to try: `deleteprop`, `deleteprop x`, `...`, `deleteprop test` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Editing a buyer + +1. Test case: `editbuyer 1 -n Tommy Jones` + Expected: First buyer in the list should have their name changed to "Tommy Jones". "Edited Buyer" message should also be + displayed on the screen with details of the edited buyer. + +2. Test case: `editbuyer -n Tommy Jones` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `editbuyer` + command and a correct example. + +3. Test case: `editbuyer 1` + Expected: "At least one field to edit must be provided" error message should be displayed. + +### Editing a property + +1. Test case: `editprop 1 -n Minecraft dirt hut` + Expected: First property in the list should have their name changed to "Minecraft dirt hut". "Edited Property" message should also be + displayed on the screen with details of the edited property. + +2. Test case: `editprop -n Minecraft dirt hut` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `editprop` + command and a correct example. + +3. Test case: `editprop 1` + Expected: "At least one field to edit must be provided" error message should be displayed. + +### Finding a buyer + +**Prerequisites**: A buyer that has 'John' in his name must exist in the buyer list. + +1. Test case: `findbuyers John` + Expected: Buyer list should be filtered to contain only buyers that have 'John' as a substring in their name (case-insensitive). + "x buyers listed" message should be displayed, where x refers to the number of buyers in the new filtered list. + +2. Test case: `findbuyers` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `findbuyers` + command and a correct example. + +### Finding a property + +**Prerequisites**: A property that has 'House' in its name must exist in the property list. + +1. Test case: `findprops house` + Expected: Property list should be filtered to contain only properties that have 'house' as a substring in their name (case-insensitive). + "x properties listed" message should be displayed, where x refers to the number of properties in the new filtered list. + +2. Test case: `findprops` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `findprops` + command and a correct example. + +### Filtering buyers + +**Prerequisites**: A buyer that has normal priority must exist in the buyer list. + +1. Test case: `filterbuyers -pr NORMAL` + Expected: Buyer list should be filtered to contain only buyers that have NORMAL as their priority. + "x buyers listed" message should be displayed, where x refers to the number of buyers in the new filtered list. + +2. Test case: `filterbuyers`, `filterbuyers 1`, `filterbuyers x` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `filterbuyers` + command and a correct example. + +### Filtering properties + +**Prerequisites**: A property that has an owner with name containing "Johnny" must exist in the property list. + +1. Test case: `filterprops -o Johnny` + Expected: Buyer list should be filtered to contain only properties whose owners have names containing "Johnny". + "x properties listed" message should be displayed, where x refers to the number of properties in the new filtered list. + +2. Test case: `filterprops`, `filterprops 1`, `filterprops x` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `filterprops` + command and a correct example. + +### Listing all buyers + +**Prerequisites**: Buyer list should be filtered to show a subset of the original list. + +1. Test case: `listbuyers` + Expected: Buyer list should return to its original state containing all buyers. "Listed all buyers" message should be displayed + on the screen. + +2. Test case: `listbuyers 1`, `listbuyers x` + Expected: Same behaviour as above. + +### Listing all properties + +**Prerequisites**: Property list should be filtered to show a subset of the original list. + +1. Test case: `listprops` + Expected: Property list should return to its original state containing all properties. "Listed all properties" message should be displayed + on the screen. + +2. Test case: `listprops 1`, `listprops x` + Expected: Same behaviour as above. + +### Matching buyers to properties + +1. Test case: `matchbuyer 5` + Expected: Buyer list should be filtered to contain all properties that match a given buyer based on their price range and + characteristics. "x matched properties for the buyer" message should be displayed on the screen, with x representing the number + of matched properties found, along with the buyer's information. + +2. Test case: `matchbuyer`, `matchbuyer 5 30`, `matchbuyer x` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `matchbuyer` + command and a correct example. + +### Matching properties to buyers -### Deleting a person +1. Test case: `matchprop 5` + Expected: Property list should be filtered to contain all buyers that match a given property based on its price and + characteristics. "x matched buyers for the property" message should be displayed on the screen, with x representing the number + of matched buyers found, along with the property's information. -1. Deleting a person while all persons are being shown +2. Test case: `matchprop`, `matchprop 5 30`, `matchprop x` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `matchprop` + command and a correct example. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. +### Sorting buyers - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +1. Test case: `sortbuyers -n ASC` + Expected: Buyer list should be sorted in increasing alphabetical order. "Sorted buyers by:" message should be displayed on the screen + with correct criteria and order. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +2. Test case: `sortbuyers`, `sortbuyers x`, `sortbuyers 1` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `sortbuyers` + command and a correct example. -1. _{ more test cases …​ }_ +3. Test case: `sortbuyers -n`, `sortbuyers -n oops`, `sortbuyers -n 1` + Expected: "Order should be ASC or DESC" error message should be displayed. -### Saving data +### Sorting properties -1. Dealing with missing/corrupted data files +1. Test case: `sortprops -n ASC` + Expected: Property list should be sorted in increasing alphabetical order. "Sorted properties by:" message should be displayed on the screen + with correct criteria and order. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +2. Test case: `sortprops`, `sortprops x`, `sortprops 1` + Expected: "Invalid command format" error message should be displayed, along with information regarding the syntax of the `sortprops` + command and a correct example. -1. _{ more test cases …​ }_ +3. Test case: `sortprops -n`, `sortprops -n oops`, `sortprops -n 1` + Expected: "Order should be ASC or DESC" error message should be displayed. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..8048e044ca0 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -20,11 +20,11 @@ First, **fork** this repo, and **clone** the fork into your computer. If you plan to use Intellij IDEA (highly recommended): 1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**. -1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
+2. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. -1. **Verify the setup**: +3. **Verify the setup**: 1. Run the `seedu.address.Main` and try a few commands. - 1. [Run the tests](Testing.md) to ensure they all pass. + 2. [Run the tests](Testing.md) to ensure they all pass. -------------------------------------------------------------------------------------------------------------------- @@ -39,15 +39,15 @@ If you plan to use Intellij IDEA (highly recommended): Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
-1. **Set up CI** +2. **Set up CI** This project comes with a GitHub Actions config files (in `.github/workflows` folder). When GitHub detects those files, it will run the CI for your project automatically at each push to the `master` branch or to any PR. No set up required. -1. **Learn the design** +3. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [Cobb's architecture](DeveloperGuide.md#architecture). -1. **Do the tutorials** +4. **Do the tutorials** These tutorials will help you get acquainted with the codebase. * [Tracing code](tutorials/TracingCode.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..3278649c498 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,7 +3,27 @@ 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. +Cobb is a **Contact and Property Management System** that aims to help property agents manage their customer +base and properties, as well as match and gain actionable insights from stored data. + +As property agents, you can make use of Cobb's flexible **filtering and sorting** systems to understand key demographics of your customer base. +Leverage on Cobb's **finding** system to quickly locate buyers that you want to retrieve information about. +Finally, make use of Cobb's **matching** systems to match-make buyers and properties or vice-versa, boosting sales potential. + +The only tools you need to make use of the full suite of capabilities Cobb has to offer are your hands and a keyboard. + +This **user guide** aims to provide you with an in-depth overview of how to set up, use, and debug Cobb. +Take a look at the [Command Summary](#command-summary) section for a quick overview of the different commands along with how to use them, +or dive into the [Quick Start](#quick-start) section to get started. + +
:exclamation: **Note:** +Be sure to check out the [Key Definitions](#key-definitions) section of the guide if you are confused +by any of the terms used! +
+ +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents * Table of Contents {:toc} @@ -11,182 +31,570 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo -------------------------------------------------------------------------------------------------------------------- ## Quick start +[Back to top](#table-of-contents) -1. Ensure you have Java `11` or above installed in your Computer. - -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +1. Ensure you have Java `11` or above installed in your Computer. This [link](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A) + (external link to Oracle) provides a step-by-step installation guide for Java, if needed. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +2. Download the latest `cobb.jar` from our [release page](https://github.com/AY2223S1-CS2103T-F12-1/tp/releases). + After clicking into the release page, scroll down slightly until you reach the **Assets** section of the page. This section should look like this:
+ ![Release Page](images/ReleasePage.png) + + Simply click on `cobb.jar`, and Cobb should begin downloading automatically on your computer! + +3. Copy the `cobb.jar` file to the folder you want to use as the _main folder_ for Cobb. All data will be created and stored + within this folder. -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.
+4. Double-click the file to start the app. A window similar to the image below should appear in a few seconds. The application should already contain some sample data, as shown below.
![Ui](images/Ui.png) -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: +Congratulations! Cobb is now set up and ready to work on your system. + +If you encounter any bugs during the setup process, please check out the [FAQ](#faq) section of this guide, which hopefully +contains some information that can help you diagnose your issue. +
+ +
:exclamation: **Caution (for advanced users):** +On first launch, Cobb will create a few files that have the extension `.json` in its *home directory*. These files are used +by Cobb to store its data. **Edit these at your own risk**, as Cobb will start with an empty database if it detects any +error in the formatting of the data in these files. +
+
+ +-------------------------------------------------------------------------------------------------------------------- + +## Key definitions +[Back to top](#table-of-contents) + +This section aims to define some terms that pop up throughout the user guide. These terms are used often, so do take note of them +before continuing! + +### Command-specific terms +1. **Command**: An instruction that you can type into the [command input box](#1-command-input-and-output-boxes) to do +something in Cobb. Refer to [Features](#features) for the list of commands. +2. **Syntax**: Denotes what you should include in your command. Most commands require specific flags and inputs to work. +3. **Flag**: Comes before (and indicates) an input to a command. For example, in the command `filterbuyers -pr NORMAL`, `-pr` is the flag and `NORMAL` is the input. + +### Buyer-specific terms +1. **Price Range**: The price range of properties that a buyer would consider buying. That is, the buyer would consider + any property whose price falls within this range. +2. **Characteristics**: The characteristics of properties that a buyer desires. For example, a buyer that has characteristics + `bright; sunny` is ideally looking for a property that is `bright` and `sunny`. +3. **Priority**: The priority that you assign to the buyer. Can be `LOW`, `NORMAL` or `HIGH`. +4. **Time of Creation**: The time at which you added the buyer to the database with the [Add Buyer Command](#adding-a-buyer-to-the-database-addbuyer). + +### Property-specific terms +1. **Characteristics**: The characteristics associated with a property. For example, a property that has characteristics + `windy; roomy` is both `windy` and `roomy`. +2. **Owner Name**: The name of the owner of the property. +3. **Owner Phone**: The phone number of the owner of the property. +4. **Time of Creation**: The time at which you added the property to the database with the [Add Property Command](#adding-a-property-to-the-database-addprop). + +-------------------------------------------------------------------------------------------------------------------- + +## Interface Layout +[Back to top](#table-of-contents) + +When you launch Cobb, Cobb will appear on your screen as a window. Let's take a look at the 4 different components +that make up this window. + +### 1. Command Input and Output Boxes +These boxes are located at the top section of the window.

+![CommandBox](images/CommandBox.png)

+The **command input box** is located where the placeholder text `Enter command here...` is.
+Clicking on it will allow you to type commands for Cobb to execute. + +The **command output box** is located directly beneath the **command input box**. Upon execution of any command, Cobb will +display some information regarding the command, regardless of whether the command is successfully or not successfully executed. +In the image above, it is displaying the message "Listed all buyers", the message shown after successfully executing +the [List Buyers Command](#list-buyers-in-database-listbuyers). + +
:exclamation: **Note:** +If a command is not successfully executed, the text within the command input box will turn red. +
+ +After clicking on the **command input box**, you can use the up and down arrow keys to navigate the commands you have entered before. +A command is considered as entered after you press the Enter key and if it is successfully executed. +* The **up arrow** brings back a previously entered command into the command input box. +* The **down arrow** does the reverse of the up arrow, bringing back a more recently entered command. +These help you type many similar commands quickly as you can simply edit a previously entered command instead of having to type the +whole command out again. + +Here are some commands you can test to start with. + +* **`listbuyers`** : Lists all buyers in the database. + +* **`addbuyer -n Tim Cook -ph 91234567 -e cook@apple.com -r 1000000-2500000 -a 10 lorong street avenue -c bright; sunny`** : +Adds a buyer named "Tim Cook" with a specified phone number, email and address to the database. +This buyer has a specified price range, and desired characteristics for the property he wants to buy. + +* **`deletebuyer 1`** : Deletes a buyer at index 1 of the [buyer list](#2-buyer-list) from the database. + +* Press the **up arrow** key once after clicking on the command input box. This brings back **`deletebuyer 1`** into the command input box. + You can edit this to a similar command such as `deletebuyer 2` before pressing the Enter key. + +* **`help`** : Displays a help window. + +* **`exit`** : Exits the application. + +You can refer to the [Features](#features) below for the details of each command. + +### 2. Buyer List +You can find the buyer list located at the left section of the window.

+

+The buyer list displays information regarding buyers who are currently stored in Cobb's database. - * **`list`** : Lists all contacts. +Note that it might not be showing *all* the buyers in the database all the time (check out the [FAQ](#faq) for more information). - * **`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. +You can also filter and modify the buyer list using the commands given in the [Features](#features) section below. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +### 3. Property List +You can find the property list located at the right section of the window.

+

+The property list displays information regarding properties that are currently stored in Cobb's database. - * **`clear`** : Deletes all contacts. +Note that it might not be showing *all* the properties in the database all the time (check out the [FAQ](#faq) for more information). - * **`exit`** : Exits the app. +You can also filter and modify the property list using the commands given in the [Features](#features) section below. -1. Refer to the [Features](#features) below for details of each command. +### 4. Help Window +This will appear as a separate window. +![Help Window](images/HelpWindow.png) +The __help window__ displays a link to Cobb's User Guide, which is the online version of this document. :) + +It appears when you execute the [Help Command](#viewing-help-help). -------------------------------------------------------------------------------------------------------------------- ## Features +[Back to top](#table-of-contents) + +Cobb's features are mostly in the form of commands you can input into the [command input box](#1-command-input-and-output-boxes). We will now go into the details about each feature of Cobb. +If you just want a quick summary of all the feature Cobb has, do take a look at the [command summary](#command-summary) section.
-**:information_source: Notes about the command format:**
+**:information_source: Notes about the command syntax:**
+ +* The first word in the command text specifies which command you are using. For example, `help` specifies that you +are using the `help` command, while `editbuyer 1 -n Jane Doe` specifies that you are using the `editbuyer` command. + + +* To specify inputs to the command, type the corresponding input's flag followed by a space, and then the value of your input.
+ e.g. `-n John Doe` will store the input value `John Doe` for the name flag, as indicated by `-n`. More details about the flags for each command will be given in each section below. + + +* Inputs not contained in any brackets must be passed into the command.
+ e.g. `-n NAME` means that a `-n NAME` input must be specified. + + +* Inputs contained in square brackets `[]` are optional.
+ e.g. `[-c CHARACTERISTICS]` means that the `-c CHARACTERISTICS` input is optional. + + +* Inputs that contain angled braces `<>` can only take any one of the values specified within the braces.
+ e.g. `-pr PRIORITY` means that the `PRIORITY` input can only take values `HIGH`, `NORMAL` or `LOW`. + + +* Inputs can be in any order.
+ e.g. if the command specifies `[-n NAME] [-ph PHONE]`, `[-ph PHONE] [-n NAME]` is also acceptable. + + +* For commands immediately followed by an `INDEX`, `INDEX` refers to the index of the entry on the + [Buyer List](#2-buyer-list) or [Property List](#3-property-list). + * Note that if an entry is not currently displayed on the list (check out the [FAQ](#faq) for the reason that this happens), + then the command cannot be executed on it, + e.g. `deletebuyer 7` if the buyer list is only 5 entries long. + + +* If commands are missing specific inputs required for it to execute, an error message will be displayed that contains + information about the syntax of the command and its required inputs. +
+ +### Add Commands +#### Adding a buyer to the database: `addbuyer` + +Adds a buyer to your database with relevant buyer information.
+Syntax: `addbuyer -n NAME -ph PHONE -e EMAIL -a ADDRESS [-r PRICE RANGE] [-c CHARACTERISTICS] [-pr PRIORITY]` + +The `-n` flag indicates the buyer's name.
+The `-ph` flag indicates the buyer’s phone number.
+The `-e` flag indicates the buyer’s email.
+The `-a` flag indicates the buyer’s home address.
+The `-r` flag indicates the price range of properties that the buyer is willing to accept. A price range must take the form `abc` - `xyz`, where `abc` and `xyz` are numbers.
+The `-c` flag indicates the characteristics that the buyer is looking for in a property, each characteristic should be separated by `;`.
+The `-pr` flag indicates the priority of the buyer. + +
:bulb: **Tip:** +The price range, characteristics and priority fields are optional.
+Only the price range and characteristics fields can be set as "Not Specified" by simply omitting their flags (i.e. `-c` or `-r`), or +by entering the flag with an empty input following it e.g. `-c `.
+The priority field will default to "Normal" if the priority flag is not used. *It cannot take in an empty input if the flag is used*. +
+ + +
:bulb: **Note:** +To ensure that your database remains neat, Cobb will warn you when you try to add duplicate buyers that have the same phone number or email. +
+ + +Examples:
+`addbuyer -n Tim -ph 87321237 -e tim@gmail.com -a S648234 -pr HIGH`: Adds a buyer named Tim who has high priority.
+`addbuyer -n Jane -ph 89991237 -e jane@gmail.com -a S123456 -r 200000-500000 -c bright; 5-room`: Adds a buyer named Jane who is looking for a "5-room" property that is "bright" and costs between $200000 - $500000. + +#### Adding a property to the database: `addprop` + +Adds a property to the database along with relevant information.
+Syntax: `addprop -n NAME -p PRICE -a ADDRESS -d DESCRIPTION -o OWNER NAME -ph OWNER PHONE [-c CHARACTERISTICS]` + +The `-n` flag indicates the property's name.
+The `-p` flag indicates the property’s price.
+The `-a` flag indicates the property’s address.
+The `-d` flag indicates the property’s description.
+The `-o` flag indicates the name of the property owner.
+The `-ph` flag indicates the phone number of the property owner.
+The `-c` flag indicates the characteristics associated with the property, each characteristic separated by `;`.
+ +
:bulb: **Tip:** +The characteristics field is optional and can be set as "Not Specified" by simply not including the `-c` flag, or +by entering the flag with an empty input following it e.g. `-c `. +
+ + +
:bulb: **Note:** +Similar to adding a duplicate buyer, Cobb will warn you when you try to add duplicate properties that have the same address. +
+ + +Example:
+`addprop -n Peak Residences -a 333 Thompson Road -p 1000000 -d Long property description -o Bob -ph 91234567 -c Toa Payoh; Bright`: +Adds a property called "Peak Residences" owned by Bob with a phone number of 91234567. It has the characteristics "Toa Payoh" and "Bright". + +### Delete Commands +#### Deleting a buyer from the database: `deletebuyer` + +Deletes the buyer at the specified index in the [Buyer List](#2-buyer-list).
+Syntax: `deletebuyer INDEX` + +Example:
+`deletebuyer 5`: Deletes the fifth buyer currently visible in the buyer list. + +#### Deleting a property from the database: `deleteprop` + +Deletes the property at the specified index in the [Property List](#3-property-list).
+Syntax: `deleteprop INDEX` + +Example:
+`deleteprop 5`: Deletes the fifth property currently visible in the property list. + +### Edit Commands +#### Edit a buyer entry in the database: `editbuyer` + +Edits a buyer’s details with new information, by specifying the relevant inputs to edit.
+Syntax: `editbuyer INDEX [-n NAME] [-ph PHONE] [-e EMAIL] [-a ADDRESS] [-r PRICE RANGE] [-c CHARACTERISTICS] [-pr PRIORITY]` + +The `INDEX` indicates the buyer in the [Buyer List](#2-buyer-list) to be edited.
+The `-n` flag indicates the buyer's new name.
+The `-ph` flag indicates the buyer's new phone number.
+The `-e` flag indicates the buyer's new email.
+The `-a` flag indicates the buyer's new home address.
+The `-r` flag indicates the new price range of properties that the buyer can accept.
+The `-c` flag indicates the new characteristics that the buyer is looking for in a property.
+The `-pr` flag indicates the new priority of the buyer. + +
:bulb: **Tip:** +Notice how all the flags are optional, and you can choose to edit any number of them at once.
+Only the price range and characteristics fields can be reset by entering their flags with an empty input following it e.g. `-c `.
+
+ +Examples:
+`editbuyer 3 -n John Doe -e johndoe@yahoo.com -r 40000-50000 -pr HIGH`: Edits buyer at index 3 to have a new name "John Doe", new email "johndoe@yahoo.com", new acceptable price range of $40000 - $500000, and a high priority.
+`editbuyer 1 -c bright; sunny`: Edits buyer at index 1 to have new desired characteristics of "bright" and "sunny". + +#### Edit a property entry in database: `editprop` + +Edits a property’s details with new information in specified categories.
+Syntax: `editprop INDEX [-n NAME] [-p PRICE] [-a ADDRESS] [-d DESCRIPTION] [-o OWNER NAME] [-ph OWNER PHONE] [-c CHARACTERISTICS]` + +The `INDEX` indicates the property in the [Property List](#3-property-list) to be edited.
+The `-n` flag indicates the property's new name.
+The `-p` flag indicates the property’s new price.
+The `-a` flag indicates the property’s new address.
+The `-d` flag indicates the property’s new description.
+The `-o` flag indicates the property's owner's new name.
+The `-ph` flag indicates the property's owner's new phone number.
+The `-c` flag indicates the property's new characteristics.
+ +
:bulb: **Tip:** +Only the characteristics fields can be edited to "Not Specified" by entering its flag and an empty input following it e.g. `-c `. +
+ +Example:
+`editprop 3 -n Hill Residence -a Block 225 -p 750000`: Edits property at index 3 of the list to have a new name Hill Residence, a new address Block 225, and new price of $750000. + +### List Commands + +
:exclamation: **Note:** +These commands can be used to view all buyers and properties again, after a [Filter Command](#filter-commands), +[Find Command](#find-commands), or [Match Command](#match-commands) is executed. You can check out the [FAQ](#faq) for more information on this. +
+ +#### List buyers in database: `listbuyers` + +Updates the [Buyer List](#2-buyer-list) to show all buyers in your database, that is, removes all filters you have applied.
+Syntax: `listbuyers` + +#### List properties in database: `listprops` -* 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`. +Updates the [Property List](#3-property-list) to show all properties in your database, that is, removes all filters you have applied.
+Syntax: `listprops` + +
+
+ +### Find 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`. +
:exclamation: **Note:** +Using these commands cause the [Buyer List](#2-buyer-list) and [Property List](#3-property-list) to only show a portion of +buyers and properties in the database. Please use the [List Commands](#list-commands) if you want to show all buyers and properties again. -* 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. +Also, no flags are used for these commands and so the search phrase is to be supplied directly. +
+ + +#### Find buyer entry in database: `findbuyers` + +Searches through your database and displays all buyers whose names contain the given phrase (case-insensitive).
+Syntax: `findbuyers PHRASE` + +Examples:
+`findbuyers John`: Looks for all buyers that have “John” in their name.
+`findbuyers John T`: Looks for all buyers that have the phrase "John T" in their name. -* 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. +#### Find property entry in database: `findprops` -* 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. +Searches through your database and displays all properties whose names contain the given phrase (case-insensitive).
+Syntax: `findprops PHRASE` -* 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`. +Examples:
+`findprops Peak`: Looks for all properties that have “Peak” in their name.
+`findprops Peak Residence Hut`: Looks for all properties that have the phrase "Peak Residence Hut" in their name. +### Filter Commands + +
:exclamation: **Note:** +Using commands might cause the [Buyer List](#2-buyer-list) and [Property List](#3-property-list) to only show a portion of +buyers and properties in the database. Please use the [List Commands](#list-commands) if you want to show all buyers and properties again.
-### Viewing help : `help` +#### Filter buyers in database (multiple conditions): `filterbuyers` + +Filters buyers in your database according to multiple given conditions, and updates the [Buyer List](#2-buyer-list). -Shows a message explaning how to access the help page. +Syntax: `filterbuyers [-p PRICE] [-c CHARACTERISTICS] [-pr PRIORITY] [-fuzzy]` -![help message](images/helpMessage.png) +The `-p` flag filters buyers with a price range containing the specified price.
+The `-c` flag filters buyers that have **ALL** the specified `;`-separated characteristics.
+The `-pr` flag filters buyers according to the specified priority level.
+The `-fuzzy` flag indicates that fuzzy filtering will be applied: +- filtered buyers will only need to satisfy **one** of the conditions supplied (if there are more than one) +- the `-c` flag will filter buyers that have **at least one** of the specified `;`-separated characteristics rather than all -Format: `help` +
:bulb: **Tip:** +By default, if multiple conditions are provided, the filter command will filter buyers who match ALL the conditions, unless the `fuzzy` flag is provided.
+The `-c` flag will take in `;`-separated characteristics. This means that if you supply the following input: `filterbuyers -c bright; sunny -fuzzy`, +Cobb will match buyers that have either `bright` or `sunny` in their characteristics, that is, these two characteristics +are taken as individual characteristics. +
-### Adding a person: `add` +Examples:
+`filterbuyers -p 500000 -c bright; sunny -pr HIGH`: Filters all buyers that have a price range containing $500000 *AND* desired characteristics of bright *AND* sunny *AND* a `HIGH` priority.
+`filterbuyers -p 500000 -c bright; sunny -pr HIGH -fuzzy`: Filters all buyers that have a price range containing $500000 *OR* desired characteristics of bright *OR* sunny *OR* a `HIGH` priority. -Adds a person to the address book. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +#### Filter property in database (multiple conditions): `filterprops` + +Filters properties in your database according to multiple given conditions, and updates the [Property List](#3-property-list). +Syntax: `filterprops [-r PRICE RANGE] [-c CHARACTERISTICS] [-o OWNER NAME] [-fuzzy]` + +The `-r` flag filters properties with a price within the specified price range.
+The `-c` flag filters properties that have **ALL** the specified ";"-separated characteristics.
+The `-o` flag filters properties that have the specified owner.
+The `-fuzzy` flag indicates that fuzzy filtering will be applied: +- filtered properties will only need to satisfy **one** of the conditions supplied (if there are more than one) +- the `-c` flag will filter properties that have **at least one** of the specified `;`-separated characteristics rather than all.
:bulb: **Tip:** -A person can have any number of tags (including 0) +Please see the tips above for more information regarding the `-c` and `-fuzzy` flags.
-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` +Examples:
+`filterprops -r 500000-1000000 -c bright; sunny -o GARY`: Filters all properties that have a price in the range $500000 - $1000000 *AND* have characteristics of bright *AND* sunny *AND* is owned by Gary.
+`filterprops -r 500000-1000000 -c bright; sunny -o GARY -fuzzy`: Filters all properties that have a price in the range $500000 - $1000000 *OR* have characteristics of bright *OR* sunny *OR* is owned by Gary. + +### Sort Commands + +
:bulb: **Note:** +Note that you can only specify **one input** for these commands, as Cobb can only sort by **one** particular condition. +
-### Listing all persons : `list` +#### Sort buyers in database: `sortbuyers` -Shows a list of all persons in the address book. +Sorts buyers in your database according to a single given condition, and updates the [Buyer List](#2-buyer-list). +Syntax: `sortbuyers [-n NAME] [-r PRICE RANGE] [-pr PRIORITY] [-t TIME OF CREATION]` -Format: `list` +The `-n` flag indicates to sort buyers by name in ascending or descending order.
+The `-r` flag indicates to sort buyers by price range in ascending order (by lower bound) or descending order (by upper bound).
+The `-pr` flag indicates to sort buyers by priority level in ascending or descending order.
+The `-t` flag indicates to sort buyers by time of creation in ascending or descending order.
-### Editing a person : `edit` +Examples:
+`sortbuyers -pr DESC`: Sorts buyers from `HIGH` priority level to `LOW` priority level.
+`sortbuyers -r ASC`: Sorts buyers according to the lower price bound in ascending order.
+`sortbuyers -t ASC`: Sorts buyers according to the time of entry from least recent to most recent. -Edits an existing person in the address book. +#### Sort properties in database: `sortprops` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Sorts properties in your database according to a single given condition, and updates the [Property List](#3-property-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. +Syntax: `sortprops [-n NAME] [-p PRICE] [-t TIME OF CREATION]` -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. +The `-n` flag indicates to sort properties by name in ascending or descending order.
+The `-p` flag indicates to sort properties by price in ascending or descending order.
+The `-t` flag indicates to sort properties by time of creation in ascending or descending order. -### Locating persons by name: `find` +Examples:
+`sortprops -p DESC`: Sorts properties from highest to lowest price.
+`sortprops -t ASC`: Sorts properties by time of entry from least recent to most recent. -Finds persons whose names contain any of the given keywords. +### Match Commands -Format: `find KEYWORD [MORE_KEYWORDS]` +
:exclamation: **Note:** +These commands might cause the [Buyer List](#2-buyer-list) and [Property List](#3-property-list) to only show a portion of +buyers and properties in the database. You can use the [List Commands](#list-commands) to show all buyers and properties again. +
-* 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` +#### Match specified buyer to properties: `matchbuyer` -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +Intelligently matches a buyer in your database to all properties that are suitable for the buyer. +These properties must be within the buyer's price range and satisfy at least one of the buyer's characteristics to be considered suitable. -### Deleting a person : `delete` +
:exclamation: **Note:** +Any properties with price lower than the lower bound of the buyer's price range will be ignored since the match command looks for the most suitable properties for the buyer, instead of all properties that the buyer can afford. +
-Deletes the specified person from the address book. +Syntax: `matchbuyer INDEX [-strict]` -Format: `delete INDEX` +The `INDEX` indicates the buyer in the [Buyer List](#2-buyer-list) to be matched.
+The `-strict` flag indicates to reduce the matches to only properties that match *all* of the buyer's characteristics.
-* 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, …​ +Examples:
+`matchbuyer 5 -strict`: Matches buyer 5 to existing properties in the database based on price range and *all* desired characteristics. -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. +#### Match specified property to buyers: `matchprop` -### Clearing all entries : `clear` +Intelligently matches a property in your database to all buyers who might be interested in that property. +The property's price must be within the buyer's price range and the buyer should desire at least one of the property's characteristics. -Clears all entries from the address book. +
:exclamation: **Note:** +Any buyers with price range higher than the property's price will be ignored since the match command looks for the most suitable buyers for the property, instead of all buyers that can afford the property. +
-Format: `clear` +Syntax: `matchprop INDEX [-strict]` -### Exiting the program : `exit` +The `INDEX` indicates the property in the [Property List](#3-property-list) to be matched.
+The `-strict` flag indicates to reduce the matches to only buyers that desire *all* the property's characteristics.
-Exits the program. +Examples:
+`matchprop 5`: Matches property 5 to existing buyers in the database based on price and *at least one* characteristic. -Format: `exit` +### Clearing the program: `clear` + +Clears Cobb (deletes all entries in your Buyer List and Property List). + +Syntax: `clear` + +### Exiting the program: `exit` + +Exits Cobb (closes the program). + +Syntax: `exit` + +### Viewing help: `help` + +Displays a window containing a link for you to access Cobb's User Guide for further help. + +Syntax: `help` ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +Cobb's data is saved for you automatically after any command that changes the data. There is no need to save manually. ### Editing the data file -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +Cobb's data is saved as 2 separate JSON files `[JAR file location]/data/buyerbook.json` and `[JAR file location]/data/propertybook.json`. If you are an advanced user, you are welcome to update data directly by editing these data files.
: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. +If your changes to a data file make its format invalid, Cobb will discard all data and start with a sample data file on the next run.
-### Archiving data files `[coming in v2.0]` - -_Details coming soon ..._ - -------------------------------------------------------------------------------------------------------------------- ## FAQ +[Back to top](#table-of-contents) + +**Q**: Why am I not seeing some properties and buyers in my property and buyer lists?
+**A**: You might be looking at a filtered portion of the lists. This is because you previously executed a +[Filter Command](#filter-commands) or a [Find Command](#find-commands) or a [Match Command](#match-commands). To view all buyers and properties again, +use the [List Commands](#list-commands). + +**Q**: How do I transfer my data to another computer?
+**A**: Install the app in the other computer and overwrite the empty data files it creates with the files that contain your original data (found in Cobb's original data folder). +Alternatively, copy and paste the data file with the *same name* from your old computer. + +**Q**: Help! I can't seem to get a command to work...
+**A**: Please refer to the [features](#features) section of our guide for command information and syntax. Make sure that you have supplied all necessary inputs for the command and specified the flags in a correct manner! + +**Q**: How do I run the app if double-clicking the jar file does nothing?
+**A**: If you are experiencing this problem, there is a high likelihood that Java has not been installed correctly on your computer. +Please take a look at the [quick start](#quick-start) section of the guide for more setup information! For the more technically inclined, try running this command in the jar file's home directory: `java -jar cobb.jar`
+If the problem persists, please report the bug to us. + +**Q**: I deleted my data file! Is there any way to recover the data that I lost?
+**A**: Try looking in your computer's trash bin on macOS or recycle bin on Windows for the files that were deleted. If the files can't be found, then we apologise, but there is currently +no way for you to retrieve lost data. :( + +**Q**: How do I uninstall Cobb?
+**A**: We are sad to see you go :( Cobb is not installed onto your hard drive, so you only need to delete the folder that contains `cobb.jar` (that is, the *home folder* of Cobb). -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**Q**: I don't understand some terms used in the guide...
+**A**: Please check out the [key definitions](#key-definitions) portion of the guide and see if the term that you are confused about is documented there! -------------------------------------------------------------------------------------------------------------------- -## Command summary - -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +## Command Summary +[Back to top](#table-of-contents) + +| Action | Format, Examples | +|-----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Add buyer](#adding-a-buyer-to-the-database-addbuyer) | `addbuyer -n NAME -ph PHONE -e EMAIL -a address [-r PRICE RANGE] [-c CHARACTERISTICS] [-pr PRIORITY]`
e.g. `addbuyer -n Tim -ph 87321237 -e tim@gmail.com -a S648234 -pr HIGH` | +| [Add property](#adding-a-property-to-the-database-addprop) | `addprop -n NAME -p PRICE -a ADDRESS -d DESCRIPTION -o OWNER NAME -ph OWNER PHONE [-c CHARACTERISTICS]`
e.g.`addprop -n Peak Residences -a 333 Thompson Road -p 1000000 -d long property description -o Bob -ph 91234567 -c Toa Payoh; Bright` | +| [Delete buyer](#deleting-a-buyer-from-the-database-deletebuyer) | `deletebuyer INDEX`
e.g. `deletebuyer 5` | +| [Delete property](#deleting-a-property-from-the-database-deleteprop) | `deleteprop INDEX`
e.g. `deleteprop 5` | +| [Edit buyer](#edit-a-buyer-entry-in-the-database-editbuyer) | `editbuyer INDEX [-n NAME] [-ph PHONE] [-e EMAIL] [-a ADDRESS] [-r PRICE RANGE] [-c CHARACTERISTICS] [-pr PRIORITY]`
e.g. `editbuyer 3 -n John Doe -e johndoe@yahoo.com -r 40000-50000 -pr HIGH` | +| [Edit property](#edit-a-property-entry-in-database-editprop) | `editprop INDEX [-n NAME] [-p PRICE] [-a ADDRESS] [-d DESCRIPTION] [-c CHARACTERISTICS] [-o OWNER NAME] [-ph OWNER PHONE]`
e.g. `editprop 3 -n Hill Residence -a Block 225 -ph 82000100` | +| [List buyers](#list-buyers-in-database-listbuyers) | `listbuyers` | +| [List properties](#list-properties-in-database-listprops) | `listprops` | +| [Find buyers](#find-buyer-entry-in-database-findbuyers) | `findbuyers PHRASE`
e.g. `findbuyers John` | +| [Find properties](#find-property-entry-in-database-findprops) | `findprops PHRASE`
e.g. `findprops Heng Mui Keng` | +| [Filter buyers](#filter-buyers-in-database-multiple-conditions-filterbuyers) | `filterbuyers [-p PRICE] [-c CHARACTERISTICS] [-pr PRIORITY] [-fuzzy]`
e.g. `filterbuyers -p 500000 -c bright; sunny -pr HIGH -fuzzy` | +| [Filter properties](#filter-property-in-database-multiple-conditions-filterprops) | `filterprops [-r PRICE RANGE] [-c CHARACTERISTICS] [-o OWNER NAME] [-fuzzy]`
e.g. `filterprops -r 500000-1000000 -c bright; sunny -o GARY -fuzzy` | +| [Sort buyers](#sort-buyers-in-database-sortbuyers) | `sortbuyers [-n NAME] [-r PRICE RANGE] [-pr PRIORITY] [-t TIME OF CREATION]`
e.g. `sortbuyers -pr DESC` | +| [Sort properties](#sort-properties-in-database-sortprops) | `sortprops [-n NAME] [-p PRICE] [-t TIME OF CREATION]`
e.g. `sortprops -p DESC` | +| [Match buyer](#match-specified-buyer-to-properties-matchbuyer) | `matchbuyer INDEX [-strict]`
e.g. `matchbuyer 1 -strict` | +| [Match property](#match-specified-property-to-buyers-matchprop) | `matchprop INDEX [-strict]`
e.g. `matchprop 1` | +| [Clear](#clearing-the-program-clear) | `clear` | +| [Exit Cobb](#exiting-the-program-exit) | `exit` | +| [Get help](#viewing-help-help) | `help` | + diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..869d52edafb 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Cobb" theme: minima header_pages: @@ -13,3 +13,4 @@ github_icon: "images/github-icon.png" plugins: - jemoji + diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..cac22f62c21 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "Cobb - Property Address Book"; font-size: 32px; } } diff --git a/docs/diagrams/AddBuyerActivityDiagram.puml b/docs/diagrams/AddBuyerActivityDiagram.puml new file mode 100644 index 00000000000..f270aac0f3f --- /dev/null +++ b/docs/diagrams/AddBuyerActivityDiagram.puml @@ -0,0 +1,37 @@ +@startuml +start +:User executes add buyer command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([User provided all required parameters]) +:Buyer's name, phone, email and address are parsed; +:New entryTime is created for the buyer; +if () then ([priceRange provided]) +:Buyer's priceRange is parsed; +note left + A buyer's priceRange is optional +end note +else([else]) +endif +if () then ([characteristics provided]) +:Buyer's characteristics is parsed; +note left + A buyer's characteristics are optional +end note +else([else]) +endif +if () then ([priority provided]) +:Buyer's priority is parsed; +note left + A buyer's priority is optional +end note +else([else]) +endif +#palegreen:New Buyer is returned; +stop +else([User failed to provide required parameters correctly]) +#pink:Cobb displays an error; +end +@enduml diff --git a/docs/diagrams/AddBuyerObjectDiagram.puml b/docs/diagrams/AddBuyerObjectDiagram.puml new file mode 100644 index 00000000000..34f55c477a9 --- /dev/null +++ b/docs/diagrams/AddBuyerObjectDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Final state + +class AddBuyerCommand as "__:AddBuyerCommand__" + +package Buyer { + class Buyer as "__:Buyer__" + class Name as "__name:Name__" + class Address as "__address:Address__" + class Priority as "__priority:Priority__" + class LocalDateTime as "__entryTime:LocalDateTime__" + class Phone as "__phone:Phone__" + class Email as "__email:Email__" + class PriceRange as "__priceRange:PriceRange__" +} + +AddBuyerCommand --> Buyer + +Buyer --> Name +Buyer --> Address +Buyer --> LocalDateTime +Buyer --> Phone +Buyer --> Priority +Buyer --> Email +Buyer --> PriceRange + +Name -[hidden]> Address +Address -[hidden]> LocalDateTime +Email -[hidden]> LocalDateTime +Phone -[hidden]> LocalDateTime +@end diff --git a/docs/diagrams/AddPropertyActivityDiagram.puml b/docs/diagrams/AddPropertyActivityDiagram.puml new file mode 100644 index 00000000000..78425ee5880 --- /dev/null +++ b/docs/diagrams/AddPropertyActivityDiagram.puml @@ -0,0 +1,26 @@ + +@startuml +start +:User executes add property command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([User provided all required parameters]) +:Property's name, price, address, description,\n ownerName and ownerPhone are parsed; +:New entryTime is created for the property; +if () then ([characteristics provided]) +:Property's characteristics is parsed; +note left + For a property, only its + characteristics are optional. +end note +else([else]) +endif +#palegreen:New Property is returned; +stop +else([User failed to provide required parameters correctly]) +#pink:Cobb displays an error; +end +@enduml + diff --git a/docs/diagrams/AddPropertyObjectDiagram.puml b/docs/diagrams/AddPropertyObjectDiagram.puml new file mode 100644 index 00000000000..9506439cbf7 --- /dev/null +++ b/docs/diagrams/AddPropertyObjectDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 + +title Final state + +class AddPropertyCommand as "__:AddPropertyCommand__" + +package Property { + class Property as "__:Property__" + class PropertyName as "__propertyName:PropertyName__" + class Address as "__address:Address__" + class LocalDateTime as "__propertyEntryTime:LocalDateTime__" + class Price as "__price:Price__" + package Owner { + class Owner as "__owner:Owner__" + class Phone as "__phone:Phone__" + } + +} + + +AddPropertyCommand --> Property + +Property --> PropertyName +Property --> Address +Property --> LocalDateTime +Property --> Price +Property --> Owner +Owner --> Phone +PropertyName -[hidden]> Address +Address -[hidden]> LocalDateTime +Phone -[hidden]> LocalDateTime +@end diff --git a/docs/diagrams/AlternativeBuyerAndProperty.puml b/docs/diagrams/AlternativeBuyerAndProperty.puml new file mode 100644 index 00000000000..6277a2a08b3 --- /dev/null +++ b/docs/diagrams/AlternativeBuyerAndProperty.puml @@ -0,0 +1,39 @@ +@startuml +'https://plantuml.com/class-diagram + +class Buyer { +-String name +-String phone +-String address +-String email +-LocalDateTime entryTime +-String priority +-String price +-String[] characteristicsArray + ++ boolean isSameBuyer(Buyer otherBuyer) ++ boolean isValidPrice(String test) ++ boolean isValidPriceRange(String test) ++ boolean isWithinPriceRange(String price) ++ boolean isValidCharacteristics(String test) ++ boolean containsCharacteristic(String characteristic) +} + +class Property { +-String propertyName +-String address +-String description +-String ownerName +-String ownerPhone +-LocalDateTime propertyEntryTime +-String price +-String characteristicsArray + ++ boolean isSameProperty(Property otherProperty) ++ boolean isGreaterThanOrEqual(String price) ++ boolean isSmallerThanOrEqual(String price) ++ boolean isValidCharacteristic(String test) ++ boolean containsCharacteristic(String characteristic) +} + +@enduml diff --git a/docs/diagrams/ArchitectureSequence.puml b/docs/diagrams/ArchitectureSequence.puml new file mode 100644 index 00000000000..90ba9604133 --- /dev/null +++ b/docs/diagrams/ArchitectureSequence.puml @@ -0,0 +1,35 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam sequence { +ParticipantFontColor #white +ParticipantBorderColor #red + +LifeLineBorderColor #firebrick +} + +hide footbox +actor User #black + +participant ":UI" as UI #green +participant ":Logic" as Logic #blue +participant ":Model" as Model #firebrick +participant ":Storage" as Storage #orange + +User -> UI : "deletebuyer 1" +activate UI #green +UI -> Logic : execute("deletebuyer 1") +activate Logic #blue +Logic -> Model : deleteBuyer(target) +activate Model #firebrick +return +Logic -> Storage : saveBuyerBook(buyerBook) +activate Storage #orange +Storage -> Storage ++ #GoldenRod :Save to file +return +return +return +return + + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..510ed34bc56 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,19 +7,19 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "deletebuyer 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("deletebuyer 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteBuyer(target) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveBuyerBook(buyerBook) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 5731f9cbaa1..ef47f352387 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,27 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +Cobb *-down-> "1" BuyerBook +Cobb *-down-> "1" PropertyBook +BuyerBook *-down-> "1" UniqueBuyerList +PropertyBook *-down-> "1" UniquePropertyList -UniqueTagList *-right-> "*" Tag -UniquePersonList -right-> Person +UniqueBuyerList -down-> "*" Buyer +UniquePropertyList -down-> "*" Property -Person -up-> "*" Tag - -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address +Buyer *--> "1" Name +Buyer *--> "1" Phone +Buyer *--> "1" Priority +Buyer *--> "1" Email +Buyer *--> "0..1" PriceRange +Buyer *--> "1" Address +Buyer *--> "0..1" Characteristics +Buyer *--> "1" LocalDateTime +Property *--> "1" Address +Property *--> "0..1" Characteristics +Property *--> "1" LocalDateTime +Property *--> "1" PropertyName +Property *--> "1" Description +Property *--> "1" Price +Property *--> "1" Owner @enduml diff --git a/docs/diagrams/BuyerClassDiagram.puml b/docs/diagrams/BuyerClassDiagram.puml new file mode 100644 index 00000000000..d3ab5111dcb --- /dev/null +++ b/docs/diagrams/BuyerClassDiagram.puml @@ -0,0 +1,39 @@ +@startuml +'https://plantuml.com/class-diagram + +class Buyer { +-Name name +-Phone phone +-Address address +-Email email +-LocalDateTime entryTime +-Priority priority + ++ boolean isSameBuyer(Buyer otherBuyer) +} + +class PriceRange { +-Price low +-Price high + ++ boolean isValidPriceRange(String test) ++ boolean isWithinPriceRange(Price p) +} + +class Characteristics { +-String[] characteristicsArray + ++boolean isValidCharacteristics(String test) ++boolean containsCharacteristic(String characteristic) +} + +enum Priority { +LOW +NORMAL +HIGH +} + +Buyer --> " desiredCharacteristics 0..1" Characteristics +Buyer --> "priceRange 0..1" PriceRange + +@enduml diff --git a/docs/diagrams/CommandRetrieverClassDiagram.puml b/docs/diagrams/CommandRetrieverClassDiagram.puml new file mode 100644 index 00000000000..a4881eeb756 --- /dev/null +++ b/docs/diagrams/CommandRetrieverClassDiagram.puml @@ -0,0 +1,23 @@ +@startuml +'https://plantuml.com/class-diagram + +class CommandBox +class CommandRetriever + +CommandBox*--CommandRetriever + +class CommandBox { +-handleCommandEntered() +-handleKeyReleased(KeyEvent e) +} + +class CommandRetriever { +ArrayList commandHistory +int index +String currentCommand ++addCommand() ++getNextCommand() ++getPreviousCommand() +} + +@enduml diff --git a/docs/diagrams/CommandRetrieverSequenceDiagram.puml b/docs/diagrams/CommandRetrieverSequenceDiagram.puml new file mode 100644 index 00000000000..800576e6f5d --- /dev/null +++ b/docs/diagrams/CommandRetrieverSequenceDiagram.puml @@ -0,0 +1,69 @@ + @startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +Actor User as user USER_COLOR +Participant ":UI" as ui UI_COLOR +Participant ":CommandBox" as commandbox UI_COLOR_T1 +Participant ":CommandRetriever" as commandretriever UI_COLOR_T2 +Participant ":TextField" as textfield MODEL_COLOR +Participant ":Logic" as logic LOGIC_COLOR + +user -[UI_COLOR]> ui: "listbuyers" +activate ui UI_COLOR +ui -[UI_COLOR]> commandbox: handleCommandEntered() +activate commandbox UI_COLOR_T1 +commandbox -[UI_COLOR_T1]> logic: execute("listbuyers") +activate logic LOGIC_COLOR +logic -[LOGIC_COLOR]-> commandbox +deactivate logic +commandbox -[UI_COLOR_T1]> commandretriever: addCommand("listbuyers", textfield) +activate commandretriever UI_COLOR_T2 +commandretriever -[UI_COLOR_T2]> textfield: setText("") +activate textfield MODEL_COLOR +textfield -[MODEL_COLOR]-> commandretriever +deactivate textfield +commandretriever -[UI_COLOR_T2]-> commandbox +deactivate commandretriever +commandbox -[UI_COLOR_T1]-> ui +deactivate commandbox +ui -[UI_COLOR]-> user +deactivate ui + +user -[UI_COLOR]> ui: Key released +activate ui UI_COLOR +ui -[UI_COLOR]> commandbox: handleKeyReleased() +activate commandbox UI_COLOR_T1 +alt Key is UP +commandbox -[UI_COLOR_T1]> commandretriever: getPreviousCommand(textfield) +activate commandretriever UI_COLOR_T2 +opt A previous command exists +opt Command is user's currently inputted command +commandretriever -[UI_COLOR_T2]> commandretriever: setCurrentCommand(command) +end +commandretriever -[UI_COLOR_T2]> textfield: setText(previousCommand) +activate textfield MODEL_COLOR +textfield -[MODEL_COLOR]-> commandretriever +deactivate textfield +end +commandretriever -[UI_COLOR_T2]-> commandbox + +else Key is DOWN +commandbox -[UI_COLOR_T1]> commandretriever: getNextCommand(textfield) +opt A next command exists +opt Command is latest command in history +commandretriever -[UI_COLOR_T2]> commandretriever: getCurrentCommand() +end +commandretriever -[UI_COLOR_T2]> textfield: setText(nextCommand) +activate textfield MODEL_COLOR +textfield -[MODEL_COLOR]-> commandretriever +deactivate textfield +end +commandretriever -[UI_COLOR_T2]-> commandbox +deactivate commandretriever +end +commandbox -[UI_COLOR_T1]-> ui +deactivate commandbox +ui -[UI_COLOR]-> user +deactivate ui +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..9cab1fc5c2d 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,66 +3,66 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR -participant ":CommandResult" as CommandResult LOGIC_COLOR +participant ":CobbParser" as CobbParser LOGIC_COLOR +participant ":DeleteBuyerCommandParser" as DeleteBuyerCommandParser LOGIC_COLOR +participant "d:DeleteBuyerCommand" as DeleteBuyerCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR end box box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("deletebuyer 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> CobbParser : parseCommand("deletebuyer 1") +activate CobbParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create DeleteBuyerCommandParser +CobbParser -> DeleteBuyerCommandParser +activate DeleteBuyerCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +DeleteBuyerCommandParser --> CobbParser +deactivate DeleteBuyerCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +CobbParser -> DeleteBuyerCommandParser : parse("1") +activate DeleteBuyerCommandParser -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +create DeleteBuyerCommand +DeleteBuyerCommandParser -> DeleteBuyerCommand +activate DeleteBuyerCommand -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand +DeleteBuyerCommand --> DeleteBuyerCommandParser : d +deactivate DeleteBuyerCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +DeleteBuyerCommandParser --> CobbParser : d +deactivate DeleteBuyerCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +DeleteBuyerCommandParser -[hidden]-> CobbParser +destroy DeleteBuyerCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +CobbParser --> LogicManager : d +deactivate CobbParser -LogicManager -> DeleteCommand : execute() -activate DeleteCommand +LogicManager -> DeleteBuyerCommand : execute(model) +activate DeleteBuyerCommand -DeleteCommand -> Model : deletePerson(1) +DeleteBuyerCommand -> Model : deleteBuyer(buyer) activate Model -Model --> DeleteCommand +Model --> DeleteBuyerCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +DeleteBuyerCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> DeleteBuyerCommand: result deactivate CommandResult -DeleteCommand --> LogicManager : result -deactivate DeleteCommand +DeleteBuyerCommand --> LogicManager : result +deactivate DeleteBuyerCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/EditSequenceDiagram.puml b/docs/diagrams/EditSequenceDiagram.puml new file mode 100644 index 00000000000..55133488d2b --- /dev/null +++ b/docs/diagrams/EditSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":CobbParser" as CobbParser LOGIC_COLOR +participant ":EditBuyerCommandParser" as EditBuyerCommandParser LOGIC_COLOR +participant "e:EditBuyerCommand" as EditBuyerCommand 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("editbuyer 1 -r 10000-50000") +activate LogicManager + +LogicManager -> CobbParser : parseCommand("editbuyer 1 -r 10000-50000") +activate CobbParser + +create EditBuyerCommandParser +CobbParser -> EditBuyerCommandParser +activate EditBuyerCommandParser + +EditBuyerCommandParser --> CobbParser +deactivate EditBuyerCommandParser + +CobbParser -> EditBuyerCommandParser : parse("1 -r 10000-50000") +activate EditBuyerCommandParser + +create EditBuyerCommand +EditBuyerCommandParser -> EditBuyerCommand +activate EditBuyerCommand + +EditBuyerCommand --> EditBuyerCommandParser : e +deactivate EditBuyerCommand + +EditBuyerCommandParser --> CobbParser : e +deactivate EditBuyerCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditBuyerCommandParser -[hidden]-> CobbParser +destroy EditBuyerCommandParser + +CobbParser --> LogicManager : e +deactivate CobbParser + +LogicManager -> EditBuyerCommand : execute() +activate EditBuyerCommand + +EditBuyerCommand -> EditBuyerCommand : createEditedBuyer(buyerToEdit, editBuyerDescriptor) +activate EditBuyerCommand +EditBuyerCommand --> EditBuyerCommand : editedBuyer +deactivate EditBuyerCommand + +EditBuyerCommand -> Model : setBuyer(buyerToEdit, editedBuyer) + +EditBuyerCommand -> Model : updateFilteredBuyerList(PREDICATE_SHOW_ALL_BUYERS) + +create CommandResult +EditBuyerCommand -> CommandResult +activate CommandResult + +CommandResult --> EditBuyerCommand +deactivate CommandResult + +EditBuyerCommand --> LogicManager : result +deactivate EditBuyerCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FilterPredicatesClassDiagram.puml b/docs/diagrams/FilterPredicatesClassDiagram.puml new file mode 100644 index 00000000000..9db8638c926 --- /dev/null +++ b/docs/diagrams/FilterPredicatesClassDiagram.puml @@ -0,0 +1,61 @@ +@startuml +'https://plantuml.com/class-diagram +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class "<>\nPredicate" as Predicate +Class "{abstract}\nAbstractFilterPropsPredicate" as AbstractFilterPropsPredicate { ++ test(Property property) +} +Class "{abstract}\nAbstractFilterBuyersPredicate" as AbstractFilterBuyersPredicate { ++ test(Buyer buyer) +} +class BuyerNameContainsSubstringPredicate { +- String string +} +class FilterBuyersByPricePredicate { +- Price price +} +class FilterBuyersByPriorityPredicate { +- Priority priority +} +class FilterBuyersContainingAllCharacteristicsPredicate { +- Characteristics characteristics +} +class FilterBuyersContainingAnyCharacteristicsPredicate { +- Characteristics characteristics +} +class FilterPropsByOwnerNamePredicate { +- Name name +} +class FilterPropsByPricePredicate { +- Price price +} +class FilterPropsContainingAllCharacteristicsPredicate { +- Characteristics characteristics +} +class FilterPropsContainingAnyCharacteristicsPredicate { +- Characteristics characteristics +} +class PropertyNameContainsSubstringPredicate{ +- String string +} + + +AbstractFilterBuyersPredicate .up.|> Predicate +AbstractFilterPropsPredicate .up.|> Predicate + +BuyerNameContainsSubstringPredicate --l|> AbstractFilterBuyersPredicate +FilterBuyersByPricePredicate --r|> AbstractFilterBuyersPredicate +FilterBuyersByPriorityPredicate --d|> AbstractFilterBuyersPredicate +FilterBuyersContainingAllCharacteristicsPredicate --up|> AbstractFilterBuyersPredicate +FilterBuyersContainingAnyCharacteristicsPredicate --up|> AbstractFilterBuyersPredicate + +FilterPropsByOwnerNamePredicate --l|> AbstractFilterPropsPredicate +FilterPropsByPricePredicate --r|> AbstractFilterPropsPredicate +FilterPropsContainingAllCharacteristicsPredicate --d|> AbstractFilterPropsPredicate +FilterPropsContainingAnyCharacteristicsPredicate --up|> AbstractFilterPropsPredicate +PropertyNameContainsSubstringPredicate --up|> AbstractFilterPropsPredicate +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..b0ae05b67fd 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -6,7 +6,7 @@ skinparam classBackgroundColor LOGIC_COLOR package Logic { -Class AddressBookParser +Class CobbParser Class XYZCommand Class CommandResult Class "{abstract}\nCommand" as Command @@ -27,8 +27,8 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .right.|> Logic -LogicManager -right->"1" AddressBookParser -AddressBookParser ..> XYZCommand : creates > +LogicManager -right->"1" CobbParser +CobbParser ..> XYZCommand : creates > XYZCommand -up-|> Command LogicManager .left.> Command : executes > @@ -38,7 +38,7 @@ LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +note right of XYZCommand: XYZCommand = AddBuyerCommand, \nFindBuyersCommand, etc Logic ..> CommandResult LogicManager .down.> CommandResult diff --git a/docs/diagrams/MatchBuyerCommandSequenceDiagram.puml b/docs/diagrams/MatchBuyerCommandSequenceDiagram.puml new file mode 100644 index 00000000000..20d9461b0f8 --- /dev/null +++ b/docs/diagrams/MatchBuyerCommandSequenceDiagram.puml @@ -0,0 +1,62 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +Participant ":MatchBuyerCommand" as matchCommand LOGIC_COLOR +Participant ":ArrayList" as arrayList USER_COLOR +Participant ":FilterPropsByPricePredicate" as pricePred MODEL_COLOR_T1 +Participant ":FilterPropsContainingAnyCharacteristicPredicate" as charPred MODEL_COLOR_T1 +Participant ":FilterPropertiesCommand" as filterProps LOGIC_COLOR + + +[-[LOGIC_COLOR]> matchCommand: execute(model) +activate matchCommand LOGIC_COLOR + +create arrayList +matchCommand -[LOGIC_COLOR]> arrayList +activate arrayList USER_COLOR +arrayList --[USER_COLOR]> matchCommand +deactivate arrayList + +create pricePred +matchCommand -[LOGIC_COLOR]> pricePred: Buyer's PriceRange +activate pricePred MODEL_COLOR_T1 +pricePred --[MODEL_COLOR_T1]> matchCommand +deactivate pricePred + +matchCommand -[LOGIC_COLOR]> arrayList: add(filterPropsByPricePredicate) +activate arrayList USER_COLOR +arrayList --[USER_COLOR]> matchCommand +deactivate arrayList + +create charPred +matchCommand -[LOGIC_COLOR]> charPred: Buyer's Characteristics +activate charPred MODEL_COLOR_T1 +charPred --[MODEL_COLOR_T1]> matchCommand +deactivate charPred + +matchCommand -[LOGIC_COLOR]> arrayList: add(filterPropsContainingAnyCharacteristicPredicate) +activate arrayList USER_COLOR +arrayList --[USER_COLOR]> matchCommand +deactivate arrayList + +matchCommand -[LOGIC_COLOR]> arrayList: stream().reduce(Predicate::and) +activate arrayList USER_COLOR +arrayList --[USER_COLOR]> matchCommand: combinedPredicate +deactivate arrayList + +create filterProps +matchCommand -[LOGIC_COLOR]> filterProps: combinedPredicate +activate filterProps LOGIC_COLOR +filterProps --[LOGIC_COLOR]> matchCommand +deactivate filterProps + +matchCommand -[LOGIC_COLOR]> filterProps: execute(model) +activate filterProps LOGIC_COLOR +filterProps --[LOGIC_COLOR]> matchCommand +deactivate filterProps + +[<[LOGIC_COLOR]-- matchCommand: CommandResult +deactivate matchCommand + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..2e85dc83fb9 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,46 +5,75 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <>{ -Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nReadOnlyBuyerBook" as ReadOnlyBuyerBook Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs +Class "<>\nReadOnlyPropertyBook" as ReadOnlyPropertyBook Class "<>\nModel" as Model -Class AddressBook +Class BuyerBook +Class PropertyBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person +Class UniqueBuyerList +Class UniquePropertyList + +Class Buyer Class Address Class Email Class Name Class Phone -Class Tag +Class PriceRange +Class Characteristics +Class Priority +Class LocalDateTime +Class Property +Class PropertyName +Class Price +Class Description +Class Owner } Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +BuyerBook .up.|> ReadOnlyBuyerBook +PropertyBook .up.|> ReadOnlyPropertyBook ModelManager .up.|> Model -Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook -ModelManager -right-> "1" UserPrefs +Model .down.> ReadOnlyUserPrefs +Model .down.> ReadOnlyBuyerBook +Model .down.> ReadOnlyPropertyBook +ModelManager -left-> "1" BuyerBook +ModelManager -down-> "1" UserPrefs +ModelManager -right-> "1" PropertyBook UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +BuyerBook *--> "1" UniqueBuyerList +PropertyBook *--> "1" UniquePropertyList +UniqueBuyerList --> "~* all" Buyer +Buyer *--> "1" Name +Buyer *--> "1" Phone +Buyer *--> "1" Priority +Buyer *--> "1" Email +Buyer *--> "0..1" PriceRange +Buyer *--> "1" Address +Buyer *--> "0..1" Characteristics +Buyer *--> "1" LocalDateTime +UniquePropertyList --> "~* all" Property +Property *--> "1" Address +Property *--> "0..1" Characteristics +Property *--> "1" LocalDateTime +Property *--> "1" PropertyName +Property *--> "1" Description +Property *--> "1" Price +Property *--> "1" Owner -Name -[hidden]right-> Phone +ModelManager -[hidden]up-> ReadOnlyPropertyBook +Name -[hidden]left-> Phone Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Address -[hidden]left-> PriceRange -ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Buyer +ModelManager -->"~* filtered" Property @enduml diff --git a/docs/diagrams/Owner.puml b/docs/diagrams/Owner.puml new file mode 100644 index 00000000000..ffaea1c5097 --- /dev/null +++ b/docs/diagrams/Owner.puml @@ -0,0 +1,42 @@ +@startuml +'https://plantuml.com/class-diagram + +class Property { +-PropertyName propertyName +-Address address +-Description description +-Owner Owner +-LocalDateTime propertyEntryTime + ++ boolean isSameProperty(Property otherProperty) +} + +class Price { +-String value +-Double numericalValue + ++ boolean isGreaterThanOrEqual(Price p) ++ boolean isSmallerThanOrEqual(Price p) +} + +class Characteristics { +-String[] characteristicsArray + ++boolean isValidCharacteristics(String test) ++boolean containsCharacteristic(String characteristic) +} + +class Owner { +-Name name +-Phone phone + ++getName() ++getPhone() +} + + +Property --> " characteristics 0..1" Characteristics +Property --> "price 1" Price +Property "1" --> "1 owner "Owner + +@enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index 0c7424de6e0..2892188b04c 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -9,7 +9,7 @@ Class XYZCommand package "Parser classes"{ Class "<>\nParser" as Parser -Class AddressBookParser +Class CobbParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> CobbParser -AddressBookParser .down.> XYZCommandParser: creates > +CobbParser .down.> XYZCommandParser: creates > XYZCommandParser ..> XYZCommand : creates > -AddressBookParser ..> Command : returns > +CobbParser ..> Command : returns > XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/Property.puml b/docs/diagrams/Property.puml new file mode 100644 index 00000000000..48847f8fe5e --- /dev/null +++ b/docs/diagrams/Property.puml @@ -0,0 +1,34 @@ +@startuml +'https://plantuml.com/class-diagram + +class Property { +-PropertyName propertyName +-Address address +-Description description +-Owner Owner +-LocalDateTime propertyEntryTime + ++ boolean isSameProperty(Property otherProperty) +} + +class Price { ++String value +-Double numericalValue + ++ boolean isGreaterThanOrEqual(Price p) ++ boolean isSmallerThanOrEqual(Price p) +} + +class Characteristics { +-String[] characteristicsArray + ++boolean isValidCharacteristics(String test) ++boolean containsCharacteristic(String characteristic) +} + + + +Property --> " characteristics 0..1" Characteristics +Property --> "price 1" Price + +@enduml diff --git a/docs/diagrams/SortBuyerComparatorsClassDiagram.puml b/docs/diagrams/SortBuyerComparatorsClassDiagram.puml new file mode 100644 index 00000000000..95a58b0813a --- /dev/null +++ b/docs/diagrams/SortBuyerComparatorsClassDiagram.puml @@ -0,0 +1,40 @@ +@startuml +'https://plantuml.com/class-diagram + +Class "<>\nComparator" as Comparator + +Class BuyerComparator { ++ int compare(Buyer firstBuyer, Buyer secondBuyer) +} + +class BuyerNameComparator { +- Order order ++ int compare(Name firstName, Name secondName) +} + +class PriceRangeComparator { +- Order order ++ int compare(PriceRange firstPriceRange, PriceRange secondPriceRange) +} + +class PriorityComparator { +- Order order ++ int compare(Priority firstPriority, Priority secondPriority) +} + +class TimeComparator { +- Order order ++ int compare(LocalDateTime firstTime, LocalDateTime secondTime) +} + +BuyerComparator ..|> Comparator +BuyerNameComparator ..|> Comparator +PriceRangeComparator ..|> Comparator +PriorityComparator ..|> Comparator +TimeComparator ..|> Comparator + +BuyerComparator --> "nameComparator 0..1" BuyerNameComparator +BuyerComparator --> "priceRangeComparator 0..1" PriceRangeComparator +BuyerComparator --> "priorityComparator 0..1" PriorityComparator +BuyerComparator --> "timeComparator 0..1" TimeComparator +@enduml diff --git a/docs/diagrams/SortPropComparatorsClassDiagram.puml b/docs/diagrams/SortPropComparatorsClassDiagram.puml new file mode 100644 index 00000000000..a2d23cc4e1d --- /dev/null +++ b/docs/diagrams/SortPropComparatorsClassDiagram.puml @@ -0,0 +1,33 @@ +@startuml +'https://plantuml.com/class-diagram + +Class "<>\nComparator" as Comparator + +Class PropertyComparator { ++ int compare(Buyer firstBuyer, Buyer secondBuyer) +} + +class PropertyNameComparator { +- Order order ++ int compare(PropertyName firstName, PropertyName secondName) +} + +class PriceComparator { +- Order order ++ int compare(Price firstPrice, Price secondPrice) +} + +class TimeComparator { +- Order order ++ int compare(LocalDateTime firstTime, LocalDateTime secondTime) +} + +PropertyComparator ..|> Comparator +PropertyNameComparator ..|> Comparator +PriceComparator ..|> Comparator +TimeComparator ..|> Comparator + +PropertyComparator --> "nameComparator 0..1" PropertyNameComparator +PropertyComparator --> "priceComparator 0..1" PriceComparator +PropertyComparator --> "timeComparator 0..1" TimeComparator +@enduml diff --git a/docs/diagrams/SortSequenceDiagram.puml b/docs/diagrams/SortSequenceDiagram.puml new file mode 100644 index 00000000000..8ed47adec04 --- /dev/null +++ b/docs/diagrams/SortSequenceDiagram.puml @@ -0,0 +1,60 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +Participant ":SortBuyersCommand" as SortBuyersCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":BuyerBook" as BuyerBook MODEL_COLOR +participant ":UniqueBuyerList" as UniqueBuyerList MODEL_COLOR +end box + +box External EXTERNAL_COLOR +Participant ":ArrayList" as ArrayList EXTERNAL_COLOR_T1 +Participant ":ObservableList" as ObservableList EXTERNAL_COLOR_T2 +end box + +[-[LOGIC_COLOR]> SortBuyersCommand: execute(model) +activate SortBuyersCommand + +SortBuyersCommand -> Model : sortBuyerList(comparator) +activate Model + +Model -> BuyerBook : getBuyerList() +activate BuyerBook + +BuyerBook -> Model : buyerList +deactivate BuyerBook + +Model -> ArrayList : sort(comparator) +activate ArrayList + +ArrayList -> Model +deactivate ArrayList + +Model -> BuyerBook : setBuyers(sortedList) +activate BuyerBook + +BuyerBook -> UniqueBuyerList : setBuyers(buyerList) +activate UniqueBuyerList + +UniqueBuyerList -> ObservableList : setAll(internalList) +activate ObservableList + +ObservableList -> UniqueBuyerList +deactivate ObservableList + +UniqueBuyerList -> BuyerBook +deactivate UniqueBuyerList + +BuyerBook -> Model +deactivate BuyerBook + +Model -> SortBuyersCommand +deactivate Model + +[<[LOGIC_COLOR]-- SortBuyersCommand: CommandResult +deactivate SortBuyersCommand +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..ae2b93b2793 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -14,12 +14,19 @@ Class JsonUserPrefsStorage Class "<>\nStorage" as Storage Class StorageManager -package "AddressBook Storage" #F4F6F6{ -Class "<>\nAddressBookStorage" as AddressBookStorage -Class JsonAddressBookStorage -Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag +package "BuyerBook Storage" #F4F6F6{ +Class "<>\nBuyerBookStorage" as BuyerBookStorage +Class JsonBuyerBookStorage +Class JsonSerializableBuyerBook +Class JsonAdaptedBuyer +} + +package "PropertyBook Storage" #F4F6F6{ +Class "<>\nPropertyBookStorage" as PropertyBookStorage +Class JsonPropertyBookStorage +Class JsonSerializablePropertyBook +Class JsonAdaptedProperty +Class JsonAdaptedOwner } } @@ -29,15 +36,21 @@ HiddenOutside ..> Storage StorageManager .up.|> Storage StorageManager -up-> "1" UserPrefsStorage -StorageManager -up-> "1" AddressBookStorage +StorageManager -up-> "1" BuyerBookStorage +StorageManager -up-> "1" PropertyBookStorage -Storage -left-|> UserPrefsStorage -Storage -right-|> AddressBookStorage +Storage -down-|> UserPrefsStorage +Storage -down-|> BuyerBookStorage +Storage -down-|> PropertyBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage -JsonAddressBookStorage .up.|> AddressBookStorage -JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonBuyerBookStorage .up.|> BuyerBookStorage +JsonBuyerBookStorage ..> JsonSerializableBuyerBook +JsonSerializableBuyerBook --> "*" JsonAdaptedBuyer +JsonPropertyBookStorage .up.|> PropertyBookStorage +JsonPropertyBookStorage ..> JsonSerializablePropertyBook +JsonSerializablePropertyBook --> "*" JsonAdaptedProperty +JsonAdaptedProperty --> "1" JsonAdaptedOwner + @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..ed1be812147 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,10 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class BuyerListPanel +Class BuyerCard +Class PropertyListPanel +Class PropertyCard Class StatusBarFooter Class CommandBox } @@ -32,29 +34,36 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" BuyerListPanel +MainWindow *-down-> "1" PropertyListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard +BuyerListPanel -down-> "*" BuyerCard +PropertyListPanel -down-> "*" PropertyCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +BuyerListPanel --|> UiPart +BuyerCard --|> UiPart +PropertyListPanel --|> UiPart +PropertyCard -left-|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +BuyerCard .-down.> Model +PropertyCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +BuyerListPanel -[hidden]left- HelpWindow +PropertyListPanel -[hidden]right- BuyerListPanel HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter + MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml index fad8b0adeaa..b1a72c593f2 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/style.puml @@ -31,12 +31,20 @@ !define STORAGE_COLOR_T3 #806600 !define STORAGE_COLOR_T2 #544400 +!define EXTERNAL_COLOR #EC9006 +!define EXTERNAL_COLOR_T1 #DC6601 +!define EXTERNAL_COLOR_T2 #D24E01 + !define USER_COLOR #000000 skinparam BackgroundColor #FFFFFFF skinparam Shadowing false +skinparam default { + FontSize 11 +} + skinparam Class { FontColor #FFFFFF BorderThickness 1 diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..84dd6f98b3e 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editBuyerDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/AddBuyerActivityDiagram.png b/docs/images/AddBuyerActivityDiagram.png new file mode 100644 index 00000000000..fd27ad30629 Binary files /dev/null and b/docs/images/AddBuyerActivityDiagram.png differ diff --git a/docs/images/AddBuyerObjectDiagram-Final_state.png b/docs/images/AddBuyerObjectDiagram-Final_state.png new file mode 100644 index 00000000000..33e2ce4cb41 Binary files /dev/null and b/docs/images/AddBuyerObjectDiagram-Final_state.png differ diff --git a/docs/images/AddPropertyActivityDiagram.png b/docs/images/AddPropertyActivityDiagram.png new file mode 100644 index 00000000000..59b27817c4d Binary files /dev/null and b/docs/images/AddPropertyActivityDiagram.png differ diff --git a/docs/images/AddPropertyObjectDiagram-Final_state.png b/docs/images/AddPropertyObjectDiagram-Final_state.png new file mode 100644 index 00000000000..5706427de51 Binary files /dev/null and b/docs/images/AddPropertyObjectDiagram-Final_state.png differ diff --git a/docs/images/AlternativeBuyerAndProperty.png b/docs/images/AlternativeBuyerAndProperty.png new file mode 100644 index 00000000000..590be0593a1 Binary files /dev/null and b/docs/images/AlternativeBuyerAndProperty.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..e591a8b173d 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagramNew.png b/docs/images/ArchitectureSequenceDiagramNew.png new file mode 100644 index 00000000000..290151213a8 Binary files /dev/null and b/docs/images/ArchitectureSequenceDiagramNew.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 1ec62caa2a5..80850038452 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/BuyerClassDiagram.png b/docs/images/BuyerClassDiagram.png new file mode 100644 index 00000000000..e54d3c162c7 Binary files /dev/null and b/docs/images/BuyerClassDiagram.png differ diff --git a/docs/images/BuyerList.png b/docs/images/BuyerList.png new file mode 100644 index 00000000000..76237d923ea Binary files /dev/null and b/docs/images/BuyerList.png differ diff --git a/docs/images/CommandBox.png b/docs/images/CommandBox.png new file mode 100644 index 00000000000..277632142cf Binary files /dev/null and b/docs/images/CommandBox.png differ diff --git a/docs/images/CommandRetrieverClassDiagram.png b/docs/images/CommandRetrieverClassDiagram.png new file mode 100644 index 00000000000..9c72090dca3 Binary files /dev/null and b/docs/images/CommandRetrieverClassDiagram.png differ diff --git a/docs/images/CommandRetrieverSequenceDiagram.png b/docs/images/CommandRetrieverSequenceDiagram.png new file mode 100644 index 00000000000..b68f5c66f7e Binary files /dev/null and b/docs/images/CommandRetrieverSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..2b5fd7f7fee 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditSequenceDiagram.png b/docs/images/EditSequenceDiagram.png new file mode 100644 index 00000000000..e272ffd13be Binary files /dev/null and b/docs/images/EditSequenceDiagram.png differ diff --git a/docs/images/FilterPredicatesClassDiagram.png b/docs/images/FilterPredicatesClassDiagram.png new file mode 100644 index 00000000000..c35a41051e5 Binary files /dev/null and b/docs/images/FilterPredicatesClassDiagram.png differ diff --git a/docs/images/FullPropertyClassDiagram.png b/docs/images/FullPropertyClassDiagram.png new file mode 100644 index 00000000000..8e06e570519 Binary files /dev/null and b/docs/images/FullPropertyClassDiagram.png differ diff --git a/docs/images/HelpWindow.png b/docs/images/HelpWindow.png new file mode 100644 index 00000000000..fd37d0a5a65 Binary files /dev/null and b/docs/images/HelpWindow.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..fa3a8566d40 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/MatchBuyerCommandSequenceDiagram.png b/docs/images/MatchBuyerCommandSequenceDiagram.png new file mode 100644 index 00000000000..4da30e584d0 Binary files /dev/null and b/docs/images/MatchBuyerCommandSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..f6c009de9de 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/OwnerClassDiagram.png b/docs/images/OwnerClassDiagram.png new file mode 100644 index 00000000000..cc2ddd8b631 Binary files /dev/null and b/docs/images/OwnerClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index e7b4c8880cd..ac127840ff4 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/PersonClassDiagram.png b/docs/images/PersonClassDiagram.png new file mode 100644 index 00000000000..78f37f4741a Binary files /dev/null and b/docs/images/PersonClassDiagram.png differ diff --git a/docs/images/PropertyClassDiagram.png b/docs/images/PropertyClassDiagram.png new file mode 100644 index 00000000000..50d22a7efcb Binary files /dev/null and b/docs/images/PropertyClassDiagram.png differ diff --git a/docs/images/PropertyClassDiagramNew.png b/docs/images/PropertyClassDiagramNew.png new file mode 100644 index 00000000000..446313383b6 Binary files /dev/null and b/docs/images/PropertyClassDiagramNew.png differ diff --git a/docs/images/PropertyList.png b/docs/images/PropertyList.png new file mode 100644 index 00000000000..86c11f690ac Binary files /dev/null and b/docs/images/PropertyList.png differ diff --git a/docs/images/ReleasePage.png b/docs/images/ReleasePage.png new file mode 100644 index 00000000000..9158ba6c0e1 Binary files /dev/null and b/docs/images/ReleasePage.png differ diff --git a/docs/images/SortBuyerComparatorsClassDiagram.png b/docs/images/SortBuyerComparatorsClassDiagram.png new file mode 100644 index 00000000000..b98a2376f65 Binary files /dev/null and b/docs/images/SortBuyerComparatorsClassDiagram.png differ diff --git a/docs/images/SortPropComparatorsClassDiagram.png b/docs/images/SortPropComparatorsClassDiagram.png new file mode 100644 index 00000000000..d2dacdaa554 Binary files /dev/null and b/docs/images/SortPropComparatorsClassDiagram.png differ diff --git a/docs/images/SortSequenceDiagram.png b/docs/images/SortSequenceDiagram.png new file mode 100644 index 00000000000..8fea48bdce7 Binary files /dev/null and b/docs/images/SortSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..344fae6f847 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..24dbb737585 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..7526859c09c 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/chngchngchng.png b/docs/images/chngchngchng.png new file mode 100644 index 00000000000..533c87ee2d7 Binary files /dev/null and b/docs/images/chngchngchng.png differ diff --git a/docs/images/chryslinelim.png b/docs/images/chryslinelim.png new file mode 100644 index 00000000000..a235c21be7d Binary files /dev/null and b/docs/images/chryslinelim.png differ diff --git a/docs/images/favicon.png b/docs/images/favicon.png new file mode 100644 index 00000000000..576b7a8bf80 Binary files /dev/null and b/docs/images/favicon.png differ diff --git a/docs/images/jchilling.png b/docs/images/jchilling.png new file mode 100644 index 00000000000..463349f4d2c Binary files /dev/null and b/docs/images/jchilling.png differ diff --git a/docs/images/riccqi.png b/docs/images/riccqi.png new file mode 100644 index 00000000000..a4366420a7f Binary files /dev/null and b/docs/images/riccqi.png differ diff --git a/docs/images/zsiggg.png b/docs/images/zsiggg.png new file mode 100644 index 00000000000..f23528242ac Binary files /dev/null and b/docs/images/zsiggg.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..fe708de3d6e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,16 @@ --- layout: page -title: AddressBook Level-3 +title: Cobb --- -[![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/AY2223S1-CS2103T-F12-1/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S1-CS2103T-F12-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-F12-1/tp/branch/master/graph/badge.svg?token=IIS9IA9J5L)](https://codecov.io/gh/AY2223S1-CS2103T-F12-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). +**Cobb is an optimised object-oriented command-line entry management application that aims to make database management for Real Estate Agents easier and more accessible.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using Cobb, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing Cobb, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/chngchngchng.md b/docs/team/chngchngchng.md new file mode 100644 index 00000000000..bfb8c153d66 --- /dev/null +++ b/docs/team/chngchngchng.md @@ -0,0 +1,66 @@ +--- +layout: page +title: Chng Ian's Project Portfolio Page +--- + +### Project: Cobb + +Cobb is a desktop address book application for property agents. The user interacts with it using a CLI, and it has a GUI created with JavaFX. + +Given below are my contributions to the project. + +* **Code Contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=chngchngchng&breakdown=true) + +* **Enhancements implemented**: + 1. Created initial person role package + 2. Created Property column to list properties + 3. Created Property `ObservableArrayList` and all related functionality + 4. Added filterbuyer command and relevant predicates + 5. Add multifilter and matchprop commands + 6. Edited and refactored existing commands to better fit functionality +* **Contributions to the UG**: + 1. Created overall new structure of the UG, updated from original AB3 UG. + 2. Added most new sections to the UG + 3. Updated features section of the UG to reflect all new commands that were added to the application + 4. Wrote key definitions section of the UG + 5. Wrote interface layout section of the UG + 6. Wrote introduction to the UG + 7. Wrote quick start section of the UG +* **Contributions to the DG**: + 1. Added section on choices made regarding how users are indexed + 2. Added section on how filter commands work and how the predicates are composed + 3. Added UML diagrams for filter commands explaining the internal structure of predicates used + 4. Created more use cases for the application +* **Contributions to team-based tasks**: + 1. Refactored `price` and `priceRange` + 2. Refactored `filter` and `match` commands + 3. Refactored `person` role package + 4. Tested and fixed bugs regarding `priceRange` + 5. Tested and fixed bugs regarding `propertyList` + 6. Tested and fixed bugs regarding `Buyer` + 7. Tested and fixed bugs regarding `filterbuyers` and `filterprops` + 8. Wrote test cases for `AddPropertyCommandParser`, `DeletePropertyCommandParser`, `EditPropertyCommandParser`, `FindPropertiesCommandParser`, + 9. Wrote test utility for `Property` + 10. Wrote test cases for `FilterPropsByOwnerNamePredicate`, `FilterPropsByPriceRangePredicate`, `FilterPropsContainingAllCharacteristicsPredicate`, `FilterPropsContainingAnyCharacteristicsPredicate`. +* **Review/mentoring contributions:**
+Pull requests reviewed: + 1. [#229](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/229) + 2. [#227](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/227) + 3. [#226](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/226) + 4. [#225](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/225) + 5. [#219](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/219) + 6. [#148](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/148) + 7. [#147](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/147) + 8. [#120](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/120) + 9. [#119](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/119) + 10. [#110](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/110) + 11. [#108](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/108) + 12. [#84](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/84) + 13. [#82](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/82) + 14. [#60](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/60) + 15. [#50](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/50) + 16. [#43](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/43) +* **Contributions beyond the project team**: + 1. Main participant in tutorials + 2. Helped fellow teams review and test their applications + diff --git a/docs/team/chryslinelim.md b/docs/team/chryslinelim.md new file mode 100644 index 00000000000..1b9ddda2c9f --- /dev/null +++ b/docs/team/chryslinelim.md @@ -0,0 +1,58 @@ +--- +layout: page +title: Chrysline Lim's Project Portfolio Page +--- + +### Project: Cobb + +Cobb is a desktop address book application for property agents. The user interacts with it using a CLI, and it has a GUI created with JavaFX. + +Given below are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=chryslinelim&breakdown=true) + +- **Enhancements implemented**: + * Add `Characteristics` field to `Property` [#109](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/109) + * Adapt `findbuyers` and `findprops` to match by substring [#223](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/223) + * Add property list status bar footer (UI) [#224](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/224) + * Allow users to reset all optional `Buyer` and `Property` fields to unspecified [#231](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/231) + +- **New features**: + * Implement these `Buyer`-related commands with additional `PriceRange` and `Characteristics` fields + - `addbuyer`: [#65](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/65) + - `editbuyer`: [#82](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/82) + * Implement `filterprops` by price range command [#110](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/110) + * Implement `sortbuyers` command [#143](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/143) + +- **Contributions to the UG**: + * Major changes to the features and command summary sections to match final implementation [#237](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/237) + +- **Contributions to the DG**: + * Wrote initial target user profile, value proposition and user stories sections [#47](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/47) + * Wrote section on creating a `Buyer` [#117](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/117) + * Wrote section on `Sort` feature [#249](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/249), [#250](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/250) + +- **Contributions to team-based tasks**: + * Fix bugs + - `-c` flag only accepting a single word [#109](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/109) + - wrong error message shown when `Index` passed to command is out of bounds [#219](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/219) + - possible input overflows [#219](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/219) + * Managed team's GitHub issue tracker + +- **Review/mentoring contributions**: + * Some non-trivial pull-request reviews: + - [#89](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/89) + - [#111](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/111) + - [#116](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/116) + +- **Contributions beyond the project team**: + * Product pitch for CS2101 + * Reported bugs accepted by team during PE-D: + - [#117](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/117) + - [#118](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/118) + - [#137](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/137) + - [#140](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/140) + - [#141](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/141) + - [#144](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/144) + - [#145](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/145) + - [#146](https://github.com/AY2223S1-CS2103T-T11-3/tp/issues/146) diff --git a/docs/team/jchilling.md b/docs/team/jchilling.md new file mode 100644 index 00000000000..d542ea7ba9b --- /dev/null +++ b/docs/team/jchilling.md @@ -0,0 +1,69 @@ +--- +layout: page +title: Chen Hung-Yu's Project Portfolio Page +--- + +### Project: Cobb + +Cobb is a Contact and Property Management System that aims to help property agents and brokers manage their customer base and properties, as well as match and gain actionable insights from stored data. + +Given below are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=jchilling&breakdown=true) +- **Enhancements to existing features**: + * Add `Priority` field to `Buyer` [#111](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/111) + * What it does: Represents the priority for buyer. + * Justification: In real life, different clients'(buyers) case have different urgency. Useful feature to let the user tag a buyer's priority. + * Add `LocalDateTime` field to `Buyer` and `Property` [#148](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/148) + * What it does: The application automatically captures the entry time for `Buyer` and `Property` by a `LocalDateTime` object. + * Justification: This enhancement enables a simpler implementation of the sort commands and preserves the original list order by allowing users to sort by entry time. + * Add ability to filter buyers by `Priority` [#125](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/125) + * What it does: Enable `filterbuyers` command to filter buyers' `Priority` in ascending or descending order. +- **New Features**: + * Implementation of `seedu.address.model.property` package [#60](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/60), [#72](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/72) + * What it does: Represents a property and its associations as classes within the Cobb codebase. + * Justification: The package follows the Object-Oriented Programming paradigm that is used throughout Cobb. + * Implementation of `listprops` command [#85](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/85) + * What it does: Display all properties stored in the application on the Property List panel. + * Justification: Command for user to return to original property list after filter commands. + * Implementation of `deleteprop` command [#85](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/85) + * What it does: Delete the property at given index. + * Justification: Command for user to modify the property list. + * Implementation of `sortprop` command [#148](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/148) + * What it does: Sort properties according to the specified attribute. + * Justification: Useful feature for users to sort the property list by a specified attribute in an ascending/descending. + +- **Documentation**: + * User Guide: + * Updated documentations for `findbuyers` and `findprops` commands + * Added documentations for `sortbuyers` and `sortprops` commands + * Added documentation for `clear` command + + * Developer Guide: + * Updated all UML diagrams to reflect Cobb's design architecture and `UI`, `Logic`, `Storage`, `Model` components + * Updated previous outdated descriptions to reflect current state of Cobb + * Updated all prior UML diagrams to use plantUML + * Updated NFRs + * Added "Create a property" section under "Implementation" + * Modified "Create a buyer" section under "Implementation" + +- **Contributions to team-based tasks**: + * Enabled assertions in Gradle + * Managed issue tracker + +- **Review/mentoring contributions**: + * PRs reviewed (with non-trivial comment): + [#68](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/68), + [#83](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/83), + [#137](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/137) + * Reported bugs during PE-D accepted: + [#157](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/157), + [#159](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/159), + [#163](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/163), + [#172](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/172), + [#175](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/175), + [#179](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/179), + [#186](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/186), + [#192](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/192), + [#193](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/193) + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/riccqi.md b/docs/team/riccqi.md new file mode 100644 index 00000000000..8a649f16474 --- /dev/null +++ b/docs/team/riccqi.md @@ -0,0 +1,49 @@ +--- +layout: page +title: Qi Zhi's Project Portfolio Page +--- + +### Project: Cobb + +Cobb is a desktop address book application for property agents. The user interacts with it using a CLI, and it has a GUI created with JavaFX. + +Given below are my contributions to the project. + +* **Code Contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=riccqi&breakdown=true) + +* **Enhancements implemented**: + * Added Owner class under Property class. + * Added ability to filter properties by owner name. + * Refactor for Person to Buyer class, and improved file structure. + * Added `matchbuyer` command. + * Implemented the `editprop` command. + * Implemented storage for `Property` with `Owner` class. +* **Contributions to the UG**: + * Categorised the features for buyers and properties. + * Added documentation for the features `matchbuyer` and `filter` + * Rearrange the order of the features in the UG to make it more logical. +* **Contributions to the DG**: + * Added documentation for the `EditBuyerCommand` and `EditPropertyCommand` classes. + * Added Sequence diagram for the `EditBuyerCommand` class. + * Rearranged the priority of the user stories to make it more logical. + * Added use cases for the edit commands. + * Added owner class design docs to DG, including alternative implementation. + * Improved implementation details of the `matchbuyer` command. +* **Contributions to team-based tasks**: + * Performed the product releases + * Set up the GitHub organization + * Created the initial mockup of the UI +* **Review/mentoring contributions:** + * Reviewed 20 PRs (Links to PR reviews with non-trivial comments below) + * [#147](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/147) + * [#128](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/128) + * [#113](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/113) + * [#109](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/109) + * [#85](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/85) + * [#83](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/83) + * [#66](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/66) + * [#65](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/65) + * Co-reviewed 3 PRs (Links to PR reviews with non-trivial comments below) + * [#110](https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/110) +* **Contributions beyond the project team**: + * Did the product demo for CS2101 diff --git a/docs/team/zsiggg.md b/docs/team/zsiggg.md new file mode 100644 index 00000000000..1971a0a071d --- /dev/null +++ b/docs/team/zsiggg.md @@ -0,0 +1,80 @@ +--- +layout: page +title: Zsigmond Poh's Project Portfolio Page +--- + +### Project: Cobb + +Cobb is a desktop address book application for property agents. The user interacts with it using a CLI, and it has a GUI created with JavaFX. + +Given below are my contributions to the project. + +* **Code contributed**: +[RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=zsiggg&breakdown=true) +* **Enhancements implemented**: + * Navigate command history with arrow keys in command box + * `addprop` command + * `filterbuyers` and `filterprops` by characteristics + * `-all` flags for `filterbuyers` and `filterprops` + * `-strict` flag for `matchprops` and `matchbuyer` +* **Contributions to the UG**: + * Added links (e.g. for Buyer List and Property List) to different sections in the document for readability + * Added explanations for the filtered view of the buyer and property lists in FAQ, List Command and Filter Command sections + * Updated command syntax and examples throughout the document to the current implementation, making sure that all + examples work when pasted into the application + * Added explanation for the navigation of command history using arrow keys in the Interface Layout section +* **Contributions to the DG**: + * Created and updated UML diagrams (sequence and class diagrams) with PlantUML + * CommandRetriever class diagram + * CommandRetriever sequence diagram + * MatchBuyerCommand sequence diagram + * Added the implementation of the navigation of command history feature + * Added the implementation of the match commands + * Added the introduction section + * Small updates to UML diagrams and writeups in the design section +* **Contributions to team-based tasks**: + * Pull requests added: + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/227 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/222 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/216 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/215 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/151 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/42 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/113 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/121 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/119 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/128 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/146 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/145 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/147 + * Add test cases for sort and filter commands + * Refactor test cases that previously failed due to updates in code base + * Refactor `ArgumentTokenizer` and test cases to require a whitespace after the flag, to allow for flags with similar + prefixes (e.g. `-ph` and `-p`) + * Refactoring of `personModel` to `personBook` to `buyerBook` + * Refactoring all occurrences of `person` to `buyer` + * Implemented `-fuzzy` flag for filter commands and `-strict` for match commands + * Implemented ability to use up and down arrow keys to navigate command history + * Implemented filter by characteristics (a part of the filter commands) + * Added `PropertyBookStorage` and its associated JSON classes in `storage` package + * Added `PropertyBook` in `model` package + * Implemented `addprop` + * Updated Kanban board for user stories on GitHub Projects to match how the stories were allocated to each milestone + * Set up CI for the team repository +* **Review/mentoring contributions**: + * Pull requests reviewed: + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/229 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/217 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/152 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/147 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/142 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/128 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/125 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/119 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/110 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/96 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/83 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/72 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/70 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/53 + * https://github.com/AY2223S1-CS2103T-F12-1/tp/pull/42 diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 880c701042f..394b4e8592e 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -28,7 +28,7 @@ package seedu.address.logic.commands; import seedu.address.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing buyer in the address book. */ public class RemarkCommand extends Command { @@ -65,8 +65,8 @@ Following the convention in other commands, we add relevant messages as constant ``` java public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + + ": Edits the remark of the buyer identified " + + "by the index number used in the last buyer listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -101,8 +101,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the buyer in the filtered buyer list to edit the remark + * @param remark of the buyer to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -225,11 +225,11 @@ If you are stuck, check out the sample ## Add `Remark` to the model -Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of buyer data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the buyer’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a buyer. ### Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.buyer`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input validation. @@ -240,9 +240,9 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` ## Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each buyer. -Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). +Simply add the following to [`seedu.address.ui.BuyerCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). **`PersonCard.java`:** @@ -295,7 +295,7 @@ While the changes to code may be minimal, the test data will have to be updated
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! +:exclamation: You must delete AddressBook’s storage file located at `/data/personmodel.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
@@ -311,9 +311,9 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c **`PersonCard.java`:** ``` java -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person buyer, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(buyer.getRemark().value); } ``` @@ -343,25 +343,25 @@ save it with `Model#setPerson()`. throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = new Person( - personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), - personToEdit.getAddress(), remark, personToEdit.getTags()); + Person buyerToEdit = lastShownList.get(index.getZeroBased()); + Person editedBuyer = new Person( + buyerToEdit.getName(), buyerToEdit.getPhone(), buyerToEdit.getEmail(), + buyerToEdit.getAddress(), remark, buyerToEdit.getTags()); - model.setPerson(personToEdit, editedPerson); + model.setPerson(buyerToEdit, editedBuyer); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(generateSuccessMessage(editedPerson)); + return new CommandResult(generateSuccessMessage(editedBuyer)); } /** * Generates a command execution success message based on whether * the remark is added to or removed from - * {@code personToEdit}. + * {@code buyerToEdit}. */ - private String generateSuccessMessage(Person personToEdit) { + private String generateSuccessMessage(Person buyerToEdit) { String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; - return String.format(message, personToEdit); + return String.format(message, buyerToEdit); } ``` diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..4e4fe065d71 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re ### Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. +The `address` field in `Person` is actually an instance of the `seedu.address.model.address.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. * :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences` ![Usages detected](../images/remove/UnsafeDelete.png) @@ -100,7 +100,7 @@ In `src/test/data/`, data meant for testing purposes are stored. While keeping t ```json { - "persons": [ { + "buyers": [ { "name": "Person with invalid name field: Ha!ns Mu@ster", "phone": "9482424", "email": "hans@example.com", diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..b0683466bd2 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -189,22 +189,22 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ @Override public CommandResult execute(Model model) throws CommandException { ... - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + Person buyerToEdit = lastShownList.get(index.getZeroBased()); + Person editedBuyer = createEditedPerson(buyerToEdit, editBuyerDescriptor); + if (!buyerToEdit.isSamePerson(editedBuyer) && model.hasPerson(editedBuyer)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(buyerToEdit, editedBuyer); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedBuyer)); } ``` 1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically, - * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data. - * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
- FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
- To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. + * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the buyer data. + * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ buyers.
+ FYI, The 'filtered list' is the list of buyers resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the buyers so that the user can see the edited buyer along with all other buyers. If this was a `find` command, we would be setting that list to contain the search results instead.
+ To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of buyers is being tracked.
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) @@ -231,7 +231,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ * {@code JsonSerializableAddressBook}. */ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll( + buyers.addAll( source.getPersonList() .stream() .map(JsonAdaptedPerson::new) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..04ec628e437 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,16 +15,20 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; +import seedu.address.model.BuyerBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.PropertyBook; +import seedu.address.model.ReadOnlyBuyerBook; +import seedu.address.model.ReadOnlyPropertyBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.BuyerBookStorage; +import seedu.address.storage.JsonBuyerBookStorage; +import seedu.address.storage.JsonPropertyBookStorage; import seedu.address.storage.JsonUserPrefsStorage; +import seedu.address.storage.PropertyBookStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; @@ -36,7 +40,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, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +52,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing BuyerBook ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +60,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + BuyerBookStorage buyerBookStorage = new JsonBuyerBookStorage(userPrefs.getBuyerBookFilePath()); + PropertyBookStorage propertyBookStorage = new JsonPropertyBookStorage(userPrefs.getPropertyBookFilePath()); + storage = new StorageManager(buyerBookStorage, propertyBookStorage, userPrefsStorage); initLogging(config); @@ -69,28 +74,46 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * 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 buyer book, property book and + * {@code userPrefs}.
The data from the sample buyer book and property book will be used instead if those in + * {@code storage} cannot be found, or an empty buyer book and property book will be used instead if errors + * occur when reading those in {@code storage}. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional buyerBookOptional; + ReadOnlyBuyerBook buyerBook; + Optional propertyBookOptional; + ReadOnlyPropertyBook propertyBook; + + try { + buyerBookOptional = storage.readBuyerBook(); + if (!buyerBookOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample BuyerBook"); + } + buyerBook = buyerBookOptional.orElseGet(SampleDataUtil::getSampleBuyerBook); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. Will be starting with an empty BuyerBook"); + buyerBook = new BuyerBook(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty BuyerBook"); + buyerBook = new BuyerBook(); + } + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + propertyBookOptional = storage.readPropertyBook(); + if (!propertyBookOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample PropertyBook"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + propertyBook = propertyBookOptional.orElseGet(SampleDataUtil::getSamplePropertyBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty PropertyBook"); + propertyBook = new PropertyBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty PropertyBook"); + propertyBook = new PropertyBook(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(buyerBook, propertyBook, userPrefs); } private void initLogging(Config config) { @@ -151,7 +174,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "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"); + logger.warning("Problem while reading from the file. Will be starting with an empty BuyerBook"); initializedPrefs = new UserPrefs(); } @@ -167,13 +190,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting BuyerBook " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Cobb ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 431e7185e76..7cf178159c6 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "cobb.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..6e3bacb4d5b 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,10 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_INVALID_BUYER_DISPLAYED_INDEX = "The buyer index provided does not exist."; + public static final String MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX = "The property index provided does not exist."; + public static final String MESSAGE_BUYERS_LISTED_OVERVIEW = "%1$d buyers listed."; + public static final String MESSAGE_PROPERTY_LISTED_OVERVIEW = "%1$d properties listed."; + public static final String MESSAGE_INVALID_PROPERTY_BUYER_MATCH = + "Cannot match buyer who has no desired characteristics or budget indicated."; } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..1c920695520 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -7,8 +7,10 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyBuyerBook; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.property.Property; /** * API of the Logic component @@ -24,19 +26,35 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the BuyerBook. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.address.model.Model#getBuyerBook() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyBuyerBook getBuyerBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of buyers */ + ObservableList getFilteredBuyerList(); /** - * Returns the user prefs' address book file path. + * Returns the PropertyBook. + * + * @see seedu.address.model.Model#getPropertyBook() + */ + ReadOnlyPropertyBook getPropertyBook(); + + /** Returns an unmodifiable view of the filtered list of properties */ + ObservableList getFilteredPropertyList(); + + + /** + * Returns the user prefs' buyer book file path. + */ + Path getBuyerBookFilePath(); + + /** + * Returns the user prefs' property book file path. */ - Path getAddressBookFilePath(); + Path getPropertyBookFilePath(); /** * Returns the user prefs' GUI settings. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..add398bac56 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -10,11 +10,13 @@ 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.CobbParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyBuyerBook; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.property.Property; import seedu.address.storage.Storage; /** @@ -26,7 +28,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final CobbParser cobbParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -34,7 +36,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + cobbParser = new CobbParser(); } @Override @@ -42,11 +44,12 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = cobbParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveBuyerBook(model.getBuyerBook()); + storage.savePropertyBook(model.getPropertyBook()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -55,18 +58,33 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyBuyerBook getBuyerBook() { + return model.getBuyerBook(); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredBuyerList() { + return model.getFilteredBuyerList(); } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public ReadOnlyPropertyBook getPropertyBook() { + return model.getPropertyBook(); + } + + @Override + public ObservableList getFilteredPropertyList() { + return model.getFilteredPropertyList(); + } + + @Override + public Path getBuyerBookFilePath() { + return model.getBuyerBookFilePath(); + } + + @Override + public Path getPropertyBookFilePath() { + return model.getPropertyBookFilePath(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +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 seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "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_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(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 AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..9d691b3e47c 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,22 +2,24 @@ import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; +import seedu.address.model.BuyerBook; import seedu.address.model.Model; +import seedu.address.model.PropertyBook; /** - * Clears the address book. + * Clears both buyer and property list. */ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "Properties and Buyers list have been cleared"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.setAddressBook(new AddressBook()); + model.setBuyerBook(new BuyerBook()); + model.setPropertyBook(new PropertyBook()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -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.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @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/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 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 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 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 getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @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..1c3efd0b8c9 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,7 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Cobb as requested ..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.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 static final String COMMAND_WORD = "find"; - - 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; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -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 static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/buyer/AddBuyerCommand.java b/src/main/java/seedu/address/logic/commands/buyer/AddBuyerCommand.java new file mode 100644 index 00000000000..ffc7811a88d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/AddBuyerCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CHARACTERISTICS; +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_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +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.buyer.Buyer; + +/** + * Adds a buyer to the address book. + */ +public class AddBuyerCommand extends Command { + + public static final String COMMAND_WORD = "addbuyer"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a buyer to Cobb.\n" + + "Parameters: " + + PREFIX_NAME + " NAME " + + PREFIX_PHONE + " PHONE " + + PREFIX_EMAIL + " EMAIL " + + PREFIX_ADDRESS + " ADDRESS " + + "[" + PREFIX_PRICE_RANGE + " PRICE RANGE] " + + "[" + PREFIX_CHARACTERISTICS + " CHARACTERISTICS] " + + "[" + PREFIX_PRIORITY + " PRIORITY ]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + " John " + + PREFIX_PHONE + " 98765432 " + + PREFIX_EMAIL + " john@gmail.com " + + PREFIX_ADDRESS + " 311 Clementi Ave 2, #02-25 " + + PREFIX_PRICE_RANGE + " 600000 - 800000 " + + PREFIX_CHARACTERISTICS + " 5-ROOM; SOUTH-FACING " + + PREFIX_PRIORITY + " HIGH "; + + public static final String MESSAGE_SUCCESS = "New buyer added!\n%1$s"; + public static final String MESSAGE_DUPLICATE_BUYER = "This buyer already exists in Cobb"; + + private final Buyer toAdd; + + /** + * Creates an AddBuyerCommand to add the specified {@code Buyer} + */ + public AddBuyerCommand(Buyer buyer) { + requireNonNull(buyer); + toAdd = buyer; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasBuyer(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_BUYER); + } + + model.addBuyer(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 AddBuyerCommand // instanceof handles nulls + && toAdd.equals(((AddBuyerCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/DeleteBuyerCommand.java b/src/main/java/seedu/address/logic/commands/buyer/DeleteBuyerCommand.java new file mode 100644 index 00000000000..979875b4e13 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/DeleteBuyerCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.commands.buyer; + +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.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.buyer.Buyer; + +/** + * Deletes a buyer identified using it's displayed index from the address book. + */ +public class DeleteBuyerCommand extends Command { + + public static final String COMMAND_WORD = "deletebuyer"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the buyer identified by the index number used in the displayed buyer list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_BUYER_SUCCESS = "Deleted Buyer!\n%1$s"; + + private final Index targetIndex; + + public DeleteBuyerCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredBuyerList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BUYER_DISPLAYED_INDEX); + } + + Buyer buyerToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteBuyer(buyerToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_BUYER_SUCCESS, buyerToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteBuyerCommand // instanceof handles nulls + && targetIndex.equals(((DeleteBuyerCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/EditBuyerCommand.java b/src/main/java/seedu/address/logic/commands/buyer/EditBuyerCommand.java new file mode 100644 index 00000000000..ae0e0ff2a5c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/EditBuyerCommand.java @@ -0,0 +1,271 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CHARACTERISTICS; +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_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BUYERS; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +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.address.Address; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.Email; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.buyer.Priority; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.PriceRange; + +/** + * Edits the details of an existing buyer in the address book. + */ +public class EditBuyerCommand extends Command { + + public static final String COMMAND_WORD = "editbuyer"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the buyer identified " + + "by the index number used in the displayed buyer list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX " + + "[" + PREFIX_NAME + " NAME] " + + "[" + PREFIX_PHONE + " PHONE] " + + "[" + PREFIX_EMAIL + " EMAIL] " + + "[" + PREFIX_ADDRESS + " ADDRESS] " + + "[" + PREFIX_PRICE_RANGE + " PRICE RANGE] " + + "[" + PREFIX_CHARACTERISTICS + " CHARACTERISTICS] " + + "[" + PREFIX_PRIORITY + " PRIORITY]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + " 91234567 " + + PREFIX_EMAIL + " johndoe@example.com"; + + public static final String MESSAGE_EDIT_BUYER_SUCCESS = "Edited Buyer!\n%1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_BUYER = "This buyer already exists in Cobb."; + + private final Index index; + private final EditBuyerDescriptor editBuyerDescriptor; + + /** + * @param index of the buyer in the filtered buyer list to edit + * @param editBuyerDescriptor details to edit the buyer with + */ + public EditBuyerCommand(Index index, EditBuyerDescriptor editBuyerDescriptor) { + requireNonNull(index); + requireNonNull(editBuyerDescriptor); + + this.index = index; + this.editBuyerDescriptor = new EditBuyerDescriptor(editBuyerDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredBuyerList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BUYER_DISPLAYED_INDEX); + } + + Buyer buyerToEdit = lastShownList.get(index.getZeroBased()); + Buyer editedBuyer = createEditedBuyer(buyerToEdit, editBuyerDescriptor); + + if (!buyerToEdit.isSameBuyer(editedBuyer) && model.hasBuyer(editedBuyer)) { + throw new CommandException(MESSAGE_DUPLICATE_BUYER); + } + + model.setBuyer(buyerToEdit, editedBuyer); + model.updateFilteredBuyerList(PREDICATE_SHOW_ALL_BUYERS); + return new CommandResult(String.format(MESSAGE_EDIT_BUYER_SUCCESS, editedBuyer)); + } + + /** + * Creates and returns a {@code Buyer} with the details of {@code buyerToEdit} + * edited with {@code editBuyerDescriptor}. + */ + private static Buyer createEditedBuyer(Buyer buyerToEdit, EditBuyerDescriptor editBuyerDescriptor) { + assert buyerToEdit != null; + + Name updatedName = editBuyerDescriptor.getName().orElse(buyerToEdit.getName()); + Phone updatedPhone = editBuyerDescriptor.getPhone().orElse(buyerToEdit.getPhone()); + Email updatedEmail = editBuyerDescriptor.getEmail().orElse(buyerToEdit.getEmail()); + Address updatedAddress = editBuyerDescriptor.getAddress().orElse(buyerToEdit.getAddress()); + PriceRange updatedPriceRange = editBuyerDescriptor + .getPriceRange() + .orElse(buyerToEdit.getPriceRange().orElse(null)); + if (updatedPriceRange != null && updatedPriceRange.isReset()) { + updatedPriceRange = null; + } + Characteristics updatedCharacteristics = editBuyerDescriptor + .getDesiredCharacteristics() + .orElse(buyerToEdit.getDesiredCharacteristics().orElse(null)); + if (updatedCharacteristics != null && updatedCharacteristics.isReset()) { + updatedCharacteristics = null; + } + Priority updatedPriority = editBuyerDescriptor.getPriority().orElse(buyerToEdit.getPriority()); + + LocalDateTime entryTime = buyerToEdit.getEntryTime(); + + Buyer newBuyer = new Buyer(updatedName, updatedPhone, updatedEmail, updatedAddress, + updatedPriceRange, updatedCharacteristics, updatedPriority, entryTime); + + return newBuyer; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditBuyerCommand)) { + return false; + } + + // state check + EditBuyerCommand e = (EditBuyerCommand) other; + return index.equals(e.index) + && editBuyerDescriptor.equals(e.editBuyerDescriptor); + } + + /** + * Stores the details to edit the buyer with. Each non-empty field value will replace the + * corresponding field value of the buyer. + */ + public static class EditBuyerDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private PriceRange priceRange; + private Characteristics characteristics; + private Priority priority; + + public EditBuyerDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code EditBuyerDescriptor} is used internally. + */ + public EditBuyerDescriptor(EditBuyerDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setPriceRange(toCopy.priceRange); + setDesiredCharacteristics(toCopy.characteristics); + setPriority(toCopy.priority); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, email, address, priceRange, characteristics, priority); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setPriceRange(PriceRange priceRange) { + this.priceRange = priceRange; + } + + public Optional getPriceRange() { + return Optional.ofNullable(priceRange); + } + + public void setDesiredCharacteristics(Characteristics characteristics) { + this.characteristics = characteristics; + } + + public Optional getDesiredCharacteristics() { + return Optional.ofNullable(characteristics); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setPriority(Priority priority) { + this.priority = priority; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional getPriority() { + return Optional.ofNullable(priority); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditBuyerDescriptor)) { + return false; + } + + // state check + EditBuyerDescriptor e = (EditBuyerDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getAddress().equals(e.getAddress()) + && getPriceRange().equals(e.getPriceRange()) + && getDesiredCharacteristics().equals(e.getDesiredCharacteristics()) + && getPriority().equals(e.getPriority()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/FilterBuyersCommand.java b/src/main/java/seedu/address/logic/commands/buyer/FilterBuyersCommand.java new file mode 100644 index 00000000000..1834a130062 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/FilterBuyersCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FUZZY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.buyer.Buyer; + + +/** + * Filters and lists all buyers in the buyer list that have a price range that accepts the given price, + * contains all the characteristics in the given characteristics list, or have the given the priority. + * More than one filtering criteria can be accepted, buyers matching any or all of the given criteria can be returned + * based on whether the user passes in the 'PREFIX_MATCH_ALL' flag. + * Keyword matching is case-insensitive. + */ +public class FilterBuyersCommand extends Command { + + public static final String COMMAND_WORD = "filterbuyers"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all buyers in Cobb that satisfy" + + " ALL the conditions provided for price, characteristics and priority.\n" + + "Pass in " + PREFIX_FUZZY + " to loosen the filtering criteria, meaning filtered buyers will only need" + + " to satisfy the given price OR contain at least one given characteristic OR have the given priority.\n" + + "Parameters: [" + PREFIX_PRICE + " PRICE] " + + "[" + PREFIX_CHARACTERISTICS + " CHARACTERISTICS] " + + "[" + PREFIX_PRIORITY + " PRIORITY] " + + "[" + PREFIX_FUZZY + "]\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_PRIORITY + " NORMAL"; + + private final Predicate predicate; + + public FilterBuyersCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredBuyerList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_BUYERS_LISTED_OVERVIEW, model.getFilteredBuyerList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterBuyersCommand // instanceof handles nulls + && predicate.equals(((FilterBuyersCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/FindBuyersCommand.java b/src/main/java/seedu/address/logic/commands/buyer/FindBuyersCommand.java new file mode 100644 index 00000000000..68e2c4a65ea --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/FindBuyersCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.buyer.BuyerNameContainsSubstringPredicate; + +/** + * Finds and lists all buyers in buyer book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindBuyersCommand extends Command { + + public static final String COMMAND_WORD = "findbuyers"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all buyers whose names contain the specified string (case insensitive).\n" + + "Example: " + COMMAND_WORD + " John"; + + private final BuyerNameContainsSubstringPredicate predicate; + + public FindBuyersCommand(BuyerNameContainsSubstringPredicate predicate) { + this.predicate = predicate; + } + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredBuyerList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_BUYERS_LISTED_OVERVIEW, model.getFilteredBuyerList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindBuyersCommand // instanceof handles nulls + && predicate.equals(((FindBuyersCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/ListBuyersCommand.java b/src/main/java/seedu/address/logic/commands/buyer/ListBuyersCommand.java new file mode 100644 index 00000000000..7ee7cb37c66 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/ListBuyersCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BUYERS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all buyers in the buyer book to the user. + */ +public class ListBuyersCommand extends Command { + + public static final String COMMAND_WORD = "listbuyers"; + + public static final String MESSAGE_SUCCESS = "Listed all buyers"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredBuyerList(PREDICATE_SHOW_ALL_BUYERS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/MatchBuyerCommand.java b/src/main/java/seedu/address/logic/commands/buyer/MatchBuyerCommand.java new file mode 100644 index 00000000000..f40472ece04 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/MatchBuyerCommand.java @@ -0,0 +1,106 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STRICT; + +import java.util.ArrayList; +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.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.commands.property.FilterPropertiesCommand; +import seedu.address.model.Model; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.property.FilterPropsByPriceRangePredicate; +import seedu.address.model.property.FilterPropsContainingAllCharacteristicsPredicate; +import seedu.address.model.property.FilterPropsContainingAnyCharacteristicPredicate; +import seedu.address.model.property.Property; + +/** + * Matches {@code properties} to {@code buyers} that either has a price within the buyer's price range, + * or has at least 1 characteristic that the buyer has as well. + */ +public class MatchBuyerCommand extends Command { + + public static final String COMMAND_WORD = "matchbuyer"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Matches a buyer to all properties that are within the buyer's budget" + + " and has at least one of the buyer's desired characteristics.\n" + + "Pass in " + PREFIX_STRICT + " to reduce the matches to only properties that satisfy all of the" + + " buyer's desired characteristics.\n" + + "Parameters: INDEX " + + "[" + PREFIX_STRICT + "]\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_STRICT; + + public static final String MESSAGE_MATCHED_BUYER_SUCCESS = "%s matched properties for the buyer:\n%s"; + + private final Index targetIndex; + private final boolean isMatchingAll; + + /** + * Constructor for MatchBuyerCommand. + * + * @param targetIndex the index of the property to be matched. + * @param isMatchingAll whether all the conditions specified must be satisfied in each of the resulting buyers. + */ + public MatchBuyerCommand(Index targetIndex, boolean isMatchingAll) { + this.targetIndex = targetIndex; + this.isMatchingAll = isMatchingAll; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredBuyerList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BUYER_DISPLAYED_INDEX); + } + + Buyer buyerToMatch = lastShownList.get(targetIndex.getZeroBased()); + + ArrayList> predicatesList = new ArrayList<>(); + + if (buyerToMatch.getDesiredCharacteristics().isEmpty() && buyerToMatch.getPriceRange().isEmpty()) { + throw new + CommandException(Messages.MESSAGE_INVALID_PROPERTY_BUYER_MATCH); + } else { + if (buyerToMatch.getPriceRange().isPresent()) { + predicatesList.add(new FilterPropsByPriceRangePredicate(buyerToMatch.getPriceRange().get())); + } + + if (buyerToMatch.getDesiredCharacteristics().isPresent()) { + if (isMatchingAll) { + predicatesList.add(new FilterPropsContainingAllCharacteristicsPredicate( + buyerToMatch.getDesiredCharacteristics().get())); + } else { + predicatesList.add(new FilterPropsContainingAnyCharacteristicPredicate( + buyerToMatch.getDesiredCharacteristics().get())); + } + } + } + + assert(!predicatesList.isEmpty()); + + Predicate combinedPredicate; + combinedPredicate = predicatesList.stream().reduce(Predicate::and).get(); + + new FilterPropertiesCommand(combinedPredicate).execute(model); + + return new CommandResult(String.format(MESSAGE_MATCHED_BUYER_SUCCESS, + model.getFilteredPropertyList().size(), buyerToMatch)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchBuyerCommand // instanceof handles nulls + && targetIndex.equals(((MatchBuyerCommand) other).targetIndex)) + && isMatchingAll == (((MatchBuyerCommand) other).isMatchingAll); + } +} diff --git a/src/main/java/seedu/address/logic/commands/buyer/SortBuyersCommand.java b/src/main/java/seedu/address/logic/commands/buyer/SortBuyersCommand.java new file mode 100644 index 00000000000..5d3ace78b17 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/buyer/SortBuyersCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.commands.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.logic.sortcomparators.Order.OrderType.DESC; + +import java.util.Comparator; + +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.buyer.Buyer; + +/** + * Sorts the buyers list. + */ +public class SortBuyersCommand extends Command { + + public static final String COMMAND_WORD = "sortbuyers"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts buyers by name, price range, priority, or time of creation in ascending or descending order." + + " You can only sort by one criteria at a time.\n" + + "Parameters: " + + "[" + PREFIX_NAME + " NAME] " + + "[" + PREFIX_PRICE_RANGE + " PRICE RANGE] " + + "[" + PREFIX_PRIORITY + " PRIORITY] " + + "[" + PREFIX_TIME + " TIME OF CREATION]\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + " " + DESC; + + public static final String MESSAGE_SUCCESS = "Sorted buyers by: %s"; + + private final Comparator comparator; + + /** + * Creates a SortBuyerCommand to add the specified {@code Buyer} + * @param comparator + */ + public SortBuyersCommand(Comparator comparator) { + requireNonNull(comparator); + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.sortBuyerList(comparator); + return new CommandResult(String.format(MESSAGE_SUCCESS, comparator.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortBuyersCommand // instanceof handles nulls + && comparator.equals(((SortBuyersCommand) other).comparator)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/AddPropertyCommand.java b/src/main/java/seedu/address/logic/commands/property/AddPropertyCommand.java new file mode 100644 index 00000000000..368e321ce53 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/AddPropertyCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_OWNER_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; + +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.property.Property; + +/** + * Adds a property to the property book. + */ +public class AddPropertyCommand extends Command { + + public static final String COMMAND_WORD = "addprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a property to Cobb.\n" + + "Parameters: " + + PREFIX_NAME + " NAME " + + PREFIX_PRICE + " PRICE " + + PREFIX_ADDRESS + " ADDRESS " + + PREFIX_DESCRIPTION + " DESCRIPTION " + + PREFIX_OWNER_NAME + " OWNER NAME " + + PREFIX_PHONE + " OWNER PHONE " + + "[" + PREFIX_CHARACTERISTICS + " CHARACTERISTICS]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + " Clementi Flat " + + PREFIX_PRICE + " 500000 " + + PREFIX_ADDRESS + " 311 Clementi Ave 2, #02-25 " + + PREFIX_DESCRIPTION + " Ready to be bought " + + PREFIX_OWNER_NAME + " John " + + PREFIX_PHONE + " 98765432 " + + PREFIX_CHARACTERISTICS + " 3-Room; Near School"; + + public static final String MESSAGE_SUCCESS = "New property added!\n%1$s"; + public static final String MESSAGE_DUPLICATE_PROPERTY = "This property already exists in Cobb"; + + private final Property property; + + /** + * Creates an AddPropertyCommand to add the specified {@code Property} + */ + public AddPropertyCommand(Property property) { + requireNonNull(property); + this.property = property; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasProperty(property)) { + throw new CommandException(MESSAGE_DUPLICATE_PROPERTY); + } + + model.addProperty(property); + return new CommandResult(String.format(MESSAGE_SUCCESS, property)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddPropertyCommand // instanceof handles nulls + && property.equals(((AddPropertyCommand) other).property)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/DeletePropertyCommand.java b/src/main/java/seedu/address/logic/commands/property/DeletePropertyCommand.java new file mode 100644 index 00000000000..d5d87f1953a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/DeletePropertyCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.commands.property; + +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.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.property.Property; + +/** + * Deletes a property identified using it's displayed index from the property book. + */ +public class DeletePropertyCommand extends Command { + + public static final String COMMAND_WORD = "deleteprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the property identified by the index number used in the displayed property list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PROPERTY_SUCCESS = "Deleted Property!\n%1$s"; + + private final Index targetIndex; + + public DeletePropertyCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPropertyList = model.getFilteredPropertyList(); + + if (targetIndex.getZeroBased() >= lastShownPropertyList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX); + } + + Property propertyToDelete = lastShownPropertyList.get(targetIndex.getZeroBased()); + model.deleteProperty(propertyToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_PROPERTY_SUCCESS, propertyToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeletePropertyCommand // instanceof handles nulls + && targetIndex.equals(((DeletePropertyCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/EditPropertyCommand.java b/src/main/java/seedu/address/logic/commands/property/EditPropertyCommand.java new file mode 100644 index 00000000000..8564a202f7f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/EditPropertyCommand.java @@ -0,0 +1,261 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_OWNER_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PROPERTIES; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +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.address.Address; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; +import seedu.address.model.property.Description; +import seedu.address.model.property.Owner; +import seedu.address.model.property.Property; +import seedu.address.model.property.PropertyName; + +/** + * Edits the details of an existing property in the property book. + */ +public class EditPropertyCommand extends Command { + + public static final String COMMAND_WORD = "editprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the property identified " + + "by the index number used in the displayed property list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX " + + "[" + PREFIX_NAME + " NAME] " + + "[" + PREFIX_PRICE + " PRICE] " + + "[" + PREFIX_ADDRESS + " ADDRESS] " + + "[" + PREFIX_DESCRIPTION + " DESC] " + + "[" + PREFIX_OWNER_NAME + " OWNER NAME] " + + "[" + PREFIX_PHONE + " OWNER PHONE] " + + "[" + PREFIX_CHARACTERISTICS + " CHARACTERISTICS]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PRICE + " 500000 " + + PREFIX_ADDRESS + " Heng Mui Keng Terrace 22 " + + PREFIX_CHARACTERISTICS + " 5-Room; Secluded "; + + public static final String MESSAGE_EDIT_PROPERTY_SUCCESS = "Edited Property!\n%1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_PROPERTY = "This property already exists in Cobb."; + + private final Index index; + private final EditPropertyDescriptor editPropertyDescriptor; + + /** + * @param index of the Property in the filtered Property list to edit + * @param editPropertyDescriptor details to edit the Property with + */ + public EditPropertyCommand(Index index, EditPropertyDescriptor editPropertyDescriptor) { + requireAllNonNull(index, editPropertyDescriptor); + + this.index = index; + this.editPropertyDescriptor = new EditPropertyDescriptor(editPropertyDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPropertyList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX); + } + + Property propertyToEdit = lastShownList.get(index.getZeroBased()); + Property editedProperty = createEditedProperty(propertyToEdit, editPropertyDescriptor); + + if (!propertyToEdit.isSameProperty(editedProperty) && model.hasProperty(editedProperty)) { + throw new CommandException(MESSAGE_DUPLICATE_PROPERTY); + } + + model.setProperty(propertyToEdit, editedProperty); + model.updateFilteredPropertyList(PREDICATE_SHOW_ALL_PROPERTIES); + return new CommandResult(String.format(MESSAGE_EDIT_PROPERTY_SUCCESS, editedProperty)); + } + + /** + * Creates and returns a {@code Property} with the details of {@code propertyToEdit} + * edited with {@code descriptor}. + */ + private static Property createEditedProperty(Property propertyToEdit, EditPropertyDescriptor descriptor) { + assert propertyToEdit != null; + + PropertyName updatedPropertyName = descriptor.getName().orElse(propertyToEdit.getPropertyName()); + Price updatedPrice = descriptor.getPrice().orElse(propertyToEdit.getPrice()); + Address updatedAddress = descriptor.getAddress().orElse(propertyToEdit.getAddress()); + Description updatedDescription = descriptor.getDescription().orElse(propertyToEdit.getDescription()); + Characteristics updatedCharacteristics = descriptor + .getCharacteristics() + .orElse(propertyToEdit.getCharacteristics().orElse(null)); + if (updatedCharacteristics != null && updatedCharacteristics.isReset()) { + updatedCharacteristics = null; + } + + Name updatedOwnerName = descriptor.getOwnerName().orElse(propertyToEdit.getOwnerName()); + Phone updatedOwnerPhone = descriptor.getOwnerPhone().orElse(propertyToEdit.getOwnerPhone()); + Owner updatedOwner = new Owner(updatedOwnerName, updatedOwnerPhone); + + // Should this be updated + LocalDateTime entryTime = propertyToEdit.getPropertyEntryTime(); + + return new Property(updatedPropertyName, updatedPrice, updatedAddress, updatedDescription, + updatedCharacteristics, updatedOwner, entryTime); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPropertyCommand)) { + return false; + } + + // state check + EditPropertyCommand e = (EditPropertyCommand) other; + return index.equals(e.index) + && editPropertyDescriptor.equals(e.editPropertyDescriptor); + } + + /** + * Stores the details to edit the Property with. Each non-empty field value will replace the + * corresponding field value of the Property. + */ + public static class EditPropertyDescriptor { + private PropertyName name; + private Price price; + private Address address; + private Description description; + private Characteristics characteristics; + private Name ownerName; + private Phone ownerPhone; + + public EditPropertyDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPropertyDescriptor(EditPropertyDescriptor toCopy) { + setName(toCopy.name); + setPrice(toCopy.price); + setDescription(toCopy.description); + setAddress(toCopy.address); + setCharacteristics(toCopy.characteristics); + setOwnerName(toCopy.ownerName); + setOwnerPhone(toCopy.ownerPhone); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, price, address, description, address, characteristics, + ownerName, ownerPhone); + } + + public void setName(PropertyName propertyName) { + this.name = propertyName; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPrice(Price price) { + this.price = price; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setOwnerName(Name name) { + this.ownerName = name; + } + + public Optional getOwnerName() { + return Optional.ofNullable(ownerName); + } + + public void setOwnerPhone(Phone phone) { + this.ownerPhone = phone; + } + + public Optional getOwnerPhone() { + return Optional.ofNullable(ownerPhone); + } + + public void setCharacteristics(Characteristics characteristics) { + this.characteristics = characteristics; + } + + public Optional getCharacteristics() { + return Optional.ofNullable(characteristics); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPropertyDescriptor)) { + return false; + } + + // state check + EditPropertyDescriptor e = (EditPropertyDescriptor) other; + + return getName().equals(e.getName()) + && getPrice().equals(e.getPrice()) + && getDescription().equals(e.getDescription()) + && getAddress().equals(e.getAddress()) + && getCharacteristics().equals(e.getCharacteristics()) + && getOwnerName().equals(e.getOwnerName()) + && getOwnerPhone().equals(e.getOwnerPhone()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/FilterPropertiesCommand.java b/src/main/java/seedu/address/logic/commands/property/FilterPropertiesCommand.java new file mode 100644 index 00000000000..3d9eac34bde --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/FilterPropertiesCommand.java @@ -0,0 +1,63 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FUZZY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_OWNER_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE_RANGE; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.property.Property; + + +/** + * Filters and lists all properties in the property list that have a price that is within the given price, + * contains all the characteristics in the given characteristics list, or have the given seller. + * More than one filtering criteria can be accepted, properties matching any OR all of the given criteria can be + * returned based on whether the user passes in the 'PREFIX_MATCH_ALL' flag. + * Keyword matching is case-insensitive. + */ +public class FilterPropertiesCommand extends Command { + + public static final String COMMAND_WORD = "filterprops"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all properties in Cobb that satisfy ALL the" + + " conditions provided for price range, characteristics and owner name.\n" + + "Pass in " + PREFIX_FUZZY + " to loosen the filtering criteria, meaning filtered properties will only" + + " need to satisfy the given price range OR contain at least one given characteristic OR have the given" + + " owner name.\n" + + "Parameters: " + + "[" + PREFIX_PRICE_RANGE + " PRICE RANGE] " + + "[" + PREFIX_CHARACTERISTICS + " CHARACTERISTICS] " + + "[" + PREFIX_OWNER_NAME + " OWNER NAME] " + + "[" + PREFIX_FUZZY + "]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CHARACTERISTICS + " Near MRT; Kid-Friendly " + PREFIX_FUZZY; + + private final Predicate predicate; + + public FilterPropertiesCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPropertyList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PROPERTY_LISTED_OVERVIEW, model.getFilteredPropertyList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPropertiesCommand // instanceof handles nulls + && predicate.equals(((FilterPropertiesCommand) other).predicate)); // state check + } +} + diff --git a/src/main/java/seedu/address/logic/commands/property/FindPropertiesCommand.java b/src/main/java/seedu/address/logic/commands/property/FindPropertiesCommand.java new file mode 100644 index 00000000000..53b1addc87b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/FindPropertiesCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.property.PropertyNameContainsSubstringPredicate; + +/** + * Finds and lists all properties in property book whose name contains the given string. + * Keyword matching is case insensitive. + */ +public class FindPropertiesCommand extends Command { + + public static final String COMMAND_WORD = "findprops"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all properties whose names contain the specified string (case insensitive).\n" + + "Example: " + COMMAND_WORD + " college"; + + private final PropertyNameContainsSubstringPredicate predicate; + + public FindPropertiesCommand(PropertyNameContainsSubstringPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPropertyList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PROPERTY_LISTED_OVERVIEW, model.getFilteredPropertyList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindPropertiesCommand // instanceof handles nulls + && predicate.equals(((FindPropertiesCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/ListPropertiesCommand.java b/src/main/java/seedu/address/logic/commands/property/ListPropertiesCommand.java new file mode 100644 index 00000000000..7374a154736 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/ListPropertiesCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PROPERTIES; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all properties in the property book to the user. + */ +public class ListPropertiesCommand extends Command { + + public static final String COMMAND_WORD = "listprops"; + + public static final String MESSAGE_SUCCESS = "Listed all properties"; + + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPropertyList(PREDICATE_SHOW_ALL_PROPERTIES); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/MatchPropertyCommand.java b/src/main/java/seedu/address/logic/commands/property/MatchPropertyCommand.java new file mode 100644 index 00000000000..9ada2187e86 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/MatchPropertyCommand.java @@ -0,0 +1,98 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STRICT; + +import java.util.ArrayList; +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.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.buyer.FilterBuyersCommand; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.FilterBuyersByPricePredicate; +import seedu.address.model.buyer.FilterBuyersContainingAllCharacteristicsPredicate; +import seedu.address.model.buyer.FilterBuyersContainingAnyCharacteristicPredicate; +import seedu.address.model.property.Property; + +/** + * Matches {@code properties} to {@code buyers} that either has a price within the buyer's price range, + * or has at least 1 characteristic that the buyer has as well. + */ +public class MatchPropertyCommand extends Command { + + public static final String COMMAND_WORD = "matchprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Matches a property to all buyers with a suitable budget" + + " and desire at least one of the property's characteristics.\n" + + "Pass in " + PREFIX_STRICT + " to reduce the matches to only buyers that desire all of the" + + " property's characteristics.\n" + + "Parameters: INDEX " + + "[" + PREFIX_STRICT + "]\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_STRICT; + + public static final String MESSAGE_MATCHED_PROPERTY_SUCCESS = "%s matched buyers for the property:\n%s"; + + private final Index targetIndex; + private final boolean isMatchingAll; + + /** + * Constructor for MatchPropertyCommand. + * + * @param targetIndex the index of the property to be matched. + * @param isMatchingAll whether all the conditions specified must be satisfied in each of the resulting buyers. + */ + public MatchPropertyCommand(Index targetIndex, boolean isMatchingAll) { + this.targetIndex = targetIndex; + this.isMatchingAll = isMatchingAll; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPropertyList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX); + } + + Property propertyToMatch = lastShownList.get(targetIndex.getZeroBased()); + + ArrayList> predicatesList = new ArrayList<>(); + predicatesList.add(new FilterBuyersByPricePredicate(propertyToMatch.getPrice())); + if (propertyToMatch.getCharacteristics().isPresent()) { + if (isMatchingAll) { + predicatesList.add(new FilterBuyersContainingAllCharacteristicsPredicate( + propertyToMatch.getCharacteristics().get())); + } else { + predicatesList.add(new FilterBuyersContainingAnyCharacteristicPredicate( + propertyToMatch.getCharacteristics().get())); + } + } + + // predicatesList must not be empty, since at least FilterBuyerByPricePredicate should be added + assert(!predicatesList.isEmpty()); + + Predicate combinedPredicate; + combinedPredicate = predicatesList.stream().reduce(Predicate::and).get(); + + new FilterBuyersCommand(combinedPredicate).execute(model); + + return new CommandResult(String.format(MESSAGE_MATCHED_PROPERTY_SUCCESS, + model.getFilteredBuyerList().size(), propertyToMatch)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchPropertyCommand // instanceof handles nulls + && targetIndex.equals(((MatchPropertyCommand) other).targetIndex)) + && isMatchingAll == (((MatchPropertyCommand) other).isMatchingAll); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/property/SortPropertiesCommand.java b/src/main/java/seedu/address/logic/commands/property/SortPropertiesCommand.java new file mode 100644 index 00000000000..a944eeaf865 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/property/SortPropertiesCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.logic.sortcomparators.Order.OrderType.ASC; + +import java.util.Comparator; + +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.property.Property; + +/** + * Sorts the properties list. + */ +public class SortPropertiesCommand extends Command { + public static final String COMMAND_WORD = "sortprops"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts properties by property name, price or time of creation in ascending or descending order." + + " You can only sort by one criteria at a time.\n" + + "Parameters: " + + "[" + PREFIX_NAME + " NAME] " + + "[" + PREFIX_PRICE + " PRICE] " + + "[" + PREFIX_TIME + " TIME OF CREATION]\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + " " + ASC; + + public static final String MESSAGE_SUCCESS = "Sorted properties by: %s"; + + private final Comparator comparator; + + /** + * Creates a SortPropertiesCommand to add the specified {@code Property} + * @param comparator + */ + public SortPropertiesCommand(Comparator comparator) { + requireNonNull(comparator); + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.sortPropertyList(comparator); + return new CommandResult(String.format(MESSAGE_SUCCESS, comparator.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortPropertiesCommand // instanceof handles nulls + && comparator.equals(((SortPropertiesCommand) other).comparator)); + } +} 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 { - - /** - * 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 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 deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -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.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @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 parseCommand(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, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - 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(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..8af9a124644 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -70,11 +70,14 @@ private static List 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); - return prefixIndex == -1 ? -1 - : prefixIndex + 1; // +1 as offset for whitespace + int prefixIndex = argsString.indexOf(" " + prefix + " ", fromIndex); + int endingPrefixIndex = argsString.indexOf(" " + prefix, fromIndex); + return prefixIndex != -1 + ? prefixIndex + 1 // +1 as offset for whitespace + : endingPrefixIndex != -1 && endingPrefixIndex == argsString.length() - 1 - prefix.length() + ? endingPrefixIndex + 1 // +1 as offset for whitespace + : -1; } - /** * Extracts prefixes and their argument values, and returns an {@code ArgumentMultimap} object that maps the * extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..a5d4e9586fe 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -6,10 +6,17 @@ public class CliSyntax { /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_NAME = new Prefix("-n"); + public static final Prefix PREFIX_OWNER_NAME = new Prefix("-o"); + public static final Prefix PREFIX_PHONE = new Prefix("-ph"); + public static final Prefix PREFIX_EMAIL = new Prefix("-e"); + public static final Prefix PREFIX_ADDRESS = new Prefix("-a"); + public static final Prefix PREFIX_PRIORITY = new Prefix("-pr"); + public static final Prefix PREFIX_PRICE_RANGE = new Prefix("-r"); + public static final Prefix PREFIX_CHARACTERISTICS = new Prefix("-c"); + public static final Prefix PREFIX_PRICE = new Prefix("-p"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("-d"); + public static final Prefix PREFIX_FUZZY = new Prefix("-fuzzy"); + public static final Prefix PREFIX_STRICT = new Prefix("-strict"); + public static final Prefix PREFIX_TIME = new Prefix("-t"); } diff --git a/src/main/java/seedu/address/logic/parser/CobbParser.java b/src/main/java/seedu/address/logic/parser/CobbParser.java new file mode 100644 index 00000000000..7a0b0bb5095 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CobbParser.java @@ -0,0 +1,134 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.buyer.AddBuyerCommand; +import seedu.address.logic.commands.buyer.DeleteBuyerCommand; +import seedu.address.logic.commands.buyer.EditBuyerCommand; +import seedu.address.logic.commands.buyer.FilterBuyersCommand; +import seedu.address.logic.commands.buyer.FindBuyersCommand; +import seedu.address.logic.commands.buyer.ListBuyersCommand; +import seedu.address.logic.commands.buyer.MatchBuyerCommand; +import seedu.address.logic.commands.buyer.SortBuyersCommand; +import seedu.address.logic.commands.property.AddPropertyCommand; +import seedu.address.logic.commands.property.DeletePropertyCommand; +import seedu.address.logic.commands.property.EditPropertyCommand; +import seedu.address.logic.commands.property.FilterPropertiesCommand; +import seedu.address.logic.commands.property.FindPropertiesCommand; +import seedu.address.logic.commands.property.ListPropertiesCommand; +import seedu.address.logic.commands.property.MatchPropertyCommand; +import seedu.address.logic.commands.property.SortPropertiesCommand; +import seedu.address.logic.parser.buyer.AddBuyerCommandParser; +import seedu.address.logic.parser.buyer.DeleteBuyerCommandParser; +import seedu.address.logic.parser.buyer.EditBuyerCommandParser; +import seedu.address.logic.parser.buyer.FilterBuyersCommandParser; +import seedu.address.logic.parser.buyer.FindBuyersCommandParser; +import seedu.address.logic.parser.buyer.MatchBuyerCommandParser; +import seedu.address.logic.parser.buyer.SortBuyersCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.property.AddPropertyCommandParser; +import seedu.address.logic.parser.property.DeletePropertyCommandParser; +import seedu.address.logic.parser.property.EditPropertyCommandParser; +import seedu.address.logic.parser.property.FilterPropertiesCommandParser; +import seedu.address.logic.parser.property.FindPropertiesCommandParser; +import seedu.address.logic.parser.property.MatchPropertyCommandParser; +import seedu.address.logic.parser.property.SortPropertiesCommandParser; + +/** + * Parses user input. + */ +public class CobbParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @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 parseCommand(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, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case AddBuyerCommand.COMMAND_WORD: + return new AddBuyerCommandParser().parse(arguments); + + case EditBuyerCommand.COMMAND_WORD: + return new EditBuyerCommandParser().parse(arguments); + + case DeleteBuyerCommand.COMMAND_WORD: + return new DeleteBuyerCommandParser().parse(arguments); + + case DeletePropertyCommand.COMMAND_WORD: + return new DeletePropertyCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindBuyersCommand.COMMAND_WORD: + return new FindBuyersCommandParser().parse(arguments); + + case FindPropertiesCommand.COMMAND_WORD: + return new FindPropertiesCommandParser().parse(arguments); + + case ListBuyersCommand.COMMAND_WORD: + return new ListBuyersCommand(); + + case ListPropertiesCommand.COMMAND_WORD: + return new ListPropertiesCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case AddPropertyCommand.COMMAND_WORD: + return new AddPropertyCommandParser().parse(arguments); + + case EditPropertyCommand.COMMAND_WORD: + return new EditPropertyCommandParser().parse(arguments); + + case FilterBuyersCommand.COMMAND_WORD: + return new FilterBuyersCommandParser().parse(arguments); + + case FilterPropertiesCommand.COMMAND_WORD: + return new FilterPropertiesCommandParser().parse(arguments); + + case SortBuyersCommand.COMMAND_WORD: + return new SortBuyersCommandParser().parse(arguments); + + case SortPropertiesCommand.COMMAND_WORD: + return new SortPropertiesCommandParser().parse(arguments); + + case MatchBuyerCommand.COMMAND_WORD: + return new MatchBuyerCommandParser().parse(arguments); + + case MatchPropertyCommand.COMMAND_WORD: + return new MatchPropertyCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -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.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * 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 { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java 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 { - - /** - * 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 tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * 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 { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..17f4896f66d 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -1,16 +1,42 @@ package seedu.address.logic.parser; +import java.util.stream.Stream; + import seedu.address.logic.commands.Command; import seedu.address.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. */ -public interface Parser { +public abstract class Parser { /** * Parses {@code userInput} into a command and returns it. * @throws ParseException if {@code userInput} does not conform the expected format */ - T parse(String userInput) throws ParseException; + public abstract T parse(String userInput) throws ParseException; + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if more than one {@code Prefix} is present in the given + * {@code ArgumentMultimap}. + */ + public boolean areMoreThanOnePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isPresent()).count() > 1; + } + + /** + * Returns false if no {@code Prefix} is present in the given + * {@code ArgumentMultimap}. + */ + public boolean isAnyPrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isPresent()).count() > 0; + } } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..b2feb14afbe 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,26 +1,31 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; 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.tag.Tag; +import seedu.address.logic.sortcomparators.Order; +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Email; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.buyer.Priority; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; +import seedu.address.model.price.PriceRange; +import seedu.address.model.property.Description; +import seedu.address.model.property.Owner; +import seedu.address.model.property.PropertyName; + /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_INDEX = "Index is not a positive integer or is too large."; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -96,29 +101,129 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String priority} into a {@code Priority}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code priority} is invalid. + */ + public static Priority parsePriority(String priority) throws ParseException { + requireNonNull(priority); + String trimmedPriority = priority.trim().toUpperCase(); + if (!Priority.isValidPriority(trimmedPriority)) { + throw new ParseException(Priority.MESSAGE_CONSTRAINTS); + } + return new Priority(trimmedPriority); + } + + /** + * Parses a {@code String description} into a {@code Description}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code description} is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses {@code String range} into a {@code PriceRange}. + */ + public static PriceRange parsePriceRange(String range) throws ParseException { + requireNonNull(range); + String trimmedRange = range.trim(); + if (!PriceRange.isValidPriceRange(trimmedRange)) { + throw new ParseException(PriceRange.MESSAGE_CONSTRAINTS); + } + if (trimmedRange.isEmpty()) { + return PriceRange.RESET_PRICE_RANGE; + } + return new PriceRange(trimmedRange); + } + + /** + * Parses {@code String characteristics} into a {@code Characteristics}. + */ + public static Characteristics parseCharacteristics(String characteristics) throws ParseException { + requireNonNull(characteristics); + String trimmedCharacteristics = characteristics.trim(); + if (!Characteristics.isValidCharacteristics(trimmedCharacteristics)) { + throw new ParseException(Characteristics.MESSAGE_CONSTRAINTS); + } + if (trimmedCharacteristics.isEmpty()) { + return Characteristics.RESET_CHARACTERISTICS; + } + return new Characteristics(trimmedCharacteristics); + } + + /** + * Parses a {@code String price} into a {@code Price}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code price} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static Price parsePrice(String price) throws ParseException { + requireNonNull(price); + String trimmedPrice = price.trim(); + if (!Price.isValidPrice(trimmedPrice)) { + throw new ParseException(Price.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Price(trimmedPrice); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String propertyName} into a {@code PropertyName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code propertyName} is invalid. + */ + public static PropertyName parsePropertyName(String propertyName) throws ParseException { + requireNonNull(propertyName); + String trimmedPropertyName = propertyName.trim(); + if (!PropertyName.isValidPropertyName(trimmedPropertyName)) { + throw new ParseException(PropertyName.MESSAGE_CONSTRAINTS); + } + return new PropertyName(trimmedPropertyName); + } + + /** + * Parses a {@code String ownerName} and a {@code String ownerPhone} into an {@code Owner}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code ownerName} or {@code ownerPhone} is invalid. + */ + public static Owner parseOwner(String ownerName, String ownerPhone) throws ParseException { + requireAllNonNull(ownerName, ownerPhone); + String trimmedOwnerName = ownerName.trim(); + String trimmedOwnerPhone = ownerPhone.trim(); + + if (!Name.isValidName(trimmedOwnerName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + + if (!Phone.isValidPhone(trimmedOwnerPhone)) { + throw new ParseException(Phone.MESSAGE_CONSTRAINTS); + } + + return new Owner(new Name(trimmedOwnerName), new Phone(trimmedOwnerPhone)); + } + + /** + * Parses a {@code String order} into an {@code Order}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code order} is invalid. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static Order parseOrder(String order) throws ParseException { + requireNonNull(order); + String trimmedOrder = order.trim(); + if (!Order.isValidOrder(trimmedOrder)) { + throw new ParseException(Order.MESSAGE_CONSTRAINTS); } - return tagSet; + return new Order(trimmedOrder); } } diff --git a/src/main/java/seedu/address/logic/parser/buyer/AddBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/AddBuyerCommandParser.java new file mode 100644 index 00000000000..e2f21adb843 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/AddBuyerCommandParser.java @@ -0,0 +1,80 @@ +package seedu.address.logic.parser.buyer; + +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_CHARACTERISTICS; +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_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +import java.time.LocalDateTime; + +import seedu.address.logic.commands.buyer.AddBuyerCommand; +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.exceptions.ParseException; +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.Email; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.buyer.Priority; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.PriceRange; + + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddBuyerCommandParser extends Parser { + + /** + * 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 AddBuyerCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_PRICE_RANGE, PREFIX_CHARACTERISTICS, PREFIX_PRIORITY); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddBuyerCommand.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()); + LocalDateTime entryTime = LocalDateTime.now(); + + // if user does not specify a budget for the buyer, then there will be no PriceRange object + PriceRange priceRange = null; + if (argMultimap.getValue(PREFIX_PRICE_RANGE).isPresent()) { + priceRange = ParserUtil.parsePriceRange(argMultimap.getValue(PREFIX_PRICE_RANGE).get()); + priceRange = priceRange.isReset() ? null : priceRange; + } + + Characteristics characteristics = null; + if (argMultimap.getValue(PREFIX_CHARACTERISTICS).isPresent()) { + characteristics = ParserUtil.parseCharacteristics(argMultimap.getValue(PREFIX_CHARACTERISTICS).get()); + characteristics = characteristics.isReset() ? null : characteristics; + } + + Priority priority = new Priority("normal"); + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + priority = ParserUtil.parsePriority(argMultimap.getValue(PREFIX_PRIORITY).get()); + } + + Buyer buyer = new Buyer(name, phone, email, address, priceRange, characteristics, priority, entryTime); + + return new AddBuyerCommand(buyer); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/buyer/DeleteBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/DeleteBuyerCommandParser.java new file mode 100644 index 00000000000..e9064b89a79 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/DeleteBuyerCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser.buyer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.buyer.DeleteBuyerCommand; +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 DeleteBuyerCommand object + */ +public class DeleteBuyerCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteBuyerCommand + * and returns a DeleteBuyerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteBuyerCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteBuyerCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteBuyerCommand(index); + } catch (ParseException pe) { + throw pe; + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/buyer/EditBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/EditBuyerCommandParser.java new file mode 100644 index 00000000000..ed10b665118 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/EditBuyerCommandParser.java @@ -0,0 +1,82 @@ +package seedu.address.logic.parser.buyer; + +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_CHARACTERISTICS; +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_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.buyer.EditBuyerCommand; +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.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditBuyerCommand object + */ +public class EditBuyerCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditBuyerCommand + * and returns an EditBuyerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditBuyerCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_PRICE_RANGE, PREFIX_CHARACTERISTICS, PREFIX_PRIORITY); + + if (argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditBuyerCommand.MESSAGE_USAGE)); + } + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw pe; + } + + EditBuyerCommand.EditBuyerDescriptor editBuyerDescriptor = new EditBuyerCommand.EditBuyerDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editBuyerDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editBuyerDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editBuyerDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editBuyerDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_PRICE_RANGE).isPresent()) { + editBuyerDescriptor.setPriceRange(ParserUtil + .parsePriceRange(argMultimap.getValue(PREFIX_PRICE_RANGE).get())); + } + if (argMultimap.getValue(PREFIX_CHARACTERISTICS).isPresent()) { + editBuyerDescriptor.setDesiredCharacteristics(ParserUtil + .parseCharacteristics(argMultimap.getValue(PREFIX_CHARACTERISTICS).get())); + } + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + editBuyerDescriptor.setPriority(ParserUtil + .parsePriority(argMultimap.getValue(PREFIX_PRIORITY).get())); + } + + if (!editBuyerDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditBuyerCommand.MESSAGE_NOT_EDITED); + } + + return new EditBuyerCommand(index, editBuyerDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/buyer/FilterBuyersCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/FilterBuyersCommandParser.java new file mode 100644 index 00000000000..ff7941bfad2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/FilterBuyersCommandParser.java @@ -0,0 +1,90 @@ +package seedu.address.logic.parser.buyer; + +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_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FUZZY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.buyer.FilterBuyersCommand; +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.exceptions.ParseException; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.FilterBuyersByPricePredicate; +import seedu.address.model.buyer.FilterBuyersByPriorityPredicate; +import seedu.address.model.buyer.FilterBuyersContainingAllCharacteristicsPredicate; +import seedu.address.model.buyer.FilterBuyersContainingAnyCharacteristicPredicate; +import seedu.address.model.buyer.Priority; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; + +/** + * Parses user input to create a {@code FilterBuyersCommand}. + */ +public class FilterBuyersCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterBuyersCommand + * and returns an FilterBuyersCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FilterBuyersCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CHARACTERISTICS, PREFIX_PRICE, + PREFIX_PRIORITY, PREFIX_FUZZY); + + if (!isAnyPrefixPresent(argMultimap, PREFIX_PRICE, PREFIX_CHARACTERISTICS, PREFIX_PRIORITY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FilterBuyersCommand.MESSAGE_USAGE)); + } + + List> predicatesList = new ArrayList<>(); + + + if (argMultimap.getValue(PREFIX_PRICE).isPresent()) { + Price price = ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get()); + predicatesList.add(new FilterBuyersByPricePredicate(price)); + } + + if (argMultimap.getValue(PREFIX_CHARACTERISTICS).isPresent()) { + Characteristics characteristics = ParserUtil.parseCharacteristics( + argMultimap.getValue(PREFIX_CHARACTERISTICS).get()); + if (characteristics.isReset()) { + throw new ParseException("-c flag should not be left empty."); + } + if (argMultimap.getValue(PREFIX_FUZZY).isPresent()) { + predicatesList.add(new FilterBuyersContainingAnyCharacteristicPredicate(characteristics)); + } else { + predicatesList.add(new FilterBuyersContainingAllCharacteristicsPredicate(characteristics)); + } + } + + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + Priority priority = ParserUtil.parsePriority( + argMultimap.getValue(PREFIX_PRIORITY).get()); + predicatesList.add(new FilterBuyersByPriorityPredicate(priority)); + } + + // predicatesList must not be empty, since at least 1 prefix should be present + assert(!predicatesList.isEmpty()); + + Predicate combinedPredicate; + if (arePrefixesPresent(argMultimap, PREFIX_FUZZY)) { + combinedPredicate = predicatesList.stream().reduce(Predicate::or).get(); + } else { + combinedPredicate = predicatesList.stream().reduce(Predicate::and).get(); + } + return new FilterBuyersCommand(combinedPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/buyer/FindBuyersCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/FindBuyersCommandParser.java new file mode 100644 index 00000000000..58010c252cd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/FindBuyersCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser.buyer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.buyer.FindBuyersCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.buyer.BuyerNameContainsSubstringPredicate; + +/** + * Parses input arguments and creates a new FindBuyersCommand object + */ +public class FindBuyersCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindBuyersCommand + * and returns a FindBuyersCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindBuyersCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindBuyersCommand.MESSAGE_USAGE)); + } + + return new FindBuyersCommand(new BuyerNameContainsSubstringPredicate(trimmedArgs)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/buyer/MatchBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/MatchBuyerCommandParser.java new file mode 100644 index 00000000000..bdc0c6a100a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/MatchBuyerCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser.buyer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STRICT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.buyer.MatchBuyerCommand; +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.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MatchBuyerCommand object + */ +public class MatchBuyerCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MatchBuyerCommand + * and returns a MatchBuyerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MatchBuyerCommand parse(String args) throws ParseException { + try { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_STRICT); + Index index = ParserUtil.parseIndex(argMultimap.getPreamble()); + boolean isMatchingAll = arePrefixesPresent(argMultimap, PREFIX_STRICT); + + return new MatchBuyerCommand(index, isMatchingAll); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchBuyerCommand.MESSAGE_USAGE), pe); + } + + } + +} diff --git a/src/main/java/seedu/address/logic/parser/buyer/SortBuyersCommandParser.java b/src/main/java/seedu/address/logic/parser/buyer/SortBuyersCommandParser.java new file mode 100644 index 00000000000..26d0cc00062 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/buyer/SortBuyersCommandParser.java @@ -0,0 +1,86 @@ +package seedu.address.logic.parser.buyer; + +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.Optional; + +import seedu.address.logic.commands.buyer.SortBuyersCommand; +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.exceptions.ParseException; +import seedu.address.logic.sortcomparators.BuyerComparator; +import seedu.address.logic.sortcomparators.BuyerNameComparator; +import seedu.address.logic.sortcomparators.Order; +import seedu.address.logic.sortcomparators.PriceRangeComparator; +import seedu.address.logic.sortcomparators.PriorityComparator; +import seedu.address.logic.sortcomparators.TimeComparator; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Priority; +import seedu.address.model.price.PriceRange; + +/** + * Parses user input to create a {@code SortBuyersCommand}. + */ +public class SortBuyersCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SortBuyersCommand + * and returns a SortBuyersCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public SortBuyersCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_NAME, PREFIX_PRICE_RANGE, PREFIX_PRIORITY, PREFIX_TIME); + + if (areMoreThanOnePrefixesPresent(argMultimap, + PREFIX_NAME, PREFIX_PRICE_RANGE, PREFIX_PRIORITY, PREFIX_TIME) + || !isAnyPrefixPresent(argMultimap, + PREFIX_NAME, PREFIX_PRICE_RANGE, PREFIX_PRIORITY, PREFIX_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortBuyersCommand.MESSAGE_USAGE)); + } + + Comparator buyerComparator = null; + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_NAME).get()); + Comparator nameComparator = new BuyerNameComparator(order); + buyerComparator = new BuyerComparator(nameComparator, null, null, null); + } + + if (argMultimap.getValue(PREFIX_PRICE_RANGE).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_PRICE_RANGE).get()); + Comparator> priceRangeComparator = new PriceRangeComparator(order); + buyerComparator = new BuyerComparator(null, priceRangeComparator, null, null); + } + + if (argMultimap.getValue(PREFIX_PRIORITY).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_PRIORITY).get()); + Comparator priorityComparator = new PriorityComparator(order); + buyerComparator = new BuyerComparator(null, null, priorityComparator, null); + } + + if (argMultimap.getValue(PREFIX_TIME).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_TIME).get()); + Comparator timeComparator = new TimeComparator(order); + buyerComparator = new BuyerComparator(null, null, null, timeComparator); + } + + + return new SortBuyersCommand(buyerComparator); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/property/AddPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/property/AddPropertyCommandParser.java new file mode 100644 index 00000000000..6e76d0431e1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/AddPropertyCommandParser.java @@ -0,0 +1,74 @@ +package seedu.address.logic.parser.property; + +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_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_OWNER_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; + +import java.time.LocalDateTime; + +import seedu.address.logic.commands.property.AddPropertyCommand; +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.exceptions.ParseException; +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; +import seedu.address.model.property.Description; +import seedu.address.model.property.Owner; +import seedu.address.model.property.Property; +import seedu.address.model.property.PropertyName; + +/** + * Parses input arguments and creates a new AddPropertyCommand object + */ +public class AddPropertyCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddPropertyCommand + * and returns an AddPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPropertyCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PRICE, PREFIX_ADDRESS, + PREFIX_DESCRIPTION, PREFIX_CHARACTERISTICS, PREFIX_OWNER_NAME, PREFIX_PHONE); + + if (!arePrefixesPresent( + argMultimap, PREFIX_NAME, PREFIX_PRICE, PREFIX_ADDRESS, PREFIX_DESCRIPTION, + PREFIX_OWNER_NAME, PREFIX_PHONE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPropertyCommand.MESSAGE_USAGE)); + } + + PropertyName propertyName = ParserUtil.parsePropertyName(argMultimap.getValue(PREFIX_NAME).get()); + Price price = ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + + Name ownerName = ParserUtil.parseName(argMultimap.getValue(PREFIX_OWNER_NAME).get()); + Phone ownerPhone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Owner newOwner = new Owner(ownerName, ownerPhone); + LocalDateTime entryTime = LocalDateTime.now(); + + Characteristics characteristics = null; + if (argMultimap.getValue(PREFIX_CHARACTERISTICS).isPresent()) { + characteristics = ParserUtil.parseCharacteristics(argMultimap.getValue(PREFIX_CHARACTERISTICS).get()); + characteristics = characteristics.isReset() ? null : characteristics; + } + + Property property = new Property(propertyName, price, address, description, + characteristics, newOwner, entryTime); + + return new AddPropertyCommand(property); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/property/DeletePropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/property/DeletePropertyCommandParser.java new file mode 100644 index 00000000000..68bc419060b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/DeletePropertyCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser.property; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.property.DeletePropertyCommand; +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 DeletePropertyCommand object + */ +public class DeletePropertyCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeletePropertyCommand + * and returns a DeletePropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeletePropertyCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeletePropertyCommand.MESSAGE_USAGE)); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new DeletePropertyCommand(index); + } catch (ParseException pe) { + throw pe; + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/property/EditPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/property/EditPropertyCommandParser.java new file mode 100644 index 00000000000..f8369cac849 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/EditPropertyCommandParser.java @@ -0,0 +1,83 @@ +package seedu.address.logic.parser.property; + +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_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_OWNER_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.property.EditPropertyCommand; +import seedu.address.logic.commands.property.EditPropertyCommand.EditPropertyDescriptor; +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.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditPropertyCommand object + */ +public class EditPropertyCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditPropertyCommand + * and returns an EditPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditPropertyCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer + .tokenize(args, PREFIX_NAME, PREFIX_PRICE, PREFIX_ADDRESS, PREFIX_DESCRIPTION, PREFIX_OWNER_NAME, + PREFIX_PHONE, PREFIX_CHARACTERISTICS); + + if (argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditPropertyCommand.MESSAGE_USAGE)); + } + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw pe; + } + + EditPropertyDescriptor editPropertyDescriptor = new EditPropertyDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editPropertyDescriptor.setName(ParserUtil.parsePropertyName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PRICE).isPresent()) { + editPropertyDescriptor.setPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editPropertyDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editPropertyDescriptor.setDescription(ParserUtil + .parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + if (argMultimap.getValue(PREFIX_OWNER_NAME).isPresent()) { + editPropertyDescriptor.setOwnerName(ParserUtil + .parseName(argMultimap.getValue(PREFIX_OWNER_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editPropertyDescriptor.setOwnerPhone(ParserUtil + .parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_CHARACTERISTICS).isPresent()) { + editPropertyDescriptor.setCharacteristics(ParserUtil + .parseCharacteristics(argMultimap.getValue(PREFIX_CHARACTERISTICS).get())); + } + + if (!editPropertyDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditPropertyCommand.MESSAGE_NOT_EDITED); + } + + return new EditPropertyCommand(index, editPropertyDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/property/FilterPropertiesCommandParser.java b/src/main/java/seedu/address/logic/parser/property/FilterPropertiesCommandParser.java new file mode 100644 index 00000000000..17a0ab6113c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/FilterPropertiesCommandParser.java @@ -0,0 +1,91 @@ +package seedu.address.logic.parser.property; + +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_CHARACTERISTICS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FUZZY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_OWNER_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE_RANGE; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.property.FilterPropertiesCommand; +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.exceptions.ParseException; +import seedu.address.model.buyer.Name; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.PriceRange; +import seedu.address.model.property.FilterPropsByOwnerNamePredicate; +import seedu.address.model.property.FilterPropsByPriceRangePredicate; +import seedu.address.model.property.FilterPropsContainingAllCharacteristicsPredicate; +import seedu.address.model.property.FilterPropsContainingAnyCharacteristicPredicate; +import seedu.address.model.property.Property; + +/** + * Parses user input to create a {@code FilterPropertiesCommand}. + */ +public class FilterPropertiesCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterPropertiesCommand + * and returns a FilterPropertiesCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FilterPropertiesCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_PRICE_RANGE, + PREFIX_CHARACTERISTICS, PREFIX_OWNER_NAME, PREFIX_FUZZY); + + if (!isAnyPrefixPresent(argMultimap, PREFIX_PRICE_RANGE, PREFIX_CHARACTERISTICS, PREFIX_OWNER_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPropertiesCommand.MESSAGE_USAGE)); + } + + List> predicatesList = new ArrayList<>(); + + if (argMultimap.getValue(PREFIX_PRICE_RANGE).isPresent()) { + PriceRange priceRange = ParserUtil.parsePriceRange(argMultimap.getValue(PREFIX_PRICE_RANGE).get()); + if (priceRange.isReset()) { + throw new ParseException("-r flag should not be left empty."); + } + predicatesList.add(new FilterPropsByPriceRangePredicate(priceRange)); + } + + if (argMultimap.getValue(PREFIX_CHARACTERISTICS).isPresent()) { + Characteristics characteristics = ParserUtil.parseCharacteristics( + argMultimap.getValue(PREFIX_CHARACTERISTICS).get()); + if (characteristics.isReset()) { + throw new ParseException("-c flag should not be left empty."); + } + if (argMultimap.getValue(PREFIX_FUZZY).isPresent()) { + predicatesList.add(new FilterPropsContainingAnyCharacteristicPredicate(characteristics)); + } else { + predicatesList.add(new FilterPropsContainingAllCharacteristicsPredicate(characteristics)); + } + } + + if (argMultimap.getValue(PREFIX_OWNER_NAME).isPresent()) { + Name ownerName = ParserUtil.parseName(argMultimap.getValue(PREFIX_OWNER_NAME).get()); + predicatesList.add(new FilterPropsByOwnerNamePredicate(ownerName)); + } + + // predicatesList must not be empty, since at least 1 prefix should be present + assert(!predicatesList.isEmpty()); + + Predicate combinedPredicate; + if (arePrefixesPresent(argMultimap, PREFIX_FUZZY)) { + combinedPredicate = predicatesList.stream().reduce(Predicate::or).get(); + } else { + combinedPredicate = predicatesList.stream().reduce(Predicate::and).get(); + } + return new FilterPropertiesCommand(combinedPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/property/FindPropertiesCommandParser.java b/src/main/java/seedu/address/logic/parser/property/FindPropertiesCommandParser.java new file mode 100644 index 00000000000..4d6ab107916 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/FindPropertiesCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser.property; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.property.FindPropertiesCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.property.PropertyNameContainsSubstringPredicate; + +/** + * Parses input arguments and creates a new FindPropertiesCommand object + */ +public class FindPropertiesCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindPropertiesCommand + * and returns a FindPropertiesCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindPropertiesCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPropertiesCommand.MESSAGE_USAGE)); + } + + return new FindPropertiesCommand(new PropertyNameContainsSubstringPredicate(trimmedArgs)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/property/MatchPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/property/MatchPropertyCommandParser.java new file mode 100644 index 00000000000..4eb6bb59ba1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/MatchPropertyCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser.property; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STRICT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.property.MatchPropertyCommand; +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.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MatchPropertyCommand object + */ +public class MatchPropertyCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MatchPropertyCommand + * and returns a MatchPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MatchPropertyCommand parse(String args) throws ParseException { + try { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_STRICT); + Index index = ParserUtil.parseIndex(argMultimap.getPreamble()); + boolean isMatchingAll = arePrefixesPresent(argMultimap, PREFIX_STRICT); + + return new MatchPropertyCommand(index, isMatchingAll); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MatchPropertyCommand.MESSAGE_USAGE), pe); + } + + } + +} diff --git a/src/main/java/seedu/address/logic/parser/property/SortPropertiesCommandParser.java b/src/main/java/seedu/address/logic/parser/property/SortPropertiesCommandParser.java new file mode 100644 index 00000000000..bdb5bf63419 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/property/SortPropertiesCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser.property; + +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.time.LocalDateTime; +import java.util.Comparator; + +import seedu.address.logic.commands.property.SortPropertiesCommand; +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.exceptions.ParseException; +import seedu.address.logic.sortcomparators.Order; +import seedu.address.logic.sortcomparators.PriceComparator; +import seedu.address.logic.sortcomparators.PropertyComparator; +import seedu.address.logic.sortcomparators.PropertyNameComparator; +import seedu.address.logic.sortcomparators.TimeComparator; +import seedu.address.model.price.Price; +import seedu.address.model.property.Property; +import seedu.address.model.property.PropertyName; + +/** + * Parses user input to create a {@code SortBuyersCommand}. + */ +public class SortPropertiesCommandParser extends Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SortBuyersCommand + * and returns a SortBuyersCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public SortPropertiesCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PRICE, PREFIX_TIME); + + if (areMoreThanOnePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PRICE, PREFIX_TIME) + || !isAnyPrefixPresent(argMultimap, PREFIX_NAME, PREFIX_PRICE, PREFIX_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SortPropertiesCommand.MESSAGE_USAGE)); + } + + Comparator propertyComparator = null; + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_NAME).get()); + Comparator propertyNameComparator = new PropertyNameComparator(order); + propertyComparator = new PropertyComparator(propertyNameComparator, null, null); + } + + if (argMultimap.getValue(PREFIX_PRICE).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_PRICE).get()); + Comparator priceComparator = new PriceComparator(order); + propertyComparator = new PropertyComparator(null, priceComparator, null); + } + + if (argMultimap.getValue(PREFIX_TIME).isPresent()) { + Order order = ParserUtil.parseOrder(argMultimap.getValue(PREFIX_TIME).get()); + Comparator timeComparator = new TimeComparator(order); + propertyComparator = new PropertyComparator(null, null, timeComparator); + } + + return new SortPropertiesCommand(propertyComparator); + } +} + diff --git a/src/main/java/seedu/address/logic/sortcomparators/BuyerComparator.java b/src/main/java/seedu/address/logic/sortcomparators/BuyerComparator.java new file mode 100644 index 00000000000..78b24efda0b --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/BuyerComparator.java @@ -0,0 +1,62 @@ +package seedu.address.logic.sortcomparators; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.Optional; + +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Priority; +import seedu.address.model.price.PriceRange; + +/** + * A comparator to compare two Buyers in the list. + */ +public class BuyerComparator implements Comparator { + private Optional> nameComparator; + private Optional>> priceRangeComparator; + private Optional> priorityComparator; + private Optional> timeComparator; + + /** + * Creates a BuyerComparator object. + */ + public BuyerComparator(Comparator nameComparator, + Comparator> priceRangeComparator, + Comparator priorityComparator, + Comparator timeComparator) { + this.nameComparator = Optional.ofNullable(nameComparator); + this.priceRangeComparator = Optional.ofNullable(priceRangeComparator); + this.priorityComparator = Optional.ofNullable(priorityComparator); + this.timeComparator = Optional.ofNullable(timeComparator); + } + + @Override + public int compare(Buyer firstBuyer, Buyer secondBuyer) { + if (nameComparator.isPresent()) { + return nameComparator.get().compare(firstBuyer.getName(), secondBuyer.getName()); + } else if (priceRangeComparator.isPresent()) { + return priceRangeComparator.get() + .compare(firstBuyer.getPriceRange(), secondBuyer.getPriceRange()); + } else if (priorityComparator.isPresent()) { + return priorityComparator.get() + .compare(firstBuyer.getPriority(), secondBuyer.getPriority()); + } else { + return timeComparator.get() + .compare(firstBuyer.getEntryTime(), secondBuyer.getEntryTime()); + } + } + + @Override + public String toString() { + if (nameComparator.isPresent()) { + return nameComparator.get().toString(); + } else if (priceRangeComparator.isPresent()) { + return priceRangeComparator.get().toString(); + } else if (priorityComparator.isPresent()) { + return priorityComparator.get().toString(); + } else { + return timeComparator.get().toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/BuyerNameComparator.java b/src/main/java/seedu/address/logic/sortcomparators/BuyerNameComparator.java new file mode 100644 index 00000000000..a1edf67efe6 --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/BuyerNameComparator.java @@ -0,0 +1,36 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.model.buyer.Name; + +/** + * A comparator to compare two Names. + */ +public class BuyerNameComparator implements Comparator { + + private final Order order; + + /** + * Constructs a {@code NameComparator}. + * + * @param order The specified order of comparison. + */ + public BuyerNameComparator(Order order) { + requireNonNull(order); + this.order = order; + } + + @Override + public int compare(Name firstName, Name secondName) { + int comparisonValue = firstName.compareTo(secondName); + return order.equals(new Order("ASC")) ? comparisonValue : -comparisonValue; + } + + @Override + public String toString() { + return "Name, " + order; + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/Order.java b/src/main/java/seedu/address/logic/sortcomparators/Order.java new file mode 100644 index 00000000000..c354a82e896 --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/Order.java @@ -0,0 +1,63 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Arrays; + +/** + * Represents a specified sorting order. + */ +public class Order { + + /** + * Enum specifying the different ordering types. + */ + public enum OrderType { + ASC, + DESC + } + + public static final String MESSAGE_CONSTRAINTS = "Order should be ASC or DESC."; + + private final OrderType orderType; + + /** + * Constructs an {@code Order}. + * + * @param specifiedOrder A valid order. + */ + public Order(String specifiedOrder) { + requireNonNull(specifiedOrder); + specifiedOrder = specifiedOrder.toUpperCase(); + checkArgument(isValidOrder(specifiedOrder), MESSAGE_CONSTRAINTS); + orderType = OrderType.valueOf(specifiedOrder); + } + + /** + * Returns true if a given string is a valid order. + */ + public static boolean isValidOrder(String test) { + return Arrays.stream(OrderType.values()).anyMatch(p -> test.equalsIgnoreCase(p.name())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Order // instanceof handles nulls + && orderType.equals(((Order) other).orderType)); // state check + } + + @Override + public int hashCode() { + return orderType.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return orderType.toString(); + } +} + diff --git a/src/main/java/seedu/address/logic/sortcomparators/PriceComparator.java b/src/main/java/seedu/address/logic/sortcomparators/PriceComparator.java new file mode 100644 index 00000000000..755d972a364 --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/PriceComparator.java @@ -0,0 +1,36 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.model.price.Price; + +/** + * A comparator to compare two Prices. + */ +public class PriceComparator implements Comparator { + + private final Order order; + + /** + * Constructs a {@code PriceComparator}. + * + * @param order The specified order of comparison. + */ + public PriceComparator(Order order) { + requireNonNull(order); + this.order = order; + } + + @Override + public int compare(Price firstPrice, Price secondPrice) { + int comparisonValue = firstPrice.isGreaterThanOrEqual(secondPrice) ? 1 : -1; + return order.equals(new Order("ASC")) ? comparisonValue : -comparisonValue; + } + + @Override + public String toString() { + return "Price, " + order; + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/PriceRangeComparator.java b/src/main/java/seedu/address/logic/sortcomparators/PriceRangeComparator.java new file mode 100644 index 00000000000..5b333f1c479 --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/PriceRangeComparator.java @@ -0,0 +1,47 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; +import java.util.Optional; + +import seedu.address.model.price.PriceRange; + + +/** + * A comparator to compare two PriceRanges. + */ +public class PriceRangeComparator implements Comparator> { + + private final Order order; + + /** + * Constructs a {@code PriceRangeComparator}. + * + * @param order The specified order of comparison. + */ + public PriceRangeComparator(Order order) { + requireNonNull(order); + this.order = order; + } + + @Override + public int compare(Optional firstPriceRange, Optional secondPriceRange) { + return firstPriceRange.isEmpty() && secondPriceRange.isEmpty() + ? 0 + : firstPriceRange.isEmpty() + ? 1 + : secondPriceRange.isEmpty() + ? -1 + : order.equals(new Order("ASC")) + ? firstPriceRange.get().compareLowerBound(secondPriceRange.get()) + : firstPriceRange.get().compareUpperBound(secondPriceRange.get()); + } + + @Override + public String toString() { + return "Price Range, " + order + + String.format(" (based on %s bound of price range)", + order.equals(new Order("asc")) ? "lower" : "upper"); + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/PriorityComparator.java b/src/main/java/seedu/address/logic/sortcomparators/PriorityComparator.java new file mode 100644 index 00000000000..6c55f97dc4f --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/PriorityComparator.java @@ -0,0 +1,37 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.model.buyer.Priority; + + +/** + * A comparator to compare two Priorities. + */ +public class PriorityComparator implements Comparator { + + private final Order order; + + /** + * Constructs a {@code PriorityComparator}. + * + * @param order The specified order of comparison. + */ + public PriorityComparator(Order order) { + requireNonNull(order); + this.order = order; + } + + @Override + public int compare(Priority firstPriority, Priority secondPriority) { + int comparisonValue = firstPriority.compareTo(secondPriority); + return order.equals(new Order("ASC")) ? -comparisonValue : comparisonValue; + } + + @Override + public String toString() { + return "Priority, " + order; + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/PropertyComparator.java b/src/main/java/seedu/address/logic/sortcomparators/PropertyComparator.java new file mode 100644 index 00000000000..c72abda5fdc --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/PropertyComparator.java @@ -0,0 +1,58 @@ +package seedu.address.logic.sortcomparators; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.Optional; + +import seedu.address.model.price.Price; +import seedu.address.model.property.Property; +import seedu.address.model.property.PropertyName; + +/** + * A comparator to compare two Properties in the list. + */ +public class PropertyComparator implements Comparator { + private Optional> propertyNameComparator; + private Optional> priceComparator; + + private Optional> timeComparator; + + /** + * Creates a BuyerComparator object. + */ + public PropertyComparator(Comparator propertyNameComparator, + Comparator priceComparator, + Comparator timeComparator) { + this.propertyNameComparator = Optional.ofNullable(propertyNameComparator); + this.priceComparator = Optional.ofNullable(priceComparator); + this.timeComparator = Optional.ofNullable(timeComparator); + } + + @Override + public int compare(Property firstProperty, Property secondProperty) { + if (propertyNameComparator.isPresent()) { + return propertyNameComparator.get().compare(firstProperty.getPropertyName(), + secondProperty.getPropertyName()); + } else if (priceComparator.isPresent()) { + return priceComparator.get() + .compare(firstProperty.getPrice(), secondProperty.getPrice()); + } else { + return timeComparator.get() + .compare(firstProperty.getPropertyEntryTime(), + secondProperty.getPropertyEntryTime()); + } + + + } + + @Override + public String toString() { + if (propertyNameComparator.isPresent()) { + return propertyNameComparator.get().toString(); + } else if (priceComparator.isPresent()) { + return priceComparator.get().toString(); + } else { + return timeComparator.get().toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/PropertyNameComparator.java b/src/main/java/seedu/address/logic/sortcomparators/PropertyNameComparator.java new file mode 100644 index 00000000000..61eac34b29a --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/PropertyNameComparator.java @@ -0,0 +1,37 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.model.property.PropertyName; + + +/** + * A comparator to compare two PropertyNames. + */ +public class PropertyNameComparator implements Comparator { + + private final Order order; + + /** + * Constructs a {@code PropertyNameComparator}. + * + * @param order The specified order of comparison. + */ + public PropertyNameComparator(Order order) { + requireNonNull(order); + this.order = order; + } + + @Override + public int compare(PropertyName firstName, PropertyName secondName) { + int comparisonValue = firstName.compareTo(secondName); + return order.equals(new Order("ASC")) ? comparisonValue : -comparisonValue; + } + + @Override + public String toString() { + return "Property Name, " + order; + } +} diff --git a/src/main/java/seedu/address/logic/sortcomparators/TimeComparator.java b/src/main/java/seedu/address/logic/sortcomparators/TimeComparator.java new file mode 100644 index 00000000000..63af2845492 --- /dev/null +++ b/src/main/java/seedu/address/logic/sortcomparators/TimeComparator.java @@ -0,0 +1,35 @@ +package seedu.address.logic.sortcomparators; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDateTime; +import java.util.Comparator; + +/** + * A comparator to compare two LocalDateTimes. + */ +public class TimeComparator implements Comparator { + + private final Order order; + + /** + * Constructs a {@code TimeComparator}. + * + * @param order The specified order of comparison. + */ + public TimeComparator(Order order) { + requireNonNull(order); + this.order = order; + } + + @Override + public int compare(LocalDateTime firstTime, LocalDateTime secondTime) { + int comparisonValue = firstTime.compareTo(secondTime); + return order.equals(new Order("ASC")) ? comparisonValue : -comparisonValue; + } + + @Override + public String toString() { + return "Time of creation, " + order; + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * 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. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * 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); - } - - /** - * 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); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/BuyerBook.java b/src/main/java/seedu/address/model/BuyerBook.java new file mode 100644 index 00000000000..e9d8021bd4c --- /dev/null +++ b/src/main/java/seedu/address/model/BuyerBook.java @@ -0,0 +1,120 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.UniqueBuyerList; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSameBuyer comparison) + */ +public class BuyerBook implements ReadOnlyBuyerBook { + + private final UniqueBuyerList buyers; + + /* + * 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. + */ + { + buyers = new UniqueBuyerList(); + } + + public BuyerBook() {} + + /** + * Creates an BuyerBook using the Buyers in the {@code toBeCopied} + */ + public BuyerBook(ReadOnlyBuyerBook toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the buyer list with {@code buyers}. + * {@code buyers} must not contain duplicate buyers. + */ + public void setBuyers(List buyers) { + this.buyers.setBuyers(buyers); + } + + /** + * Resets the existing data of this {@code BuyerBook} with {@code newData}. + */ + public void resetData(ReadOnlyBuyerBook newData) { + requireNonNull(newData); + + setBuyers(newData.getBuyerList()); + } + + //// buyer-level operations + + /** + * Returns true if a buyer with the same identity as {@code buyer} exists in the address book. + */ + public boolean hasBuyer(Buyer buyer) { + requireNonNull(buyer); + return buyers.contains(buyer); + } + + /** + * Adds a buyer to the address book. + * The buyer must not already exist in the address book. + */ + public void addBuyer(Buyer p) { + buyers.add(p); + } + + /** + * Replaces the given buyer {@code target} in the list with {@code editedBuyer}. + * {@code target} must exist in the address book. + * The buyer identity of {@code editedBuyer} must not be the same as another existing buyer in the address book. + */ + public void setBuyer(Buyer target, Buyer editedBuyer) { + requireNonNull(editedBuyer); + + buyers.setBuyer(target, editedBuyer); + } + + /** + * Removes {@code key} from this {@code BuyerBook}. + * {@code key} must exist in the buyer book. + */ + public void removeBuyer(Buyer key) { + buyers.remove(key); + } + + + //// util methods + + @Override + public String toString() { + return buyers.asUnmodifiableObservableList().size() + " buyers"; + } + + @Override + public ObservableList getBuyerList() { + return buyers.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof BuyerBook // instanceof handles nulls + && buyers.equals(((BuyerBook) other).buyers)); + } + + @Override + public int hashCode() { + return buyers.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..79bfd951dfe 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,18 +1,29 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Comparator; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.property.Property; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** + * {@code Predicate} that always evaluate to true + */ + Predicate PREDICATE_SHOW_ALL_BUYERS = unused -> true; + + /** + * {@code Predicate} that always evaluate to true + */ + Predicate PREDICATE_SHOW_ALL_PROPERTIES = unused -> true; + + //=========== UserPrefs ================================================================================== /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -35,53 +46,136 @@ public interface Model { void setGuiSettings(GuiSettings guiSettings); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' buyer book file path. */ - Path getAddressBookFilePath(); + Path getBuyerBookFilePath(); /** - * Sets the user prefs' address book file path. + * Sets the user prefs' buyer book file path. */ - void setAddressBookFilePath(Path addressBookFilePath); + void setBuyerBookFilePath(Path buyerBookFilePath); + + /** + * Returns the user prefs' property book file path. + */ + Path getPropertyBookFilePath(); + + /** + * Sets the user prefs' property book file path. + */ + void setPropertyBookFilePath(Path propertyBookFilePath); + + //=========== BuyerBook ================================================================================ /** - * Replaces address book data with the data in {@code addressBook}. + * Replaces buyer book data with the data in {@code buyerBook}. */ - void setAddressBook(ReadOnlyAddressBook addressBook); + void setBuyerBook(ReadOnlyBuyerBook buyerBook); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Returns the BuyerBook + */ + ReadOnlyBuyerBook getBuyerBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a buyer with the same identity as {@code buyer} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasBuyer(Buyer buyer); /** - * Deletes the given person. - * The person must exist in the address book. + * Deletes the given buyer. + * {@code buyer} must exist in the buyer book. */ - void deletePerson(Person target); + void deleteBuyer(Buyer buyer); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Adds the given buyer. + * {@code buyer} must not already exist in the buyer book. */ - void addPerson(Person person); + void addBuyer(Buyer buyer); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given buyer {@code target} with {@code editedBuyer}. * {@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 buyer identity of {@code editedBuyer} must not be the same as another existing buyer in the address book. + */ + void setBuyer(Buyer target, Buyer editedBuyer); + + //=========== Filtered Buyer List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the filtered buyer list + */ + ObservableList getFilteredBuyerList(); + + /** + * Updates the filter of the filtered buyer list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredBuyerList(Predicate predicate); + + /** + * Sorts the buyer book's buyer list by the given {@code comparator}. + * @throws NullPointerException if {@code comparator} is null. */ - void setPerson(Person target, Person editedPerson); + void sortBuyerList(Comparator comparator); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + //=========== PropertyBook ================================================================================ /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Replaces property book data with the data in {@code propertyBook}. + */ + void setPropertyBook(ReadOnlyPropertyBook propertyBook); + + /** + * Returns the PropertyBook + */ + ReadOnlyPropertyBook getPropertyBook(); + + /** + * Returns true if a property with the same identity as {@code property} exists in the property book. + */ + boolean hasProperty(Property property); + + /** + * Deletes the given property. + * The property must exist in the property book. + */ + void deleteProperty(Property target); + + /** + * Adds the given property. + * {@code property} must not already exist in the property book. + */ + void addProperty(Property property); + + /** + * Replaces the given property {@code target} with {@code editedProperty}. + * {@code target} must exist in the property book. + * The property identity of {@code editedProperty} must not be the same as another existing property in the address + * book. + */ + void setProperty(Property target, Property editedProperty); + + //=========== Filtered Property List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the filtered property list + */ + ObservableList getFilteredPropertyList(); + + /** + * Updates the filter of the filtered property list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredPropertyList(Predicate predicate); + + /** + * Sorts the property book's property list by the given {@code comparator}. + * @throws NullPointerException if {@code comparator} is null. + */ + void sortPropertyList(Comparator comparator); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..f4c82a706bb 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,9 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.function.Predicate; import java.util.logging.Logger; @@ -11,35 +14,44 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.property.Property; /** - * Represents the in-memory model of the address book data. + * Represents the in-memory model of the buyer book and property book data. */ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final BuyerBook buyerBook; + private final PropertyBook propertyBook; + private final FilteredList filteredBuyers; + private final FilteredList filteredProperties; + /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given buyerBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); + public ModelManager(ReadOnlyBuyerBook buyerBook, ReadOnlyPropertyBook propertyBook, + ReadOnlyUserPrefs userPrefs) { + requireAllNonNull(buyerBook, propertyBook, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with buyer book: " + buyerBook + " and property book: " + propertyBook + + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.buyerBook = new BuyerBook(buyerBook); + this.propertyBook = new PropertyBook(propertyBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredBuyers = new FilteredList<>(this.buyerBook.getBuyerList()); + filteredProperties = new FilteredList<>(this.propertyBook.getPropertyList()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new BuyerBook(), new PropertyBook(), new UserPrefs()); } + //=========== UserPrefs ================================================================================== @Override @@ -65,67 +77,146 @@ public void setGuiSettings(GuiSettings guiSettings) { } @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); + public Path getBuyerBookFilePath() { + return userPrefs.getBuyerBookFilePath(); } @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); + public void setBuyerBookFilePath(Path buyerBookFilePath) { + requireNonNull(buyerBookFilePath); + userPrefs.setBuyerBookFilePath(buyerBookFilePath); } - //=========== AddressBook ================================================================================ + @Override + public Path getPropertyBookFilePath() { + return userPrefs.getPropertyBookFilePath(); + } @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public void setPropertyBookFilePath(Path propertyBookFilePath) { + requireNonNull(propertyBookFilePath); + userPrefs.setPropertyBookFilePath(propertyBookFilePath); } + //=========== BuyerBook ================================================================================ + @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public void setBuyerBook(ReadOnlyBuyerBook buyerBook) { + this.buyerBook.resetData(buyerBook); } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public ReadOnlyBuyerBook getBuyerBook() { + return buyerBook; } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public boolean hasBuyer(Buyer buyer) { + requireNonNull(buyer); + return buyerBook.hasBuyer(buyer); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void deleteBuyer(Buyer target) { + buyerBook.removeBuyer(target); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void addBuyer(Buyer buyer) { + buyerBook.addBuyer(buyer); + updateFilteredBuyerList(PREDICATE_SHOW_ALL_BUYERS); + } - addressBook.setPerson(target, editedPerson); + @Override + public void setBuyer(Buyer target, Buyer editedBuyer) { + requireAllNonNull(target, editedBuyer); + buyerBook.setBuyer(target, editedBuyer); } - //=========== Filtered Person List Accessors ============================================================= + //=========== Filtered Buyer List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * Returns an unmodifiable view of the list of {@code Buyer} backed by the internal list of + * {@code BuyerBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredBuyerList() { + return filteredBuyers; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredBuyerList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredBuyers.setPredicate(predicate); + } + + @Override + public void sortBuyerList(Comparator comparator) { + requireNonNull(comparator); + List buyerList = new ArrayList<>(buyerBook.getBuyerList()); + buyerList.sort(comparator); + buyerBook.setBuyers(buyerList); + } + + + //=========== PropertyBook ================================================================================ + + @Override + public void setPropertyBook(ReadOnlyPropertyBook propertyBook) { + this.propertyBook.resetData(propertyBook); + } + + @Override + public ReadOnlyPropertyBook getPropertyBook() { + return propertyBook; + } + + @Override + public boolean hasProperty(Property property) { + requireNonNull(property); + return propertyBook.hasProperty(property); + } + + @Override + public void deleteProperty(Property target) { + propertyBook.removeProperty(target); + } + + @Override + public void addProperty(Property property) { + propertyBook.addProperty(property); + updateFilteredPropertyList(PREDICATE_SHOW_ALL_PROPERTIES); + } + + @Override + public void setProperty(Property target, Property editedProperty) { + requireAllNonNull(target, editedProperty); + propertyBook.setProperty(target, editedProperty); + } + + //=========== Filtered Property List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Property} backed by the internal list of + * {@code PropertyBook} + */ + @Override + public ObservableList getFilteredPropertyList() { + return filteredProperties; + } + + @Override + public void updateFilteredPropertyList(Predicate predicate) { + requireNonNull(predicate); + filteredProperties.setPredicate(predicate); + } + + @Override + public void sortPropertyList(Comparator comparator) { + requireNonNull(comparator); + List sortedList = new ArrayList<>(propertyBook.getPropertyList()); + sortedList.sort(comparator); + propertyBook.setProperties(sortedList); } @Override @@ -142,9 +233,11 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } + return userPrefs.equals(other.userPrefs) + && buyerBook.equals(other.buyerBook) + && propertyBook.equals(other.propertyBook) + && filteredBuyers.equals(other.filteredBuyers) + && filteredProperties.equals(other.filteredProperties); + } } diff --git a/src/main/java/seedu/address/model/PropertyBook.java b/src/main/java/seedu/address/model/PropertyBook.java new file mode 100644 index 00000000000..767ebbabbd5 --- /dev/null +++ b/src/main/java/seedu/address/model/PropertyBook.java @@ -0,0 +1,120 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.property.Property; +import seedu.address.model.property.UniquePropertyList; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSameProperty comparison) + */ +public class PropertyBook implements ReadOnlyPropertyBook { + + private final UniquePropertyList properties; + + /* + * 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. + */ + { + properties = new UniquePropertyList(); + } + + public PropertyBook() {} + + /** + * Creates a PropertyBook using the Properties in the {@code propertyBook} + */ + public PropertyBook(ReadOnlyPropertyBook propertyBook) { + this(); + resetData(propertyBook); + } + + //// list overwrite operations + + /** + * Replaces the contents of the property list with {@code properties}. + * {@code properties} must not contain duplicate properties. + */ + public void setProperties(List properties) { + this.properties.setProperties(properties); + } + + /** + * Resets the existing data of this {@code PropertyBook} with {@code newData}. + */ + public void resetData(ReadOnlyPropertyBook newData) { + requireNonNull(newData); + + setProperties(newData.getPropertyList()); + } + + //// property-level operations + + /** + * Returns true if a property with the same identity as {@code property} exists in the property book. + */ + public boolean hasProperty(Property property) { + requireNonNull(property); + return properties.contains(property); + } + + /** + * Adds a property to the property book. + * The property must not already exist in the property book. + */ + public void addProperty(Property p) { + properties.add(p); + } + + /** + * Replaces the given property {@code target} in the list with {@code editedProperty}. + * {@code target} must exist in the property book. + * The property identity of {@code editedProperty} must not be the same as another existing property in the + * property book. + */ + public void setProperty(Property target, Property editedProperty) { + requireNonNull(editedProperty); + + properties.setProperty(target, editedProperty); + } + + /** + * Removes {@code key} from this {@code PropertyBook}. + * {@code key} must exist in the property book. + */ + public void removeProperty(Property key) { + properties.remove(key); + } + + //// util methods + + @Override + public String toString() { + return properties.asUnmodifiableObservableList().size() + " properties"; + } + + @Override + public ObservableList getPropertyList() { + return properties.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PropertyBook // instanceof handles nulls + && properties.equals(((PropertyBook) other).properties)); + } + + @Override + public int hashCode() { + return properties.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyBuyerBook.java b/src/main/java/seedu/address/model/ReadOnlyBuyerBook.java new file mode 100644 index 00000000000..b6cd1fcd706 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyBuyerBook.java @@ -0,0 +1,17 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.buyer.Buyer; + +/** + * Unmodifiable view of a Buyer list + */ +public interface ReadOnlyBuyerBook { + + /** + * Returns an unmodifiable view of the buyers' list. + * This list will not contain any duplicate buyers. + */ + ObservableList getBuyerList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyPropertyBook.java b/src/main/java/seedu/address/model/ReadOnlyPropertyBook.java new file mode 100644 index 00000000000..129c5d738ae --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyPropertyBook.java @@ -0,0 +1,17 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.property.Property; + +/** + * Unmodifiable view of a Property list + */ +public interface ReadOnlyPropertyBook { + + /** + * Returns an unmodifiable view of the property list. + * This list will not contain any duplicate properties. + */ + ObservableList getPropertyList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java index befd58a4c73..793fa7a0864 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java @@ -11,6 +11,8 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getBuyerBookFilePath(); + + Path getPropertyBookFilePath(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..ab0d7ba3fdc 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,8 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path buyerBookFilePath = Paths.get("data" , "buyerbook.json"); + private Path propertyBookFilePath = Paths.get("data", "propertybook.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +36,8 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setBuyerBookFilePath(newUserPrefs.getBuyerBookFilePath()); + setPropertyBookFilePath(newUserPrefs.getPropertyBookFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +49,22 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getBuyerBookFilePath() { + return buyerBookFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setBuyerBookFilePath(Path buyerBookFilePath) { + requireNonNull(buyerBookFilePath); + this.buyerBookFilePath = buyerBookFilePath; + } + + public Path getPropertyBookFilePath() { + return propertyBookFilePath; + } + + public void setPropertyBookFilePath(Path propertyBookFilePath) { + requireNonNull(propertyBookFilePath); + this.propertyBookFilePath = propertyBookFilePath; } @Override @@ -68,19 +79,21 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && buyerBookFilePath.equals(o.buyerBookFilePath) + && propertyBookFilePath.equals(o.propertyBookFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, buyerBookFilePath, propertyBookFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nBuyers data file location : " + buyerBookFilePath); + sb.append("\nProperty data file location : " + propertyBookFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/address/Address.java similarity index 85% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/address/Address.java index 60472ca22a0..b15e9b95866 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/address/Address.java @@ -1,15 +1,15 @@ -package seedu.address.model.person; +package seedu.address.model.address; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. + * Represents a Buyer'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"; + 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, @@ -46,7 +46,7 @@ public String toString() { 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 + && value.equalsIgnoreCase(((Address) other).value)); // state check } @Override diff --git a/src/main/java/seedu/address/model/buyer/AbstractFilterBuyersPredicate.java b/src/main/java/seedu/address/model/buyer/AbstractFilterBuyersPredicate.java new file mode 100644 index 00000000000..f79e0baaf50 --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/AbstractFilterBuyersPredicate.java @@ -0,0 +1,15 @@ +package seedu.address.model.buyer; + +import java.util.function.Predicate; + +/** + * An abstract class for predicates within the FilterBuyers method. + * Since there are three possible ways that a {@code FilterBuyersCommand} can be executed, + * we need to create an abstract class that can be passed into the {@code FilterBuyersCommand} constructor, + * after which each individual predicate's behaviour is determined through polymorphism. + */ +public abstract class AbstractFilterBuyersPredicate implements Predicate { + + @Override + public abstract boolean test(Buyer buyer); +} diff --git a/src/main/java/seedu/address/model/buyer/Buyer.java b/src/main/java/seedu/address/model/buyer/Buyer.java new file mode 100644 index 00000000000..c4037daf71e --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/Buyer.java @@ -0,0 +1,155 @@ +package seedu.address.model.buyer; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.model.address.Address; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.PriceRange; + + +/** + * Represents a Buyer in the buyer book. + * Guarantees: field values are validated, immutable. + * Only priceRange and desiredCharacteristics may be null. + */ +public class Buyer { + + // Identity fields + private final Name name; + private final Phone phone; + private final Email email; + + // Data fields + private final Address address; + private final Optional priceRange; + private final Optional desiredCharacteristics; + private final Priority priority; + + private final LocalDateTime entryTime; + + /** + * Every field must be present and not null. + */ + public Buyer(Name name, Phone phone, Email email, Address address, + PriceRange priceRange, Characteristics characteristics, + Priority priority, LocalDateTime entryTime) { + requireAllNonNull(name, phone, email, address, priority, entryTime); + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.priceRange = Optional.ofNullable(priceRange); + this.desiredCharacteristics = Optional.ofNullable(characteristics); + this.priority = priority; + this.entryTime = entryTime; + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + public Address getAddress() { + return address; + } + + public LocalDateTime getEntryTime() { + return entryTime; + } + + public Optional getPriceRange() { + return this.priceRange; + } + + public Optional getDesiredCharacteristics() { + return this.desiredCharacteristics; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Priority getPriority() { + return this.priority; + } + + /** + * Returns true if both buyers have the same phone or email. + * This defines a weaker notion of equality between two buyers. + */ + public boolean isSameBuyer(Buyer otherBuyer) { + if (otherBuyer == this) { + return true; + } + + return otherBuyer != null + && (otherBuyer.getPhone().equals(getPhone()) + || otherBuyer.getEmail().equals(getEmail())); + } + + /** + * Returns true if both buyers have the same identity and data fields. + * This defines a stronger notion of equality between two buyers. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Buyer)) { + return false; + } + + Buyer otherBuyer = (Buyer) other; + return otherBuyer.getName().equals(getName()) + && otherBuyer.getPhone().equals(getPhone()) + && otherBuyer.getEmail().equals(getEmail()) + && otherBuyer.getAddress().equals(getAddress()) + && otherBuyer.getPriceRange().equals(getPriceRange()) + && otherBuyer.getDesiredCharacteristics().equals(getDesiredCharacteristics()) + && otherBuyer.getPriority().equals(getPriority()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, email, address, priority); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Name: ") + .append(getName()) + .append("\nPhone: ") + .append(getPhone()) + .append("\nEmail: ") + .append(getEmail()) + .append("\nAddress: ") + .append(getAddress()) + .append("\nBudget: ") + .append(getPriceRange().map(PriceRange::toString).orElse("Not Specified")) + .append("\nCharacteristics: ") + .append(getDesiredCharacteristics() + .map(Characteristics::toString) + .orElse("Not Specified")); + + builder.append("\nPriority: ") + .append(getPriority()); + + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/buyer/BuyerNameContainsSubstringPredicate.java b/src/main/java/seedu/address/model/buyer/BuyerNameContainsSubstringPredicate.java new file mode 100644 index 00000000000..e60622fa1cb --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/BuyerNameContainsSubstringPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.buyer; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Buyer}'s {@code Name} contains the given string. + */ +public class BuyerNameContainsSubstringPredicate implements Predicate { + private final String string; + + public BuyerNameContainsSubstringPredicate(String string) { + this.string = string.toLowerCase(); + } + + @Override + public boolean test(Buyer buyer) { + String name = buyer.getName().fullName.toLowerCase(); + return name.contains(string); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof BuyerNameContainsSubstringPredicate // instanceof handles nulls + && string.equals(((BuyerNameContainsSubstringPredicate) other).string)); // state check + } +} + diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/buyer/Email.java similarity index 91% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/buyer/Email.java index f866e7133de..0510b7b87b2 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/buyer/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.buyer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Buyer's email in the buyer book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { @@ -18,9 +18,10 @@ public class Email { + "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" + + " - end with a domain label at least 1 character 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."; + + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.\n" + + "It should not be blank."; // 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 + "]" diff --git a/src/main/java/seedu/address/model/buyer/FilterBuyersByPricePredicate.java b/src/main/java/seedu/address/model/buyer/FilterBuyersByPricePredicate.java new file mode 100644 index 00000000000..635d539668c --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/FilterBuyersByPricePredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.buyer; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.price.Price; + +/** + * Tests that a {@code Buyer}'s {@code PriceRange} contains the given price value. + */ +public class FilterBuyersByPricePredicate extends AbstractFilterBuyersPredicate { + + private final Price price; + + /** + * Standard constructor for the predicate. + */ + public FilterBuyersByPricePredicate(Price price) { + requireNonNull(price); + this.price = price; + } + + @Override + public boolean test(Buyer p) { + // N.B.: Returns true if the target buyer does not have a PriceRange object in their attributes. + if (p.getPriceRange().isEmpty()) { + return true; + } + return p.getPriceRange().get().isWithinPriceRange(price); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterBuyersByPricePredicate // instanceof handles nulls + && price.equals(((FilterBuyersByPricePredicate) other).price)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/buyer/FilterBuyersByPriorityPredicate.java b/src/main/java/seedu/address/model/buyer/FilterBuyersByPriorityPredicate.java new file mode 100644 index 00000000000..40366de9bbf --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/FilterBuyersByPriorityPredicate.java @@ -0,0 +1,32 @@ +package seedu.address.model.buyer; + +import static java.util.Objects.requireNonNull; + +/** + * Tests that a {@code Buyer}'s {@code priority} contains the given priority value. + */ +public class FilterBuyersByPriorityPredicate extends AbstractFilterBuyersPredicate { + + private final Priority priority; + + /** + * Standard constructor for the predicate. + */ + public FilterBuyersByPriorityPredicate(Priority priority) { + requireNonNull(priority); + this.priority = priority; + } + + @Override + public boolean test(Buyer p) { + return p.getPriority().equals(priority); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterBuyersByPriorityPredicate // instanceof handles nulls + && priority.equals(((FilterBuyersByPriorityPredicate) other).priority)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/buyer/FilterBuyersContainingAllCharacteristicsPredicate.java b/src/main/java/seedu/address/model/buyer/FilterBuyersContainingAllCharacteristicsPredicate.java new file mode 100644 index 00000000000..e781fe43633 --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/FilterBuyersContainingAllCharacteristicsPredicate.java @@ -0,0 +1,40 @@ +package seedu.address.model.buyer; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.characteristics.Characteristics; + +/** + * Tests that a given {@code Buyer}'s {@code DesiredCharacteristics} contains all the given characteristics. + */ +public class FilterBuyersContainingAllCharacteristicsPredicate extends AbstractFilterBuyersPredicate { + + private final Characteristics givenCharacteristics; + + /** + * Standard constructor for the predicate. + */ + public FilterBuyersContainingAllCharacteristicsPredicate(Characteristics characteristics) { + requireNonNull(characteristics); + this.givenCharacteristics = characteristics; + } + + @Override + public boolean test(Buyer p) { + // N.B.: Returns false if the target buyer does not have a DesiredCharacteristics object in their attributes. + if (p.getDesiredCharacteristics().isEmpty()) { + return false; + } + return p.getDesiredCharacteristics().get().containsAllGivenCharacteristics(givenCharacteristics); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterBuyersContainingAllCharacteristicsPredicate // instanceof handles nulls + && givenCharacteristics.equals(( + // state check + (FilterBuyersContainingAllCharacteristicsPredicate) other).givenCharacteristics)); + + } +} diff --git a/src/main/java/seedu/address/model/buyer/FilterBuyersContainingAnyCharacteristicPredicate.java b/src/main/java/seedu/address/model/buyer/FilterBuyersContainingAnyCharacteristicPredicate.java new file mode 100644 index 00000000000..8ec069c451d --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/FilterBuyersContainingAnyCharacteristicPredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.buyer; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.characteristics.Characteristics; + +/** + * Tests that a given {@code Buyer}'s {@code DesiredCharacteristics} contains any of the given characteristics. + */ +public class FilterBuyersContainingAnyCharacteristicPredicate extends AbstractFilterBuyersPredicate { + + private final Characteristics givenCharacteristics; + + /** + * Standard constructor for the predicate. + */ + public FilterBuyersContainingAnyCharacteristicPredicate(Characteristics characteristics) { + requireNonNull(characteristics); + this.givenCharacteristics = characteristics; + } + + @Override + public boolean test(Buyer p) { + // N.B.: Returns false if the target buyer does not have a DesiredCharacteristics object in their attributes. + if (p.getDesiredCharacteristics().isEmpty()) { + return false; + } + return p.getDesiredCharacteristics().get().containsAnyGivenCharacteristic(givenCharacteristics); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterBuyersContainingAnyCharacteristicPredicate // instanceof handles nulls + && givenCharacteristics.equals(( + (FilterBuyersContainingAnyCharacteristicPredicate) other).givenCharacteristics)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/buyer/Name.java similarity index 67% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/buyer/Name.java index 79244d71cf7..4b7624dd100 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/buyer/Name.java @@ -1,22 +1,24 @@ -package seedu.address.model.person; +package seedu.address.model.buyer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Buyer's name in the buyer 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"; + "Names should only be in English and can contain certain special characters like hyphens and periods. " + + "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 static final String VALIDATION_REGEX = "[a-zA-Z \\-.']+"; + public final String fullName; @@ -35,7 +37,7 @@ public Name(String name) { * Returns true if a given string is a valid name. */ public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && !test.isBlank(); } @@ -48,12 +50,15 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.equalsIgnoreCase(((Name) other).fullName)); // state check + } + + public int compareTo(Object other) { + return fullName.compareToIgnoreCase(((Name) other).fullName); } @Override public int hashCode() { return fullName.hashCode(); } - } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/buyer/Phone.java similarity index 86% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/buyer/Phone.java index 872c76b382f..dae72046a64 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/buyer/Phone.java @@ -1,17 +1,17 @@ -package seedu.address.model.person; +package seedu.address.model.buyer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Buyer's phone number in the buyer 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"; + "Phone numbers should only contain numbers and be at least 3 digits long."; public static final String VALIDATION_REGEX = "\\d{3,}"; public final String value; diff --git a/src/main/java/seedu/address/model/buyer/Priority.java b/src/main/java/seedu/address/model/buyer/Priority.java new file mode 100644 index 00000000000..69191c723ad --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/Priority.java @@ -0,0 +1,66 @@ +package seedu.address.model.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Arrays; + +/** + * Represents a Priority in the buyer book. + * Guarantees: immutable; name is valid as declared in {@link #isValidPriority(String)} + */ +public class Priority implements Comparable { + + private enum PriorityName { + HIGH, NORMAL, LOW; + } + + public static final String MESSAGE_CONSTRAINTS = "If -pr flag is used, high, low or normal must be specified."; + + public final PriorityName specifiedPriority; + + /** + * Constructs a {@code Priority}. + * + * @param specifiedPriority A valid priority. + */ + public Priority(String specifiedPriority) { + requireNonNull(specifiedPriority); + specifiedPriority = specifiedPriority.toUpperCase(); + checkArgument(isValidPriority(specifiedPriority), MESSAGE_CONSTRAINTS); + this.specifiedPriority = PriorityName.valueOf(specifiedPriority); + } + + /** + * Returns true if a given string is a valid priority. + */ + public static boolean isValidPriority(String test) { + return Arrays.stream(PriorityName.values()).anyMatch(p -> test.equalsIgnoreCase(p.name())); + } + + @Override + public int compareTo(Priority other) { + return specifiedPriority.compareTo(other.specifiedPriority); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Priority // instanceof handles nulls + && specifiedPriority.equals(((Priority) other).specifiedPriority)); // state check + } + + @Override + public int hashCode() { + return specifiedPriority.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + //return '[' + specifiedPriority.toString() + ']'; + return specifiedPriority.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/buyer/UniqueBuyerList.java b/src/main/java/seedu/address/model/buyer/UniqueBuyerList.java new file mode 100644 index 00000000000..a85c5cb4783 --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/UniqueBuyerList.java @@ -0,0 +1,137 @@ +package seedu.address.model.buyer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.buyer.exceptions.BuyerNotFoundException; +import seedu.address.model.buyer.exceptions.DuplicateBuyerException; + +/** + * A list of buyers that enforces uniqueness between its elements and does not allow nulls. + * A buyer is considered unique by comparing using {@code Buyer#isSameBuyer(Buyer)}. As such, adding and updating of + * buyers uses Buyer#isSameBuyer(Buyer) for equality so as to ensure that the buyer being added or updated is + * unique in terms of identity in the UniqueBuyerList. However, the removal of a buyer uses Buyer#equals(Object) so + * as to ensure that the buyer with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Buyer#isSameBuyer(Buyer) + */ +public class UniqueBuyerList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent buyer as the given argument. + */ + public boolean contains(Buyer toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameBuyer); + } + + /** + * Adds a buyer to the list. + * The buyer must not already exist in the list. + */ + public void add(Buyer toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateBuyerException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the buyer {@code target} in the list with {@code editedBuyer}. + * {@code target} must exist in the list. + * The buyer identity of {@code editedBuyer} must not be the same as another existing buyer in the list. + */ + public void setBuyer(Buyer target, Buyer editedBuyer) { + requireAllNonNull(target, editedBuyer); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new BuyerNotFoundException(); + } + + if (!target.isSameBuyer(editedBuyer) && contains(editedBuyer)) { + throw new DuplicateBuyerException(); + } + + internalList.set(index, editedBuyer); + } + + /** + * Removes the equivalent buyer from the list. + * The buyer must exist in the list. + */ + public void remove(Buyer toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new BuyerNotFoundException(); + } + } + + public void setBuyers(UniqueBuyerList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code buyers}. + * {@code buyers} must not contain duplicate buyers. + */ + public void setBuyers(List buyers) { + requireAllNonNull(buyers); + if (!buyersAreUnique(buyers)) { + throw new DuplicateBuyerException(); + } + + internalList.setAll(buyers); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueBuyerList // instanceof handles nulls + && internalList.equals(((UniqueBuyerList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code buyers} contains only unique buyers. + */ + private boolean buyersAreUnique(List buyers) { + for (int i = 0; i < buyers.size() - 1; i++) { + for (int j = i + 1; j < buyers.size(); j++) { + if (buyers.get(i).isSameBuyer(buyers.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/buyer/exceptions/BuyerNotFoundException.java b/src/main/java/seedu/address/model/buyer/exceptions/BuyerNotFoundException.java new file mode 100644 index 00000000000..92d272ed82e --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/exceptions/BuyerNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.buyer.exceptions; + +/** + * Signals that the operation is unable to find the specified buyer. + */ +public class BuyerNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/buyer/exceptions/DuplicateBuyerException.java b/src/main/java/seedu/address/model/buyer/exceptions/DuplicateBuyerException.java new file mode 100644 index 00000000000..59148e2ff55 --- /dev/null +++ b/src/main/java/seedu/address/model/buyer/exceptions/DuplicateBuyerException.java @@ -0,0 +1,11 @@ +package seedu.address.model.buyer.exceptions; + +/** + * Signals that the operation will result in duplicate Buyers + * (Buyers are considered duplicates if they have the same identity). + */ +public class DuplicateBuyerException extends RuntimeException { + public DuplicateBuyerException() { + super("Operation would result in duplicate buyers"); + } +} diff --git a/src/main/java/seedu/address/model/characteristics/Characteristics.java b/src/main/java/seedu/address/model/characteristics/Characteristics.java new file mode 100644 index 00000000000..69a803f4ce5 --- /dev/null +++ b/src/main/java/seedu/address/model/characteristics/Characteristics.java @@ -0,0 +1,106 @@ +package seedu.address.model.characteristics; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Arrays; + +/** + * Represents the characteristics of a property. + * Individual characteristics are separated by semicolons. + */ +public class Characteristics { + public static final String MESSAGE_CONSTRAINTS = "Characteristics can take any values."; + + public static final Characteristics RESET_CHARACTERISTICS = new Characteristics("RESET"); + + private static final String CHARACTERISTIC_DELIMITER = ";"; + + private final String[] characteristicsArray; + + /** + * Constructs a {@code Characteristics}. + * Guarantees: Immutable, is valid as declared in isValidCharacteristics. + */ + public Characteristics(String characteristics) { + requireNonNull(characteristics); + checkArgument(isValidCharacteristics(characteristics), MESSAGE_CONSTRAINTS); + + characteristicsArray = characteristics.split(CHARACTERISTIC_DELIMITER); + for (int i = 0; i < characteristicsArray.length; i++) { + characteristicsArray[i] = characteristicsArray[i].trim(); + } + } + + /** + * Returns the characteristics array represented by {@code Characteristics}. + */ + public String[] getCharacteristicsArray() { + return characteristicsArray; + } + + /** + * Any string input, including an empty one, is a valid characteristic. + */ + public static boolean isValidCharacteristics(String test) { + requireNonNull(test); + return true; + } + + /** + * Returns true if a given string is contained in the + * characteristics array. + */ + private boolean containsCharacteristic(String characteristic) { + return Arrays.stream(characteristicsArray) + .anyMatch(c -> c.toLowerCase().contains(characteristic.toLowerCase())); + } + + /** + * Returns true if this {@code Characteristic} contains all the characteristics + * of the given {@code Characteristic}. + */ + public boolean containsAllGivenCharacteristics(Characteristics other) { + return Arrays.stream(other.getCharacteristicsArray()) + .allMatch(c -> containsCharacteristic(c)); + } + + /** + * Returns true if this {@code Characteristic} contains at least 1 characteristic + * of the given {@code Characteristic}. + */ + public boolean containsAnyGivenCharacteristic(Characteristics other) { + return Arrays.stream(other.getCharacteristicsArray()) + .anyMatch(c -> containsCharacteristic(c)); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + // string representation has to be exactly the same as in user input format + // so that when saved and then retrieved from storage, can be parsed back directly + for (int i = 0; i < characteristicsArray.length; i++) { + sb.append(characteristicsArray[i]) + .append("; "); + } + // remove last "; " as we don't want it to be repeatedly appended + // as we store the string and retrieve it repeatedly + return sb.substring(0, sb.length() - 2); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Characteristics // instanceof handles nulls + && Arrays.equals(characteristicsArray, ((Characteristics) other).characteristicsArray)); // state check + } + + @Override + public int hashCode() { + return characteristicsArray.hashCode(); + } + + public boolean isReset() { + return characteristicsArray[0].equals("RESET"); + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 8ff1d83fe89..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,123 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - 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 getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * 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) { - return true; - } - - return otherPerson != null - && 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; - } - - if (!(other instanceof Person)) { - return false; - } - - 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()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.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. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(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. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/price/Price.java b/src/main/java/seedu/address/model/price/Price.java new file mode 100644 index 00000000000..f9db83e8dae --- /dev/null +++ b/src/main/java/seedu/address/model/price/Price.java @@ -0,0 +1,86 @@ +package seedu.address.model.price; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Property's price + * Guarantees: immutable; is valid as declared in {@link #isValidPrice(String)} + */ +public class Price { + + public static final String MESSAGE_CONSTRAINTS = + "Price should only contain numbers and an optional exponent within the maximum range of a Double," + + " and it should not be blank." + + " For example: 123.45"; + private static final String VALIDATION_REGEX = "^[0-9]*\\.*[0-9]+$"; + private static final double EPSILON = 0.01d; + public final String value; + private final double numericalValue; + + /** + * Constructs a {@code Price}. + * + * @param price A valid price number. + */ + public Price(String price) { + requireNonNull(price); + checkArgument(isValidPrice(price), MESSAGE_CONSTRAINTS); + + value = price; + numericalValue = Double.parseDouble(price); + } + + /** + * Returns true if a given string is a valid price. + */ + public static boolean isValidPrice(String test) { + if (!test.matches(VALIDATION_REGEX)) { + return false; + } + // check for possible overflow + try { + Double.parseDouble(test); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * Returns true if the stored numerical value is greater than or equal to a given {@code Price}. + */ + public boolean isGreaterThanOrEqual(Price p) { + double d = p.getNumericalValue(); + return numericalValue - d > EPSILON || numericalValue - d == 0; + } + + /** + * Returns true if the stored numerical value is smaller than or equal to a given {@code Price}. + */ + public boolean isSmallerThanOrEqual(Price p) { + double d = p.getNumericalValue(); + return d - numericalValue > EPSILON || numericalValue - d == 0; + } + + public double getNumericalValue() { + return this.numericalValue; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Price // instanceof handles nulls + && value.equals(((Price) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/price/PriceRange.java b/src/main/java/seedu/address/model/price/PriceRange.java new file mode 100644 index 00000000000..4b39db98e94 --- /dev/null +++ b/src/main/java/seedu/address/model/price/PriceRange.java @@ -0,0 +1,139 @@ +package seedu.address.model.price; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.DecimalFormat; + +/** + * Represents a Price Range for a property that a buyer can accept. + * Guarantees: immutable; is valid as declared. + */ +public class PriceRange { + + public static final String MESSAGE_CONSTRAINTS = "Price ranges must be specified in the form: " + + "-, where and are non-negative numbers within the maximum range of a Double " + + "and must be smaller than ."; + /* + * The first part of the range must be digits, followed by a hyphen (whitespaces optional), + * and then followed by more digits. A decimal / floating point value is also valid. + * This helps prevent "123a-123b" from being considered a valid input. + */ + public static final String VALIDATION_REGEX = "[0-9]*\\.?[0-9]+\\s*-\\s*[0-9]*\\.?[0-9]+"; + + public static final PriceRange RESET_PRICE_RANGE = new PriceRange(); + + public final boolean isReset; + private final Price low; + private final Price high; + + /** + * Constructs a {@code PriceRange}. + * Guarantees: Immutable, is valid as declared in isValidPriceRange. + */ + public PriceRange(String priceRange) { + requireNonNull(priceRange); + checkArgument(isValidPriceRange(priceRange), MESSAGE_CONSTRAINTS); + + String[] rangeValues = priceRange.split("-"); + this.low = new Price(rangeValues[0].trim()); + this.high = new Price(rangeValues[1].trim()); + this.isReset = false; + } + + /** + * Constructs a {@code PriceRange} that is going to be reset. + */ + private PriceRange() { + this.low = null; + this.high = null; + this.isReset = true; + } + + /** + * Returns true if a given string is a valid price range in format. + * Left value of the price range must be smaller than the right value of the price range. + * Both values must be non-negative and within the maximum range of a Double. + * Accepts an empty string. + */ + public static boolean isValidPriceRange(String test) { + requireNonNull(test); + + if (test.trim().isEmpty()) { + return true; + } + + boolean isValid = test.matches(VALIDATION_REGEX); + + // to prevent out of bounds error below + if (!isValid) { + return false; + } + + String[] rangeValues = test.split("-"); + + double leftValue; + double rightValue; + + try { + leftValue = Double.parseDouble(rangeValues[0].trim()); + rightValue = Double.parseDouble(rangeValues[1].trim()); + } catch (NumberFormatException e) { + return false; + } + + boolean isLeftValueValid = leftValue >= 0; + boolean isRightValueValid = rightValue >= 0; + + return isLeftValueValid + && isRightValueValid + && (leftValue - rightValue <= 0); + } + + /** + * Checks if a given Price is within the PriceRange. + */ + public boolean isWithinPriceRange(Price p) { + return (low.isSmallerThanOrEqual(p) && high.isGreaterThanOrEqual(p)); + } + + /** + * Compare the upperbound of two PriceRange objects. + * Upperbounds will always be compared in descending order. + */ + public int compareUpperBound(PriceRange other) { + return high.isSmallerThanOrEqual(other.high) ? 1 : -1; + } + + /** + * Compare the lowerbound of two PriceRange objects. + * Lowerbounds will always be compared in ascending order. + */ + public int compareLowerBound(PriceRange other) { + return low.isGreaterThanOrEqual(other.low) ? 1 : -1; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + // to avoid floats being saved to storage in scientific notation + // and causing parsing bugs when being converted back into a float + DecimalFormat df = new DecimalFormat("#"); + df.setMaximumFractionDigits(0); + sb.append(df.format(low.getNumericalValue())); + sb.append(" - "); + sb.append(df.format(high.getNumericalValue())); + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PriceRange // instanceof handles nulls + && this.toString().equals(((PriceRange) other).toString())); // state check + } + + public boolean isReset() { + return isReset; + } +} diff --git a/src/main/java/seedu/address/model/property/AbstractFilterPropsPredicate.java b/src/main/java/seedu/address/model/property/AbstractFilterPropsPredicate.java new file mode 100644 index 00000000000..93cabd1ad38 --- /dev/null +++ b/src/main/java/seedu/address/model/property/AbstractFilterPropsPredicate.java @@ -0,0 +1,14 @@ +package seedu.address.model.property; + +import java.util.function.Predicate; + +/** + * An abstract class for predicates within the FilterProps method. + * Since there are three possible ways that a {@code FilterPropertiesCommand} can be executed, + * we need to create an abstract class that can be passed into the {@code FilterPropertiesCommand} constructor, + * after which each individual predicate's behaviour is determined through polymorphism. + */ +public abstract class AbstractFilterPropsPredicate implements Predicate { + @Override + public abstract boolean test(Property property); +} diff --git a/src/main/java/seedu/address/model/property/Description.java b/src/main/java/seedu/address/model/property/Description.java new file mode 100644 index 00000000000..ea181c9dff9 --- /dev/null +++ b/src/main/java/seedu/address/model/property/Description.java @@ -0,0 +1,57 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Property's description in the property book. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class Description { + + public static final String MESSAGE_CONSTRAINTS = "Descriptions can take any values, and it should not be blank."; + + /* + * The first character of the description 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 Description}. + * + * @param description A valid description. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + value = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(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 Description // instanceof handles nulls + && value.equals(((Description) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/property/FilterPropsByOwnerNamePredicate.java b/src/main/java/seedu/address/model/property/FilterPropsByOwnerNamePredicate.java new file mode 100644 index 00000000000..d154da1f8b1 --- /dev/null +++ b/src/main/java/seedu/address/model/property/FilterPropsByOwnerNamePredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.buyer.Name; + +/** + * Tests that a {@code Property}'s {@code Owner} matches the given owner's name. + */ +public class FilterPropsByOwnerNamePredicate extends AbstractFilterPropsPredicate { + private final Name ownerName; + + /** + * Standard constructor for the predicate. + */ + public FilterPropsByOwnerNamePredicate(Name ownerName) { + requireNonNull(ownerName); + this.ownerName = ownerName; + } + + @Override + public boolean test(Property p) { + String curName = p.getOwnerName().fullName.toLowerCase(); + return curName.contains(ownerName.fullName.toLowerCase()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPropsByOwnerNamePredicate // instanceof handles nulls + && ownerName.equals(( + (FilterPropsByOwnerNamePredicate) other).ownerName)); // state check + } +} diff --git a/src/main/java/seedu/address/model/property/FilterPropsByPriceRangePredicate.java b/src/main/java/seedu/address/model/property/FilterPropsByPriceRangePredicate.java new file mode 100644 index 00000000000..3a652973685 --- /dev/null +++ b/src/main/java/seedu/address/model/property/FilterPropsByPriceRangePredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.price.PriceRange; + + + +/** + * Tests that a {@code Property}'s {@code Price} is within the given price range. + */ +public class FilterPropsByPriceRangePredicate extends AbstractFilterPropsPredicate { + + private final PriceRange priceRange; + + /** + * Standard constructor for the predicate. + */ + public FilterPropsByPriceRangePredicate(PriceRange priceRange) { + requireNonNull(priceRange); + this.priceRange = priceRange; + } + + @Override + public boolean test(Property p) { + return priceRange != null && priceRange.isWithinPriceRange(p.getPrice()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPropsByPriceRangePredicate // instanceof handles nulls + && priceRange.equals(((FilterPropsByPriceRangePredicate) other).priceRange)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/property/FilterPropsContainingAllCharacteristicsPredicate.java b/src/main/java/seedu/address/model/property/FilterPropsContainingAllCharacteristicsPredicate.java new file mode 100644 index 00000000000..556c7121887 --- /dev/null +++ b/src/main/java/seedu/address/model/property/FilterPropsContainingAllCharacteristicsPredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.characteristics.Characteristics; + +/** + * Tests that a given {@code Property}'s {@code DesiredCharacteristics} contains all the given characteristics. + */ +public class FilterPropsContainingAllCharacteristicsPredicate extends AbstractFilterPropsPredicate { + + private final Characteristics givenCharacteristics; + + /** + * Standard constructor for the predicate. + */ + public FilterPropsContainingAllCharacteristicsPredicate(Characteristics characteristics) { + requireNonNull(characteristics); + this.givenCharacteristics = characteristics; + } + + @Override + public boolean test(Property p) { + // N.B.: Returns false if the target property does not have a Characteristics object in their attributes. + if (p.getCharacteristics().isEmpty()) { + return false; + } + return p.getCharacteristics().get().containsAllGivenCharacteristics(givenCharacteristics); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPropsContainingAllCharacteristicsPredicate // instanceof handles nulls + && givenCharacteristics.equals(( + (FilterPropsContainingAllCharacteristicsPredicate) other).givenCharacteristics)); // state check + } +} diff --git a/src/main/java/seedu/address/model/property/FilterPropsContainingAnyCharacteristicPredicate.java b/src/main/java/seedu/address/model/property/FilterPropsContainingAnyCharacteristicPredicate.java new file mode 100644 index 00000000000..6531070663c --- /dev/null +++ b/src/main/java/seedu/address/model/property/FilterPropsContainingAnyCharacteristicPredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.characteristics.Characteristics; + +/** + * Tests that a given {@code Property}'s {@code Characteristics} any of the given characteristics. + */ +public class FilterPropsContainingAnyCharacteristicPredicate extends AbstractFilterPropsPredicate { + + private final Characteristics givenCharacteristics; + + /** + * Standard constructor for the predicate. + */ + public FilterPropsContainingAnyCharacteristicPredicate(Characteristics characteristics) { + requireNonNull(characteristics); + this.givenCharacteristics = characteristics; + } + + @Override + public boolean test(Property p) { + // N.B.: Returns false if the target property does not have a Characteristics object in their attributes. + if (p.getCharacteristics().isEmpty()) { + return false; + } + return p.getCharacteristics().get().containsAnyGivenCharacteristic(givenCharacteristics); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPropsContainingAnyCharacteristicPredicate // instanceof handles nulls + && givenCharacteristics.equals(( + (FilterPropsContainingAnyCharacteristicPredicate) other).givenCharacteristics)); // state check + } +} diff --git a/src/main/java/seedu/address/model/property/Owner.java b/src/main/java/seedu/address/model/property/Owner.java new file mode 100644 index 00000000000..e01b8eeed89 --- /dev/null +++ b/src/main/java/seedu/address/model/property/Owner.java @@ -0,0 +1,68 @@ +package seedu.address.model.property; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; + +/** + * Encapsulates all attributes for a particular Owner. + */ +public class Owner { + + public final Name name; + public final Phone phone; + + /** + * Every field must be present for an Owner to be correctly initialised. + */ + public Owner(Name name, Phone phone) { + requireAllNonNull(name, phone); + + this.name = name; + this.phone = phone; + } + + public Name getName() { + return this.name; + } + + public Phone getPhone() { + return this.phone; + } + + /** + * Returns true if both owners have the same phone number. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Owner)) { + return false; + } + + Owner otherOwner = (Owner) other; + return otherOwner != null + && otherOwner.getPhone().equals(this.phone); + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.phone); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Owner Name: ") + .append(getName()) + .append("\nOwner Phone: ") + .append(getPhone()); + return sb.toString(); + } +} diff --git a/src/main/java/seedu/address/model/property/Property.java b/src/main/java/seedu/address/model/property/Property.java new file mode 100644 index 00000000000..c870b104894 --- /dev/null +++ b/src/main/java/seedu/address/model/property/Property.java @@ -0,0 +1,149 @@ +package seedu.address.model.property; + + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; + +/** + * Represents a Property in the property book. + * Guarantees: field values are validated, immutable. + * Only characteristics may be null. + */ +public class Property { + + // Identity fields + private final PropertyName propertyName; + private final Price price; + + // Data fields + private final Address address; + private final Description description; + private final Optional characteristics; + private final Owner owner; + private final LocalDateTime propertyEntryTime; + + /** + * Every field must be present and not null. + */ + public Property(PropertyName propertyName, Price price, Address address, Description description, + Characteristics characteristics, Owner owner, LocalDateTime propertyEntryTime) { + requireAllNonNull(propertyName, price, address, description, owner, propertyEntryTime); + this.propertyName = propertyName; + this.price = price; + this.address = address; + this.description = description; + this.characteristics = Optional.ofNullable(characteristics); + this.owner = owner; + this.propertyEntryTime = propertyEntryTime; + } + + public PropertyName getPropertyName() { + return propertyName; + } + + public Address getAddress() { + return address; + } + + public Price getPrice() { + return price; + } + + public Description getDescription() { + return description; + } + + public LocalDateTime getPropertyEntryTime() { + return propertyEntryTime; + } + + public Owner getOwner() { + return owner; + } + + public Name getOwnerName() { + return owner.getName(); + } + + public Phone getOwnerPhone() { + return owner.getPhone(); + } + + public Optional getCharacteristics() { + return this.characteristics; + } + + /** + * Returns true if both properties have the same address. + * This defines a weaker notion of equality between two properties. + */ + public boolean isSameProperty(Property otherProperty) { + if (otherProperty == this) { + return true; + } + + return otherProperty != null + && otherProperty.getAddress().equals(getAddress()); + } + + /** + * Returns true if both properties have the same identity and data fields. + * This defines a stronger notion of equality between two properties. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Property)) { + return false; + } + + Property otherProperty = (Property) other; + return otherProperty.getPropertyName().equals(getPropertyName()) + && otherProperty.getPrice().equals(getPrice()) + && otherProperty.getAddress().equals(getAddress()) + && otherProperty.getDescription().equals(getDescription()) + && otherProperty.getCharacteristics().equals(getCharacteristics()) + && otherProperty.getOwner().equals(getOwner()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(propertyName, price, address, description, characteristics, owner); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Name: ") + .append(getPropertyName()) + .append("\nAddress: ") + .append(getAddress()) + .append("\nPrice: ") + .append(getPrice()) + .append("\nDescription: ") + .append(getDescription()); + + builder.append("\nCharacteristics: ") + .append(getCharacteristics() + .map(Characteristics::toString) + .orElse("Not Specified")); + + builder.append("\n") + .append(getOwner()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/property/PropertyName.java b/src/main/java/seedu/address/model/property/PropertyName.java new file mode 100644 index 00000000000..21f65212c2d --- /dev/null +++ b/src/main/java/seedu/address/model/property/PropertyName.java @@ -0,0 +1,63 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Property's name in the property book. + * Guarantees: immutable; is valid as declared in {@link #isValidPropertyName(String)} + */ +public class PropertyName { + + public static final String MESSAGE_CONSTRAINTS = + "Property names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public PropertyName(String name) { + requireNonNull(name); + checkArgument(isValidPropertyName(name), MESSAGE_CONSTRAINTS); + fullName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidPropertyName(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 PropertyName // instanceof handles nulls + && fullName.equalsIgnoreCase(((PropertyName) other).fullName)); // state check + } + + public int compareTo(Object other) { + return fullName.compareToIgnoreCase(((PropertyName) other).fullName); + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/property/PropertyNameContainsSubstringPredicate.java b/src/main/java/seedu/address/model/property/PropertyNameContainsSubstringPredicate.java new file mode 100644 index 00000000000..4697b804329 --- /dev/null +++ b/src/main/java/seedu/address/model/property/PropertyNameContainsSubstringPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.property; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Property}'s {@code Name} contains the given string. + */ +public class PropertyNameContainsSubstringPredicate implements Predicate { + private final String string; + + public PropertyNameContainsSubstringPredicate(String string) { + this.string = string.toLowerCase(); + } + + @Override + public boolean test(Property property) { + String name = property.getPropertyName().fullName.toLowerCase(); + return name.contains(string); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PropertyNameContainsSubstringPredicate // instanceof handles nulls + && string.equals(((PropertyNameContainsSubstringPredicate) other).string)); // state check + } +} + diff --git a/src/main/java/seedu/address/model/property/UniquePropertyList.java b/src/main/java/seedu/address/model/property/UniquePropertyList.java new file mode 100644 index 00000000000..dde698a7ed5 --- /dev/null +++ b/src/main/java/seedu/address/model/property/UniquePropertyList.java @@ -0,0 +1,139 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.property.exceptions.DuplicatePropertyException; +import seedu.address.model.property.exceptions.PropertyNotFoundException; + + +/** + * A list of properties that enforces uniqueness between its elements and does not allow nulls. + * A property is considered unique by comparing using {@code Property#isSameProperty(Property) }. + * As such, adding and updating of properties uses Property#isSameProperty(Property) for equality + * to ensure that the property being added or updated is unique in terms of identity in the + * UniquePropertyList. However, the removal of a property uses Property#equals(Object) to + * ensure that the property with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Property#isSameProperty(Property) + */ +public class UniquePropertyList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent buyer as the given argument. + */ + public boolean contains(Property toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameProperty); + } + + /** + * Adds a buyer to the list. + * The buyer must not already exist in the list. + */ + public void add(Property toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePropertyException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the property {@code target} in the list with {@code editedProperty}. + * {@code target} must exist in the list. + * The property identity of {@code editedProperty} must not be the same as another existing property in the list. + */ + public void setProperty(Property target, Property editedProperty) { + requireAllNonNull(target, editedProperty); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PropertyNotFoundException(); + } + + if (!target.isSameProperty(editedProperty) && contains(editedProperty)) { + throw new DuplicatePropertyException(); + } + + internalList.set(index, editedProperty); + } + + /** + * Removes the equivalent buyer from the list. + * The buyer must exist in the list. + */ + public void remove(Property toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PropertyNotFoundException(); + } + } + + public void setProperties(UniquePropertyList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code properties}. + * {@code properties} must not contain duplicate properties. + */ + public void setProperties(List properties) { + requireAllNonNull(properties); + if (!propertiesAreUnique(properties)) { + throw new DuplicatePropertyException(); + } + + internalList.setAll(properties); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePropertyList // instanceof handles nulls + && internalList.equals(((UniquePropertyList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code properties} contains only unique properties. + */ + private boolean propertiesAreUnique(List properties) { + for (int i = 0; i < properties.size() - 1; i++) { + for (int j = i + 1; j < properties.size(); j++) { + if (properties.get(i).isSameProperty(properties.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/property/exceptions/DuplicatePropertyException.java b/src/main/java/seedu/address/model/property/exceptions/DuplicatePropertyException.java new file mode 100644 index 00000000000..79af50e1a37 --- /dev/null +++ b/src/main/java/seedu/address/model/property/exceptions/DuplicatePropertyException.java @@ -0,0 +1,12 @@ +package seedu.address.model.property.exceptions; + +/** + * Signals that the operation will result in duplicate Properties + * (Properties are considered duplicates if they have the same identity). + */ +public class DuplicatePropertyException extends RuntimeException { + + public DuplicatePropertyException() { + super("Operation would result in duplicate properties"); + } +} diff --git a/src/main/java/seedu/address/model/property/exceptions/PropertyNotFoundException.java b/src/main/java/seedu/address/model/property/exceptions/PropertyNotFoundException.java new file mode 100644 index 00000000000..a17a2f94169 --- /dev/null +++ b/src/main/java/seedu/address/model/property/exceptions/PropertyNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.property.exceptions; + +/** + * Signals that the operation is unable to find the specified property. + */ +public class PropertyNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index b0ea7e7dad7..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..5181560698d 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,60 +1,121 @@ package seedu.address.model.util; -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.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import java.time.LocalDateTime; +import java.time.Month; + +import seedu.address.model.BuyerBook; +import seedu.address.model.PropertyBook; +import seedu.address.model.ReadOnlyBuyerBook; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.Email; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.buyer.Priority; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; +import seedu.address.model.price.PriceRange; +import seedu.address.model.property.Description; +import seedu.address.model.property.Owner; +import seedu.address.model.property.Property; +import seedu.address.model.property.PropertyName; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code BuyerBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + + + public static LocalDateTime[] getSampleTime() { + int thisYear = 2022; + int day = 6; + int hour = 17; + int minutes = 30; + return new LocalDateTime[] { + LocalDateTime.of(thisYear, Month.JANUARY, day, hour, minutes), + LocalDateTime.of(thisYear, Month.FEBRUARY, day, hour, minutes), + LocalDateTime.of(thisYear, Month.MARCH, day, hour, minutes), + LocalDateTime.of(thisYear, Month.APRIL, day, hour, minutes), + LocalDateTime.of(thisYear, Month.MAY, day, hour, minutes), + LocalDateTime.of(thisYear, Month.JUNE, day, hour, minutes), + }; + } + + public static Buyer[] getSampleBuyers() { + return new Buyer[]{ + new Buyer(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), + new PriceRange("200000 - 250000"), new Characteristics("Bright; 5-Room"), + new Priority("high"), getSampleTime()[0]), + new Buyer(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new PriceRange("250000 - 280000"), new Characteristics("Clean; Large"), + new Priority("low"), getSampleTime()[1]), + new Buyer(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new PriceRange("300000 - 400000"), new Characteristics("Near MRT"), + new Priority("low"), getSampleTime()[2]), + new Buyer(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new PriceRange("500000 - 800000"), new Characteristics("Near School"), + new Priority("normal"), getSampleTime()[3]), + new Buyer(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), + new PriceRange("200000 - 250000"), new Characteristics("Bishan"), + new Priority("normal"), getSampleTime()[4]), + new Buyer(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), + new PriceRange("100000 - 150000"), new Characteristics("Toa Payoh; Kid-Friendly"), + new Priority("low"), getSampleTime()[5]) + }; + } + + public static Owner[] getSampleOwners() { + return new Owner[]{ + new Owner(new Name("Alex Yeoh"), new Phone("87438807")), + new Owner(new Name("Bernice Yu"), new Phone("99272758")), + new Owner(new Name("Charlotte Oliveiro"), new Phone("93210283")), + new Owner(new Name("David Li"), new Phone("91031282")), + new Owner(new Name("Irfan Ibrahim"), new Phone("92492021")), + new Owner(new Name("Roy Balakrishnan"), new Phone("92624417")) }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + public static Property[] getSampleProperties() { + return new Property[]{ + new Property(new PropertyName("Residential College 4"), new Price("50000"), + new Address("6 College Avenue East"), new Description("A place for NUS students to stay."), + new Characteristics("Bright; Sunny"), getSampleOwners()[0], getSampleTime()[0]), + new Property(new PropertyName("Tembusu College"), new Price("9999"), + new Address("26 College Avenue East"), new Description("A place for NUS students to stay."), + new Characteristics("Near MRT"), getSampleOwners()[1], getSampleTime()[1]), + new Property(new PropertyName("Peak Residence"), new Price("10000000"), + new Address("333 Thompson Road"), + new Description("latest freehold luxurious exclusive condominium."), + new Characteristics("Near School"), getSampleOwners()[2], getSampleTime()[2]), + new Property(new PropertyName("Hut"), new Price("25000"), + new Address("25 Regent Road"), + new Description("new 3-room HDB flat"), + new Characteristics("Kid-Friendly"), getSampleOwners()[3], getSampleTime()[3]), + }; + } + + public static ReadOnlyBuyerBook getSampleBuyerBook() { + BuyerBook sampleAb = new BuyerBook(); + for (Buyer sampleBuyer : getSampleBuyers()) { + sampleAb.addBuyer(sampleBuyer); } return sampleAb; } - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); + public static ReadOnlyPropertyBook getSamplePropertyBook() { + PropertyBook propertyBook = new PropertyBook(); + for (Property sampleProperty : getSampleProperties()) { + propertyBook.addProperty(sampleProperty); + } + return propertyBook; } + } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/BuyerBookStorage.java b/src/main/java/seedu/address/storage/BuyerBookStorage.java new file mode 100644 index 00000000000..300f4346edc --- /dev/null +++ b/src/main/java/seedu/address/storage/BuyerBookStorage.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.BuyerBook; +import seedu.address.model.ReadOnlyBuyerBook; + +/** + * Represents a storage for {@link BuyerBook}. + */ +public interface BuyerBookStorage { + + /** + * Returns the file path of the data file. + */ + Path getBuyerBookFilePath(); + + /** + * Returns BuyerBook data as a {@link ReadOnlyBuyerBook}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readBuyerBook() throws DataConversionException, IOException; + + /** + * @see #getBuyerBookFilePath() + */ + Optional readBuyerBook(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyBuyerBook} to the storage. + * @param buyerBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveBuyerBook(ReadOnlyBuyerBook buyerBook) throws IOException; + + /** + * @see #saveBuyerBook(ReadOnlyBuyerBook) + */ + void saveBuyerBook(ReadOnlyBuyerBook buyerBook, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedBuyer.java b/src/main/java/seedu/address/storage/JsonAdaptedBuyer.java new file mode 100644 index 00000000000..f87affe8b7a --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedBuyer.java @@ -0,0 +1,151 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.buyer.Email; +import seedu.address.model.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.buyer.Priority; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.PriceRange; + +/** + * Jackson-friendly version of {@link Buyer}. + */ +class JsonAdaptedBuyer { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Buyer's %s field is missing!"; + + private final String name; + private final String phone; + private final String email; + private final String address; + // priceRange and desiredCharacteristics cannot be null; converted to "" for saving to storage if null + private final String priceRange; + private final String desiredCharacteristics; + private final String specifiedPriority; + private final String entryTime; + + /** + * Constructs a {@code JsonAdaptedBuyer} with the given buyer details. + */ + @JsonCreator + public JsonAdaptedBuyer(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("priceRange") String priceRange, + @JsonProperty("desiredCharacteristics") String desiredCharacteristics, + @JsonProperty("priority") String specifiedPriority, + @JsonProperty("entryTime") String entryTime) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.priceRange = priceRange; + this.entryTime = entryTime; + this.desiredCharacteristics = desiredCharacteristics; + this.specifiedPriority = specifiedPriority; + } + + /** + * Converts a given {@code Buyer} into this class for Jackson use. + */ + public JsonAdaptedBuyer(Buyer source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + priceRange = source.getPriceRange().map(PriceRange::toString).orElse(""); + desiredCharacteristics = source.getDesiredCharacteristics() + .map(Characteristics::toString) + .orElse(""); + specifiedPriority = source.getPriority().toString(); + entryTime = source.getEntryTime().toString(); + } + + /** + * Converts this Jackson-friendly adapted buyer object into the model's {@code Buyer} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted buyer. + */ + public Buyer toModelType() throws IllegalValueException { + + if (entryTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + "time of creation not found")); + } + + final LocalDateTime modelTime = LocalDateTime.parse(entryTime); + + if (specifiedPriority == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Priority.class.getSimpleName())); + } + if (!Priority.isValidPriority(specifiedPriority)) { + throw new IllegalValueException(Priority.MESSAGE_CONSTRAINTS); + } + final Priority modelPriority = new Priority(specifiedPriority); + + 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); + + if (priceRange == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PriceRange.class.getSimpleName())); + } + if (!priceRange.isEmpty() && !PriceRange.isValidPriceRange(priceRange)) { + throw new IllegalValueException(PriceRange.MESSAGE_CONSTRAINTS); + } + final PriceRange modelPriceRange = priceRange.isEmpty() ? null : new PriceRange(priceRange); + + if (desiredCharacteristics == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Characteristics.class.getSimpleName())); + } + if (!desiredCharacteristics.isEmpty() + && !Characteristics.isValidCharacteristics(desiredCharacteristics)) { + throw new IllegalValueException(Characteristics.MESSAGE_CONSTRAINTS); + } + final Characteristics modelCharacteristics = desiredCharacteristics.isEmpty() + ? null + : new Characteristics(desiredCharacteristics); + + + return new Buyer(modelName, modelPhone, modelEmail, modelAddress, + modelPriceRange, modelCharacteristics, modelPriority, modelTime); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedOwner.java b/src/main/java/seedu/address/storage/JsonAdaptedOwner.java new file mode 100644 index 00000000000..be3b2b1bc44 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedOwner.java @@ -0,0 +1,52 @@ +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.buyer.Name; +import seedu.address.model.buyer.Phone; +import seedu.address.model.buyer.Priority; +import seedu.address.model.property.Owner; + +/** + * Jackson-friendly version of {@link Owner}. + */ +class JsonAdaptedOwner { + + private final String name; + private final String phone; + + /** + * Constructs a {@code JsonAdaptedOwner} with the given {@code name} and {@code phone}. + */ + @JsonCreator + public JsonAdaptedOwner(@JsonProperty("name") String name, @JsonProperty("phone") String phone) { + this.name = name; + this.phone = phone; + } + + /** + * Converts a given {@code Owner} into this class for Jackson use. + */ + public JsonAdaptedOwner(Owner source) { + name = source.getName().fullName; + phone = source.getPhone().value; + } + + /** + * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted tag. + */ + public Owner toModelType() throws IllegalValueException { + if (!Name.isValidName(name)) { + throw new IllegalValueException(Priority.MESSAGE_CONSTRAINTS); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Priority.MESSAGE_CONSTRAINTS); + } + return new Owner(new Name(name), new Phone(phone)); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -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.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; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - 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 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 tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * 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() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - 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); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedProperty.java b/src/main/java/seedu/address/storage/JsonAdaptedProperty.java new file mode 100644 index 00000000000..50fccb1227c --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedProperty.java @@ -0,0 +1,138 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.address.Address; +import seedu.address.model.buyer.Name; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.Price; +import seedu.address.model.property.Description; +import seedu.address.model.property.Owner; +import seedu.address.model.property.Property; +import seedu.address.model.property.PropertyName; + + +/** + * Jackson-friendly version of {@link Property}. + */ +class JsonAdaptedProperty { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Property's %s field is missing!"; + + private final String propertyName; + private final String price; + private final String address; + private final String description; + // characteristics cannot be null; converted to "" for saving to storage if null + private final String characteristics; + private final String entryTime; + private final JsonAdaptedOwner owner; + + /** + * Constructs a {@code JsonAdaptedProperty} with the given property details. + */ + @JsonCreator + public JsonAdaptedProperty(@JsonProperty("name") String propertyName, @JsonProperty("price") String price, + @JsonProperty("address") String address, @JsonProperty("description") String description, + @JsonProperty("characteristics") String characteristics, + @JsonProperty("owner") JsonAdaptedOwner owner, + @JsonProperty("entryTime") String entryTime) { + this.propertyName = propertyName; + this.price = price; + this.address = address; + this.description = description; + this.characteristics = characteristics; + this.entryTime = entryTime; + this.owner = owner; + } + + /** + * Converts a given {@code Property} into this class for Jackson use. + */ + public JsonAdaptedProperty(Property source) { + propertyName = source.getPropertyName().fullName; + price = source.getPrice().value; + address = source.getAddress().value; + description = source.getDescription().value; + entryTime = source.getPropertyEntryTime().toString(); + characteristics = source.getCharacteristics() + .map(Characteristics::toString) + .orElse(""); + owner = new JsonAdaptedOwner(source.getOwner()); + } + + /** + * Converts this Jackson-friendly adapted property object into the model's {@code Property} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted property. + */ + public Property toModelType() throws IllegalValueException { + + if (entryTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + "time of creation not found")); + } + + final LocalDateTime modelTime = LocalDateTime.parse(entryTime); + + if (propertyName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PropertyName.class.getSimpleName())); + } + if (!PropertyName.isValidPropertyName(propertyName)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final PropertyName modelPropertyName = new PropertyName(propertyName); + + if (price == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Price.class.getSimpleName())); + } + if (!Price.isValidPrice(price)) { + throw new IllegalValueException(Price.MESSAGE_CONSTRAINTS); + } + final Price modelPrice = new Price(price); + + 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); + + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + final Description modelDescription = new Description(propertyName); + + if (characteristics == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Characteristics.class.getSimpleName())); + } + if (!characteristics.isEmpty() + && !Characteristics.isValidCharacteristics(characteristics)) { + throw new IllegalValueException(Characteristics.MESSAGE_CONSTRAINTS); + } + final Characteristics modelCharacteristics = characteristics.isEmpty() + ? null + : new Characteristics(characteristics); + + if (owner == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Owner.class.getSimpleName())); + } + final Owner modelOwner = owner.toModelType(); + + return new Property(modelPropertyName, modelPrice, modelAddress, modelDescription, + modelCharacteristics, modelOwner, modelTime); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonBuyerBookStorage.java similarity index 50% rename from src/main/java/seedu/address/storage/JsonAddressBookStorage.java rename to src/main/java/seedu/address/storage/JsonBuyerBookStorage.java index dfab9daaa0d..4c886c50f48 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonBuyerBookStorage.java @@ -12,47 +12,47 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyBuyerBook; /** - * A class to access AddressBook data stored as a json file on the hard disk. + * A class to access BuyerBook data stored as a json file on the hard disk. */ -public class JsonAddressBookStorage implements AddressBookStorage { +public class JsonBuyerBookStorage implements BuyerBookStorage { - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(JsonBuyerBookStorage.class); private Path filePath; - public JsonAddressBookStorage(Path filePath) { + public JsonBuyerBookStorage(Path filePath) { this.filePath = filePath; } - public Path getAddressBookFilePath() { + public Path getBuyerBookFilePath() { return filePath; } @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); + public Optional readBuyerBook() throws DataConversionException { + return readBuyerBook(filePath); } /** - * Similar to {@link #readAddressBook()}. + * Similar to {@link #readBuyerBook()}. * * @param filePath location of the data. Cannot be null. * @throws DataConversionException if the file is not in the correct format. */ - public Optional readAddressBook(Path filePath) throws DataConversionException { + public Optional readBuyerBook(Path filePath) throws DataConversionException { requireNonNull(filePath); - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { + Optional jsonBuyerBook = JsonUtil.readJsonFile( + filePath, JsonSerializableBuyerBook.class); + if (!jsonBuyerBook.isPresent()) { return Optional.empty(); } try { - return Optional.of(jsonAddressBook.get().toModelType()); + return Optional.of(jsonBuyerBook.get().toModelType()); } catch (IllegalValueException ive) { logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); throw new DataConversionException(ive); @@ -60,21 +60,21 @@ public Optional readAddressBook(Path filePath) throws DataC } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); + public void saveBuyerBook(ReadOnlyBuyerBook buyerBook) throws IOException { + saveBuyerBook(buyerBook, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. + * Similar to {@link #saveBuyerBook(ReadOnlyBuyerBook)}. * * @param filePath location of the data. Cannot be null. */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); + public void saveBuyerBook(ReadOnlyBuyerBook buyerBook, Path filePath) throws IOException { + requireNonNull(buyerBook); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); + JsonUtil.saveJsonFile(new JsonSerializableBuyerBook(buyerBook), filePath); } } diff --git a/src/main/java/seedu/address/storage/JsonPropertyBookStorage.java b/src/main/java/seedu/address/storage/JsonPropertyBookStorage.java new file mode 100644 index 00000000000..b1d946462d6 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonPropertyBookStorage.java @@ -0,0 +1,80 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.ReadOnlyPropertyBook; + +/** + * A class to access BuyerBook data stored as a json file on the hard disk. + */ +public class JsonPropertyBookStorage implements PropertyBookStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonPropertyBookStorage.class); + + private Path filePath; + + public JsonPropertyBookStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getPropertyBookFilePath() { + return filePath; + } + + @Override + public Optional readPropertyBook() throws DataConversionException { + return readPropertyBook(filePath); + } + + /** + * Similar to {@link #readPropertyBook()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readPropertyBook(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonPropertyBook = JsonUtil.readJsonFile( + filePath, JsonSerializablePropertyBook.class); + if (!jsonPropertyBook.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonPropertyBook.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException { + savePropertyBook(propertyBook, filePath); + } + + /** + * Similar to {@link #savePropertyBook(ReadOnlyPropertyBook)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void savePropertyBook(ReadOnlyPropertyBook propertyBook, Path filePath) throws IOException { + requireNonNull(propertyBook); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializablePropertyBook(propertyBook), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @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())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableBuyerBook.java b/src/main/java/seedu/address/storage/JsonSerializableBuyerBook.java new file mode 100644 index 00000000000..82e90a8072d --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableBuyerBook.java @@ -0,0 +1,60 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.BuyerBook; +import seedu.address.model.ReadOnlyBuyerBook; +import seedu.address.model.buyer.Buyer; + +/** + * An Immutable BuyerBook that is serializable to JSON format. + */ +@JsonRootName(value = "buyerbook") +class JsonSerializableBuyerBook { + + public static final String MESSAGE_DUPLICATE_BUYER = "Buyers list contains duplicate buyer(s)."; + + private final List buyers = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableBuyerBook} with the given buyers. + */ + @JsonCreator + public JsonSerializableBuyerBook(@JsonProperty("buyers") List buyers) { + this.buyers.addAll(buyers); + } + + /** + * Converts a given {@code ReadOnlyBuyerBook} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableBuyerBook}. + */ + public JsonSerializableBuyerBook(ReadOnlyBuyerBook source) { + buyers.addAll(source.getBuyerList().stream().map(JsonAdaptedBuyer::new).collect(Collectors.toList())); + } + + /** + * Converts this buyer book into the model's {@code BuyerBook} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public BuyerBook toModelType() throws IllegalValueException { + BuyerBook buyerBook = new BuyerBook(); + for (JsonAdaptedBuyer jsonAdaptedBuyer : buyers) { + Buyer buyer = jsonAdaptedBuyer.toModelType(); + if (buyerBook.hasBuyer(buyer)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_BUYER); + } + buyerBook.addBuyer(buyer); + } + return buyerBook; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializablePropertyBook.java b/src/main/java/seedu/address/storage/JsonSerializablePropertyBook.java new file mode 100644 index 00000000000..3a8b2704d22 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializablePropertyBook.java @@ -0,0 +1,60 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.PropertyBook; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.property.Property; + +/** + * An Immutable PropertyBook that is serializable to JSON format. + */ +@JsonRootName(value = "propertyBook") +class JsonSerializablePropertyBook { + + public static final String MESSAGE_DUPLICATE_PROPERTY = "Properties list contains duplicate property."; + + private final List properties = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializablePropertyBook} with the given properties. + */ + @JsonCreator + public JsonSerializablePropertyBook(@JsonProperty("properties") List properties) { + this.properties.addAll(properties); + } + + /** + * Converts a given {@code ReadOnlyPropertyBook} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializablePropertyBook}. + */ + public JsonSerializablePropertyBook(ReadOnlyPropertyBook source) { + properties.addAll(source.getPropertyList().stream().map(JsonAdaptedProperty::new).collect(Collectors.toList())); + } + + /** + * Converts this property book into the model's {@code PropertyBook} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public PropertyBook toModelType() throws IllegalValueException { + PropertyBook propertyBook = new PropertyBook(); + for (JsonAdaptedProperty jsonAdaptedProperty : properties) { + Property property = jsonAdaptedProperty.toModelType(); + if (propertyBook.hasProperty(property)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PROPERTY); + } + propertyBook.addProperty(property); + } + return propertyBook; + } + +} diff --git a/src/main/java/seedu/address/storage/PropertyBookStorage.java b/src/main/java/seedu/address/storage/PropertyBookStorage.java new file mode 100644 index 00000000000..0a7e943f8da --- /dev/null +++ b/src/main/java/seedu/address/storage/PropertyBookStorage.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.PropertyBook; +import seedu.address.model.ReadOnlyPropertyBook; + +/** + * Represents a storage for {@link PropertyBook}. + */ +public interface PropertyBookStorage { + + /** + * Returns the file path of the data file. + */ + Path getPropertyBookFilePath(); + + /** + * Returns PropertyBook data as a {@link ReadOnlyPropertyBook}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readPropertyBook() throws DataConversionException, IOException; + + /** + * @see #getPropertyBookFilePath() + */ + Optional readPropertyBook(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyPropertyBook} to the storage. + * @param propertyBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException; + + /** + * @see #savePropertyBook(ReadOnlyPropertyBook) + */ + void savePropertyBook(ReadOnlyPropertyBook propertyBook, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index beda8bd9f11..277b3eeac8c 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -5,14 +5,14 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyBuyerBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends BuyerBookStorage, UserPrefsStorage, PropertyBookStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -21,12 +21,12 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getBuyerBookFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readBuyerBook() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveBuyerBook(ReadOnlyBuyerBook buyerBook) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 6cfa0162164..7a2c0fd1daf 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,25 +7,29 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyBuyerBook; +import seedu.address.model.ReadOnlyPropertyBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of BuyerBook data in local storage. */ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private BuyerBookStorage buyerBookStorage; private UserPrefsStorage userPrefsStorage; + private PropertyBookStorage propertyBookStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code BuyerBookStorage} and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - this.addressBookStorage = addressBookStorage; + public StorageManager(BuyerBookStorage buyerBookStorage, PropertyBookStorage propertyBookStorage, + UserPrefsStorage userPrefsStorage) { + this.buyerBookStorage = buyerBookStorage; this.userPrefsStorage = userPrefsStorage; + this.propertyBookStorage = propertyBookStorage; } // ================ UserPrefs methods ============================== @@ -46,33 +50,62 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ BuyerBook methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getBuyerBookFilePath() { + return buyerBookStorage.getBuyerBookFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readBuyerBook() throws DataConversionException, IOException { + return readBuyerBook(buyerBookStorage.getBuyerBookFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readBuyerBook(Path filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return buyerBookStorage.readBuyerBook(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveBuyerBook(ReadOnlyBuyerBook buyerBook) throws IOException { + saveBuyerBook(buyerBook, buyerBookStorage.getBuyerBookFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveBuyerBook(ReadOnlyBuyerBook buyerBook, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + buyerBookStorage.saveBuyerBook(buyerBook, filePath); + } + + // ================ PropertyBook methods ============================== + + @Override + public Path getPropertyBookFilePath() { + return propertyBookStorage.getPropertyBookFilePath(); + } + + @Override + public Optional readPropertyBook() throws DataConversionException, IOException { + return readPropertyBook(propertyBookStorage.getPropertyBookFilePath()); + } + + @Override + public Optional readPropertyBook(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return propertyBookStorage.readPropertyBook(filePath); + } + + @Override + public void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException { + savePropertyBook(propertyBook, propertyBookStorage.getPropertyBookFilePath()); + } + + @Override + public void savePropertyBook(ReadOnlyPropertyBook propertyBook, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + propertyBookStorage.savePropertyBook(propertyBook, filePath); } } diff --git a/src/main/java/seedu/address/ui/BuyerCard.java b/src/main/java/seedu/address/ui/BuyerCard.java new file mode 100644 index 00000000000..611d6dd0f21 --- /dev/null +++ b/src/main/java/seedu/address/ui/BuyerCard.java @@ -0,0 +1,87 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.buyer.Buyer; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.price.PriceRange; + +/** + * An UI component that displays information of a {@code Buyer}. + */ +public class BuyerCard extends UiPart { + + private static final String FXML = "BuyerListCard.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 The issue on BuyerBook level 4 + */ + + public final Buyer buyer; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + + @FXML + private FlowPane priority; + + @FXML + private Label priceRange; + + @FXML + private Label desiredCharacteristics; + + /** + * Creates a {@code BuyerCard} with the given {@code Buyer} and index to display. + */ + public BuyerCard(Buyer buyer, int displayedIndex) { + super(FXML); + this.buyer = buyer; + id.setText(displayedIndex + ". "); + name.setText(buyer.getName().fullName); + phone.setText(buyer.getPhone().value); + address.setText(buyer.getAddress().value); + email.setText(buyer.getEmail().value); + priority.getChildren().add(new Label(buyer.getPriority().toString())); + priceRange.setText("Budget: " + buyer.getPriceRange() + .map(PriceRange::toString).orElse("Not Specified")); + desiredCharacteristics.setText("Characteristics: " + buyer + .getDesiredCharacteristics().map(Characteristics::toString) + .orElse("Not Specified")); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BuyerCard)) { + return false; + } + + // state check + BuyerCard card = (BuyerCard) other; + return id.getText().equals(card.id.getText()) + && buyer.equals(card.buyer); + } +} diff --git a/src/main/java/seedu/address/ui/BuyerListPanel.java b/src/main/java/seedu/address/ui/BuyerListPanel.java new file mode 100644 index 00000000000..be5538faa90 --- /dev/null +++ b/src/main/java/seedu/address/ui/BuyerListPanel.java @@ -0,0 +1,52 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.buyer.Buyer; + +/** + * Panel containing the list of buyers. + */ +public class BuyerListPanel extends UiPart { + private static final String FXML = "BuyerListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(BuyerListPanel.class); + + @FXML + private ListView buyerListView; + + private ObservableList currentlyDisplayedBuyerList; + + /** + * Creates a {@code BuyerListPanel} with the given {@code ObservableList}. + */ + public BuyerListPanel(ObservableList filteredBuyerList) { + super(FXML); + buyerListView.setItems(filteredBuyerList); + buyerListView.setCellFactory(listView -> new BuyerListViewCell()); + currentlyDisplayedBuyerList = filteredBuyerList; + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Buyer} using a {@code BuyerCard}. + */ + class BuyerListViewCell extends ListCell { + @Override + protected void updateItem(Buyer buyer, boolean empty) { + super.updateItem(buyer, empty); + + if (empty || buyer == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new BuyerCard(buyer, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/BuyerStatusBarFooter.java b/src/main/java/seedu/address/ui/BuyerStatusBarFooter.java new file mode 100644 index 00000000000..26442b0f511 --- /dev/null +++ b/src/main/java/seedu/address/ui/BuyerStatusBarFooter.java @@ -0,0 +1,28 @@ +package seedu.address.ui; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +/** + * A ui for the status bar that is displayed at the footer of the buyer list. + */ +public class BuyerStatusBarFooter extends UiPart { + + private static final String FXML = "BuyerStatusBarFooter.fxml"; + + @FXML + private Label saveBuyerLocationStatus; + + /** + * Creates a {@code BuyerStatusBarFooter} with the given {@code Path}. + */ + public BuyerStatusBarFooter(Path saveBuyerLocation) { + super(FXML); + saveBuyerLocationStatus.setText(Paths.get(".").resolve(saveBuyerLocation).toString()); + } + +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..21a043d5428 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,8 +1,12 @@ package seedu.address.ui; +import java.util.ArrayList; + import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; @@ -16,6 +20,9 @@ public class CommandBox extends UiPart { public static final String ERROR_STYLE_CLASS = "error"; private static final String FXML = "CommandBox.fxml"; + private CommandRetriever commandRetriever; + private int previousCommandsIndex; + private final CommandExecutor commandExecutor; @FXML @@ -27,6 +34,7 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; + commandRetriever = new CommandRetriever(); // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } @@ -43,12 +51,30 @@ private void handleCommandEntered() { try { commandExecutor.execute(commandText); - commandTextField.setText(""); + commandRetriever.addCommand(commandText, commandTextField); } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); } } + /** + * Handles any key release. + * UP arrow invokes {@link CommandRetriever#getPreviousCommand(TextField commandTextField)}. + * DOWN arrow invokes {@link CommandRetriever#getNextCommand(TextField commandTextField)}. + * Does nothing for other keys. + * + * @param e KeyEvent denoting the key that was released + */ + @FXML + private void handleKeyReleased(KeyEvent e) { + KeyCode keyCode = e.getCode(); + if (keyCode.equals(KeyCode.UP)) { + commandRetriever.getPreviousCommand(commandTextField); + } else if (keyCode.equals(KeyCode.DOWN)) { + commandRetriever.getNextCommand(commandTextField); + } + } + /** * Sets the command box style to use the default style. */ @@ -82,4 +108,88 @@ public interface CommandExecutor { CommandResult execute(String commandText) throws CommandException, ParseException; } + /** + * Contains all successfully executed commands from the last application start. + * Keeps track of the currently displayed command's index in the history. + */ + private static class CommandRetriever { + private final ArrayList commandHistory; + private int index; + private String currentCommand; + + public CommandRetriever() { + commandHistory = new ArrayList<>(); + currentCommand = ""; + + index = 0; + } + + /** + * Adds a successfully executed command into {@code commandRetriever}, and sets + * {@code textField} to an empty string. + * + * @param command the user-inputted String of the successfully executed command. + */ + public void addCommand(String command, TextField textField) { + commandHistory.add(command); + textField.setText(""); + + index = commandHistory.size(); + } + + private boolean isCurrentCommand() { + return index == commandHistory.size(); + } + + private boolean isMostRecentInCommandHistory() { + return commandHistory.size() > 0 && index == commandHistory.size() - 1; + } + + private boolean noPreviousCommand() { + return index <= 0; + } + + private boolean noNextCommand() { + return index >= commandHistory.size(); + } + + private void setCurrentCommand(String command) { + currentCommand = command; + } + + private String getCurrentCommand() { + return currentCommand; + } + + /** + * Sets the text in {@code textField} to the previous command, if a previous command exists. + * If the current command is the most recent command, adds that to history. + * + * @param textField the TextField where the user inputs commands in. + */ + public void getPreviousCommand(TextField textField) { + if (noPreviousCommand()) { + return; + } else if (isCurrentCommand()) { + setCurrentCommand(textField.getText()); + } + textField.setText(commandHistory.get(--index)); + } + + /** + * Sets the text in {@code textField} to the next command, if a next command exists. + * + * @param textField the TextField where the user inputs commands in. + */ + public void getNextCommand(TextField textField) { + if (noNextCommand()) { + return; + } else if (isMostRecentInCommandHistory()) { + textField.setText(getCurrentCommand()); + index++; + } else { + textField.setText(commandHistory.get(++index)); + } + } + } } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..3e77921d89a 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-f12-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..ef672d87350 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -31,7 +31,8 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private BuyerListPanel buyerListPanel; + private PropertyListPanel propertyListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -42,13 +43,19 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane buyerListPanelPlaceholder; + + @FXML + private StackPane propertyListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @FXML - private StackPane statusbarPlaceholder; + private StackPane buyerStatusBarPlaceholder; + + @FXML + private StackPane propertyStatusBarPlaceholder; /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. @@ -110,14 +117,20 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + buyerListPanel = new BuyerListPanel(logic.getFilteredBuyerList()); + buyerListPanelPlaceholder.getChildren().add(buyerListPanel.getRoot()); + + propertyListPanel = new PropertyListPanel(logic.getFilteredPropertyList()); + propertyListPanelPlaceholder.getChildren().add(propertyListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + BuyerStatusBarFooter buyerStatusBarFooter = new BuyerStatusBarFooter(logic.getBuyerBookFilePath()); + buyerStatusBarPlaceholder.getChildren().add(buyerStatusBarFooter.getRoot()); + + PropertyStatusBarFooter propertyStatusBarFooter = new PropertyStatusBarFooter(logic.getPropertyBookFilePath()); + propertyStatusBarPlaceholder.getChildren().add(propertyStatusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); @@ -163,8 +176,12 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public BuyerListPanel getBuyerListPanel() { + return buyerListPanel; + } + + public PropertyListPanel getPropertyListPanel() { + return propertyListPanel; } /** diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 7fc927bc5d9..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.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 The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * 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.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -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.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/PropertyCard.java b/src/main/java/seedu/address/ui/PropertyCard.java new file mode 100644 index 00000000000..c939b090717 --- /dev/null +++ b/src/main/java/seedu/address/ui/PropertyCard.java @@ -0,0 +1,81 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.characteristics.Characteristics; +import seedu.address.model.property.Property; + +/** + * An UI component that displays information of a {@code Buyer}. + */ +public class PropertyCard extends UiPart { + + private static final String FXML = "PropertyListCard.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 The issue on AddressBook level 4 + */ + + public final Property property; + + @FXML + private HBox cardPane; + @FXML + private Label propertyName; + @FXML + private Label id; + @FXML + private Label price; + @FXML + private Label address; + @FXML + private Label description; + @FXML + private Label ownerName; + @FXML + private Label ownerPhone; + @FXML + private Label characteristics; + + /** + * Creates a {@code PropertyCard} with the given {@code Property} and index to display. + */ + public PropertyCard(Property property, int displayedIndex) { + super(FXML); + this.property = property; + id.setText(displayedIndex + ". "); + propertyName.setText(property.getPropertyName().fullName); + price.setText(property.getPrice().value); + address.setText(property.getAddress().value); + description.setText(property.getDescription().value); + characteristics.setText("Characteristics: " + property + .getCharacteristics().map(Characteristics::toString) + .orElse("Not Specified")); + ownerName.setText("Owner Name: " + property.getOwnerName()); + ownerPhone.setText("Owner Phone: " + property.getOwnerPhone()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PropertyCard)) { + return false; + } + + // state check + PropertyCard card = (PropertyCard) other; + return id.getText().equals(card.id.getText()) + && property.equals(card.property); + } +} diff --git a/src/main/java/seedu/address/ui/PropertyListPanel.java b/src/main/java/seedu/address/ui/PropertyListPanel.java new file mode 100644 index 00000000000..3a561cbc9e4 --- /dev/null +++ b/src/main/java/seedu/address/ui/PropertyListPanel.java @@ -0,0 +1,52 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.property.Property; + + +/** + * Panel containing the list of properties. + */ +public class PropertyListPanel extends UiPart { + private static final String FXML = "PropertyListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PropertyListPanel.class); + + @FXML + private ListView propertyListView; + + private ObservableList currentlyDisplayedPropertyList; + + /** + * Creates a {@code PropertyListPanel} with the given {@code ObservableList}. + */ + public PropertyListPanel(ObservableList filteredPropertyList) { + super(FXML); + propertyListView.setItems(filteredPropertyList); + propertyListView.setCellFactory(listView -> new PropertyListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Property} using a {@code PropertyCard}. + */ + class PropertyListViewCell extends ListCell { + @Override + protected void updateItem(Property property, boolean empty) { + super.updateItem(property, empty); + + if (empty || property == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PropertyCard(property, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/PropertyStatusBarFooter.java similarity index 51% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/address/ui/PropertyStatusBarFooter.java index b577f829423..f4ff4aaf819 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/PropertyStatusBarFooter.java @@ -8,21 +8,20 @@ import javafx.scene.layout.Region; /** - * A ui for the status bar that is displayed at the footer of the application. + * A ui for the status bar that is displayed at the footer of the property list. */ -public class StatusBarFooter extends UiPart { +public class PropertyStatusBarFooter extends UiPart { - private static final String FXML = "StatusBarFooter.fxml"; + private static final String FXML = "PropertyStatusBarFooter.fxml"; @FXML - private Label saveLocationStatus; + private Label savePropertyLocationStatus; /** * Creates a {@code StatusBarFooter} with the given {@code Path}. */ - public StatusBarFooter(Path saveLocation) { + public PropertyStatusBarFooter(Path savePropertyLocation) { super(FXML); - saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); + savePropertyLocationStatus.setText(Paths.get(".").resolve(savePropertyLocation).toString()); } - } diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/BuyerListCard.fxml similarity index 78% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/BuyerListCard.fxml index f08ea32ad55..fa9c052f142 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/BuyerListCard.fxml @@ -25,12 +25,14 @@ -