diff --git a/.gitignore b/.gitignore index 5e59b862ba4..633c9b61726 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store + +# Do not add docs folder \ No newline at end of file diff --git a/README.adoc b/README.adoc index e36efe534bb..118d8ccdbd1 100644 --- a/README.adoc +++ b/README.adoc @@ -1,8 +1,7 @@ -= Address Book (Level 3) += Contact Tracing App ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/se-edu/addressbook-level3.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] +https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/AY1920S2-CS2103T-W15-1/main.svg?branch=master[Build Status]] https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] https://www.codacy.com/app/damith/addressbook-level3?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level3&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] @@ -15,9 +14,28 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. +*Contact Tracing App* +**** +Contact Tracing APP is a efficient and lightweight tool for contact tracing for government managers and individual users. +This will help you save countless hours by collecting contact data and generate report automatically. Besides, this application provides a great privacy protect system to prevent privacy leak. +Worring about your health and want to know whether the area you live is safe now? Don't hesitate and start to try this app now! +**** + +*Who is it for?* +***** +- The government managers who want to get informantion about epidemic situation for their decision making. +- All citizens who want to know the situation of the spread of virus. +- Database developers who are working for collecting and managing the information about virus spreeading. +***** + +*Features* +**** +- Generate detailed reports from the app with several clicks. +- Identify anonymous individuals who are highly connected within the society for senior government officer. +- Identify hotpots where people tend to grather within the society. +- Individual users can get access with their personal information including citizenship ID, name, etc. +**** + == Site Map @@ -29,8 +47,8 @@ endif::[] == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. -* Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] == Licence : link:LICENSE[MIT] + + + diff --git a/build.gradle b/build.gradle index 93029ef8262..97bbc2bd4be 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,9 @@ dependencies { implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' + implementation group: 'com.itextpdf', name: 'kernel', version: '7.0.4' + implementation group: 'com.itextpdf', name: 'layout', version: '7.1.9' + implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.1' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..a91e4c74cce 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,27 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 3 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + -We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. +Covid-19 contact tracing App - We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. -== Project Team - -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] - -Role: Project Advisor - -''' - -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +This app was developed as an extension to AddressBook Level 3 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. -Role: Team Lead + -Responsibilities: UI - -''' +== Project Team -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Johan Kok +image::johankok.png[width="150", align="left"] +{empty}[http://www.johankzk.com/[homepage]] [https://github.com/JKOK005[github]] -Role: Developer + -Responsibilities: Data +Role: Minion +Responsibilities: UI + Backend ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Fu Yujian +image::yujian.jpeg[width="150", align="left"] +{empty}[https://github.com/Yujian-Fu[github]] -Role: Developer + -Responsibilities: Dev Ops + Threading +Role: Minion +Responsibilities: UI + Backend ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] - -Role: Developer + -Responsibilities: UI - -''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..1ab0c98c09c 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,6 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level3/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/AY1920S2-CS2103-W15-1/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `johan.kok@u.nus.edu` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..6984f0aab95 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Developer Guide += ContactTracing - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -14,7 +14,7 @@ ifdef::env-github[] endif::[] :repoURL: https://github.com/se-edu/addressbook-level3/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Us`      Since: `Mar 2020`      Licence: `MIT` == Setting up @@ -30,217 +30,156 @@ image::ArchitectureDiagram.png[] The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. -[TIP] -The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. -Refer to the <> to learn how to create and edit diagrams. - -`Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. 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 method where necessary. - -<> represents a collection of classes used by multiple other components. -The following class plays an important role at the architecture level: - -* `LogsCenter` : Used by many classes to write log messages to the App's log file. - -The rest of the App consists of four components. - -* <>: The UI of the App. -* <>: The command executor. -* <>: Holds the data of the App in-memory. -* <>: Reads data from, and writes data to, the hard disk. - -Each of the four components - -* Defines its _API_ in an `interface` with the same name as the Component. -* Exposes its functionality using a `{Component Name}Manager` class. - -For example, the `Logic` component (see the class diagram given below) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. - -.Class Diagram of the Logic Component -image::LogicClassDiagram.png[] - -[discrete] -==== 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`. - -.Component interactions for `delete 1` command -image::ArchitectureSequenceDiagram.png[] - -The sections below give more details of each component. - -[[Design-Ui]] -=== UI component - -.Structure of the UI Component -image::UiClassDiagram.png[] - -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] - -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. - -The `UI` component uses 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 link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`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. - -[[Design-Logic]] -=== Logic component - -[[fig-LogicClassDiagram]] -.Structure of the Logic Component -image::LogicClassDiagram.png[] - -*API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] - -. `Logic` uses the `AddressBookParser` class to parse the user command. -. This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person). -. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. - -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. - -.Interactions Inside the Logic Component for the `delete 1` Command -image::DeleteSequenceDiagram.png[] - -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. - -[[Design-Model]] -=== Model component - -.Structure of the Model Component -image::ModelClassDiagram.png[] - -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] - -The `Model`, - -* stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes 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. -* does not depend on any of the other three components. - -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:BetterModelClassDiagram.png[] - -[[Design-Storage]] -=== Storage component - -.Structure of the Storage Component -image::StorageClassDiagram.png[] +[width="65%",cols="25%,",options="header",] +|======================================================================= +|Class | Definition +| LandingPage | First display page for user +| Command controller | Parses and executes user command +| Search | Class that handles individual user search query +| Generate Report | Class that generate PDF reports +| Model | Class that handles modelling & message format +| Storage | Class that handles all data access +| ?? | Placeholder for whatever we don't know needs to be there. +|======================================================================= -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +[[Use-case-diagram]] +== Use case diagram +.Use case diagram for our ContactTracing app +image::UseCaseDiagram.png[] -The `Storage` component, +The functionalities for our application are highlighted in the use case diagram. +This diagram was enacted based on premilinary brainstorming of our user stories. -* can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +== Class diagram illustration -[[Design-Commons]] -=== Common classes +.Class diagram +image::ClassDiagram.png[] -Classes used by multiple components are in the `seedu.addressbook.commons` package. +=== Class definitions -== Implementation +We created the following classes (and their relationship) in order to fulfil our use cases in <>. -This section describes some noteworthy details on how certain features are implemented. +`ContactTracingMainApp` Entry point to launch our contact tracing application -// tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation +`AppUiManager` Handles rendering information to user and response to user input -The 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: +`AppMainWindow` Class that sets FXML stage and component arrangements. The class consists of +a `CommandBox` for receiving user input , a `ResultDisplay` for displaying feedback to user +and `BluetoothPingPanel` for rendering search information. -* `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. +`BluetoothPingPanel` will render different information types, depending on user command. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +* `BluetoothPingSummaryPanel` renders all information regarding `BluetoothPing` model +* `PersonSummaryPanel` renders all information regarding `Person` model -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +`AppLogicManager` handles all backend logic for the application. +It relies on `DaoRouter` to retrieve relevant in-memory storage data. -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. +At present, we only have two kinds of `InMemoryStorage` classes. All `InMemoryStorage` classes +have their data loaded at runtime. Any modifications to the data will not be presisted, in accordance +with project requirements for no internet connectivity. -image::UndoRedoState0.png[] +* `BluetoothPingStorageAccess` stores all collected `BluetoothPing` traces +* `UserStorageAccess` stores all `Person` objects -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. +`CommandRouter` routes user requests to relevant `Command` class types, which form the core +execution logic of the system. -image::UndoRedoState1.png[] +Each `Command` class uses `Conditions` / `Aggregators` classes to drive the implementation of certain logics +and generate PDF report with `ReportGenerator`. For example, we can have a `FilterDangerCommand` first using +the `GroupByIDPairsAggregators` to merge and count all collect `BluetoothPings` based on their User ID pairs, +then pass it through the `DangerConditions` class to infer which User ID Pairs have had too much contact time. +Later, the search result will be passed to `DangerReportGenerator` to generate report with a table showing +all danger ID pairs with summary message. To generate a report, `RecordReader` will be used to illustrate the +given search result and pass data points to `PDFWriter` to generate a PDF file with summary on records. -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`. +The return of a successfully executed command is an `AppMessage` class which signals to `AppUiManager` +how the results should be rendered to the user. The return result of the execution command displayed to +the user includes two parts: the execution message and the corresponding instances extracted from the +storage. -image::UndoRedoState2.png[] +== How the architecture components interact with each other -[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`. +=== User Launches App -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. +.When user issues an instruction to the application +image::SequenceDiagram.png[] -image::UndoRedoState3.png[] +When the user launches the application, his request goes through a series of classes +as illustrated in the figure above. -[NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book 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. +=== Retrieving command / storage -The following sequence diagram shows how the undo operation works: +.Logic for route(request / command) for Command Router +image::CommandSequenceDiagram.png[] -image::UndoSequenceDiagram.png[] +Figure illustrates how a command is chosen based on a user defined *COMMAND_WORD*. -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. +The exact concepts can be applied for selecting the data access object we want to use. -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. +.Logic for route(request / command) for Dao Router +image::DaoRouterSequenceDiagram.png[] +Here, the `DaoRouter` infers the type of data access needed by looking at class `AppCommand`. -[NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book 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. +Routing is done based on whether the `AppCommand` class inherits from the list of avaible storage access classes. +At present, we only have `BluetoothPingStorageAccess` or `UserStorageAccess`. -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. +== The activities in using this app +image::ActivityDiagram.png[] -image::UndoRedoState4.png[] +ActivityDiagram illustrate the precession handling the input command. For all kinds of commands from +the command box, firstly, we deal with the invalid command and exit command. All invalid commands +will get a "Unknown command" error +and the App will be closed with an `exit` command. Then all other valid commands are +be parsed and three different types of operator are used to solve the commands according to +the command type. The command execution result will be shown in the UIWindow. -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. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +== Models -image::UndoRedoState5.png[] +All our models are stored and loaded at runtime in memory. -The following activity diagram summarizes what happens when a user executes a new command: +`BluetoothPings` class contains the following fields -image::CommitActivityDiagram.png[] +[width="65%",cols="25%,",options="header",] +|======================================================================= +|Field name | Description +| epochTs | Recorded timestamp in Unix Timing +| userIDs | User pairs [A, B] for each registered device interaction +|======================================================================= -==== Design Considerations +`BluetoothPingsSummary` class is a summary of all user ids recorded -===== Aspect: How undo & redo executes +[width="65%",cols="25%,",options="header",] +|======================================================================= +|Field name | Description +| userIDs | User pairs [A, B] for each registered device interaction +| counts | Total summed instances of all pairs [A, B] in the database +|======================================================================= -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by 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. +`Person` contains personal details of a user -===== Aspect: Data structure to support the undo/redo commands +[width="65%",cols="25%,",options="header",] +|======================================================================= +|Field name | Description +| userID | Registered user id of the person +| name | Person's name +| mobile | Phone number +| nric | NRIC identification beginning with S and ending with some alphabet +| age | Person's age +|======================================================================= -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. -// end::undoredo[] +== Design Considerations -// tag::dataencryption[] -=== [Proposed] Data Encryption +=== Consideration: How can we extend our platform for customization of search features -_{Explain here how the data encryption feature will be implemented}_ +* Alternative 1 (Current choice): Introduces concepts such as `conditions` and `aggregations` that are used by AppLogic +** Pros: Clean separation of logic +** Pros: Easily extensible to new conditions +** Cons: Added complexity may cause confusing to new users -// end::dataencryption[] +* Alternative 2: Define fixed use cases and build code on those features +** Pros: Developers don't need to be confused with additional concepts +** Cons: Lack of flexibility and extensibility for new featuress === Logging @@ -262,6 +201,7 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). + == Documentation Refer to the guide <>. @@ -275,17 +215,8 @@ Refer to the guide <>. Refer to the guide <>. [appendix] -== Product Scope - -*Target user profile*: - -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps - -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +== Address book lvl 3 +This project is a fork of Address-book lvl 3 [ref](https://github.com/nus-cs2103-AY1920S2/addressbook-level3) [appendix] == User Stories @@ -295,88 +226,13 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un [width="59%",cols="22%,<23%,<25%,<30%",options="header",] |======================================================================= |Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App - -|`* * *` |user |add a new person | -|`* * *` |user |delete a person |remove entries that I no longer need - -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list - -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident - -|`*` |user with many persons in the address book |sort persons by name |locate a person easily |======================================================================= -_{More to be added}_ - [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) - -[discrete] -=== Use case: Delete person - -*MSS* - -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person -+ -Use case ends. - -*Extensions* - -[none] -* 2a. The list is empty. -+ -Use case ends. - -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. - -_{More to be added}_ - -[appendix] -== Non Functional Requirements - -. Should work on any <> as long as it has Java `11` or above installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -_{More to be added}_ - -[appendix] -== Glossary - -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X - -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others - -[appendix] -== Product Survey - -*Product Name* - -Author: ... - -Pros: - -* ... -* ... - -Cons: - -* ... -* ... +(For all use cases below, the *System* is the `Contact tracing app` and the *Actor* is the `user`, unless specified otherwise) [appendix] == Instructions for Manual Testing @@ -388,38 +244,14 @@ These instructions only provide a starting point for testers to work on; testers === Launch and Shutdown +Launching the application is as simple as +```java +java -jar +``` + . Initial launch .. Download the jar file and copy into an empty folder -.. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +.. Run the command above -. Saving window preferences - -.. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + - Expected: The most recent window size and location is retained. - -_{ more test cases ... }_ - -=== Deleting a person - -. Deleting a person while all persons are listed - -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. 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. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. - -_{ more test cases ... }_ - -=== Saving data - -. Dealing with missing/corrupted data files - -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ - -_{ more test cases ... }_ + Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..ef0269791a1 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - User Guide += ContactTracing - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,20 +12,58 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:releaseUrl: https://github.com/AY1920S2-CS2103-W15-1/main/releases +:epochTiming: https://www.epochconverter.com -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Us`      Since: `Mar 2020`      Licence: `MIT` == Introduction -AddressBook Level 3 (AB3) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB3 is *optimized for those who prefer to work with 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. Interested? Jump to the <> to get started. Enjoy! +Gov Tech has recently launched the TraceTogether app to combat the rapid spread of the COVID-19 +infection. In their app, users who are within certain distances from each other will have +their interactions recorded via message exchange using bluetooth. + +Our Contact Tracing App is an extension of the TraceTogether app. We forsee a scenario where +the government has collected all the user's (anonymized) bluetooth ping data and can not +perform analytics and visulization over them. + +This is for efficient contract tracing, in order to make better policies when it comes to virus spreading control. +Our app allows efficient search over the data and generates a detailed report with record summary for upper +management review. + +== Target audiences + +We target mostly the analytics teams within government agencies, senior government managers and decision makers +are also our potantial users. + +=== User personas + +Our product is designed for the following personas: + +* Tech savvy: + +We assume that these users are familiar with technology and mediocre level of programming. They should be comfortable interacting with dashboards and would prefer more control over the software, as compared to the average user. + +* Pressed for timing: + +These users have to manage multiple projects in their day to day task. As such, speed of information retrieval and display is important to the functionality of the product, especially since we are dealing with a situation where a second delay may cause the infection to spread further. + +* Rich: + +The govt has too much money to throw around. We need not emphasise on cost cutting measures that reduces cost at the expense of system efficiency. We are not dealing with a start up here that lacks the funds needed to pay for our solution. + +* Believes that security is paramount: + +Not saying that security is not important for an application, just that we are dealing with government agencies and personal user data here. == Quick Start . Ensure you have Java `11` or above installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. +. Download the latest `ContactTracing.jar` link:{releaseUrl}/releases[here]. . Copy the file to the folder you want to use as the home folder for your Address Book. . Double-click the file to start the app. The GUI should appear in a few seconds. +. All PDF file report will be generated in './result' folder. + + image::Ui.png[width="790"] + @@ -33,145 +71,154 @@ image::Ui.png[width="790"] e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +* `from to `: Searches for all interactions from start to end timestamp +* `id is `: Searches for all interactions with the user ID +* `pairs and `: Searches for all interactions with the containing user 1 and user 2 +* `danger `: Searches for user pairs which has occured more than *threshold +* `person`: Shows all registered users in the system +* `person_by `: Shows all information wrt to a user +* `person_add /name /mobile /nric /age `: Adds a new user to the system +* `person_delete /userid `: Deletes an existing user to the system + +. With commands introduced above, users can save data points and summary into a PDF report. +. All reports will be saved to the ./result folder. +* `report from to ` : Generate a report with all interaction in tha time range. +* `report id is `: Generate a report with all interactions with a given user ID. +* `report pairs and `: Generate a report with all interactions containing user1 and user2. +* `report danger `: Generate a report with all danger cases. +* `report all`: Generate a report with all interaction cases. +* *`exit`* : Exits the app. . Refer to <> for details of each command. [[Features]] == Features -==== -*Command Format* +=== Querying over collected trace data +==== Searching over timestamp : `From` +Retrieves collected trace data based on by timestamp filter. -* 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`. -* 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`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* 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. -==== +Format: `from to ` -=== Viewing help : `help` +Example: -Format: `help` +* To view recorded pings from Epoch 1500000000 - 1500001000 : `from 1500000000 to 1500001000` +* To view all pings : `from 1 to 1500100000` -=== Adding a person: `add` +*Note: Currently accepted timestamp is in epoch timing. +For example on how to use the timing, refer link:{epochTiming}[here] -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +==== Searching over user ID : `id is` +Retrieves all trace data based on a given user id -[TIP] -A person can have any number of tags (including 0) +Format: `id is ` -Examples: +Example: `id is 1` -* `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` +*Note: Currently we add 20 users into our database with userID from 1 to 20. -=== Listing all persons : `list` +==== Searching over user ID pairs : `pairs` +Retrieves all trace data that contains interactions between 2 user pairs -Shows a list of all persons in the address book. + -Format: `list` +Format: `pairs and ` -=== Editing a person : `edit` +Example: `pairs 1 and 12` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +==== Searching for danger signs : `danger` +Identifies user pairs that are most at risk, based on occurrence spanning more than a threshold count. +These individuals are obviously not practicing good social distancing and are a threat to the community. -**** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. -**** +Format: `danger ` -Examples: +Example: `danger 5` will flag out user pairs which are present more than 5 times -* `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. +=== Querying user related information +==== Searching all users: `person` +Shows all users registered in the system -=== Locating persons by name: `find` +Format: `person` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +==== Filtering by user: `person_by` +Applies a filter to perform quick search on a user -**** -* 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` -**** +Format: `person_by ` -Examples: +Example: `person_by 1` will find information on user ID 1 -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +==== Adding a new user: `person_add` +Registers a new user to the system -// tag::delete[] -=== Deleting a person : `delete` +Format: `person_add /name /mobile /nric /age ` -Deletes the specified person from the address book. + -Format: `delete INDEX` +Example: `person_add /name John David /mobile 9213 /nric S1323923P /age 50` adds a new user with fields -**** -* 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, ... -**** +===== Constraints +* Sequence must abide in sequence /name, /mobile, /nric, /age -Examples: +==== Adding a new user: `person_deletes` +Registers a new user to the system -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +Format: `person_delete /userid ` -// end::delete[] -=== Clearing all entries : `clear` +Example: `person_delete /userid 1` deletes any record with user ID 1 -Clears all entries from the address book. + -Format: `clear` +=== Generating reports +==== Reporting all instances: `report all` +Report all interaction instances in database. -=== Exiting the program : `exit` +Example: `report all` -Exits the program. + -Format: `exit` +==== Reporting over time range: `report time` +Generate a report includes all interaction instances in a time range -=== Saving the data +Format: `report time from to ` -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +Example: `report time from 1500000000 to 1500003000` -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +==== Report all danger cases: `report danger` +Generate a report with user pairs that are most at risk, based on occurrence spanning more than a threshold count. -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +Format: `report danger ` + +Example: `report danger 10` + +==== Report user pair cases: `report pairs` +Generate a report includes user pairs with given userIDs + +Format: `report pairs and ` + +Example: `report pairs 1 and 12` + +==== Report user with a given ID: `report id` +Generate a report includes interactions with a specific given ID + +Format: `report id is ` + +Example: `report id is 1` + +==== Report information of each person in database: `report_person` +Generate a report with the information of all person data points in dababase + +Format: `report_person` + +==== Report information of a person with the given ID: `report_person id` +Generate a report with the information of a specific person with a a given ID + +Format: `report_person ` + +Example: `report_person 1` + +=== Help +Key in `help` to see all possible commands == FAQ -*Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. - -== Command Summary - -* *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` +*Q*: Does the application need internet to run? + +*A*: *NO*. We will be using hardcoded data for this project. Any display data you see is meant +to simulate actual deployment conditions where our App will receive a steady stream of data. + +*Q*: Will my personal data be protected well? + +*A*: There will be strict authority sytem to prevent privacy leak, Only people who have been +granted permission are able to get access to user privacy data and all data published to public will be +anonymous. + diff --git a/docs/images/ActivityDiagram.png b/docs/images/ActivityDiagram.png new file mode 100644 index 00000000000..c22e65ca249 Binary files /dev/null and b/docs/images/ActivityDiagram.png differ diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png index aa2d337d932..84819553246 100644 Binary files a/docs/images/ArchitectureDiagram.png and b/docs/images/ArchitectureDiagram.png differ diff --git a/docs/images/ClassDiagram.png b/docs/images/ClassDiagram.png new file mode 100644 index 00000000000..cc5dbc39651 Binary files /dev/null and b/docs/images/ClassDiagram.png differ diff --git a/docs/images/CommandSequenceDiagram.png b/docs/images/CommandSequenceDiagram.png new file mode 100644 index 00000000000..9bb0b8a3331 Binary files /dev/null and b/docs/images/CommandSequenceDiagram.png differ diff --git a/docs/images/DaoRouterSequenceDiagram.png b/docs/images/DaoRouterSequenceDiagram.png new file mode 100644 index 00000000000..348affe6c7f Binary files /dev/null and b/docs/images/DaoRouterSequenceDiagram.png differ diff --git a/docs/images/SequenceDiagram.png b/docs/images/SequenceDiagram.png new file mode 100644 index 00000000000..f893c99c29d Binary files /dev/null and b/docs/images/SequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..320a1fd2e58 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UseCaseDiagram.png b/docs/images/UseCaseDiagram.png new file mode 100644 index 00000000000..dca7afe31d8 Binary files /dev/null and b/docs/images/UseCaseDiagram.png differ diff --git a/docs/images/johankok.png b/docs/images/johankok.png new file mode 100644 index 00000000000..a1c4c266880 Binary files /dev/null and b/docs/images/johankok.png differ diff --git a/docs/images/yujian.jpeg b/docs/images/yujian.jpeg new file mode 100644 index 00000000000..f3220d438d7 Binary files /dev/null and b/docs/images/yujian.jpeg differ diff --git a/docs/team/Yujian.adoc b/docs/team/Yujian.adoc new file mode 100644 index 00000000000..53b2262aca6 --- /dev/null +++ b/docs/team/Yujian.adoc @@ -0,0 +1,91 @@ += YujianFU - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Contact Tracing + +== Link to RepoSense +Code contributions can be found in https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=Yujian-Fu&tabRepo=AY1920S2-CS2103-W15-1%2Fmain%5Bmaster%5D[RepoSense] + +--- +== Overview + +The contact tracing app is an extension of the Trace Together app recently developed by GovTech. +It aims to simplify the way analysts query over data to understand cluster hotspots and individual behaviour. +This app is built over https://github.com/nus-cs2103-AY1920S2/addressbook-level3[AddressBook - Level 3] + +== Summary of contributions +=== Feature requests +* *Major enhancement*: Implement the report generation function of this app. Create PDF file for data visualization. + +* *Major enhancement*: add feature "Generate "Generate report of saved data points* +* *What*: Save all data points stored in database with a brief sommary for visulization. + +* *Major enhancement*: add feature *Generate report with given userID* +** *What*: Pick all interactions which hold the given ID and save them in a PDF report with summary. +** *Justification*: +*** This is useful for tracing a particular user and find all contact people of him/her. + +* *Major enhancement*: add feature *Generate report with given time range* +** *What*: Pick all interactions which happens in a given time range and save them in report with summary. +** *Justification*: This is helpful for analysts to understand the virus transmission trends in different time stages. + +* *Major enhancement*: add feature *Generate report with a given pair of userID* +** *What*: Pick all interactions between users with a given pair of userIDs. +** *Justification*: The is helpful for analysts to understand the behavior pattern between two users. + +* *Major enhancement*: add feature *Generate report with a given danger threshold* +** *What*: Pick all pairs which appear more than n times, the given threshold. +** *Justification*: This is useful for indentifying users who contact frequently with each other, breaking the social +distance rules. + +* *Major enhancement*: add feature *Generate report for user information* +** *What*: Save information of all users into a report. Only +** *Justification*: This is helpful for visualizing user information from database. + +* *Major enhancement*: add feature *Generate report with a given person ID* +** *What*: Save the basic information of person with a give ID into a PDF report. +** *Justification*: This is helpful for identifying a person with person ID. + +* *Major enhancement*: add fature *Show help list for easy use* +** *What*: Show list with all valid commands and its format for guidance. +** *Justification*: This is helpful for all users to use the commands easily. + +* *Major enhancement*: manually add random data points into database to simulate real records +** *What*: Add data points to the database simulating real records for further implementation. +** *Justification*: Build record dataset for test and demonstration of all functions in this app. + + +=== Project management +** Help review code and project documents for project. https://github.com/AY1920S2-CS2103-W15-1/main/pulls[PRs]. +** Filed several https://github.com/AY1920S2-CS2103-W15-1/main/issues[issues] for feature request based on User Stories. + + +=== User Guide contributions +Refer to https://github.com/AY1920S2-CS2103-W15-1/main/blob/master/docs/UserGuide.adoc[User Guide] + +Parts contributed: + +. Introduction +. Target audiences +. User personas +. Quick start +. Features + +=== Developer Guide contributions +Refer to https://github.com/AY1920S2-CS2103-W15-1/main/blob/master/docs/DeveloperGuide.adoc[Developer Guide] + +Parts contributed: + +. Setting up +. Design-Architecture +. Class diagram illustration +. Models + +=== Credits +Would like to thank the following for being a great source of help in this project + +** https://itextpdf.com/en/resources/books/itext-7-jump-start-tutorial-java/intro[iText tutorial] +** Team member https://github.com/JKOK005[JohanKOK] + diff --git a/docs/team/jkok005.adoc b/docs/team/jkok005.adoc new file mode 100644 index 00000000000..5190987701b --- /dev/null +++ b/docs/team/jkok005.adoc @@ -0,0 +1,138 @@ += Johan Kok (JKOK005) - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Contact Tracing + +== Link to RepoSense +Code contributions can be found in https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=JKOK005&tabRepo=AY1920S2-CS2103-W15-1%2Fmain%5Bmaster%5D[RepoSense] + +--- +== Overview + +The contact tracing app is an extension of the Trace Together app recently developed by GovTech. +It aims to simplify the way analysts query over data to understand cluster hotspots and individual behaviour. +This app is built over https://github.com/nus-cs2103-AY1920S2/addressbook-level3[AddressBook - Level 3] + +== Summary of contributions +=== Feature requests +* *Major enhancement*: defined *core backend framework of application* + +** *What*: Refractored previous code, adding new concepts such as `Conditions` / `Aggregator` / `InMemoryStorage` +that form the backbone of new features being introduced to meet user requirements. +Refer to https://github.com/AY1920S2-CS2103-W15-1/main/blob/master/docs/DeveloperGuide.adoc#class-diagram-illustration[Developer Guide] for more details. + +** *Justification*: +*** Present code base does not implement Data Access Objects (DAO) protocols for abstracting data access from the application. +*** Introducing `Conditions` / `Aggregator` classes allows easy extensibility of code base to accommodate new features. + +** *Highlights*: Team adopted this concept to build features down the road. + + +* *Major enhancement*: added feature *Searching over timestamps* + +** *What*: Picks up all pings in between a timestamp range. + +** *Justification*: +*** This is useful for analysts who want to understand interaction patterns across time. + + +* *Major enhancement*: added feature *Searching over user ID* + +** *What*: Picks up all pings that contains the user ID. + +** *Justification*: +*** This is useful for analysts who want to understand a particular user's behaviour pattern. + + +* *Major enhancement*: added feature *Searching over user ID pairs* + +** *What*: Picks up all pings that contains a user ID pairs [A, B]. + +** *Justification*: +*** This is useful for analysts who want to understand how two users are behaving and whether they are observing social distancing measures. + + +* *Major enhancement*: added feature *danger search* + +** *What*: Given a threshold value, find all user ID pairs that cross it. + +** *Justification*: +*** Allows quick identification of users who are clearly breaching social distancing rules. +Helps to narrow investigation and containment efforts. + + +* *Major enhancement*: added feature *search over user details* + +** *What*: Displays a full / filtered trace of all registered users stored in the DB. + +** *Justification*: +*** Helps link bluetooth ping user IDs' recoded with the actual user for authorities to contact. + + +* *Major enhancement*: added feature *user detail modifications* + +** *What*: Allows the admin to add / delete new users into the system. + +** *Justification*: +*** Useful when new users sign up / leave the system. + + +* *Major enhancement*: added feature *UI panel swapping* + +** *What*: Displays information on main UI for all `BluetoothPing` and `Person` models. +Designed panel swapping logic to render correct UI panel based on user requests. + +** *Justification*: +*** Prior AddressBook code base does not allow rendering of different panels to the UI, which our application requires. + + +* *Minor enhancement*: Seeded InMemory database with bluetooth / user records. + +** *What*: Manually added new records to the system that are loaded at runtime. + +** *Justification*: +*** Helps with the visualization of our application and UI debugging + +=== Project management +** Managed https://github.com/AY1920S2-CS2103-W15-1/main/releases[release] `v1.0` on Github +** Reviewed team https://github.com/AY1920S2-CS2103-W15-1/main/pulls[PRs] to ensure code quality adherence +** Reported bugs and suggestions for other teams in the class (via Catcher) +** Filed several https://github.com/AY1920S2-CS2103-W15-1/main/issues[issues] for feature request based on User Stories. +** Timekeeper for team to ensure we meet project milestones (PE Dry-run / Final submission) + +=== Dev Ops +** Set up jenkins CI/CD for repo + +=== User Guide contributions +Refer to https://github.com/AY1920S2-CS2103-W15-1/main/blob/johan-ppp/docs/UserGuide.adoc[User Guide] + +Parts contributed: + +. Introduction +. Target audiences +. Quick start +. Features +. FAQ + +=== Developer Guide contributions +Refer to https://github.com/AY1920S2-CS2103-W15-1/main/blob/master/docs/DeveloperGuide.adoc[Developer Guide] + +Parts contributed: + +. Setting up +. Design + Architecture +. Class diagram illustration +. Architecture component interactions +. Models +. Design considerations EXCEPT: +.. Logging +.. Configuration + +=== Credits +Would like to thank the following for being a great source of help in this project + +** https://stackoverflow.com/[StackOverflow] +** Team member @Yujian-Fu + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44e7c4d1d7b..23e07d217e6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Apr 10 11:47:18 PST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/results/report.pdf b/results/report.pdf new file mode 100644 index 00000000000..46b13f334bb Binary files /dev/null and b/results/report.pdf differ diff --git a/src/main/java/seedu/address/ContactTracingMainApp.java b/src/main/java/seedu/address/ContactTracingMainApp.java new file mode 100644 index 00000000000..646cd268e0c --- /dev/null +++ b/src/main/java/seedu/address/ContactTracingMainApp.java @@ -0,0 +1,92 @@ +package seedu.address; + +import javafx.application.Application; +import javafx.stage.Stage; +import seedu.address.commons.core.Config; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Version; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.util.ConfigUtil; +import seedu.address.commons.util.StringUtil; +import seedu.address.logic.AppLogic; +import seedu.address.logic.AppLogicManager; +import seedu.address.ui.AppUiManager; +import seedu.address.ui.Ui; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +public class ContactTracingMainApp extends Application { + public static final Version VERSION = new Version(0, 6, 0, true); + private static final Logger logger = LogsCenter.getLogger(ContactTracingMainApp.class); + + protected Ui ui; + protected AppLogic logic; + protected Config config; + + @Override + public void init() throws Exception { + logger.info("=============================[ Initializing Contact tracing app ]==========================="); + super.init(); + + AppParameters appParameters = AppParameters.parse(getParameters()); + this.config = initConfig(appParameters.getConfigPath()); + initLogging(this.config); + + this.logic = new AppLogicManager(); + this.ui = new AppUiManager(this.logic); + } + + private void initLogging(Config config) { + LogsCenter.init(config); + } + + /** + * Returns a {@code Config} using the file at {@code configFilePath}.
+ * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead + * if {@code configFilePath} is null. + */ + protected Config initConfig(Path configFilePath) { + Config initializedConfig; + Path configFilePathUsed; + + configFilePathUsed = Config.DEFAULT_CONFIG_FILE; + + if (configFilePath != null) { + logger.info("Custom Config file specified " + configFilePath); + configFilePathUsed = configFilePath; + } + + logger.info("Using config file : " + configFilePathUsed); + + try { + Optional configOptional = ConfigUtil.readConfig(configFilePathUsed); + initializedConfig = configOptional.orElse(new Config()); + } catch (DataConversionException e) { + logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. " + + "Using default config properties"); + initializedConfig = new Config(); + } + + //Update config file in case it was missing to begin with or there are new/unused fields + try { + ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + return initializedConfig; + } + + @Override + public void start(Stage primaryStage) { + logger.info("Starting Contact tracking app " + ContactTracingMainApp.VERSION); + ui.start(primaryStage); + } + + @Override + public void stop() { + logger.info("============================ [ Stopping Contact tracking app ] ============================="); + } +} diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index 052a5068631..5e606b47580 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -20,6 +20,6 @@ */ public class Main { public static void main(String[] args) { - Application.launch(MainApp.class, args); + Application.launch(ContactTracingMainApp.class, args); } } diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java deleted file mode 100644 index e5cfb161b73..00000000000 --- a/src/main/java/seedu/address/MainApp.java +++ /dev/null @@ -1,183 +0,0 @@ -package seedu.address; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import javafx.application.Application; -import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -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.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -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.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; - -/** - * Runs the application. - */ -public class MainApp extends Application { - - public static final Version VERSION = new Version(0, 6, 0, true); - - private static final Logger logger = LogsCenter.getLogger(MainApp.class); - - protected Ui ui; - protected Logic logic; - protected Storage storage; - protected Model model; - protected Config config; - - @Override - public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); - super.init(); - - AppParameters appParameters = AppParameters.parse(getParameters()); - config = initConfig(appParameters.getConfigPath()); - - UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); - UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); - - initLogging(config); - - model = initModelManager(storage, userPrefs); - - logic = new LogicManager(model, storage); - - ui = new UiManager(logic); - } - - /** - * 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. - */ - private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; - try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); - } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); - } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); - } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); - } - - return new ModelManager(initialData, userPrefs); - } - - private void initLogging(Config config) { - LogsCenter.init(config); - } - - /** - * Returns a {@code Config} using the file at {@code configFilePath}.
- * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead - * if {@code configFilePath} is null. - */ - protected Config initConfig(Path configFilePath) { - Config initializedConfig; - Path configFilePathUsed; - - configFilePathUsed = Config.DEFAULT_CONFIG_FILE; - - if (configFilePath != null) { - logger.info("Custom Config file specified " + configFilePath); - configFilePathUsed = configFilePath; - } - - logger.info("Using config file : " + configFilePathUsed); - - try { - Optional configOptional = ConfigUtil.readConfig(configFilePathUsed); - initializedConfig = configOptional.orElse(new Config()); - } catch (DataConversionException e) { - logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. " - + "Using default config properties"); - initializedConfig = new Config(); - } - - //Update config file in case it was missing to begin with or there are new/unused fields - try { - ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); - } catch (IOException e) { - logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); - } - return initializedConfig; - } - - /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, - * or a new {@code UserPrefs} with default configuration if errors occur when - * reading from the file. - */ - protected UserPrefs initPrefs(UserPrefsStorage storage) { - Path prefsFilePath = storage.getUserPrefsFilePath(); - logger.info("Using prefs file : " + prefsFilePath); - - UserPrefs initializedPrefs; - try { - Optional prefsOptional = storage.readUserPrefs(); - initializedPrefs = prefsOptional.orElse(new UserPrefs()); - } catch (DataConversionException e) { - logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. " - + "Using default user prefs"); - initializedPrefs = new UserPrefs(); - } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initializedPrefs = new UserPrefs(); - } - - //Update prefs file in case it was missing to begin with or there are new/unused fields - try { - storage.saveUserPrefs(initializedPrefs); - } catch (IOException e) { - logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); - } - - return initializedPrefs; - } - - @Override - public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); - ui.start(primaryStage); - } - - @Override - public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); - try { - storage.saveUserPrefs(model.getUserPrefs()); - } catch (IOException e) { - logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); - } - } -} diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/address/commons/util/AppUtil.java index da90201dfd6..0f5e5318ae8 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/address/commons/util/AppUtil.java @@ -1,9 +1,8 @@ package seedu.address.commons.util; import static java.util.Objects.requireNonNull; - import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.address.ContactTracingMainApp; /** * A container for App specific utility functions @@ -12,7 +11,7 @@ public class AppUtil { public static Image getImage(String imagePath) { requireNonNull(imagePath); - return new Image(MainApp.class.getResourceAsStream(imagePath)); + return new Image(ContactTracingMainApp.class.getResourceAsStream(imagePath)); } /** diff --git a/src/main/java/seedu/address/logic/AppLogic.java b/src/main/java/seedu/address/logic/AppLogic.java new file mode 100644 index 00000000000..485d389c1dc --- /dev/null +++ b/src/main/java/seedu/address/logic/AppLogic.java @@ -0,0 +1,11 @@ +package seedu.address.logic; + +import seedu.address.logic.messages.AppMessage; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Contact tracing application logic rendering + */ +public interface AppLogic { + public AppMessage execute(String command) throws ParseException; +} diff --git a/src/main/java/seedu/address/logic/AppLogicManager.java b/src/main/java/seedu/address/logic/AppLogicManager.java new file mode 100644 index 00000000000..4928f31c09e --- /dev/null +++ b/src/main/java/seedu/address/logic/AppLogicManager.java @@ -0,0 +1,33 @@ +package seedu.address.logic; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AppCommand; +import seedu.address.logic.messages.AppMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; +import seedu.address.logic.parser.CommandRouter; +import seedu.address.storage.DaoRouter; +import java.util.logging.Logger; + +public class AppLogicManager implements AppLogic { + private final Logger logger = LogsCenter.getLogger(AppLogicManager.class); + + /** + * Asserts that the user must always declare type M which is subclass of {@code AppStorage} + * + * @throws Exception + */ + public AppLogicManager() throws Exception { + } + + @Override + public AppMessage execute(String command) throws ParseException { + logger.info("----------------[USER COMMAND][" + command + "]"); + + AppCommand appCommand = new CommandRouter().parse(command); + AppStorage dao = DaoRouter.getInstance().getStorage(appCommand); + AppMessage result = appCommand.execute(dao); + return result; + } +} + diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 92cd8fa605a..00000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,50 +0,0 @@ -package seedu.address.logic; - -import java.nio.file.Path; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -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; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - - /** - * Returns the AddressBook. - * - * @see seedu.address.model.Model#getAddressBook() - */ - ReadOnlyAddressBook getAddressBook(); - - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Set the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index d47ce874b1a..00000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.logic; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; - -/** - * The main LogicManager of the app. - */ -public class LogicManager implements Logic { - public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Storage storage; - private final AddressBookParser addressBookParser; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.storage = storage; - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - - CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); - - try { - storage.saveAddressBook(model.getAddressBook()); - } catch (IOException ioe) { - throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); - } - - return commandResult; - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); - } - - @Override - public GuiSettings getGuiSettings() { - return model.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - model.setGuiSettings(guiSettings); - } -} diff --git a/src/main/java/seedu/address/logic/aggregators/Aggregators.java b/src/main/java/seedu/address/logic/aggregators/Aggregators.java new file mode 100644 index 00000000000..c2ea9557a51 --- /dev/null +++ b/src/main/java/seedu/address/logic/aggregators/Aggregators.java @@ -0,0 +1,7 @@ +package seedu.address.logic.aggregators; + +import java.util.ArrayList; + +public interface Aggregators { + public ArrayList collect(ArrayList initialList); +} diff --git a/src/main/java/seedu/address/logic/aggregators/GroupByIDPairsAggregators.java b/src/main/java/seedu/address/logic/aggregators/GroupByIDPairsAggregators.java new file mode 100644 index 00000000000..dd1fc9f74ea --- /dev/null +++ b/src/main/java/seedu/address/logic/aggregators/GroupByIDPairsAggregators.java @@ -0,0 +1,27 @@ +package seedu.address.logic.aggregators; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import seedu.address.model.bluetooth.BluetoothPings; +import seedu.address.model.bluetooth.BluetoothPingsSummary; + +public class GroupByIDPairsAggregators implements Aggregators { + @Override + public ArrayList collect(ArrayList initialList) { + ArrayList pingsSummary = new ArrayList(); + + Map, Long> collection = initialList.stream() + .collect(Collectors.groupingBy( + BluetoothPings::getUserIDs, + Collectors.counting() + )); + collection.forEach( + (userIdList, threshold) -> { + pingsSummary.add(new BluetoothPingsSummary(userIdList, threshold)); + }); + + return pingsSummary; + } +} 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/AppCommand.java b/src/main/java/seedu/address/logic/commands/AppCommand.java new file mode 100644 index 00000000000..e5a830b9360 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AppCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.messages.AppMessage; +import seedu.address.logic.messages.BluetoothPingsMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; + +public interface AppCommand { + + /** + * To validate that the arguments passed in are sound + * + * @param arguments String arguments + * @return AppCommand + * @throws ParseException Raise if arguments do not meet the condition + */ + public AppCommand validate(String arguments) throws ParseException; + + /** + * Command interface + * + * @param dao Data access object + * @return CommandResult + */ + public AppMessage execute(AppStorage dao); +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java deleted file mode 100644 index 64f18992160..00000000000 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ /dev/null @@ -1,20 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Represents a command with hidden internal logic and the ability to be executed. - */ -public abstract class Command { - - /** - * Executes the command and returns the result message. - * - * @param model {@code Model} which the command should operate on. - * @return feedback message of the operation result for display - * @throws CommandException If an error occurs during command execution. - */ - public abstract CommandResult execute(Model model) throws CommandException; - -} 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 deleted file mode 100644 index 3dd85a8ba90..00000000000 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.Model; - -/** - * Terminates the program. - */ -public class ExitCommand extends Command { - - public static final String COMMAND_WORD = "exit"; - - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; - - @Override - public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/FilterDangerCommand.java b/src/main/java/seedu/address/logic/commands/FilterDangerCommand.java new file mode 100644 index 00000000000..67163f34a4b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterDangerCommand.java @@ -0,0 +1,51 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.aggregators.Aggregators; +import seedu.address.logic.aggregators.GroupByIDPairsAggregators; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.DangerConditions; +import seedu.address.logic.messages.BluetoothSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.bluetooth.BluetoothPings; +import seedu.address.model.bluetooth.BluetoothPingsSummary; +import seedu.address.storage.AppStorage; +import seedu.address.storage.BluetoothPingStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FilterDangerCommand implements AppCommand, BluetoothPingStorageAccess { + private int THRESHOLD; + + public static final String COMMAND_WORD = "danger"; + private static final Pattern COMMAND_FORMAT = Pattern.compile("(?\\d+)"); + private final Logger logger = LogsCenter.getLogger(FilterDangerCommand.class); + + @Override + public AppCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + this.THRESHOLD = Integer.parseInt(matcher.group("threshold")); + return this; + } + + @Override + public BluetoothSummaryMessage execute(AppStorage dao) { + Conditions cond = new DangerConditions(this.THRESHOLD); + Aggregators agg = new GroupByIDPairsAggregators(); + ArrayList resp = dao.search(cond, agg); + + BluetoothSummaryMessage result = new BluetoothSummaryMessage("Identified dangerous users", false); + result.setToDisplayList(resp); + return result; + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterPersonCommand.java b/src/main/java/seedu/address/logic/commands/FilterPersonCommand.java new file mode 100644 index 00000000000..d1056412a1d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterPersonCommand.java @@ -0,0 +1,45 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.PersonIDConditions; +import seedu.address.logic.messages.UserSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; +import seedu.address.storage.UserStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FilterPersonCommand implements AppCommand, UserStorageAccess { + private static int PERSON_ID; + + public static final String COMMAND_WORD = "person_by"; + private static final Pattern COMMAND_FORMAT = Pattern.compile("(?\\d+)"); + private final Logger logger = LogsCenter.getLogger(FilterTimestampCommand.class); + + @Override + public FilterPersonCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + this.PERSON_ID = Integer.parseInt(matcher.group("personId")); + return this; + } + + @Override + public UserSummaryMessage execute(AppStorage dao) { + Conditions cond = new PersonIDConditions(this.PERSON_ID); + ArrayList resp = dao.search(cond); + UserSummaryMessage result = new UserSummaryMessage("Identifying by user id", false); + result.setToDisplayList(resp); + return result; + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterTimestampCommand.java b/src/main/java/seedu/address/logic/commands/FilterTimestampCommand.java new file mode 100644 index 00000000000..ab6cdbdef8f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterTimestampCommand.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.TimestampConditions; +import seedu.address.logic.messages.BluetoothPingsMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; +import seedu.address.storage.BluetoothPingStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FilterTimestampCommand implements AppCommand, BluetoothPingStorageAccess { + private static Long START_TIME; + private static Long END_TIME; + + public static final String COMMAND_WORD = "from"; + private static final Pattern COMMAND_FORMAT = Pattern.compile("(?\\d+) to (?\\d+)"); + private final Logger logger = LogsCenter.getLogger(FilterTimestampCommand.class); + + @Override + public FilterTimestampCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + this.START_TIME = Long.parseLong(matcher.group("start")); + this.END_TIME = Long.parseLong(matcher.group("end")); + return this; + } + + @Override + public BluetoothPingsMessage execute(AppStorage dao) { + Conditions cond = new TimestampConditions(this.START_TIME, this.END_TIME); + ArrayList resp = dao.search(cond); + BluetoothPingsMessage result = new BluetoothPingsMessage("Filtered by timestamp", false); + result.setToDisplayList(resp); + return result; + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterUserIDCommand.java b/src/main/java/seedu/address/logic/commands/FilterUserIDCommand.java new file mode 100644 index 00000000000..a125f08e00a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterUserIDCommand.java @@ -0,0 +1,45 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.UserIDConditions; +import seedu.address.logic.messages.BluetoothPingsMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; +import seedu.address.storage.BluetoothPingStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FilterUserIDCommand implements AppCommand, BluetoothPingStorageAccess { + private static int USER_ID; + public static final String COMMAND_WORD = "id"; + private static final Pattern COMMAND_FORMAT = Pattern.compile("is (?\\d+)"); + private final Logger logger = LogsCenter.getLogger(FilterUserIDCommand.class); + + @Override + public FilterUserIDCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + this.USER_ID = Integer.parseInt(matcher.group("userid")); + return this; + } + + @Override + public BluetoothPingsMessage execute(AppStorage dao) { + Conditions cond = new UserIDConditions(USER_ID); + ArrayList resp = dao.search(cond); + BluetoothPingsMessage result = new BluetoothPingsMessage("Identifying recods with User ID.", false); + result.setToDisplayList(resp); + return result; + } +} + diff --git a/src/main/java/seedu/address/logic/commands/FilterUserPairsCommand.java b/src/main/java/seedu/address/logic/commands/FilterUserPairsCommand.java new file mode 100644 index 00000000000..c45a4914534 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterUserPairsCommand.java @@ -0,0 +1,46 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.UserPairsConditions; +import seedu.address.logic.messages.BluetoothPingsMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; +import seedu.address.storage.BluetoothPingStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FilterUserPairsCommand implements AppCommand, BluetoothPingStorageAccess { + private int USER_A; + private int USER_B; + public static final String COMMAND_WORD = "pairs"; + private static final Pattern COMMAND_FORMAT = Pattern.compile("(?\\d+) and (?\\d+)"); + private final Logger logger = LogsCenter.getLogger(FilterUserPairsCommand.class); + + @Override + public FilterUserPairsCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + this.USER_A = Integer.parseInt(matcher.group("userA")); + this.USER_B = Integer.parseInt(matcher.group("userB")); + return this; + } + + @Override + public BluetoothPingsMessage execute(AppStorage dao) { + Conditions cond = new UserPairsConditions(USER_A, USER_B); + ArrayList resp = dao.search(cond); + BluetoothPingsMessage result = new BluetoothPingsMessage("Identifying recods with User Pairs.", false); + result.setToDisplayList(resp); + return result; + } +} 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/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..bfd11481765 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -1,21 +1,48 @@ package seedu.address.logic.commands; -import seedu.address.model.Model; +import seedu.address.logic.messages.HelpCommandMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.bluetooth.CommandList; +import seedu.address.storage.AppStorage; +import seedu.address.storage.BluetoothPingStorageAccess; -/** - * Format full help instructions for every command for display. - */ -public class HelpCommand extends Command { +import java.util.ArrayList; +import java.util.List; +public class HelpCommand implements AppCommand, BluetoothPingStorageAccess { public static final String COMMAND_WORD = "help"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" - + "Example: " + COMMAND_WORD; + public HelpCommand validate(String arguments) throws ParseException { + assert (arguments.trim().equals("")); + return this; + } + + public HelpCommandMessage execute(final AppStorage dao) + { + HelpCommandMessage result = new HelpCommandMessage("Help Guideline for Users"); + ArrayList allCommand = new ArrayList(); + + // Query over bluetooth ping records + allCommand.add(new CommandList("Timestamp filtering", "from to ")); + allCommand.add(new CommandList("UserID filtering", "id is ")); + allCommand.add(new CommandList("User pair filtering", "pairs and ")); + allCommand.add(new CommandList("Identifying dangerous users", "danger ")); + + // Query over user user information + allCommand.add(new CommandList("Show all user details", "person")); + allCommand.add(new CommandList("Filter user by ID", "person_by ")); + allCommand.add(new CommandList("New user entry", "person_add /name /mobile /nric /age ")); + allCommand.add(new CommandList("Delete present user ID", "person_delete /userid ")); - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; + // Report generation + allCommand.add(new CommandList("Report all Users with userID", "report all")); + allCommand.add(new CommandList("Report danger users", "report danger ")); + allCommand.add(new CommandList("Report cases in time range", "report time from to ")); + allCommand.add(new CommandList("Report users with a given userID", "report id is ")); + allCommand.add(new CommandList("Report all user information details", "report_person")); + allCommand.add(new CommandList("Report person information with given ID", "report_person ")); - @Override - public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + result.setToDisplayList(allCommand); + return result; } } diff --git a/src/main/java/seedu/address/logic/commands/ListAllPersonCommand.java b/src/main/java/seedu/address/logic/commands/ListAllPersonCommand.java new file mode 100644 index 00000000000..1095f31bace --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListAllPersonCommand.java @@ -0,0 +1,33 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.LiterallyNoConditions; +import seedu.address.logic.messages.UserSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.storage.AppStorage; +import seedu.address.storage.UserStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class ListAllPersonCommand implements AppCommand, UserStorageAccess { + public static final String COMMAND_WORD = "person"; + + private final Logger logger = LogsCenter.getLogger(FilterTimestampCommand.class); + + @Override + public AppCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + return this; + } + + @Override + public UserSummaryMessage execute(AppStorage dao) { + Conditions cond = new LiterallyNoConditions(); + ArrayList resp = dao.search(cond); + UserSummaryMessage result = new UserSummaryMessage("Identifying all user records.", false); + result.setToDisplayList(resp); + return result; + } +} 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/PersonAddCommand.java b/src/main/java/seedu/address/logic/commands/PersonAddCommand.java new file mode 100644 index 00000000000..ff4c1edd87b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PersonAddCommand.java @@ -0,0 +1,63 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.LiterallyNoConditions; +import seedu.address.logic.messages.UserSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.bluetooth.Person; +import seedu.address.storage.AppStorage; +import seedu.address.storage.UserStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PersonAddCommand implements AppCommand, UserStorageAccess { + public static final String COMMAND_WORD = "person_add"; + + private static final Pattern COMMAND_FORMAT = Pattern.compile( "/name (?\\w.*) " + + "/mobile (?\\d+) " + + "/nric (?\\S+) " + + "/age (?\\d+)"); + + private Person person; + private final Logger logger = LogsCenter.getLogger(FilterTimestampCommand.class); + + @Override + public AppCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + String name = matcher.group("name"); + String mobile = matcher.group("mobile"); + String nric = matcher.group("nric"); + int age = Integer.parseInt(matcher.group("age")); + this.person = new Person(name).withNric(nric).withMobile(mobile).withAge(age); + return this; + } + + @Override + public UserSummaryMessage execute(AppStorage dao) { + String message; + try { + dao.create(this.person); + message = "User addition successful"; + } + catch (Exception ex) { + message = "User addition failed with " + ex.getStackTrace(); + } + + Conditions cond = new LiterallyNoConditions(); + ArrayList resp = dao.search(cond); + UserSummaryMessage result = new UserSummaryMessage(message, false); + result.setToDisplayList(resp); + return result; + } +} diff --git a/src/main/java/seedu/address/logic/commands/PersonDeleteCommand.java b/src/main/java/seedu/address/logic/commands/PersonDeleteCommand.java new file mode 100644 index 00000000000..c7d6b1ed1cd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PersonDeleteCommand.java @@ -0,0 +1,52 @@ +package seedu.address.logic.commands; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.LiterallyNoConditions; +import seedu.address.logic.conditions.PersonIDConditions; +import seedu.address.logic.messages.UserSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.bluetooth.Person; +import seedu.address.storage.AppStorage; +import seedu.address.storage.UserStorageAccess; + +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PersonDeleteCommand implements AppCommand, UserStorageAccess { + public static final String COMMAND_WORD = "person_delete"; + + private static final Pattern COMMAND_FORMAT = Pattern.compile("/userid (?\\d+)"); + + private int USERID; + private final Logger logger = LogsCenter.getLogger(FilterTimestampCommand.class); + + @Override + public AppCommand validate(String arguments) throws ParseException { + logger.info(String.format("Validating: %s", arguments)); + final Matcher matcher = COMMAND_FORMAT.matcher(arguments.trim()); + + if (!matcher.matches()) { + String error = String.format("Command %s invalid", arguments); + throw new ParseException(error); + } + + this.USERID = Integer.parseInt(matcher.group("userid")); + return this; + } + + @Override + public UserSummaryMessage execute(AppStorage dao) { + Conditions deleteCondition = new PersonIDConditions(this.USERID); + ArrayList candidateToDelete = dao.search(deleteCondition); + dao.delete(candidateToDelete); + UserSummaryMessage result = new UserSummaryMessage("User deleted", false); + + Conditions cond = new LiterallyNoConditions(); + ArrayList resp = dao.search(cond); + result.setToDisplayList(resp); + return result; + } +} diff --git a/src/main/java/seedu/address/logic/commands/PersonReportGenerationCommand.java b/src/main/java/seedu/address/logic/commands/PersonReportGenerationCommand.java new file mode 100644 index 00000000000..44ca12818a1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PersonReportGenerationCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.LiterallyNoConditions; +import seedu.address.logic.conditions.PersonIDConditions; +import seedu.address.logic.conditions.UserIDConditions; +import seedu.address.logic.messages.AppMessage; +import seedu.address.logic.messages.BluetoothPingsMessage; +import seedu.address.logic.messages.BluetoothSummaryMessage; +import seedu.address.logic.messages.UserSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.report.PersonReportGenerator; +import seedu.address.storage.AppStorage; +import seedu.address.storage.UserStorageAccess; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PersonReportGenerationCommand implements AppCommand, UserStorageAccess { + public static final String COMMAND_WORD = "report_person"; + public static String ConType = "None"; + public static int personID; + + @Override + public PersonReportGenerationCommand validate(String arguments) throws ParseException { + + if (arguments.trim().equals("")) { + this.ConType = "person"; + } else { + Pattern person = Pattern.compile("(?\\d+)"); + Matcher m = person.matcher(arguments.trim()); + + if (m.matches()) { + this.ConType = "person_by"; + this.personID = Integer.parseInt(m.group("personId")); + } else { + String error = String.format("report_person Command %s invalid", arguments); + throw new ParseException(error); + } + } + return this; + } + + + @Override + public AppMessage execute(AppStorage dao) { + ArrayList resp = new ArrayList(); + switch (this.ConType) { + case "person": + System.out.println("Search all people"); + Conditions cond = new LiterallyNoConditions(); + resp = dao.search(cond); + break; + + case "person_by": + System.out.println("Search person by ID"); + cond = new PersonIDConditions(this.personID); + resp = dao.search(cond); + break; + + default: + System.out.println("Wrong Command"); + BluetoothPingsMessage result = new BluetoothPingsMessage("Wrong Command.", false); + return result; + + } + + if (resp.size() > 0) + { + PersonReportGenerator generator = new PersonReportGenerator(); + try { + generator.GenerateReport(resp); + } catch (Exception e) { + System.out.println("Write failed"); + UserSummaryMessage result = new UserSummaryMessage("Write failed.", false); + result.setToDisplayList(resp); + return result; + } + UserSummaryMessage result = new UserSummaryMessage("Saved report file to result folder.", false); + result.setToDisplayList(resp); + return result; + + } + else + { + UserSummaryMessage result = new UserSummaryMessage("No instance in database.", false); + result.setToDisplayList(resp); + return result; + } + } + } diff --git a/src/main/java/seedu/address/logic/commands/ReportGenerationCommand.java b/src/main/java/seedu/address/logic/commands/ReportGenerationCommand.java new file mode 100644 index 00000000000..dc4cafd774c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReportGenerationCommand.java @@ -0,0 +1,193 @@ +package seedu.address.logic.commands; + +import seedu.address.logic.aggregators.Aggregators; +import seedu.address.logic.aggregators.GroupByIDPairsAggregators; +import seedu.address.logic.conditions.*; +import seedu.address.logic.messages.AppMessage; +import seedu.address.logic.messages.BluetoothPingsMessage; +import seedu.address.logic.messages.BluetoothSummaryMessage; +import seedu.address.logic.messages.UserSummaryMessage; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.bluetooth.BluetoothPings; +import seedu.address.model.bluetooth.BluetoothPingsSummary; +import seedu.address.model.bluetooth.Person; +import seedu.address.report.DangerReportGenerator; +import seedu.address.report.ReportGenerator; +import seedu.address.storage.AppStorage; +import seedu.address.storage.BluetoothPingStorageAccess; +import seedu.address.storage.UserStorageAccess; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ReportGenerationCommand implements AppCommand, BluetoothPingStorageAccess { + public static final String COMMAND_WORD = "report"; + public static String ConType = "None"; + public static int TargetID; + public static long StartTime; + public static long EndTime; + public static int ID1; + public static int ID2; + public static int Threshold; + public static final String CON_TYPE_ERROR = "Condition type not well defined"; + + + @Override + public ReportGenerationCommand validate(String arguments) throws ParseException { + + if (arguments.trim().equals("all")) + { + ConType = "None"; + } + else + { + Pattern ID = Pattern.compile("id is (?\\d+)"); + Matcher m = ID.matcher(arguments.trim()); + if (m.matches()) + { + this.TargetID = Integer.parseInt(m.group("userid")); + this.ConType = "ID"; + } + else + { + Pattern Time = Pattern.compile("time from (?\\d+) to (?\\d+)"); + m = Time.matcher(arguments.trim()); + if (m.matches()) + { + this.StartTime = Long.parseLong(m.group("start")); + this.EndTime = Long.parseLong(m.group("end")); + this.ConType = "Time"; + } + + else + { + Pattern pair = Pattern.compile("pairs (?\\d+) and (?\\d+)"); + m = pair.matcher(arguments.trim()); + + if (m.matches()) + { + this.ID1 = Integer.parseInt(m.group("userA")); + this.ID2 = Integer.parseInt(m.group("userB")); + this.ConType = "pairs"; + } + else + { + Pattern danger = Pattern.compile("danger (?\\d+)"); + m = danger.matcher(arguments.trim()); + + if (m.matches()) + { + this.Threshold = Integer.parseInt(m.group("threshold")); + this.ConType = "danger"; + } + else + { + String error = String.format("Report Command %s invalid", arguments); + throw new ParseException(error); + } + } + } + } + } + return this; + } + + @Override + public AppMessage execute(AppStorage dao) { + ArrayList resp = new ArrayList(); + + int DangerFlag = 0; + switch (this.ConType) + { + case "ID": + System.out.println("Search by ID"); + Conditions condID = new UserIDConditions(this.TargetID); + resp = dao.search(condID); + + break; + + case "Time": + System.out.println("Search by time"); + Conditions condTime = new TimestampConditions(this.StartTime, this.EndTime); + resp = dao.search(condTime); + + break; + + case "pairs": + System.out.println("Search by pairs"); + Conditions condPairs = new UserPairsConditions(this.ID1, this.ID2); + resp = dao.search(condPairs); + break; + + case "danger": + System.out.println("search by danger"); + DangerFlag = 1; + Conditions condDanger = new DangerConditions(this.Threshold); + Aggregators agg = new GroupByIDPairsAggregators(); + resp = dao.search(condDanger, agg); + break; + + case "None": + System.out.println("Search all cases"); + resp = dao.search(); + break; + + default: + System.out.println("error command"); + BluetoothPingsMessage result = new BluetoothPingsMessage("Wrong Command.", false); + return result; + + } + + + if (DangerFlag == 1) + { + if (resp.size() > 0) { + DangerReportGenerator generator = new DangerReportGenerator(); + try { + generator.GenerateReport(resp); + } catch (Exception e) { + System.out.println("Write failed"); + BluetoothSummaryMessage result = new BluetoothSummaryMessage("Write failed.", false); + result.setToDisplayList(resp); + return result; + } + BluetoothSummaryMessage result = new BluetoothSummaryMessage("Saved report file to result folder.", false); + result.setToDisplayList(resp); + return result; + } + else + { + BluetoothSummaryMessage result = new BluetoothSummaryMessage("No instance in database.", false); + result.setToDisplayList(resp); + return result; + } + } + else + { + if (resp.size() > 0) + { + ReportGenerator generator = new ReportGenerator(); + try { + generator.GenerateReport(resp); + }catch (Exception e) + { + BluetoothPingsMessage result = new BluetoothPingsMessage("Write failed.", false); + result.setToDisplayList(resp); + return result; + } + BluetoothPingsMessage result = new BluetoothPingsMessage("Saved report file to result folder.", false); + result.setToDisplayList(resp); + return result; + } + else + { + BluetoothPingsMessage result = new BluetoothPingsMessage("No instance in database.", false); + result.setToDisplayList(resp); + return result; + } + } + } +} diff --git a/src/main/java/seedu/address/logic/conditions/Conditions.java b/src/main/java/seedu/address/logic/conditions/Conditions.java new file mode 100644 index 00000000000..e33d7c8d99c --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/Conditions.java @@ -0,0 +1,10 @@ +package seedu.address.logic.conditions; + +public interface Conditions { + + /** + * Given a class, enforce logic to see if certain properties of the class meets the condition + * This may be used in conjunction with filtering logic + */ + public Boolean satisfies(T objToTest); +} diff --git a/src/main/java/seedu/address/logic/conditions/DangerConditions.java b/src/main/java/seedu/address/logic/conditions/DangerConditions.java new file mode 100644 index 00000000000..2e9e9c63883 --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/DangerConditions.java @@ -0,0 +1,14 @@ +package seedu.address.logic.conditions; + +import seedu.address.model.bluetooth.BluetoothPingsSummary; + +public class DangerConditions implements Conditions { + private int THRESHOLD; + + public DangerConditions(int threshold) {this.THRESHOLD = threshold;} + + @Override + public Boolean satisfies(BluetoothPingsSummary objToTest) { + return objToTest.getCounts() >= this.THRESHOLD; + } +} diff --git a/src/main/java/seedu/address/logic/conditions/LiterallyNoConditions.java b/src/main/java/seedu/address/logic/conditions/LiterallyNoConditions.java new file mode 100644 index 00000000000..b6b65cac402 --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/LiterallyNoConditions.java @@ -0,0 +1,20 @@ +package seedu.address.logic.conditions; + +/** + * Literally no conditional filtering is being done here + * + * @param + */ +public class LiterallyNoConditions implements Conditions { + + /** + * Like I said. Literally no conditional filtering is being done here. + * + * @param objToTest + * @return Always True + */ + @Override + public Boolean satisfies(T objToTest) { + return true; + } +} diff --git a/src/main/java/seedu/address/logic/conditions/PersonIDConditions.java b/src/main/java/seedu/address/logic/conditions/PersonIDConditions.java new file mode 100644 index 00000000000..4a5ff0c235d --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/PersonIDConditions.java @@ -0,0 +1,16 @@ +package seedu.address.logic.conditions; + +import seedu.address.model.bluetooth.Person; + +public class PersonIDConditions implements Conditions { + private int personId; + + public PersonIDConditions(int personId) { + this.personId = personId; + } + + @Override + public Boolean satisfies(Person objToTest) { + return this.personId == objToTest.getUserId(); + } +} diff --git a/src/main/java/seedu/address/logic/conditions/TimestampConditions.java b/src/main/java/seedu/address/logic/conditions/TimestampConditions.java new file mode 100644 index 00000000000..044a7c85ae2 --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/TimestampConditions.java @@ -0,0 +1,19 @@ +package seedu.address.logic.conditions; + +import seedu.address.model.bluetooth.BluetoothPings; + +public class TimestampConditions implements Conditions { + private Long start; + private Long end; + + public TimestampConditions(Long start, Long end) { + this.start = start; + this.end = end; + } + + @Override + public Boolean satisfies(BluetoothPings objToTest) { + Long timestamp = objToTest.getEpochTs(); + return this.start <= timestamp && timestamp <= this.end; + } +} diff --git a/src/main/java/seedu/address/logic/conditions/UserIDConditions.java b/src/main/java/seedu/address/logic/conditions/UserIDConditions.java new file mode 100644 index 00000000000..3ca011dc1e3 --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/UserIDConditions.java @@ -0,0 +1,16 @@ +package seedu.address.logic.conditions; + +import seedu.address.model.bluetooth.BluetoothPings; + +public class UserIDConditions implements Conditions { + private int userid; + + public UserIDConditions(int userid) { + this.userid = userid; + } + + @Override + public Boolean satisfies(BluetoothPings objToTest) { + return objToTest.getUserIDs().contains(this.userid); + } +} diff --git a/src/main/java/seedu/address/logic/conditions/UserPairsConditions.java b/src/main/java/seedu/address/logic/conditions/UserPairsConditions.java new file mode 100644 index 00000000000..2cf6e9c3d60 --- /dev/null +++ b/src/main/java/seedu/address/logic/conditions/UserPairsConditions.java @@ -0,0 +1,18 @@ +package seedu.address.logic.conditions; + +import seedu.address.model.bluetooth.BluetoothPings; + +public class UserPairsConditions implements Conditions { + private int USER_A; + private int USER_B; + + public UserPairsConditions(int USER_A, int USER_B) { + this.USER_A = USER_A; + this.USER_B = USER_B; + } + + @Override + public Boolean satisfies(BluetoothPings objToTest) { + return objToTest.getUserIDs().contains(this.USER_A) && objToTest.getUserIDs().contains(this.USER_B); + } +} diff --git a/src/main/java/seedu/address/logic/messages/AppMessage.java b/src/main/java/seedu/address/logic/messages/AppMessage.java new file mode 100644 index 00000000000..b0a33d683a1 --- /dev/null +++ b/src/main/java/seedu/address/logic/messages/AppMessage.java @@ -0,0 +1,12 @@ +package seedu.address.logic.messages; + +import javafx.collections.ObservableList; + +public abstract class AppMessage { + public String IDENTIFIER; + public abstract Boolean getRenderFlag(); + public abstract String getIdentifier(); + public abstract ObservableList getDisplayAsObservable(); + public abstract String getFeedbackToUser(); + public abstract boolean isExit(); +} diff --git a/src/main/java/seedu/address/logic/messages/BluetoothPingsMessage.java b/src/main/java/seedu/address/logic/messages/BluetoothPingsMessage.java new file mode 100644 index 00000000000..64d1fd62aa0 --- /dev/null +++ b/src/main/java/seedu/address/logic/messages/BluetoothPingsMessage.java @@ -0,0 +1,82 @@ +package seedu.address.logic.messages; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.bluetooth.BluetoothPings; + +import java.util.ArrayList; +import java.util.Objects; +import static java.util.Objects.requireNonNull; + +public class BluetoothPingsMessage extends AppMessage { + private final String feedbackToUser; + private ArrayList toDisplayList; + private Boolean RENDER_FLAG; + public final String IDENTIFIER = "BluetoothPings"; + + /** The application should exit. */ + private final boolean exit; + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + public BluetoothPingsMessage(String feedbackToUser, boolean exit) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.exit = exit; + this.RENDER_FLAG = false; + } + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * and other fields set to their default value. + */ + public BluetoothPingsMessage(String feedbackToUser) { + this(feedbackToUser, false); + } + + public String getFeedbackToUser() { + return feedbackToUser; + } + + public Boolean getRenderFlag() { return this.RENDER_FLAG; } + + public String getIdentifier() { return this.IDENTIFIER; } + + public ObservableList getDisplayAsObservable() { return FXCollections.observableArrayList(this.toDisplayList); } + + /** + * A display list contains the necessary items needed to be rendered on the screen + * If this function is called, we set the RENDER_FLAG to true as signal that there is something to be displayed + * + * @param displayList List to be rendered on the screen + */ + public void setToDisplayList(ArrayList displayList) { + this.toDisplayList = displayList; + this.RENDER_FLAG = true; + } + + public boolean isExit() { + return exit; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BluetoothPingsMessage)) { + return false; + } + + BluetoothPingsMessage otherCommandResult = (BluetoothPingsMessage) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) + && exit == otherCommandResult.exit; + } + + @Override + public int hashCode() { + return Objects.hash(feedbackToUser, exit); + } +} diff --git a/src/main/java/seedu/address/logic/messages/BluetoothSummaryMessage.java b/src/main/java/seedu/address/logic/messages/BluetoothSummaryMessage.java new file mode 100644 index 00000000000..23d6ccf7204 --- /dev/null +++ b/src/main/java/seedu/address/logic/messages/BluetoothSummaryMessage.java @@ -0,0 +1,84 @@ +package seedu.address.logic.messages; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.bluetooth.BluetoothPingsSummary; + +import java.util.ArrayList; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +public class BluetoothSummaryMessage extends AppMessage { + private final String feedbackToUser; + private ArrayList toDisplayList; + private Boolean RENDER_FLAG; + public final String IDENTIFIER = "BluetoothPingsSummary"; + + /** The application should exit. */ + private final boolean exit; + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + public BluetoothSummaryMessage(String feedbackToUser, boolean exit) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.exit = exit; + this.RENDER_FLAG = false; + } + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * and other fields set to their default value. + */ + public BluetoothSummaryMessage(String feedbackToUser) { + this(feedbackToUser, false); + } + + public String getFeedbackToUser() { + return feedbackToUser; + } + + public Boolean getRenderFlag() { return this.RENDER_FLAG; } + + public String getIdentifier() { return this.IDENTIFIER; } + + public ObservableList getDisplayAsObservable() { return FXCollections.observableArrayList(this.toDisplayList); } + + /** + * A display list contains the necessary items needed to be rendered on the screen + * If this function is called, we set the RENDER_FLAG to true as signal that there is something to be displayed + * + * @param displayList List to be rendered on the screen + */ + public void setToDisplayList(ArrayList displayList) { + this.toDisplayList = displayList; + this.RENDER_FLAG = true; + } + + public boolean isExit() { + return exit; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BluetoothPingsMessage)) { + return false; + } + + BluetoothSummaryMessage otherCommandResult = (BluetoothSummaryMessage) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) + && exit == otherCommandResult.exit; + } + + @Override + public int hashCode() { + return Objects.hash(feedbackToUser, exit); + } + +} diff --git a/src/main/java/seedu/address/logic/messages/HelpCommandMessage.java b/src/main/java/seedu/address/logic/messages/HelpCommandMessage.java new file mode 100644 index 00000000000..6efdc4de8fe --- /dev/null +++ b/src/main/java/seedu/address/logic/messages/HelpCommandMessage.java @@ -0,0 +1,83 @@ +package seedu.address.logic.messages; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.bluetooth.CommandList; + +import java.util.ArrayList; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +public class HelpCommandMessage extends AppMessage { + private final String feedbackToUser; + private ArrayList toDisplayList; + private Boolean RENDER_FLAG; + public final String IDENTIFIER = "HelpList"; + + private final boolean exit; + + public HelpCommandMessage(String feedbackToUser, boolean exit) + { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.exit = exit; + this.RENDER_FLAG = false; + } + + public HelpCommandMessage(String feedbackToUser) + { + this(feedbackToUser, false); + } + + public String getFeedbackToUser() + { + return feedbackToUser; + } + + public Boolean getRenderFlag() + { + return this.RENDER_FLAG; + } + + public String getIdentifier() + { + return this.IDENTIFIER; + } + + public ObservableList getDisplayAsObservable() + { + return FXCollections.observableArrayList(this.toDisplayList); + } + + public void setToDisplayList(ArrayList displayList) + { + this.toDisplayList = displayList; + this.RENDER_FLAG = true; + } + + public boolean isExit() + { + return exit; + } + + public boolean equals(Object other) + { + if (other == this) + { + return true; + } + if (!(other instanceof HelpCommandMessage)) + { + return false; + } + + HelpCommandMessage otherCommandResult = (HelpCommandMessage) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) + && exit == otherCommandResult.exit; + } + + public int hashCode() + { + return Objects.hash(feedbackToUser, exit); + } + +} diff --git a/src/main/java/seedu/address/logic/messages/UserSummaryMessage.java b/src/main/java/seedu/address/logic/messages/UserSummaryMessage.java new file mode 100644 index 00000000000..f0845eb1c4e --- /dev/null +++ b/src/main/java/seedu/address/logic/messages/UserSummaryMessage.java @@ -0,0 +1,83 @@ +package seedu.address.logic.messages; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.bluetooth.Person; + +import java.util.ArrayList; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +public class UserSummaryMessage extends AppMessage { + private final String feedbackToUser; + private ArrayList toDisplayList; + private Boolean RENDER_FLAG; + public final String IDENTIFIER = "UserSummary"; + + /** The application should exit. */ + private final boolean exit; + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + public UserSummaryMessage(String feedbackToUser, boolean exit) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.exit = exit; + this.RENDER_FLAG = false; + } + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * and other fields set to their default value. + */ + public UserSummaryMessage(String feedbackToUser) { + this(feedbackToUser, false); + } + + public String getFeedbackToUser() { + return feedbackToUser; + } + + public Boolean getRenderFlag() { return this.RENDER_FLAG; } + + public String getIdentifier() { return this.IDENTIFIER; } + + public ObservableList getDisplayAsObservable() { return FXCollections.observableArrayList(this.toDisplayList); } + + /** + * A display list contains the necessary items needed to be rendered on the screen + * If this function is called, we set the RENDER_FLAG to true as signal that there is something to be displayed + * + * @param displayList List to be rendered on the screen + */ + public void setToDisplayList(ArrayList displayList) { + this.toDisplayList = displayList; + this.RENDER_FLAG = true; + } + + public boolean isExit() { + return exit; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BluetoothPingsMessage)) { + return false; + } + + UserSummaryMessage otherCommandResult = (UserSummaryMessage) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) + && exit == otherCommandResult.exit; + } + + @Override + public int hashCode() { + return Objects.hash(feedbackToUser, exit); + } +} 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/CommandRouter.java b/src/main/java/seedu/address/logic/parser/CommandRouter.java new file mode 100644 index 00000000000..d582089cd38 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CommandRouter.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser; + +import seedu.address.logic.commands.*; +import seedu.address.logic.parser.exceptions.ParseException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +public class CommandRouter { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + public static final String COMMAND_WORD = "help"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + + "Example: " + COMMAND_WORD; + + public AppCommand parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, this.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case ReportGenerationCommand.COMMAND_WORD: + return new ReportGenerationCommand().validate((arguments)); + + case FilterTimestampCommand.COMMAND_WORD: + return new FilterTimestampCommand().validate(arguments); + + case FilterUserIDCommand.COMMAND_WORD: + return new FilterUserIDCommand().validate(arguments); + + case FilterUserPairsCommand.COMMAND_WORD: + return new FilterUserPairsCommand().validate(arguments); + + case FilterDangerCommand.COMMAND_WORD: + return new FilterDangerCommand().validate(arguments); + + case ListAllPersonCommand.COMMAND_WORD: + return new ListAllPersonCommand().validate(arguments); + + case FilterPersonCommand.COMMAND_WORD: + return new FilterPersonCommand().validate(arguments); + + case PersonAddCommand.COMMAND_WORD: + return new PersonAddCommand().validate(arguments); + + case PersonDeleteCommand.COMMAND_WORD: + return new PersonDeleteCommand().validate(arguments); + + case PersonReportGenerationCommand.COMMAND_WORD: + return new PersonReportGenerationCommand().validate(arguments); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand().validate(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 deleted file mode 100644 index d6551ad8e3f..00000000000 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ /dev/null @@ -1,16 +0,0 @@ -package seedu.address.logic.parser; - -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 { - - /** - * 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; -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index b117acb9c55..00000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.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; - -/** - * 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."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} 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/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 0650c954f5c..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address 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; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } - -} 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/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java deleted file mode 100644 index befd58a4c73..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ /dev/null @@ -1,16 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; - -import seedu.address.commons.core.GuiSettings; - -/** - * Unmodifiable view of user prefs. - */ -public interface ReadOnlyUserPrefs { - - GuiSettings getGuiSettings(); - - Path getAddressBookFilePath(); - -} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java deleted file mode 100644 index 25a5fd6eab9..00000000000 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; - -import seedu.address.commons.core.GuiSettings; - -/** - * Represents User's preferences. - */ -public class UserPrefs implements ReadOnlyUserPrefs { - - private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); - - /** - * Creates a {@code UserPrefs} with default values. - */ - public UserPrefs() {} - - /** - * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}. - */ - public UserPrefs(ReadOnlyUserPrefs userPrefs) { - this(); - resetData(userPrefs); - } - - /** - * Resets the existing data of this {@code UserPrefs} with {@code newUserPrefs}. - */ - public void resetData(ReadOnlyUserPrefs newUserPrefs) { - requireNonNull(newUserPrefs); - setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); - } - - public GuiSettings getGuiSettings() { - return guiSettings; - } - - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - this.guiSettings = guiSettings; - } - - public Path getAddressBookFilePath() { - return addressBookFilePath; - } - - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof UserPrefs)) { //this handles null as well. - return false; - } - - UserPrefs o = (UserPrefs) other; - - return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); - } - - @Override - public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); - return sb.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/bluetooth/BluetoothPings.java b/src/main/java/seedu/address/model/bluetooth/BluetoothPings.java new file mode 100644 index 00000000000..f74b5d301f7 --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/BluetoothPings.java @@ -0,0 +1,21 @@ +package seedu.address.model.bluetooth; + +import java.util.List; + +public class BluetoothPings { + private Long epochTs; + private List userIDs; + + public BluetoothPings(Long epochTs, List userPairs) { + this.epochTs = epochTs; + this.userIDs = userPairs; + } + + public List getUserIDs() { + return this.userIDs; + } + + public Long getEpochTs() { + return this.epochTs; + } +} diff --git a/src/main/java/seedu/address/model/bluetooth/BluetoothPingsSummary.java b/src/main/java/seedu/address/model/bluetooth/BluetoothPingsSummary.java new file mode 100644 index 00000000000..580b8d0f365 --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/BluetoothPingsSummary.java @@ -0,0 +1,21 @@ +package seedu.address.model.bluetooth; + +import java.util.List; + +public class BluetoothPingsSummary { + private List userIDs; + private Long counts; + + public BluetoothPingsSummary(List userPairs, Long counts) { + this.userIDs = userPairs; + this.counts = counts; + } + + public List getUserIDs() { + return this.userIDs; + } + + public Long getCounts() { + return this.counts; + } +} diff --git a/src/main/java/seedu/address/model/bluetooth/CommandList.java b/src/main/java/seedu/address/model/bluetooth/CommandList.java new file mode 100644 index 00000000000..73e747b64bb --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/CommandList.java @@ -0,0 +1,19 @@ +package seedu.address.model.bluetooth; + +import java.util.List; + +public class CommandList { + private String instructions; + private String commandType; + + public CommandList(String CommandType, String instructions) { + this.commandType = CommandType; + this.instructions = instructions; + } + + public String getInstructions() { + return this.instructions; + } + + public String getCommandType() {return this.commandType;} +} diff --git a/src/main/java/seedu/address/model/bluetooth/Person.java b/src/main/java/seedu/address/model/bluetooth/Person.java new file mode 100644 index 00000000000..ad0db68f94a --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/Person.java @@ -0,0 +1,51 @@ +package seedu.address.model.bluetooth; + +public class Person { + private int userId; + private String name; + private String mobile; + private String nric; + private int age; + + public Person(String name) { + this.name = name; + } + + public Person withUserId(int userId) { + this.userId = userId; + return this; + } + + public Person withNric(String nric) { + this.nric = nric; + return this; + } + + public Person withMobile(String number) { + this.mobile = number; + return this; + } + + public Person withAge(int age) { + this.age = age; + return this; + } + + public int getUserId() { return this.userId; } + + public String getName() { + return this.name; + } + + public String getNric() { + return this.nric; + } + + public String getMobile() { return this.mobile; } + + public int getAge() { + return this.age; + } + + public Boolean equals(Person obj) { return this.nric.equals(obj.getNric()); } +} diff --git a/src/main/java/seedu/address/model/bluetooth/Timestamps.java b/src/main/java/seedu/address/model/bluetooth/Timestamps.java new file mode 100644 index 00000000000..33f35eb794e --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/Timestamps.java @@ -0,0 +1,60 @@ +package seedu.address.model.bluetooth; + +import seedu.address.model.bluetooth.formatter.DateFormatterInterface; +import seedu.address.model.bluetooth.formatter.SimpleDateFormatter; + +public class Timestamps { + private Long timestamp; + private DateFormatterInterface formatter; + + /** + * Creates a timestamp class initialized to an epoch timing + * Epoch timings are chosen by default and conversion to other timings / strings will use this value as ground truth + * + * @param epochTs Timestamp in unix epoch format + */ + public Timestamps(Long epochTs) { + this.timestamp = epochTs; + this.formatter = new SimpleDateFormatter(); + } + + /** + * Defines if the given timestamp is greater or equal to a relative value + * + * @param epochStart Lower limit value + * @return Is this timestamp greater or equal to the lower limit? + */ + public Boolean isGreaterOrEq(int epochStart) { + return this.timestamp >= epochStart; + } + + /** + * Defines if the given timestamp is smaller or equal to a relative value + * + * @param epochEnd Upper limit value + * @return Is this timestamp smaller or equal than the upper limit? + */ + public Boolean isSmallerOrEq(int epochEnd) { + return this.timestamp <= epochEnd; + } + + /** + * Defines if the given timestamp is within a bounded window of timing + * + * @param epochStart Start of the window + * @param epochEnd End of the window + * @return Is this timestamp bounded within the window? + */ + public Boolean isBound(int epochStart, int epochEnd){ + return this.isGreaterOrEq(epochStart) && this.isSmallerOrEq(epochEnd); + } + + /** + * Makes the timestamp nice looking cause people hate numbers + * + * @return Formatted timestamp string + */ + public String display() { + return this.formatter.format(this.timestamp); + } +} diff --git a/src/main/java/seedu/address/model/bluetooth/formatter/DateFormatterInterface.java b/src/main/java/seedu/address/model/bluetooth/formatter/DateFormatterInterface.java new file mode 100644 index 00000000000..6d9e2395bce --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/formatter/DateFormatterInterface.java @@ -0,0 +1,5 @@ +package seedu.address.model.bluetooth.formatter; + +public interface DateFormatterInterface { + public String format(long epochTs); +} diff --git a/src/main/java/seedu/address/model/bluetooth/formatter/SimpleDateFormatter.java b/src/main/java/seedu/address/model/bluetooth/formatter/SimpleDateFormatter.java new file mode 100644 index 00000000000..5b7b4d34dde --- /dev/null +++ b/src/main/java/seedu/address/model/bluetooth/formatter/SimpleDateFormatter.java @@ -0,0 +1,16 @@ +package seedu.address.model.bluetooth.formatter; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +public class SimpleDateFormatter implements DateFormatterInterface { + @Override + public String format(long epochTs) { + Date date = new Date(epochTs); + SimpleDateFormat format = new SimpleDateFormat("dd/MM/yy", Locale.US); // IDK what I'm doing here. All I know is that I am converting the epoch timestamp to some timezone. + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.format(date); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index a5bbe0b6a5f..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 79244d71cf7..00000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - - public final String fullName; - - /** - * Constructs a {@code Name}. - * - * @param name A valid name. - */ - public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; - } - - /** - * Returns true if a given string is a valid name. - */ - public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - 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 557a7a60cd5..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +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 of the same name have at least one other identity field that is the same. - * 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()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * 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()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java 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/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 deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -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; - -/** - * Contains utility methods for populating {@code AddressBook} 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 ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/report/DangerReportGenerator.java b/src/main/java/seedu/address/report/DangerReportGenerator.java new file mode 100644 index 00000000000..7a2c304c442 --- /dev/null +++ b/src/main/java/seedu/address/report/DangerReportGenerator.java @@ -0,0 +1,91 @@ +package seedu.address.report; + +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.element.Table; +import seedu.address.model.bluetooth.BluetoothPings; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import seedu.address.model.bluetooth.BluetoothPingsSummary; + + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class DangerReportGenerator { + public static final String DEST = "results/report.pdf"; + + public void GenerateReport(ArrayList resp) throws IOException + { + File file = new File(DEST); + file.getParentFile().mkdirs(); + new DangerReportGenerator().createPdf(DEST, resp); + } + + public void createPdf(String dest, ArrayList resp) throws IOException + { + PdfWriter writer = new PdfWriter(dest); + + + PdfDocument pdf = new PdfDocument(writer); + + + Document document = new Document(pdf, PageSize.A4.rotate()); + + document.setMargins(20, 20, 20, 20); + + + float [] pointColumnWidths = {150F, 150F, 150F}; + + Table table = new Table(pointColumnWidths); + + + table.addCell("Counts"); + table.addCell("ID1"); + table.addCell("ID2"); + + + Set IDSet = new HashSet(); + + if (resp.size() > 0) { + for (int i = 0; i < resp.size(); i++) { + BluetoothPingsSummary instance = resp.get(i); + table.addCell(instance.getCounts() + ""); + List epochTimes = instance.getUserIDs(); + table.addCell(epochTimes.get(0) + ""); + IDSet.add(epochTimes.get(0) + ""); + table.addCell(epochTimes.get(1) + ""); + IDSet.add(epochTimes.get(1) + ""); + } + } + + + + document.add(new Paragraph("CONTACT TRACING REPORT")); + document.add(new Paragraph("----------------------------------------------")); + Paragraph paragraph = new Paragraph("This is the report for dangerous cases"); + + document.add(paragraph); + + String TextString = "The ID included in this report are:"; + Iterator it = IDSet.iterator(); + while (it.hasNext()) + { + TextString = TextString + " " + it.next(); + } + + document.add(new Paragraph((TextString))); + document.add(new Paragraph("----------------------------------------------")); + + + document.add(table); + document.close(); + + } + +} diff --git a/src/main/java/seedu/address/report/PersonReportGenerator.java b/src/main/java/seedu/address/report/PersonReportGenerator.java new file mode 100644 index 00000000000..c23896ecfc7 --- /dev/null +++ b/src/main/java/seedu/address/report/PersonReportGenerator.java @@ -0,0 +1,80 @@ +package seedu.address.report; + +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.element.Table; +import seedu.address.model.bluetooth.BluetoothPings; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import seedu.address.model.bluetooth.BluetoothPingsSummary; +import seedu.address.model.bluetooth.Person; + + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class PersonReportGenerator { + public static final String DEST = "results/report.pdf"; + + public void GenerateReport(ArrayList resp) throws IOException + { + File file = new File(DEST); + file.getParentFile().mkdirs(); + new PersonReportGenerator().createPdf(DEST, resp); + } + + public void createPdf(String dest, ArrayList resp) throws IOException + { + PdfWriter writer = new PdfWriter(dest); + + + PdfDocument pdf = new PdfDocument(writer); + + Document document = new Document(pdf, PageSize.A4.rotate()); + + document.setMargins(20, 20, 20, 20); + + + float [] pointColumnWidths = {150F, 150F, 150F, 150F, 150F}; + + Table table = new Table(pointColumnWidths); + + + table.addCell("Name"); + table.addCell("ID"); + table.addCell("NRIC"); + table.addCell("Age"); + table.addCell("Contact"); + + + if (resp.size() > 0) { + for (int i = 0; i < resp.size(); i++) { + Person instance = resp.get(i); + table.addCell(instance.getName()+ ""); + table.addCell(instance.getUserId() + ""); + table.addCell(instance.getNric()+ ""); + table.addCell(instance.getAge()+ ""); + table.addCell(instance.getMobile() + ""); + } + } + + + + document.add(new Paragraph("CONTACT TRACING REPORT")); + document.add(new Paragraph("----------------------------------------------")); + document.add(new Paragraph("This is the report for person information")); + document.add(new Paragraph("The total number of instances in this report is: " + resp.size())); + document.add(new Paragraph("----------------------------------------------")); + + + document.add(table); + document.close(); + + } + +} diff --git a/src/main/java/seedu/address/report/ReportGenerator.java b/src/main/java/seedu/address/report/ReportGenerator.java new file mode 100644 index 00000000000..158801aac79 --- /dev/null +++ b/src/main/java/seedu/address/report/ReportGenerator.java @@ -0,0 +1,92 @@ +package seedu.address.report; + +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.element.Table; +import seedu.address.model.bluetooth.BluetoothPings; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; + + + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class ReportGenerator { + public static final String DEST = "results/report.pdf"; + + public void GenerateReport(ArrayList resp) throws IOException + { + File file = new File(DEST); + file.getParentFile().mkdirs(); + new ReportGenerator().createPdf(DEST, resp); + } + + public void createPdf(String dest, ArrayList resp) throws IOException + { + PdfWriter writer = new PdfWriter(dest); + + + PdfDocument pdf = new PdfDocument(writer); + + + Document document = new Document(pdf, PageSize.A4.rotate()); + + document.setMargins(20, 20, 20, 20); + + + float [] pointColumnWidths = {150F, 150F, 150F}; + + Table table = new Table(pointColumnWidths); + + + table.addCell("Time"); + table.addCell("ID1"); + table.addCell("ID2"); + + + + Set IDSet = new HashSet(); + BluetoothPings instance = resp.get(0); + + if (resp.size() > 0) { + for (int i = 0; i < resp.size(); i++) { + instance = resp.get(i); + table.addCell(instance.getEpochTs() + ""); + List epochTimes = instance.getUserIDs(); + table.addCell(epochTimes.get(0) + ""); + IDSet.add(epochTimes.get(0) + ""); + table.addCell(epochTimes.get(1) + ""); + IDSet.add(epochTimes.get(1) + ""); + } + } + + + document.add(new Paragraph("CONTACT TRACING REPORT")); + document.add(new Paragraph("----------------------------------------------")); + Paragraph paragraph = new Paragraph("The total number of instances in this report is: " + resp.size()); + + document.add(paragraph); + + String TextString = "The ID included in this file are:"; + Iterator it = IDSet.iterator(); + while (it.hasNext()) + { + TextString = TextString + " " + it.next(); + } + + document.add(new Paragraph((TextString))); + document.add(new Paragraph("----------------------------------------------")); + + + document.add(table); + document.close(); + + } + +} diff --git a/src/main/java/seedu/address/router/UiRouter.java b/src/main/java/seedu/address/router/UiRouter.java new file mode 100644 index 00000000000..199fcdc869b --- /dev/null +++ b/src/main/java/seedu/address/router/UiRouter.java @@ -0,0 +1,11 @@ +package seedu.address.router; + +import javafx.stage.Stage; +import seedu.address.ui.Ui; + +public class UiRouter implements Ui { + @Override + public void start(Stage primaryStage) { + + } +} 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/AppStorage.java b/src/main/java/seedu/address/storage/AppStorage.java new file mode 100644 index 00000000000..0a555fce8ea --- /dev/null +++ b/src/main/java/seedu/address/storage/AppStorage.java @@ -0,0 +1,38 @@ +package seedu.address.storage; + +import seedu.address.logic.aggregators.Aggregators; +import seedu.address.logic.conditions.Conditions; + +import java.util.ArrayList; + +public interface AppStorage { + + public ArrayList search(T obj); + + public void create(T obj) throws Exception; + + public void delete(ArrayList objs); + + public void delete(T obj); + + public void update(T objPast, T objNew); + + /** + * Performs an aggregated search over records passed in to identify groups of records which satisfies a condition + * @param cond {@code Condition} interface + * agg Aggregation logic + * + * @return ArrayList Filtered results + */ + public ArrayList search(Conditions cond, Aggregators agg); + + /** + * Given a condition, return all valid objects + * @param cond {@code Condition} interface + * + * @return ArrayList Filtered results + */ + public ArrayList search(Conditions cond); + + public ArrayList search(); +} diff --git a/src/main/java/seedu/address/storage/BluetoothPingStorageAccess.java b/src/main/java/seedu/address/storage/BluetoothPingStorageAccess.java new file mode 100644 index 00000000000..c30519fa99d --- /dev/null +++ b/src/main/java/seedu/address/storage/BluetoothPingStorageAccess.java @@ -0,0 +1,8 @@ +package seedu.address.storage; + +/** + * Interface to enable access policy for command classes + * + * Implementing this interface allows the command class to gain access to the underlying DAO storage + */ +public interface BluetoothPingStorageAccess {} diff --git a/src/main/java/seedu/address/storage/BluetoothPingsStorage.java b/src/main/java/seedu/address/storage/BluetoothPingsStorage.java new file mode 100644 index 00000000000..d9c93257f1f --- /dev/null +++ b/src/main/java/seedu/address/storage/BluetoothPingsStorage.java @@ -0,0 +1,177 @@ +package seedu.address.storage; + +import seedu.address.model.bluetooth.BluetoothPings; + +import java.util.ArrayList; +import java.util.Arrays; + +public class BluetoothPingsStorage extends InMemoryStorage { + public BluetoothPingsStorage() { + super(); + this.init(); + } + + /** + * TODO: Would be nice if can read from JSON file to initialize fake data + * @return + */ + private ArrayList genFakeData() { + ArrayList fakePings = new ArrayList(); + + fakePings.add(new BluetoothPings(1500000000L, Arrays.asList(11, 2))); + fakePings.add(new BluetoothPings(1500000100L, Arrays.asList(14, 9))); + fakePings.add(new BluetoothPings(1500000100L, Arrays.asList(3, 5))); + fakePings.add(new BluetoothPings(1500000200L, Arrays.asList(6, 18))); + fakePings.add(new BluetoothPings(1500000200L, Arrays.asList(4, 19))); + fakePings.add(new BluetoothPings(1500000200L, Arrays.asList(6, 20))); + fakePings.add(new BluetoothPings(1500000300L, Arrays.asList(12, 3))); + fakePings.add(new BluetoothPings(1500000400L, Arrays.asList(4, 15))); + fakePings.add(new BluetoothPings(1500000400L, Arrays.asList(16, 8))); + fakePings.add(new BluetoothPings(1500000400L, Arrays.asList(4, 7))); + fakePings.add(new BluetoothPings(1500000500L, Arrays.asList(12, 10))); + fakePings.add(new BluetoothPings(1500000500L, Arrays.asList(3, 14))); + fakePings.add(new BluetoothPings(1500000500L, Arrays.asList(12, 7))); + fakePings.add(new BluetoothPings(1500000400L, Arrays.asList(9, 10))); + fakePings.add(new BluetoothPings(1500000500L, Arrays.asList(15, 8))); + fakePings.add(new BluetoothPings(1500000600L, Arrays.asList(6, 7))); + fakePings.add(new BluetoothPings(1500000700L, Arrays.asList(8, 9))); + fakePings.add(new BluetoothPings(1500000800L, Arrays.asList(11, 9))); + fakePings.add(new BluetoothPings(1500001000L, Arrays.asList(4, 17))); + fakePings.add(new BluetoothPings(1500001300L, Arrays.asList(12, 8))); + fakePings.add(new BluetoothPings(1500001500L, Arrays.asList(5, 8))); + fakePings.add(new BluetoothPings(1500001700L, Arrays.asList(3, 19))); + fakePings.add(new BluetoothPings(1500001900L, Arrays.asList(12, 7))); + fakePings.add(new BluetoothPings(1500003000L, Arrays.asList(14, 9))); + fakePings.add(new BluetoothPings(1500003200L, Arrays.asList(2, 9))); + fakePings.add(new BluetoothPings(1500003500L, Arrays.asList(13, 8))); + fakePings.add(new BluetoothPings(1500003700L, Arrays.asList(5, 6))); + fakePings.add(new BluetoothPings(1500003900L, Arrays.asList(14, 10))); + fakePings.add(new BluetoothPings(1500004500L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500004900L, Arrays.asList(2, 13))); + fakePings.add(new BluetoothPings(1500005000L, Arrays.asList(14, 7))); + fakePings.add(new BluetoothPings(1500005500L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500005800L, Arrays.asList(14, 9))); + fakePings.add(new BluetoothPings(1500006000L, Arrays.asList(11, 2))); + fakePings.add(new BluetoothPings(1500007000L, Arrays.asList(3, 19))); + fakePings.add(new BluetoothPings(1500007500L, Arrays.asList(15, 7))); + fakePings.add(new BluetoothPings(1500008000L, Arrays.asList(18, 10))); + fakePings.add(new BluetoothPings(1500008300L, Arrays.asList(6, 9))); + fakePings.add(new BluetoothPings(1500008400L, Arrays.asList(2, 14))); + fakePings.add(new BluetoothPings(1500008600L, Arrays.asList(17, 9))); + fakePings.add(new BluetoothPings(1500008900L, Arrays.asList(13, 4))); + fakePings.add(new BluetoothPings(1500009000L, Arrays.asList(5, 16))); + fakePings.add(new BluetoothPings(1500009300L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500009400L, Arrays.asList(9, 20))); + fakePings.add(new BluetoothPings(1500009500L, Arrays.asList(13, 6))); + fakePings.add(new BluetoothPings(1500009600L, Arrays.asList(7, 18))); + fakePings.add(new BluetoothPings(1500010000L, Arrays.asList(2, 7))); + fakePings.add(new BluetoothPings(1500011000L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500011500L, Arrays.asList(11, 8))); + fakePings.add(new BluetoothPings(1500011600L, Arrays.asList(13, 4))); + fakePings.add(new BluetoothPings(1500012000L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500013000L, Arrays.asList(2, 10))); + fakePings.add(new BluetoothPings(1500013500L, Arrays.asList(15, 8))); + fakePings.add(new BluetoothPings(1500015000L, Arrays.asList(8, 9))); + fakePings.add(new BluetoothPings(1500017000L, Arrays.asList(1, 12))); + fakePings.add(new BluetoothPings(1500020100L, Arrays.asList(14, 9))); + fakePings.add(new BluetoothPings(1500023100L, Arrays.asList(3, 5))); + fakePings.add(new BluetoothPings(1500022200L, Arrays.asList(16, 8))); + fakePings.add(new BluetoothPings(1500025200L, Arrays.asList(4, 19))); + fakePings.add(new BluetoothPings(1500027200L, Arrays.asList(6, 10))); + fakePings.add(new BluetoothPings(1500029300L, Arrays.asList(12, 3))); + fakePings.add(new BluetoothPings(1500032400L, Arrays.asList(14, 5))); + fakePings.add(new BluetoothPings(1500034400L, Arrays.asList(6, 18))); + fakePings.add(new BluetoothPings(1500039400L, Arrays.asList(4, 17))); + fakePings.add(new BluetoothPings(1500043500L, Arrays.asList(2, 10))); + fakePings.add(new BluetoothPings(1500045500L, Arrays.asList(13, 4))); + fakePings.add(new BluetoothPings(1500046500L, Arrays.asList(12, 7))); + fakePings.add(new BluetoothPings(1500049400L, Arrays.asList(19, 10))); + fakePings.add(new BluetoothPings(1500050500L, Arrays.asList(5, 8))); + fakePings.add(new BluetoothPings(1500054600L, Arrays.asList(6, 7))); + fakePings.add(new BluetoothPings(1500055700L, Arrays.asList(8, 19))); + fakePings.add(new BluetoothPings(1500058800L, Arrays.asList(1, 19))); + fakePings.add(new BluetoothPings(1500064000L, Arrays.asList(4, 17))); + fakePings.add(new BluetoothPings(1500065300L, Arrays.asList(2, 8))); + fakePings.add(new BluetoothPings(1500068500L, Arrays.asList(15, 8))); + fakePings.add(new BluetoothPings(1500070700L, Arrays.asList(3, 9))); + fakePings.add(new BluetoothPings(1500071900L, Arrays.asList(12, 7))); + fakePings.add(new BluetoothPings(1500074000L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500076200L, Arrays.asList(2, 19))); + fakePings.add(new BluetoothPings(1500078500L, Arrays.asList(3, 8))); + fakePings.add(new BluetoothPings(1500080700L, Arrays.asList(5, 6))); + fakePings.add(new BluetoothPings(1500081900L, Arrays.asList(4, 20))); + fakePings.add(new BluetoothPings(1500083500L, Arrays.asList(6, 18))); + fakePings.add(new BluetoothPings(1500085900L, Arrays.asList(12, 3))); + fakePings.add(new BluetoothPings(1500086000L, Arrays.asList(4, 7))); + fakePings.add(new BluetoothPings(1500089500L, Arrays.asList(16, 8))); + fakePings.add(new BluetoothPings(1500090800L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500091000L, Arrays.asList(11, 2))); + fakePings.add(new BluetoothPings(1500098000L, Arrays.asList(3, 9))); + fakePings.add(new BluetoothPings(1500100500L, Arrays.asList(5, 17))); + fakePings.add(new BluetoothPings(1500128000L, Arrays.asList(8, 10))); + fakePings.add(new BluetoothPings(1500148300L, Arrays.asList(6, 19))); + fakePings.add(new BluetoothPings(1500168400L, Arrays.asList(2, 4))); + fakePings.add(new BluetoothPings(1500188600L, Arrays.asList(17, 9))); + fakePings.add(new BluetoothPings(1500208900L, Arrays.asList(13, 4))); + fakePings.add(new BluetoothPings(1500289000L, Arrays.asList(5, 6))); + fakePings.add(new BluetoothPings(1500309300L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500359400L, Arrays.asList(9, 10))); + fakePings.add(new BluetoothPings(1500389500L, Arrays.asList(13, 6))); + fakePings.add(new BluetoothPings(1500400000L, Arrays.asList(7, 8))); + fakePings.add(new BluetoothPings(1500412000L, Arrays.asList(4, 19))); + fakePings.add(new BluetoothPings(1500433000L, Arrays.asList(2, 10))); + fakePings.add(new BluetoothPings(1500443500L, Arrays.asList(5, 18))); + fakePings.add(new BluetoothPings(1500425000L, Arrays.asList(8, 9))); + fakePings.add(new BluetoothPings(1500457000L, Arrays.asList(1, 12))); + fakePings.add(new BluetoothPings(1500470100L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500493100L, Arrays.asList(13, 5))); + fakePings.add(new BluetoothPings(1500502200L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500555200L, Arrays.asList(14, 9))); + fakePings.add(new BluetoothPings(1500597200L, Arrays.asList(6, 10))); + fakePings.add(new BluetoothPings(1500609300L, Arrays.asList(2, 3))); + fakePings.add(new BluetoothPings(1500612400L, Arrays.asList(14, 5))); + fakePings.add(new BluetoothPings(1500634400L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500659400L, Arrays.asList(4, 17))); + fakePings.add(new BluetoothPings(1500673500L, Arrays.asList(2, 10))); + fakePings.add(new BluetoothPings(1500695500L, Arrays.asList(13, 4))); + fakePings.add(new BluetoothPings(1500706500L, Arrays.asList(2, 7))); + fakePings.add(new BluetoothPings(1500729400L, Arrays.asList(19, 10))); + fakePings.add(new BluetoothPings(1500730500L, Arrays.asList(5, 8))); + fakePings.add(new BluetoothPings(1500744600L, Arrays.asList(16, 7))); + fakePings.add(new BluetoothPings(1507065700L, Arrays.asList(8, 9))); + fakePings.add(new BluetoothPings(1500788800L, Arrays.asList(11, 9))); + fakePings.add(new BluetoothPings(1500794000L, Arrays.asList(4, 7))); + fakePings.add(new BluetoothPings(1500815300L, Arrays.asList(2, 18))); + fakePings.add(new BluetoothPings(1500838500L, Arrays.asList(15, 8))); + fakePings.add(new BluetoothPings(1500840700L, Arrays.asList(3, 9))); + fakePings.add(new BluetoothPings(1500851900L, Arrays.asList(12, 7))); + fakePings.add(new BluetoothPings(1500864000L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500876200L, Arrays.asList(12, 9))); + fakePings.add(new BluetoothPings(1500878500L, Arrays.asList(3, 8))); + fakePings.add(new BluetoothPings(1500870700L, Arrays.asList(5, 16))); + fakePings.add(new BluetoothPings(1500881900L, Arrays.asList(14, 10))); + fakePings.add(new BluetoothPings(1508073500L, Arrays.asList(6, 8))); + fakePings.add(new BluetoothPings(1500895900L, Arrays.asList(12, 3))); + fakePings.add(new BluetoothPings(1500906000L, Arrays.asList(4, 7))); + fakePings.add(new BluetoothPings(1500909500L, Arrays.asList(6, 18))); + fakePings.add(new BluetoothPings(1500900800L, Arrays.asList(4, 9))); + fakePings.add(new BluetoothPings(1500911000L, Arrays.asList(1, 12))); + fakePings.add(new BluetoothPings(1500918000L, Arrays.asList(3, 9))); + fakePings.add(new BluetoothPings(1500910500L, Arrays.asList(15, 7))); + fakePings.add(new BluetoothPings(1500928000L, Arrays.asList(8, 10))); + fakePings.add(new BluetoothPings(1500928300L, Arrays.asList(16, 9))); + fakePings.add(new BluetoothPings(1500928400L, Arrays.asList(2, 4))); + fakePings.add(new BluetoothPings(1500948600L, Arrays.asList(17, 9))); + fakePings.add(new BluetoothPings(1500948900L, Arrays.asList(3, 4))); + fakePings.add(new BluetoothPings(1500959000L, Arrays.asList(5, 16))); + fakePings.add(new BluetoothPings(1500969300L, Arrays.asList(16, 8))); + fakePings.add(new BluetoothPings(1500999400L, Arrays.asList(9, 10))); + fakePings.add(new BluetoothPings(1500999500L, Arrays.asList(3, 16))); + fakePings.add(new BluetoothPings(1500999600L, Arrays.asList(7, 18))); + return fakePings; + } + + public void init() { + this.fakeStorage = this.genFakeData(); + } +} diff --git a/src/main/java/seedu/address/storage/DaoRouter.java b/src/main/java/seedu/address/storage/DaoRouter.java new file mode 100644 index 00000000000..ad3f42271fc --- /dev/null +++ b/src/main/java/seedu/address/storage/DaoRouter.java @@ -0,0 +1,40 @@ +package seedu.address.storage; + +import seedu.address.logic.commands.AppCommand; +import seedu.address.model.bluetooth.BluetoothPings; +import seedu.address.model.bluetooth.Person; + +public class DaoRouter { + private static final InMemoryStorage bluetoothPingStorage = new BluetoothPingsStorage(); + private static final InMemoryStorage userStorage = new PersonStorage(); + private static DaoRouter routerSingleton = null; + + /** + * Enforcing Singleton design pattern + */ + private DaoRouter() { + } + + /** + * Identifies the correct storage to allocate for a given command based on a policy + * + * @param command {@code AppCommand} + * @return InMemoryStorage Instantiated DAO class + */ + public InMemoryStorage getStorage(AppCommand command) { + if (command instanceof BluetoothPingStorageAccess) { + return bluetoothPingStorage; + } else if (command instanceof UserStorageAccess) { + return userStorage; + } else { + return null; + } + } + + public static DaoRouter getInstance(){ + if (routerSingleton == null) { + routerSingleton = new DaoRouter(); + } + return routerSingleton; + } +} diff --git a/src/main/java/seedu/address/storage/InMemoryStorage.java b/src/main/java/seedu/address/storage/InMemoryStorage.java new file mode 100644 index 00000000000..a9846a935fc --- /dev/null +++ b/src/main/java/seedu/address/storage/InMemoryStorage.java @@ -0,0 +1,73 @@ +package seedu.address.storage; + +import seedu.address.logic.aggregators.Aggregators; +import seedu.address.logic.conditions.Conditions; +import seedu.address.logic.conditions.LiterallyNoConditions; + +import java.util.ArrayList; +import java.util.stream.Collectors; + +/** + * Storage is maintained in memory + * All transactions are done via memory + * + * This does not work for large scale data but suffices for this project + * + * @param + */ +public abstract class InMemoryStorage implements AppStorage { + protected ArrayList fakeStorage; + + @Override + public ArrayList search(T obj) { + return this.fakeStorage.stream() + .filter(each -> each.equals(obj)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public void create(T obj) throws Exception { + ArrayList duplicates = this.search(obj); + if (duplicates.size() > 0) { + throw new Exception("Person exists in database"); + } + this.fakeStorage.add(obj); + } + + @Override + public void update(T objPast, T objNew) { + // TODO: write some code here + } + + @Override + public void delete(ArrayList objs) { + this.fakeStorage.removeAll(objs); + } + + @Override + public void delete(T obj) { + this.fakeStorage.remove(obj); + } + + @Override + public ArrayList search(Conditions cond, Aggregators agg) { + ArrayList collection = agg.collect(this.fakeStorage); + + return collection.stream() + .filter(each -> cond.satisfies(each)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public ArrayList search(Conditions cond) { + return this.fakeStorage.stream() + .filter(each -> cond.satisfies(each)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public ArrayList search() { + Conditions cond = new LiterallyNoConditions(); + return this.search(cond); + } +} 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/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/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -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.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @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 { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), 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/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java deleted file mode 100644 index bc2bbad84aa..00000000000 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ /dev/null @@ -1,47 +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.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * A class to access UserPrefs stored in the hard disk as a json file - */ -public class JsonUserPrefsStorage implements UserPrefsStorage { - - private Path filePath; - - public JsonUserPrefsStorage(Path filePath) { - this.filePath = filePath; - } - - @Override - public Path getUserPrefsFilePath() { - return filePath; - } - - @Override - public Optional readUserPrefs() throws DataConversionException { - return readUserPrefs(filePath); - } - - /** - * Similar to {@link #readUserPrefs()} - * @param prefsFilePath location of the data. Cannot be null. - * @throws DataConversionException if the file format is not as expected. - */ - public Optional readUserPrefs(Path prefsFilePath) throws DataConversionException { - return JsonUtil.readJsonFile(prefsFilePath, UserPrefs.class); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - JsonUtil.saveJsonFile(userPrefs, filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/PersonStorage.java b/src/main/java/seedu/address/storage/PersonStorage.java new file mode 100644 index 00000000000..6ba95871c47 --- /dev/null +++ b/src/main/java/seedu/address/storage/PersonStorage.java @@ -0,0 +1,100 @@ +package seedu.address.storage; + +import seedu.address.model.bluetooth.Person; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class PersonStorage extends InMemoryStorage { + private final AtomicInteger counter = new AtomicInteger(1); + + public PersonStorage() { + super(); + this.init(); + } + + /** + * TODO: Would be nice if can read from JSON file to initialize fake data + * @return + */ + private ArrayList genFakeData() { + ArrayList fakeStorage = new ArrayList(); + fakeStorage.add(new Person("John Doe").withUserId(counter.getAndAdd(1)).withNric("S91111111Q") + .withAge(25).withMobile("9991")); + + fakeStorage.add(new Person("John Roe").withUserId(counter.getAndAdd(1)).withNric("S92222222J") + .withAge(26).withMobile("9992")); + + fakeStorage.add(new Person("John Snow").withUserId(counter.getAndAdd(1)).withNric("S93333333K") + .withAge(27).withMobile("9939")); + + fakeStorage.add(new Person("John Lim").withUserId(counter.getAndAdd(1)).withNric("S94444444L") + .withAge(28).withMobile("9994")); + + fakeStorage.add(new Person("Adam Smith").withUserId(counter.getAndAdd(1)).withNric("B82222222M") + .withAge(24).withMobile("9980")); + + fakeStorage.add(new Person("Aidan Johnson").withUserId(counter.getAndAdd(1)).withNric("D2222222L") + .withAge(29).withMobile("9987")); + + fakeStorage.add(new Person("Anthony Williams").withUserId(counter.getAndAdd(1)).withNric("L10000000C") + .withAge(32).withMobile("9983")); + + fakeStorage.add(new Person("Bob Jones").withUserId(counter.getAndAdd(1)).withNric("S93333333A") + .withAge(31).withMobile("9980")); + + fakeStorage.add(new Person("Brown Miller").withUserId(counter.getAndAdd(1)).withNric("R01111111V") + .withAge(30).withMobile("9979")); + + fakeStorage.add(new Person("Brandon Davis").withUserId(counter.getAndAdd(1)).withNric("X69999999A") + .withAge(20).withMobile("9975")); + + fakeStorage.add(new Person("Caspar Wilson").withUserId(counter.getAndAdd(1)).withNric("P03333333C") + .withAge(21).withMobile("9971")); + + fakeStorage.add(new Person("Christian Anderson").withUserId(counter.getAndAdd(1)).withNric("A21111111K") + .withAge(29).withMobile("9976")); + + fakeStorage.add(new Person("Clark Taylor").withUserId(counter.getAndAdd(1)).withNric("P34444444C") + .withAge(24).withMobile("9973")); + + fakeStorage.add(new Person("Cody Thompson").withUserId(counter.getAndAdd(1)).withNric("C67777777Z") + .withAge(29).withMobile("9970")); + + fakeStorage.add(new Person("Colin Moore").withUserId(counter.getAndAdd(1)).withNric("Q53333333I") + .withAge(30).withMobile("9969")); + + fakeStorage.add(new Person("Darwin Williams").withUserId(counter.getAndAdd(1)).withNric("D78888888C") + .withAge(31).withMobile("9963")); + + fakeStorage.add(new Person("Dick Anderson").withUserId(counter.getAndAdd(1)).withNric("L93333333C") + .withAge(27).withMobile("9960")); + + fakeStorage.add(new Person("Paul Miller").withUserId(counter.getAndAdd(1)).withNric("V50000000L") + .withAge(12).withMobile("9954")); + + fakeStorage.add(new Person("Dennis Brown").withUserId(counter.getAndAdd(1)).withNric("S61111111F") + .withAge(22).withMobile("9951")); + + fakeStorage.add(new Person("Duke Jackson").withUserId(counter.getAndAdd(1)).withNric("C54444444X") + .withAge(30).withMobile("9950")); + + return fakeStorage; + } + + /** + * Method injects a unique user ID into a created {@code Person} object + * This simulates a database with user id as unique ID + * + * @param obj Person object without user id + * @throws Exception + */ + @Override + public void create(Person obj) throws Exception { + Person objWithUserId = obj.withUserId(counter.getAndAdd(1)); + super.create(objWithUserId); + } + + public void init() { + this.fakeStorage = this.genFakeData(); + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index beda8bd9f11..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +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; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index e4f452b6cbf..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.storage; - -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.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java deleted file mode 100644 index 29eef178dbc..00000000000 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ /dev/null @@ -1,36 +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.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Represents a storage for {@link seedu.address.model.UserPrefs}. - */ -public interface UserPrefsStorage { - - /** - * Returns the file path of the UserPrefs data file. - */ - Path getUserPrefsFilePath(); - - /** - * Returns UserPrefs data from storage. - * 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 readUserPrefs() throws DataConversionException, IOException; - - /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. - * @param userPrefs cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/UserStorageAccess.java b/src/main/java/seedu/address/storage/UserStorageAccess.java new file mode 100644 index 00000000000..56afe3d9c24 --- /dev/null +++ b/src/main/java/seedu/address/storage/UserStorageAccess.java @@ -0,0 +1,3 @@ +package seedu.address.storage; + +public interface UserStorageAccess {} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/AppCommandBox.java similarity index 68% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/address/ui/AppCommandBox.java index 7d76e691f52..801131b7fed 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/AppCommandBox.java @@ -4,24 +4,19 @@ import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.messages.AppMessage; import seedu.address.logic.parser.exceptions.ParseException; -/** - * The UI component that is responsible for receiving user command inputs. - */ -public class CommandBox extends UiPart { - +public class AppCommandBox extends UiPart { public static final String ERROR_STYLE_CLASS = "error"; private static final String FXML = "CommandBox.fxml"; - private final CommandExecutor commandExecutor; + private final AppCommandBox.CommandExecutor commandExecutor; - @FXML + @javafx.fxml.FXML private TextField commandTextField; - public CommandBox(CommandExecutor commandExecutor) { + public AppCommandBox(AppCommandBox.CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; // calls #setStyleToDefault() whenever there is a change to the text of the command box. @@ -32,13 +27,9 @@ public CommandBox(CommandExecutor commandExecutor) { * Handles the Enter button pressed event. */ @FXML - private void handleCommandEntered() { - try { - commandExecutor.execute(commandTextField.getText()); - commandTextField.setText(""); - } catch (CommandException | ParseException e) { - setStyleToIndicateCommandFailure(); - } + private void handleCommandEntered() throws ParseException { + commandExecutor.execute(commandTextField.getText()); + commandTextField.setText(""); } /** @@ -71,7 +62,6 @@ public interface CommandExecutor { * * @see seedu.address.logic.Logic#execute(String) */ - CommandResult execute(String commandText) throws CommandException, ParseException; + AppMessage execute(String commandText) throws ParseException; } - } diff --git a/src/main/java/seedu/address/ui/AppMainWindow.java b/src/main/java/seedu/address/ui/AppMainWindow.java new file mode 100644 index 00000000000..8098d688a71 --- /dev/null +++ b/src/main/java/seedu/address/ui/AppMainWindow.java @@ -0,0 +1,127 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.AppLogic; +import seedu.address.logic.messages.AppMessage; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * The Main Window. Provides the basic application layout containing + * a menu bar and space where other JavaFX elements can be placed. + */ +public class AppMainWindow extends UiPart { + + private static final String FXML = "AppMainWindow.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Stage primaryStage; + private AppLogic logic; + private BluetoothPingPanel bluetoothPingPanel; + private BluetoothPingSummaryPanel bluetoothPingSummaryPanel; + private PersonSummaryPanel personSummaryPanel; + private HelpPanel helpPanel; + private ResultDisplay resultDisplay; + + @FXML + private StackPane commandBoxPlaceholder; + + @FXML + private StackPane resultDisplayPlaceholder; + + @FXML + private StackPane bluetoothPingPanelPlaceholder; + + private void renderToPanel() { + this.bluetoothPingPanelPlaceholder.getChildren().clear(); + + } + + public AppMainWindow(Stage primaryStage, AppLogic logic) { + super(FXML, primaryStage); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + public void renderToDisplay(AppMessage commandResult) { + if (commandResult.getRenderFlag()) { + this.bluetoothPingPanelPlaceholder.getChildren().clear(); + if (commandResult.getIdentifier().equals("BluetoothPings")){ + this.bluetoothPingPanel = new BluetoothPingPanel(commandResult.getDisplayAsObservable()); + this.bluetoothPingPanelPlaceholder.getChildren().add(this.bluetoothPingPanel.getRoot()); + } + else if (commandResult.getIdentifier().equals("BluetoothPingsSummary")) { + this.bluetoothPingSummaryPanel = new BluetoothPingSummaryPanel(commandResult.getDisplayAsObservable()); + this.bluetoothPingPanelPlaceholder.getChildren().add(this.bluetoothPingSummaryPanel.getRoot()); + } + else if (commandResult.getIdentifier().equals("UserSummary")) { + this.personSummaryPanel = new PersonSummaryPanel(commandResult.getDisplayAsObservable()); + this.bluetoothPingPanelPlaceholder.getChildren().add(this.personSummaryPanel.getRoot()); + } + else if (commandResult.getIdentifier().equals("HelpList")) { + this.helpPanel = new HelpPanel(commandResult.getDisplayAsObservable()); + this.bluetoothPingPanelPlaceholder.getChildren().add(this.helpPanel.getRoot()); + } + } + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + this.resultDisplay = new ResultDisplay(); + resultDisplayPlaceholder.getChildren().add(this.resultDisplay.getRoot()); + + AppCommandBox commandBox = new AppCommandBox(this::executeCommand); + this.commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + } + + void show() { + primaryStage.show(); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + primaryStage.hide(); + } + + /** + * Executes the command and returns the result. + * + * @see seedu.address.logic.Logic#execute(String) + */ + private AppMessage executeCommand(String commandText) throws ParseException { + try { + AppMessage commandResult = logic.execute(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + this.renderToDisplay(commandResult); + + if (commandResult.isExit()) { + handleExit(); + } + return commandResult; + } catch (ParseException e) { + logger.warning("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/AppUiManager.java similarity index 81% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/address/ui/AppUiManager.java index 876621d79b9..cf5afd0a856 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/AppUiManager.java @@ -1,31 +1,25 @@ package seedu.address.ui; -import java.util.logging.Logger; - import javafx.application.Platform; import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; - -/** - * The manager of the UI component. - */ -public class UiManager implements Ui { +import seedu.address.logic.AppLogic; +import seedu.address.ContactTracingMainApp; +import java.util.logging.Logger; +public class AppUiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; - private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final Logger logger = LogsCenter.getLogger(AppUiManager.class); private static final String ICON_APPLICATION = "/images/address_book_32.png"; - private Logic logic; - private MainWindow mainWindow; + private AppLogic logic; + private AppMainWindow mainWindow; - public UiManager(Logic logic) { + public AppUiManager(AppLogic logic) { super(); this.logic = logic; } @@ -38,7 +32,7 @@ public void start(Stage primaryStage) { primaryStage.getIcons().add(getImage(ICON_APPLICATION)); try { - mainWindow = new MainWindow(primaryStage, logic); + mainWindow = new AppMainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); @@ -49,7 +43,7 @@ public void start(Stage primaryStage) { } private Image getImage(String imagePath) { - return new Image(MainApp.class.getResourceAsStream(imagePath)); + return new Image(ContactTracingMainApp.class.getResourceAsStream(imagePath)); } void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { @@ -60,7 +54,7 @@ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerTex * Shows an alert dialog on {@code owner} with the given parameters. * This method only returns after the user has closed the alert dialog. */ - private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, + private static void showAlertDialogAndWait(Stage owner, Alert.AlertType type, String title, String headerText, String contentText) { final Alert alert = new Alert(type); alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); @@ -82,5 +76,4 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { Platform.exit(); System.exit(1); } - } diff --git a/src/main/java/seedu/address/ui/BluetoothPingCard.java b/src/main/java/seedu/address/ui/BluetoothPingCard.java new file mode 100644 index 00000000000..4ee1438e812 --- /dev/null +++ b/src/main/java/seedu/address/ui/BluetoothPingCard.java @@ -0,0 +1,29 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.bluetooth.BluetoothPings; + +public class BluetoothPingCard extends UiPart { + + private static final String FXML = "BluetoothPingCard.fxml"; + + public final BluetoothPings bluetoothPings; + + @FXML + private Label contacts; + + @FXML + private Label timestamp; + + public BluetoothPingCard(BluetoothPings bluetoothPings, int displayedIndex) { + super(FXML); + this.bluetoothPings = bluetoothPings; + contacts.setText(bluetoothPings.getUserIDs().toString()); + timestamp.setText(bluetoothPings.getEpochTs().toString()); + } + + @Override + public boolean equals(Object other) {return true;} +} diff --git a/src/main/java/seedu/address/ui/BluetoothPingPanel.java b/src/main/java/seedu/address/ui/BluetoothPingPanel.java new file mode 100644 index 00000000000..73041a1cfbf --- /dev/null +++ b/src/main/java/seedu/address/ui/BluetoothPingPanel.java @@ -0,0 +1,39 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.bluetooth.BluetoothPings; + +public class BluetoothPingPanel extends UiPart { + private static final String FXML = "BluetoothPingPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(BluetoothPingPanel.class); + + @FXML + private ListView bluetoothPingsListView; + + public BluetoothPingPanel(ObservableList bluetoothPingsList) { + super(FXML); + bluetoothPingsListView.setItems(bluetoothPingsList); + bluetoothPingsListView.setCellFactory(listView -> new BluetoothPingsListViewCell()); + } + + class BluetoothPingsListViewCell extends ListCell { + @Override + protected void updateItem(BluetoothPings bluetoothPings, boolean empty) { + super.updateItem(bluetoothPings, empty); + + if (empty || bluetoothPings == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new BluetoothPingCard(bluetoothPings, getIndex() +1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/BluetoothPingSummaryCard.java b/src/main/java/seedu/address/ui/BluetoothPingSummaryCard.java new file mode 100644 index 00000000000..9a6b41beb88 --- /dev/null +++ b/src/main/java/seedu/address/ui/BluetoothPingSummaryCard.java @@ -0,0 +1,29 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.bluetooth.BluetoothPingsSummary; + +public class BluetoothPingSummaryCard extends UiPart { + + private static final String FXML="BluetoothPingSummaryCard.fxml"; + + public final BluetoothPingsSummary bluetoothPings; + + @FXML + private Label contacts; + + @FXML + private Label occurrence; + + public BluetoothPingSummaryCard(BluetoothPingsSummary bluetoothPingSummary,int displayedIndex){ + super(FXML); + this.bluetoothPings = bluetoothPingSummary; + contacts.setText(bluetoothPingSummary.getUserIDs().toString()); + occurrence.setText(bluetoothPingSummary.getCounts().toString()); + } + + @Override + public boolean equals(Object other){return true;} +} diff --git a/src/main/java/seedu/address/ui/BluetoothPingSummaryPanel.java b/src/main/java/seedu/address/ui/BluetoothPingSummaryPanel.java new file mode 100644 index 00000000000..2a78ef3a3e3 --- /dev/null +++ b/src/main/java/seedu/address/ui/BluetoothPingSummaryPanel.java @@ -0,0 +1,39 @@ +package seedu.address.ui; + +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.bluetooth.BluetoothPingsSummary; + +import java.util.logging.Logger; + +public class BluetoothPingSummaryPanel extends UiPart { + private static final String FXML = "BluetoothPingPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(BluetoothPingSummaryPanel.class); + + @FXML + private ListView bluetoothPingsListView; + + public BluetoothPingSummaryPanel(ObservableList bluetoothPingSummaryList) { + super(FXML); + bluetoothPingsListView.setItems(bluetoothPingSummaryList); + bluetoothPingsListView.setCellFactory(listView -> new BluetoothPingSummaryPanel.BluetoothPingsListViewCell()); + } + + class BluetoothPingsListViewCell extends ListCell { + @Override + protected void updateItem(BluetoothPingsSummary bluetoothPingsSummary, boolean empty) { + super.updateItem(bluetoothPingsSummary, empty); + + if (empty || bluetoothPingsSummary == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new BluetoothPingSummaryCard(bluetoothPingsSummary, getIndex() +1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/HelpCard.java b/src/main/java/seedu/address/ui/HelpCard.java new file mode 100644 index 00000000000..0a247d8a396 --- /dev/null +++ b/src/main/java/seedu/address/ui/HelpCard.java @@ -0,0 +1,29 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.bluetooth.CommandList; + +public class HelpCard extends UiPart { + private static final String FXML="HelpCard.fxml"; + + public final CommandList commandList; + + @javafx.fxml.FXML + private Label commandType; + + @FXML + private Label instructions; + + public HelpCard(CommandList commandList, int displayedIndex){ + super(FXML); + this.commandList = commandList; + + commandType.setText(commandList.getCommandType()); + instructions.setText(commandList.getInstructions()); + } + + @Override + public boolean equals(Object other){return true;} +} diff --git a/src/main/java/seedu/address/ui/HelpPanel.java b/src/main/java/seedu/address/ui/HelpPanel.java new file mode 100644 index 00000000000..39d12aa65ba --- /dev/null +++ b/src/main/java/seedu/address/ui/HelpPanel.java @@ -0,0 +1,39 @@ +package seedu.address.ui; + +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.bluetooth.CommandList; + +import java.util.logging.Logger; + +public class HelpPanel extends UiPart { + private static final String FXML = "HelpPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(HelpPanel.class); + + @FXML + private ListView commandListListView; + + public HelpPanel(ObservableList commandList) { + super(FXML); + commandListListView.setItems(commandList); + commandListListView.setCellFactory(listView -> new HelpPanel.CommandListViewCell()); + } + + class CommandListViewCell extends ListCell { + @Override + protected void updateItem(CommandList commandList, boolean empty) { + super.updateItem(commandList, empty); + + if (empty || commandList == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new HelpCard(commandList, getIndex() +1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 90bbf11de97..00000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,193 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String FXML = "MainWindow.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - private Stage primaryStage; - private Logic logic; - - // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private HelpWindow helpWindow; - - @FXML - private StackPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private StackPane personListPanelPlaceholder; - - @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; - - public MainWindow(Stage primaryStage, Logic logic) { - super(FXML, primaryStage); - - // Set dependencies - this.primaryStage = primaryStage; - this.logic = logic; - - // Configure the UI - setWindowDefaultSize(logic.getGuiSettings()); - - setAccelerators(); - - helpWindow = new HelpWindow(); - } - - public Stage getPrimaryStage() { - return primaryStage; - } - - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } - - /** - * Fills up all the placeholders of this window. - */ - void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - - CommandBox commandBox = new CommandBox(this::executeCommand); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); - } - - /** - * Sets the default size based on {@code guiSettings}. - */ - private void setWindowDefaultSize(GuiSettings guiSettings) { - primaryStage.setHeight(guiSettings.getWindowHeight()); - primaryStage.setWidth(guiSettings.getWindowWidth()); - if (guiSettings.getWindowCoordinates() != null) { - primaryStage.setX(guiSettings.getWindowCoordinates().getX()); - primaryStage.setY(guiSettings.getWindowCoordinates().getY()); - } - } - - /** - * Opens the help window or focuses on it if it's already opened. - */ - @FXML - public void handleHelp() { - if (!helpWindow.isShowing()) { - helpWindow.show(); - } else { - helpWindow.focus(); - } - } - - void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - logic.setGuiSettings(guiSettings); - helpWindow.hide(); - primaryStage.hide(); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { - try { - CommandResult commandResult = logic.execute(commandText); - logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - - if (commandResult.isShowHelp()) { - handleHelp(); - } - - if (commandResult.isExit()) { - handleExit(); - } - - return commandResult; - } catch (CommandException | ParseException e) { - logger.info("Invalid command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); - throw e; - } - } -} 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 0684b088868..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,74 +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; - - 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 1328917096e..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,46 +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; - - 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/PersonSummaryCard.java b/src/main/java/seedu/address/ui/PersonSummaryCard.java new file mode 100644 index 00000000000..35f6ef44354 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonSummaryCard.java @@ -0,0 +1,42 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.bluetooth.Person; + +public class PersonSummaryCard extends UiPart { + private static final String FXML="PersonSummaryCard.fxml"; + + public final Person person; + + @FXML + private Label name; + + @FXML + private Label userid; + + @FXML + private Label nric; + + @FXML + private Label age; + + @FXML + private Label mobile; + + + public PersonSummaryCard(Person person, int displayedIndex){ + super(FXML); + this.person = person; + + name.setText(person.getName()); + nric.setText(person.getNric()); + mobile.setText(person.getMobile()); + age.setText(Integer.toString(person.getAge())); + userid.setText(Integer.toString(person.getUserId())); + } + + @Override + public boolean equals(Object other){return true;} +} diff --git a/src/main/java/seedu/address/ui/PersonSummaryPanel.java b/src/main/java/seedu/address/ui/PersonSummaryPanel.java new file mode 100644 index 00000000000..e3f3c27ef71 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonSummaryPanel.java @@ -0,0 +1,39 @@ +package seedu.address.ui; + +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.bluetooth.Person; + +import java.util.logging.Logger; + +public class PersonSummaryPanel extends UiPart { + private static final String FXML = "BluetoothPingPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(BluetoothPingSummaryPanel.class); + + @FXML + private ListView bluetoothPingsListView; + + public PersonSummaryPanel(ObservableList personSummaryList) { + super(FXML); + bluetoothPingsListView.setItems(personSummaryList); + bluetoothPingsListView.setCellFactory(listView -> new PersonSummaryPanel.personListViewCell()); + } + + 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 PersonSummaryCard(person, getIndex() +1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index fc820e01a9c..4f43333c5f4 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -4,9 +4,8 @@ import java.io.IOException; import java.net.URL; - import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import seedu.address.ContactTracingMainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -81,7 +80,7 @@ private void loadFxmlFile(URL location, T root) { private static URL getFxmlFileUrl(String fxmlFileName) { requireNonNull(fxmlFileName); String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; - URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); + URL fxmlFileUrl = ContactTracingMainApp.class.getResource(fxmlFileNameWithFolder); return requireNonNull(fxmlFileUrl); } diff --git a/src/main/main.iml b/src/main/main.iml new file mode 100644 index 00000000000..908ad4f521a --- /dev/null +++ b/src/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/view/AppMainWindow.fxml b/src/main/resources/view/AppMainWindow.fxml new file mode 100644 index 00000000000..f15a125ac01 --- /dev/null +++ b/src/main/resources/view/AppMainWindow.fxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/BluetoothPingCard.fxml b/src/main/resources/view/BluetoothPingCard.fxml new file mode 100644 index 00000000000..5bbd346bacb --- /dev/null +++ b/src/main/resources/view/BluetoothPingCard.fxml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/BluetoothPingPanel.fxml b/src/main/resources/view/BluetoothPingPanel.fxml new file mode 100644 index 00000000000..1ba48a8fb72 --- /dev/null +++ b/src/main/resources/view/BluetoothPingPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/BluetoothPingSummaryCard.fxml b/src/main/resources/view/BluetoothPingSummaryCard.fxml new file mode 100644 index 00000000000..d80b681c0c7 --- /dev/null +++ b/src/main/resources/view/BluetoothPingSummaryCard.fxml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpCard.fxml b/src/main/resources/view/HelpCard.fxml new file mode 100644 index 00000000000..fc1000d02fd --- /dev/null +++ b/src/main/resources/view/HelpCard.fxml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/HelpPanel.fxml similarity index 75% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/HelpPanel.fxml index 8836d323cc5..fdb271e7285 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/HelpPanel.fxml @@ -4,5 +4,5 @@ - + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..17217e45ae6 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -12,7 +12,7 @@ + title="Contact Tracing App" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml deleted file mode 100644 index f08ea32ad55..00000000000 --- a/src/main/resources/view/PersonListCard.fxml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/PersonSummaryCard.fxml b/src/main/resources/view/PersonSummaryCard.fxml new file mode 100644 index 00000000000..fe09f1e8731 --- /dev/null +++ b/src/main/resources/view/PersonSummaryCard.fxml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..77609bfd1d1 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -17,19 +17,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.StorageManager; import seedu.address.testutil.PersonBuilder; public class LogicManagerTest { diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java index cb8714bb055..ec9ad5ce5d6 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java @@ -7,9 +7,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; import seedu.address.testutil.PersonBuilder; diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 5865713d5dd..0daa882be5a 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -16,10 +16,6 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.person.Person; import seedu.address.testutil.PersonBuilder; diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java index 80d9110c03a..2376f89f317 100644 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java @@ -5,11 +5,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - public class ClearCommandTest { @Test diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..ad49cf6e0e9 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -15,8 +15,6 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 0f77d8295f6..172c9ccddb8 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -13,9 +13,6 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; /** diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index 1c27530fa99..f0410709bcc 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -18,11 +18,6 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java index e0288792e72..5c988077a4d 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.testutil.EditPersonDescriptorBuilder; public class EditPersonDescriptorTest { diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java index 9533c473875..4c2f949041f 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java @@ -5,9 +5,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; - public class ExitCommandTest { private Model model = new ModelManager(); private Model expectedModel = new ModelManager(); diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index 9b15db28bbb..956799bab0a 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -15,9 +15,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; import seedu.address.model.person.NameContainsKeywordsPredicate; /** diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 4904fc4352e..43e5b3c06bd 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -5,9 +5,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; - public class HelpCommandTest { private Model model = new ModelManager(); private Model expectedModel = new ModelManager(); diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java index 435ff1f7275..600c3237618 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java @@ -8,10 +8,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - /** * Contains integration tests (interaction with the Model) and unit tests for ListCommand. */ diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index 5cf487d7ebb..646a9f483b3 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index d9659205b57..22b68cc032a 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -13,15 +13,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; diff --git a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java index e4c33515768..bf7336b7908 100644 --- a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java +++ b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import seedu.address.logic.commands.Command; import seedu.address.logic.parser.exceptions.ParseException; /** diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java index 27eaec84450..a8281d3d99c 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java @@ -7,8 +7,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.DeleteCommand; - /** * As we are only doing white-box testing, our test cases do not cover path variations * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index 2ff31522486..d81e08a5da1 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -34,8 +34,6 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java index 70f4f0e79c4..530ba604565 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.FindCommand; import seedu.address.model.person.NameContainsKeywordsPredicate; public class FindCommandParserTest { diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java index ac3c3af9566..cef9f1158c3 100644 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java @@ -16,8 +16,6 @@ import org.junit.jupiter.api.io.TempDir; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; public class JsonAddressBookStorageTest { private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonAddressBookStorageTest"); diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java index 188c9058d20..f4dfe86b936 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java @@ -10,7 +10,6 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.AddressBook; import seedu.address.testutil.TypicalPersons; public class JsonSerializableAddressBookTest { diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java index 16f33f4a6bb..8a019e8f554 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java @@ -14,7 +14,6 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; public class JsonUserPrefsStorageTest { diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 99a16548970..0e337acda71 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -11,9 +11,6 @@ import org.junit.jupiter.api.io.TempDir; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; public class StorageManagerTest { diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index d53799fd110..2bc9efdd76b 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -1,6 +1,5 @@ package seedu.address.testutil; -import seedu.address.model.AddressBook; import seedu.address.model.person.Person; /** diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e..4c906e6ca71 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java @@ -4,7 +4,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 90849945183..cb502d00d14 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -8,8 +8,6 @@ import java.util.Set; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/address/testutil/TestUtil.java index 896d103eb0b..20582a6fdea 100644 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ b/src/test/java/seedu/address/testutil/TestUtil.java @@ -6,7 +6,6 @@ import java.nio.file.Paths; import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; import seedu.address.model.person.Person; /** diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index fec76fb7129..4f2e532ae9b 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -15,7 +15,6 @@ import java.util.Arrays; import java.util.List; -import seedu.address.model.AddressBook; import seedu.address.model.person.Person; /** diff --git a/src/test/java/seedu/address/ui/UiPartTest.java b/src/test/java/seedu/address/ui/UiPartTest.java index 33d82d911b8..f2e4fa9ff69 100644 --- a/src/test/java/seedu/address/ui/UiPartTest.java +++ b/src/test/java/seedu/address/ui/UiPartTest.java @@ -11,7 +11,6 @@ import org.junit.jupiter.api.io.TempDir; import javafx.fxml.FXML; -import seedu.address.MainApp; public class UiPartTest { diff --git a/src/test/test.iml b/src/test/test.iml new file mode 100644 index 00000000000..bb46440c32d --- /dev/null +++ b/src/test/test.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file