diff --git a/README.adoc b/README.adoc index e36efe534bb..aa32b056299 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 3) += TA-Tracker 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://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]] - +https://travis-ci.org/AY1920S2-CS2103T-W17-4/main[image:https://travis-ci.org/AY1920S2-CS2103T-W17-4/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/PotatoCombat/main[image:https://ci.appveyor.com/api/projects/status/hj9hoqnof01ge3pp/branch/master?svg=true[Build status]] +https://coveralls.io/github/AY1920S2-CS2103T-W17-4/main?branch=master[image:https://coveralls.io/repos/github/AY1920S2-CS2103T-W17-4/main/badge.svg?branch=master[Coverage Status]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,22 +13,22 @@ 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. +* This is a desktop personal income and task management application for NUS Computing Teaching Assistants (TAs). +* It is intended for TAs who want to be able to track and manage all of their claimable hours of teaching in NUS. == Site Map -* <> -* <> -* <> -* <> -* <> +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/UserGuide.adoc[User Guide] +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/DeveloperGuide.adoc[Developer Guide] +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/AboutUs.adoc[About Us] +* https://github.com/AY1920S2-CS2103T-W17-4/main/blob/master/docs/ContactUs.adoc[Contact Us] == 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] +* TA-Tracker is a brownfield project based on the https://github.com/nus-cs2103-AY1920S2/addressbook-level3[AddressBook-Level3] + project created by https://se-education.org[SE-EDU initiative]. == Licence : link:LICENSE[MIT] diff --git a/build.gradle b/build.gradle index 93029ef8262..0127b86deca 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { } // Specifies the entry point of the application -mainClassName = 'seedu.address.Main' +mainClassName = 'tatracker.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -67,7 +67,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'taTracker.jar' destinationDir = file("${buildDir}/jar/") } @@ -133,8 +133,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level3', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', + 'site-name': 'TA-Tracker', + 'site-githuburl': 'https://github.com/AY1920S2-CS2103T-W17-4/main', 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..79f20ad41e2 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :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} + +Acknowledgements: Based on the AddressBook-Level3 project created by https://se-education.org[SE-EDU initiative]. + + 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]] [<>] +=== GABRIEL BENEDICT TEO JIAN CHENG +image::potatocombat.png[flip = "90", width = "150", align = "left"] +{empty} [https://github.com/PotatoCombat[github]] [<>] -Role: Project Advisor +Role: Project Lead + +Responsibilities: Scheduling and tracking ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== AAKANKSHA RAI +image::aakanksha-rai.png[width="150", align="left"] +{empty}[https://github.com/aakanksha-rai[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: PlantUML Expert, Deliverables and deadlines ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== CHUA YI JING +image::chuayijing.png[width="150", align="left"] +{empty}[https://github.com/chuayijing[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: Testing ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== FATIN NABILAH BTE SUHAIMI +image::fatin99.png[width="150", align="left] +{empty}[https://github.com/fatin99[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Documentation and Code Quality ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== HUANG HAOYI +image::eclmist.png[width="150", align="left"] +{empty}[https://github.com/Eclmist[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Integration ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..c58a512093c 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,20 @@ :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. -* *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` +== Bug reports, Suggestions + +Post in our https://github.com/AY1920S2-CS2103T-W17-4/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. + +== Email us + +You can also reach us at the following emails: + +Aakanksha Rai: + +`rai.aakanksha [at] gmail.com` + +Chua Yi Jing: + +`yijing.chua [at] gmail.com` + +Fatin Nabilah Bte Suhaimi: + +`fatinnbs [at] gmail.com` + +Gabriel Benedict Teo Jian Cheng: + +`gtforce [at] gmail.com` + +Huang Haoyi: + +`samuel.huanghaoyi [at] gmail.com` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..da7efa55b9d 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Developer Guide += TA-Tracker - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-4/main/tree/master By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` @@ -34,7 +34,7 @@ The *_Architecture Diagram_* given above explains the high-level design of the A 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, +`Main` has two classes called link:{repoURL}/src/main/java/tatracker/Main.java[`Main`] and link:{repoURL}/src/main/java/tatracker/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. @@ -56,7 +56,7 @@ 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. +For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. .Class Diagram of the Logic Component image::LogicClassDiagram.png[] @@ -77,11 +77,11 @@ The sections below give more details of each component. .Structure of the UI Component image::UiClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/src/main/java/tatracker/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 consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `StudentListPanel`, `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 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/tatracker/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, @@ -96,11 +96,16 @@ The `UI` 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. +link:{repoURL}/src/main/java/tatracker/logic/Logic.java[`Logic.java`] + +. `Logic` uses the `TaTrackerParser` class to parse the user command. +. `TaTrackerParser` uses specialized command parsers to parse the user command. +.. `StudentCommandParser`, `ModuleCommandParser`, `GroupCommandParser`, and `SessionCommandParser` +parse commands that interact with the models that they are named after. +.. Other parsers, such as `HelpCommandParser` and `FilterCommandParser` parse commands that +interact with the TA-Tracker user interface. . 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 command execution can affect the `Model` (e.g. adding a student). . 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. @@ -117,17 +122,17 @@ NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X .Structure of the Model Component image::ModelClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/src/main/java/tatracker/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. +* stores the TA-Tracker 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. + +As a more OOP model, we can store a `Tag` list in `TaTracker`, which `Student` can reference. This would allow `TaTracker` to only require one `Tag` object per unique `Tag`, instead of each `Student` needing their own `Tag` object. An example of how such a model may look like is given below. + + image:BetterModelClassDiagram.png[] @@ -137,17 +142,29 @@ image:BetterModelClassDiagram.png[] .Structure of the Storage Component image::StorageClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/src/main/java/tatracker/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +* can save the TA-Tracker data in json format and read it back. + +`TA-Tracker` saves the following data: + +* a list of `Module` objects representing the modules that the user is assisting. +** each `Module` contains a list of `Session`, representing the sessions that +the user has completed for that module. +** each `Module` contains a list of `Group`, representing the groups that the user is +in charge of, such as a tutorial or lab. +** each `Group` contains a list of `Student`, representing the students enrolled in +the respective groups. + +* a separate list of `Session` objects representing the sessions that the user has scheduled in the future. [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `tatracker.commons` package. == Implementation @@ -157,39 +174,39 @@ This section describes some noteworthy details on how certain features are imple === [Proposed] Undo/Redo feature ==== Proposed Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. +The undo/redo mechanism is facilitated by `VersionedTaTracker`. +It extends `TaTracker` with an undo/redo history, stored internally as an `taTrackerStateList` and `currentStatePointer`. Additionally, it implements the following operations: -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +* `VersionedTaTracker#commit()` -- Saves the current TA-Tracker state in its history. +* `VersionedTaTracker#undo()` -- Restores the previous TA-Tracker state from its history. +* `VersionedTaTracker#redo()` -- Restores a previously undone TA-Tracker state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitTaTracker()`, `Model#undoTaTracker()` and `Model#redoTaTracker()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedTaTracker` will be initialized with the initial TA-Tracker state, and the `currentStatePointer` pointing to that single TA-Tracker state. image::UndoRedoState0.png[] -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th student in the TA-Tracker. The `delete` command calls `Model#commitTaTracker()`, causing the modified state of the TA-Tracker after the `delete 5` command executes to be saved in the `taTrackerStateList`, and the `currentStatePointer` is shifted to the newly inserted TA-Tracker state. image::UndoRedoState1.png[] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add n/David ...` to add a new student. The `add` command also calls `Model#commitTaTracker()`, causing another modified TA-Tracker state to be saved into the `taTrackerStateList`. image::UndoRedoState2.png[] [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`. +If a command fails its execution, it will not call `Model#commitTaTracker()`, so the TA-Tracker state will not be saved into the `taTrackerStateList`. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the student was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoTaTracker()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous TA-Tracker state, and restores the TA-Tracker to that state. image::UndoRedoState3.png[] [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. +If the `currentStatePointer` is at index 0, pointing to the initial TA-Tracker state, then there are no previous TA-Tracker states to restore. The `undo` command uses `Model#canUndoTaTracker()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. The following sequence diagram shows how the undo operation works: @@ -197,16 +214,16 @@ image::UndoSequenceDiagram.png[] 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 `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. +The `redo` command does the opposite -- it calls `Model#redoTaTracker()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the TA-Tracker to that state. [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. +If the `currentStatePointer` is at index `taTrackerStateList.size() - 1`, pointing to the latest TA-Tracker state, then there are no undone TA-Tracker states to restore. The `redo` command uses `Model#canRedoTaTracker()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. Commands that do not modify the TA-Tracker, such as `list`, will usually not call `Model#commitTaTracker()`, `Model#undoTaTracker()` or `Model#redoTaTracker()`. Thus, the `taTrackerStateList` remains unchanged. image::UndoRedoState4.png[] -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. 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. +Step 6. The user executes `clear`, which calls `Model#commitTaTracker()`. Since the `currentStatePointer` is not pointing at the end of the `taTrackerStateList`, all TA-Tracker 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. image::UndoRedoState5.png[] @@ -218,23 +235,320 @@ image::CommitActivityDiagram.png[] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire TA-Tracker. ** 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). +** Pros: Will use less memory (e.g. for `delete`, just save the student being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. +* **Alternative 1 (current choice):** Use a list to store the history of TA-Tracker 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`. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedTaTracker`. * **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[] +//tag::moduleview[] +=== Module View +Module view is the term used to characterise the different functionalities +related to the modules and groups that the user is affiliated with. + +==== Model Framework +The following class diagram shows how different classes are related in the +functioning of the module view. + +.Module View - Class Diagram +image::ModuleModelClassDiagram.png[] + +The TaTracker model class contains a UniqueModuleList which helps it keep track +of the different modules the user is associated with. Each module contains a +UniqueGroupList and a UniqueSessionList. + +The UniqueGroupList contains a list of all the groups of a module that the user +is affiliated with. Each group contains a UniqueStudentsList that contains the +students in that group. + +The UniqueSessionList contains a list of all the done sessions associated with the module. +This list is used in the TSS view. + +==== Implementation of the Module Add and Delete Commands + +The following sequence diagram shows the sequence of commands that take place +between the logic and model components of the TA-Tracker when the user enters the +command 'module add m/CS2103 n/Software Engineering'. + +Note: This diagram assumes that there is no module with the module code 'CS2103' +pre-existing in the TA-Tracker. + +.Module Add - Sequence Diagram +image::AddModuleSequenceDiagram.png[] + +1. LogicManager uses the TATrackerParser to first parse the user command. + +2. The TATrackerParser sees that the command is of type module and passes the +command to the ModuleCommandParser. + +3. The ModuleCommandParser sees that the command is of type add and passes the +arguments to the AddModuleCommandParser. + +4. The AddModuleCommandParser creates a Module with the given module code and +name. + +5. The AddModuleCommandParser then creates an AddModuleCommand object and passes +it the created module. The parser then returns the AddModuleCommand + +6. LogicManager calls AddModuleCommad's execute method. The AddModuleCommand object +checks whether a module with the given module code already exists in TA-Tracker. +If it does, a command exception is thrown saying that a module with the given module +code already exists in the TA-Tracker. + +7. If no such module exists, the module is added to the TA-Tracker. + +8. The SortGroupCommand returns a CommandResult with a success message. + +The command used to delete a module has been implemented in a similar way. Tha main +difference is that the DeleteModuleCommand checks whether an object with the given +module code exists in the TA-Tracker. If no such module exists, a command exception +is thrown saying that a module with the given module code doesn't exist. If it does +exist, first all the sessions linked to that module are removed. +Then the module is removed from the TA-Tracker. + +==== Implementation of the Group Add and Delete Commands + +The following activity diagram shows the steps taken by the AddGroupCommand object +when the execute method is called. + +.Group Add - Activity Diagram +image::AddGroupActivityDiagram.png[] + +It should be noted that these are the steps followed assuming that no exception is +thrown. Before getting the module from TA-Tracker's model, the DeleteGroupCommand object +checks whether such a module even exists. If it doesn't exists, it throws a command +exception saying that no such module exists. Before adding a group to the module, the +object even checks whether the module already has a group with the given group code. +If it exists, a command exception is thrown saying that there is already a group +with the given group code. + +The interactions between the logic and model components when adding a group are similar +to the interactions when deleting a group as shown below. + +The following sequence diagram shows the interactions between the logic and model +components when the user inputs the command 'group delete m/CS2103 g/G03'. + +Note: This diagram is under the case where a group with the group code G03 does exist +in the module with module code CS2103 inside the TA-Tracker. + +.Group Delete - Sequence Diagram +image::DeleteGroupSequenceDiagram.png[] + +1. LogicManager uses the TATrackerParser to first parse the user command. + +2. The TATrackerParser sees that the command is of type group and passes the +command to the GroupCommandParser. + +3. The GroupCommandParser sees that the command is of type delete and passes the +arguments to the DeleteGroupCommandParser. + +4. The DeleteGroupCommandParser creates a Module with the given module code and +a group with the given group code. + +5. The DeleteGroupCommandParser then creates a DeleteGroupCommand object and passes +it the created module and group. The parser then returns the DeleteGroupCommand + +6. LogicManager calls DeleteGroupCommand's execute method. The DeleteGroupCommand object +checks whether a module with the given module code already exists in TA-Tracker. +If it doesn't, a command exception is thrown saying that a module with the given module +code doesn't exist in the TA-Tracker. + +7. If the module exists, the DeleteGroupCommand object retrieves the module from the +model and checks whether the module has a group with the given group code. If it doesn't, +a command exception is thrown saying that no such group exists. If the group does +exist, it is removed from the module. + +8. The SortGroupCommand returns a CommandResult with a success message. + +==== Implementation of the Sort Command + +The sort command allows the user to sort the students in the module view either alphabetically +or by rating. + +The sort command can be used in three ways: + +1. sort g/GROUP_CODE m/MODULE_CODE t/TYPE : When a user enters the command in this +manner, they are sorting all the students of the given group in the given module +by type TYPE (which can be either alphabetical or by rating). + +2. sort g/MODULE_CODE t/TYPE : When a user enters a command in this manner, they +are sorting all the students of all the groups in the given module by type TYPE +(which can be either alphabetical or by rating). + +3. sort t/TYPE : When a user enters a command in this manner, they are sorting all +students of all groups of all the modules in the TA-Tracker by the type TYPE (which +can be either alphabetical or by rating). + +Since these sort commands function differently but use the same parser, the following +class structure is used. + +.Sort Commands - Class Diagram +image::SortCommandsClassDiagram.png[] + +Since the different commands use the same parser, the SortCommandParser needs to check +which prefixes have been passed and return the appropriate command accordingly. +The following activity diagram shows the steps the SortCommandParser takes once +its parse command is called (assuming that no exception is thrown). + +If the user enters a sort command with no valid prefix, a command exception is thrown +thats explains the usage of the sort command. + +.SortCommandParser - Activity Diagram +image::SortParserActivityDiagram.png[] + +The following sequence diagram illustrates the interactions between the logic and +model components when the user enters the command 'sort m/CS2103 g/G03 t/alpha'. + +Note: To allow the user to type quickly, for type both 'alpha' and 'alphabetically' +sort the students lexicographically. + +.Sort - Sequence Diagram +image::SortGroupSequenceDiagram.png[] + +1. LogicManager uses the TATrackerParser to first parse the user command. + +2. The TATrackerParser sees that the command is of type sort and passes the +command to the SortCommandParser. + +3. The SortCommandParser performs the steps shown in the previous activity diagram +and creates and returns a SortGroupCommand. + +4. LogicManager calls SortGroupCommand's execute method. + +5. The SortGroupCommand creates a Module with the given module code and a group with +the given group code. The SortGroupCommand object checks whether a module with the +given module code already exists in TA-Tracker. +If it doesn't, a command exception is thrown saying that a module with the given module +code doesn't exist in the TA-Tracker. + +6. If the module exists, the SortGroupCommand object retrieves the module from the +model and checks whether the module has a group with the given group code. If it doesn't, +a command exception is thrown saying that no such group exists. + +7. If the group does exist, it is sorted according to the type of sort specified. + +8. The SortGroupCommand returns a CommandResult with a success message. + +//tag::studentview[] +=== Student View +Student view is the term used to characterise the different functionalities +related to the students that the user is affiliated with. + +==== Model Framework +The following class diagram shows how different classes are related in the +functioning of the student view. + +.Student View - Class Diagram +image::StudentModelClassDiagram.png[] + +The TaTracker model class contains a UniqueStudentList which helps it keep track +of the different students the user is associated with. Each student has a name, +matric and a default rating of 3/5 which can be edited by the user later. Each +student also has a Phone and an Email, which is represented as an empty string +if the user does not include the optional values. + +==== Implementation of the Student Add and Delete Commands + +The following sequence diagram shows the sequence of commands that take place +between the logic and model components of the TA-Tracker when the user enters the +command 'student add n/John Doe p/98765432 e/johnd@example.com m/A0181234G'. + +Note: This diagram assumes that there is no student with the matric number 'A0181234G' +pre-existing in the TA-Tracker. + +.Student Add - Sequence Diagram +image::AddStudentSequenceDiagram.png[] + +1. LogicManager uses the TATrackerParser to first parse the user command. + +2. The TATrackerParser sees that the command is of type student and passes the +command to the StudentCommandParser. + +3. The StudentCommandParser sees that the command is of type add and passes the +arguments to the AddStudentCommandParser. + +4. The AddStudentCommandParser creates a Student with the given parameters. + +5. The AddStudentCommandParser then creates an AddStudentCommand object and passes +it the created module. The parser then returns the AddStudentCommand + +6. LogicManager calls AddStudentCommand's execute method. The AddStudentCommand object +checks whether a student with the given matric number already exists in TA-Tracker. +If it does, a command exception is thrown saying that a student with the matric number +already exists in the TA-Tracker. + +7. If no such student exists, the student is added to the TA-Tracker. + +The command used to delete a student has been implemented in a similar way. The main +difference is that the DeleteStudentCommand checks whether an object with the given +matric number exists in the TA-Tracker. If no such student exists, a command exception +is thrown saying that a student with the given matric number doesn't exist. If it does +exist, the student is removed from the TA-Tracker. + +Note: This diagram assumes that there exists a student with the matric number 'A0181234G' +pre-existing in the TA-Tracker. + +.Student Delete - Sequence Diagram +image::DeleteStudentSequenceDiagram.png[] + +1. LogicManager uses the TATrackerParser to first parse the user command. + +2. The TATrackerParser sees that the command is of type student and passes the +command to the StudentCommandParser. + +3. The StudentCommandParser sees that the command is of type delete and passes the +arguments to the DeleteStudentCommandParser. + +4. The DeleteStudentCommandParser creates a Student with the given matric number + +5. The DeleteStudentCommandParser then creates a DeleteStudentCommand object and passes +it the created student. The parser then returns the DeleteStudentCommand + +6. LogicManager calls DeleteStudentCommand's execute method. The DeleteGroupCommand object +checks whether a student with the given matric number already exists in TA-Tracker. +If it doesn't, a command exception is thrown saying that a student with the given matric number +doesn't exist in the TA-Tracker. + +7. If the student exists, the DeleteStudentCommand object retrieves the student from the +model and removes the student. + +//tag::tssview[] +=== TSS View +TSS view is the term used to characterise the different functionalities +related to the done sessions that the user is affiliated with. + +==== Model Framework +The following class diagram shows how different classes are related in the +functioning of the TSS view. + +.TSS View - Class Diagram +image::TssModelClassDiagram.png[] + +The TaTracker model class contains a TSS which helps it keep track of the total +hours the user is associated with. Each TSS contains the TotalHours of all the +done sessions, a rate which represents how much the user is paid per hour and +the total earnings of the user. + +.TSS View - Activity Diagram +image::TssActivityDiagram.png[] + +1. A DoneSessionCommand is called, +2. TotalHours is incremented according to the duration of the completed session. +3. The duration of the completed session is calculated by subtracting StartDateTime +of the session from EndDateTime and then rounding up the value to a whole. +4. TotalEarnings is calculated by multiplying TotalHours with Rate + // tag::dataencryption[] === [Proposed] Data Encryption @@ -242,6 +556,114 @@ _{Explain here how the data encryption feature will be implemented}_ // end::dataencryption[] +// tag::syntaxhighlighting[] +=== [Proposed] Syntax Highlighting feature + +The syntax highlighting feature extends the `CommandBox` in the `MainWindow` of TA-Tracker +by colouring the font of valid command words as the user is typing. Currently, when a user +inputs an invalid command, the `CommandBox` highlights the entire command in red. This feature +will improve on this existing feature by changing the highlighting to be in real-time, +and on a per word basis. + +==== Proposed Implementation of the Syntax Highlighting feature + +When the user inputs a command in the `CommandBox`, the `CommandBox` will be highlight the following: + +1. All valid command words. If a command word has more than one word, +then the syntax highlighting will only appear when all of these words are in the `CommandBox`. + +2. All valid prefixes for each current command. + +3. The values of each valid prefix, if they are valid. + +In order to keep track of all commands and parameters, the `CommandDictionary` +will store these information in a `Set`. To support the storage of all commands, +each command should have a class variable named `COMMAND_WORD`. + +A `CommandBox` has a `TextField` that the user inputs to. +To detect whether the user has changed their command input, +the CommandBox registers an event listener that is triggered whenever +the value within the `TextField` has changed. + +In the following diagrams, the `CommandBox` behaves as an input buffer that that listens to keyboard inputs. + +.Syntax Highlighting - Full Activity Diagram +image::SyntaxHighlighting.png[] + +1. `CommandBox` listens to events that change the value in its `TextField` component. + +2. User inputs a character + +(in other words, the user changes the value stored in the `TextField` of the `CommandBox`). + +3. CommandBox reads the new `user input` (value) in the `TextField`. + +4. If the `user input` has a command word that exists in the `CommandDictionary` +(i.e. a valid command word), highlight it. + + a. While the `user input` has prefixes that exists in the `CommandDictionary`, + (i.e. any valid prefixes), highlight them. + + . For each prefix, if it has a valid value in the `user input`, + (i.e. the prefix value is valid), highlight it. + +When the user inputs the `ENTER` key, the user input is executed and the syntax highlighting is removed. + +==== Design Considerations + +===== Aspect #1: How to execute syntax highlighting in real-time + +When the user is typing their command, the `CommandBox` will need to highlight all valid command words, prefixes, and parameters. + +[width="100%",cols="33%,<33%,<35%",options="header",] +|===================== +| Solution | Pros | Cons + +| **Alternative 1 (current choice)** + +Register an event listener in `CommandBox` that triggers when the command text in it changes. +| Will have improved performance as the program can idle. +| Requires careful implementation of JavaFX events and event listeners. + +| **Alternative 2** + +Constantly update the syntax highlighting (in an infinite loop) when the user input is not blank. +| Simplest implementation, for example using a constantly running update loop. +| May have performance issues, since the update loop will be running even when the `CommandBox` is inactive. + +| **Alternative 3** + +Subscribe to a application-wide update loop in a modified TA-Tracker. +| Allows integration of other real-time features, such as application notifications. +| Requires major refactoring in TA-Tracker for the large-scale update loop. + +|===================== + +===== Aspect #2: Range of syntax highlighting + +Which inputs should the syntax highlighting be limited to. There are three types of inputs: +invalid, valid and unknown inputs. + +[width="100%",cols="33%,<33%,<35%",options="header",] +|===================== +| Solution | Pros | Cons + +| **Alternative 1 (current choice)** + +Highlight valid inputs in green +| User only need to know that their input is correct. +| Valid inputs may be obscured due to the choice of colour. + +| **Alternative 2** + +Highlight invalid inputs in red +| User will know that they need to correct their invalid inputs. +| Feature already exists when submitting an invalid full command. + +The scope of invalid inputs is too broad. + +| **Alternative 3** + +All inputs +| All inputs verified in the `CommandBox`. +| Too many colours may confuse the user. + +|===================== +// end::syntaxhighlighting[] + === Logging We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. @@ -262,6 +684,46 @@ 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`). +[[Implementation-Find]] +=== Find Command + +Find Command should be used under the current view. Find Command returns specified search that contains any of the given keywords. +The search functionality is crucial to the user's experience with TaTracker. + +This section will describe in detail the implementation of the find command. + +The activity diagram below summarises what happens when a user executes the find command : + +image::FindCommandActivityDiagram.png[] + +Figure 1 Activity Diagram for the Execution of Find Command + +==== Implementation + +Find Feature consists of three main parts: + +1. validate and parse user input + +2. create a filtering predicate from user's specified keywords + +3. update the filtered list with the filtering predicate + +The find feature is facilitated by the following classes: + + * `FindCommandParser` + +It validates and parses user input to an instance of FindCommand. + + + * `FindKeywordDescriptor` + +It stores keywords specified by the user. + +image::FindCommandClassDiagram.png[] + +Figure 2 Class Diagram of Find Feature + + == Documentation Refer to the guide <>. @@ -279,13 +741,17 @@ Refer to the guide <>. *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 +* targets NUS Computing Teaching Assistants +* has a need to track and manage all their claimable hours of teaching +* has a need to keep track of their tasks and reminders (TA-related and/or personal) +* prefer apps on desktop over other platforms +* types quickly and prefers it over mouse +* experiences no discomfort with CLI navigation -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +*Value proposition*: + +* congregates all information regarding claimable hours of teaching in a single location +* provides desired (TSS) format back to users for convenient viewing [appendix] == User Stories @@ -297,33 +763,253 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un |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 | +|`* * *` |TA |see an overview of events in a week |know what I have that week in a glance -|`* * *` |user |delete a person |remove entries that I no longer need +|`* *` |TA |give students ratings |keep a track of student participation in class -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* *` |TA |delete tasks and events |remove cancelled tasks and events from my session tracker -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`*` |TA |get a message when a new task clashes with an old one |prevent clashes in my schedule -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +|`* * *` |TA |store and retrieve details of my students |I can get details relating to students whenever necessary + +|`* *` |TA |be able to get tasks on a particular date | + +|`* *` |TA |filter by a module |see events relating to a particular module clearly + +|`*` |TA |state that a task is recurring |prevent the need to put a recurring task in my schedule each week + +|`* * *` |TA |see all my claimable hours in the TSS format |type my claims easily at the end of the semester + +|`* * *` |TA |set my hourly rate |get the value of my estimated pay according to the latest rate of the semester + +|`* *` |TA |get information on how many hours I've worked so far |keep track of how much work I've done + +|`* * *` |user |change between the different pages |view the information on the different pages + +|`* * *` |TA |add students to a particular module | -_{More to be added}_ +|`* *` |TA |store my students' email ids |retrieve their email ids when I need to contact them + +|`* * *` |TA |add multiple modules |keep track of the different modules I am a TA for + +|`* * *` |TA |add a tutorial/lab group |keep track of the different tutorial and lab groups I conduct + +|`* *` |TA |delete a tutorial group |remove tasks relating to a tutorial group I am no longer the TA of + +|`* *` |TA |delete a module |remove tasks relating to a module I am no longer the TA of + +|`* * *` |TA |edit student details | + +|`* * *` |TA |remove students from a tutorial or lab group |no longer have details of students that are no longer in my tutorial/lab group + +|`* * *` |TA |mark a session as done |automatically get filled in my TSS claim section. + +|`* *` |TA |give students ratings |keep a track of student participation in class + +|`* *` |TA |delete tasks and events |remove cancelled tasks and events from my session tracker + +|`* * *` |TA |schedule consultation sessions with my students |keep track of claimable hours spent in consultations + +|`*` |TA |get a message when a new task clashes with an old one |prevent clashes in my schedule + +|`* * *` |TA |store and retrieve details of my students |I can get details relating to students whenever necessary + +|`* *` |TA |be able to get tasks on a particular date | + +|`* *` |TA |filter by a module |see events relating to a particular module clearly + +|`*` |TA |state that a task is recurring |prevent the need to put a recurring task in my schedule each week + +|`* * *` |TA |see all my claimable hours in the TSS format |type my claims easily at the end of the semester + +|`* * *` |TA |set my hourly rate |get the value of my estimated pay according to the latest rate of the semester + +|`* *` |TA |get information on how many hours I've worked so far |keep track of how much work I've done + +|`* * *` |user |change between the different pages |view the information on the different pages + +|`* * *` |TA |add students to a particular module | + +|`* *` |TA |store my students' email ids |retrieve their email ids when I need to contact them + +|`* * *` |TA |add multiple modules |keep track of the different modules I am a TA for + +|`* * *` |TA |add a tutorial/lab group |keep track of the different tutorial and lab groups I conduct + +|`* *` |TA |delete a tutorial group |remove tasks relating to a tutorial group I am no longer the TA of + +|`* *` |TA |delete a module |remove tasks relating to a module I am no longer the TA of + +|`* * *` |TA |edit student details | + +|`* * *` |TA |remove students from a tutorial or lab group |no longer have details of students that are no longer in my tutorial/lab group + +|`* * *` |TA |mark a session as done |automatically get filled in my TSS claim section. + +|`*` |user |change the default view of the application | + +|======================================================================= [appendix] == Use Cases +:sectnums!: // Disables section numbering to avoid typing [discrete] tag for headers + +(For all use cases below, the *System* is the `TA-Tracker` and the *Actor* is the `user`, unless specified otherwise) + +[discrete] +=== Use case: Viewing a page + +*MSS* + +1. User requests to view a different page. +2. TA-Tracker layout changes to show the new page. + ++ +Use case ends. + +*Extensions* + +* 1a. The requested page is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: Viewing the help menu + +*MSS* + +1. User requests to view the help menu. +2. TA-Tracker shows the list of commands. ++ +Use case ends. + +[discrete] +=== Use case: Change default view + +*MSS* + +1. User requests to change the default view to a specified page. +2. TA-Tracker changes the default view. +3. TA-Tracker shows the default view. ++ +Use case ends. + +*Extensions* + +* 1a. The given page is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: Change the hourly pay rate + +*MSS* + +1. User requests to change the hourly pay rate to a specified amount. +2. TA-Tracker changes the pay rate. +3. TA-Tracker shows an edited TSS claims page the total pay adjusted to reflect the new pay rate. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given rate is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: Add student + +*MSS* + +1. User requests to add a student. +2. TA-Tracker adds new student. +3. TA-Tracker layout changes to show the student list page. + ++ +Use case ends. + +*Extensions* + +* 1a. The input required (eg. Matric Number) to add a student is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: Add module + +*MSS* + +1. User requests to add a new module. +2. TA-Tracker adds a new module. +3. TA-Tracker layout changes to show the session list page. + ++ +Use case ends. + +*Extensions* -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +* 1a. The given module code is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. [discrete] -=== Use case: Delete person +=== Use case: Add tutorial *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to add a new tutorial. +2. TA-Tracker shows adds a new tutorial linked to the specified module. +3. TA-Tracker layout changes to show the session list page. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given module code is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[none] +* 1a. The given class code is invalid. ++ +[none] +** 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: Edit Student + +*MSS* + +1. User requests to list students. +2. TA-Tracker shows a list of students. +3. User requests to edit a specific student in the list. +4. TA-Tracker edits the student according to the specified parameters. + Use case ends. @@ -334,32 +1020,377 @@ Use case ends. + Use case ends. -* 3a. The given index is invalid. +* 3a. The given matric number is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +* 3a. The given new input for the parameter(s) are invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +=== Use case: Delete student + +*MSS* + +1. User requests to show students page. +2. TA-Tracker shows a list of students categorised by tutorial. +3. User requests to delete a specific student in the list. +4. TA-Tracker deletes the student. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given matric number is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +=== Use case: Delete module + +*MSS* + +1. User requests to show sessions page. +2. TA-Tracker shows a list of sessions categorised by modules. +3. User requests to delete a specific module in the list. +4. TA-Tracker deletes the module and all of the sessions and tutorials in it. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given module code is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +[discrete] +=== Use case: Delete tutorial + +*MSS* + +1. User requests to show students page. +2. TA-Tracker shows a list of students categorised by tutorial. +3. User requests to delete a specific tutorial in the list. +4. TA-Tracker deletes the tutorial and all of the students in it. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given class code is invalid. ++ +[none] +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 3. + +=== Use case: Add session + +*MSS* + +1. User requests to add a session. +2. TA-Tracker creates the new session. +3. TA-Tracker adds the new session into the corresponding session group. +4. TA-Tracker switches to the Schedule View in order to display the new session. ++ +Use case ends. + +*Extensions* + +[none] +. 1a. The user requests to add a recurring session. +[none] +.. 1a1. TA-Tracker creates a new session, and labels it as recurring. ++ +Use case resumes at step 3. + +[none] +. 2a. The module does not exist. +[none] +.. 2a1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +[none] +. 2b. The session group does not exist. +[none] +.. 2b1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Edit session + +*MSS* + +1. User requests to view the Schedule View. +2. TA-Tracker switches to the Schedule View. +3. User requests to edit a specific session in the view. +4. TA-Tracker edits the session. +5. TA-Tracker replaces the session in the current view with the new version. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The Schedule View is empty. ++ +Use case ends. + +* 3a. The given session UID is invalid. +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Delete session + +*MSS* + +1. User requests to view the Schedule View. +2. TA-Tracker switches to the Schedule View. +3. User requests to delete a specific session in the view. +4. TA-Tracker deletes the session. +5. TA-Tracker removes the session from the current view. + +Use case ends. + +*Extensions* + [none] -** 3a1. AddressBook shows an error message. +* 2a. The Schedule View is empty. ++ +Use case ends. + +* 3a. The given session UID is invalid. +** 3a1. TA-Tracker shows an error message. + Use case resumes at step 2. -_{More to be added}_ +=== Use case: Mark a session as done + +*MSS* + +1. User requests to view the Schedule View. +2. TA-Tracker switches to the Schedule View. +3. User requests to mark a specific session in the view as done. +4. TA-Tracker marks the session as done. +5. TA-Tracker shows a tick next to the session in the current view. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The Schedule View is empty. ++ +Use case ends. + +* 3a. The given session UID is invalid. +** 3a1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Enroll student into class + +*MSS* + +1. User requests to enroll a student in a session group. +2. TA-Tracker registers the student in the session group. +3. TA-Tracker switches to the Student View. +4. TA-Tracker shows the student in the student list for the session group. ++ +Use case ends. + +*Extensions* + +[none] +. 2a. The student does not exist. +[none] +.. 2a1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +[none] +. 2b. The module does not exist. +[none] +.. 2b1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +[none] +. 2c. The session group does not exist. +[none] +.. 2c1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Kick student from class + +*MSS* + +1. User requests to withdraw a student from a session group. +2. TA-Tracker removes the student from the session group. +3. TA-Tracker switches to the Student View. +4. TA-Tracker shows that the student is removed from the student list for the session group. ++ +Use case ends. + +*Extensions* + +[none] +. 2a. The student does not exist. +[none] +.. 2a1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +[none] +. 2b. The module does not exist. +[none] +.. 2b1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +[none] +. 2c. The session group does not exist. +[none] +.. 2c1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Filter information for a specific module + +*MSS* + +1. User requests to filter information for a specific module. +2. TA-Tracker hides unrelated information from the current view. ++ +Use case ends. + +*Extensions* + +[none] +. 1a. The module does not exist. +[none] +.. 1a1. TA-Tracker shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Find sessions matching a keyword + +*MSS* + +1. User requests to find sessions related to a specific keyword. +2. TA-Tracker retrieves a list of sessions containing the keyword in any of their fields. +3. TA-Tracker shows the list of sessions. ++ +Use case ends. + +*Extensions* + +[none] +. 2a. The search did find any matches. +[none] +.. 2a1. TA-Tracker shows an empty list. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Exit the app + +*MSS* + +1. User requests to exit the app. +2. App window closes. ++ +Use case ends. + +:sectnums: // Enables section numbering again outside of the use cases [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}_ +. `**TAT**` should be able to run on any <> as long as it has `Java 11` installed. +. A user with above average typing speed for <> (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. `**TAT**` should be able to run with or without internet connection. +. `**TAT**` should work for a single user only. +. `**TAT**` should not require user to install. +. Features implemented should be testable using manual testing and automated testing. +. `**TAT**` should support screen resolution of 1920 x 1080 or higher. +. `**TAT**` should support the English <> only. Any locale from this link:https://docs.microsoft.com/en-us/cpp/c-runtime-library/language-strings?view=vs-2019[link] that starts with "en" will be supported. [appendix] == Glossary +[horizontal] +[[tat]] TAT:: +Stands for "Teaching Assistant Tracker". It is the application this developer guide is for. + +[[ta]] TA:: +Stands for "Teaching Assistant", and in our context limited to undergraduate and graduate teaching assistants in the National University of Singapore. A teaching assistant is an individual who assists a teacher with instructional responsibilities such as holding tutorials, labs, consultations, etc. + +[[nus]] NUS:: +Stands for "National University of Singapore". + +[[module]] Module:: +Refers to one of multiple academic courses in NUS. + +[[tutorial]] Tutorial:: +A tutorial is a regular meeting between a tutor and one or several students, for discussion of a subject that is being studied. + +[[api]] API:: +Stands for "Application Programming Interface" which simplifies programming by abstracting the underlying implementation and only exposing objects or actions the developer needs. + +[[locale]] Locale:: +Stands for a setting on the user's computer that defines the user's language and region. + +[[puml]] PlantUML:: +Stands for a software tool that we use to render the diagrams used in this document. + +[[nfr]] NFR:: +Stands for "Non-functional Requirement" [[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +Stands for commonly used Operating Systems (OS) such as Windows, Linux, Unix, OS-X + +[[regular-english-text]] Regular English Text:: +Stands for text with ordinary english grammar structures and vocabulary generally used by the public. +It excludes syntax related to programming and <>. + +[[system-administration]] System Administration:: +Stands for the field of work in which someone manages one or more systems, be they software, hardware, servers or workstations +with the goal of ensuring the systems are running efficiently and effectively. + +[[MSS]] MSS:: +Stands for Main Success Scenario that describes the interaction for a given use case, which assumes that nothing goes wrong. -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others [appendix] == Product Survey @@ -402,15 +1433,15 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ -=== Deleting a person +=== Deleting a student -. Deleting a person while all persons are listed +. Deleting a student while all students are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: List all students using the `list` command. Multiple students 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. + Expected: No student 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. diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..dd305a9ebc8 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += TA-Tracker - Documentation :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-4/main/tree/master == Introduction diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index 436c1777617..a35dc183079 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -33,7 +33,7 @@ What other user stories do you think AddressBook should support? Add those user === Exercise: Add a 'Rename tag' use case * Add a use case to the `DeveloperGuide.adoc` to cover the case of _renaming of an existing tag_. -e.g. rename the tag `friends` to `buddies` (i.e. all persons who had the `friends` tag will now have +e.g. rename the tag `friends` to `buddies` (i.e. all students who had the `friends` tag will now have a `buddies` tag instead) Assume that AddressBook confirms the change with the user before carrying out the operation. @@ -108,7 +108,7 @@ image::PrintableInterface.png[width=400] String getPrintableString(Printable... printables) { ---- + -The above method can be used to get a printable string representing a bunch of person details. +The above method can be used to get a printable string representing a bunch of student details. For example, you should be able to call that method like this: + [source,java] diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..1c764285ca5 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -37,7 +37,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu == Verifying the setup -. Run the `seedu.address.Main` and try a few commands +. Run the `tatracker.Main` and try a few commands . <> to ensure they all pass. == Configurations to do before writing code diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..c934efce767 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -35,11 +35,11 @@ See <> for more info on how to run tests using G We have three types of tests: . _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `tatracker.commons.StringUtilTest` . _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` +e.g. `tatracker.storage.StorageManagerTest` . Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +e.g. `tatracker.logic.LogicManagerTest` == Troubleshooting Testing diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..a092f62437a 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,9 +1,13 @@ -= AddressBook Level 3 - User Guide += TA-Tracker - User Guide :site-section: UserGuide :toc: :toc-title: :toc-placement: preamble +:toclevels: 3 :sectnums: +:sectnumlevels: 4 +:sectlinks: +:sectanchors: :imagesDir: images :stylesDir: stylesheets :xrefstyle: full @@ -12,36 +16,82 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:repoURL: https://github.com/AY1920S2-CS2103T-W17-4/main/ -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team W17-4` Since: `Jan 2010` 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! +Are you a Teaching Assistant at School of Computing who is tired of having to keep +track of all your claimable hours in some unappealing excel spreadsheet just so you +can fill up the TSS Claims form accurately at the end of the semester? Do you want +a place where you can keep track of everything related to your teaching duties, including +keeping a track of your strongest and weakest students? If so, TA-Tracker is the tool +for you! + +TA-Tracker is a productivity tool made for NUS Computing Teaching Assistants (TAs) +who want to be able to track and manage their students and all of their claimable +hours in NUS in one place. + +TA-Tracker is tailored towards TAs who *prefer to use a desktop application to +manage their teaching duties* and is well suited 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're interested, jump to the <> to get started! == Quick Start . Ensure you have Java `11` or above installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `tatracker.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your TA tracker. . Double-click the file to start the app. The GUI should appear in a few seconds. + + image::Ui.png[width="790"] + . Type the command in the command box and press kbd:[Enter] to execute it. + 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 +. Refer to <> for details of each command. + +NOTE: TA-Tracker data is saved periodically. + +[[Layout]] +== Layout +This is a brief overview of the layout of the TA-Tracker. The TA-Tracker is divided +into three different views - the Student View, the Session View and the Claims View. + +=== Student View +Under tha students tab, the Student View is used to show you the students that you're teaching. The students +have been grouped according to module and group. The Student view has been divided into +three sections. + +The first section shows a list of all the modules that you are a teaching +assistant for. + +The second section shows a list of all groups in a module of your choice. +If you haven't chosen anything, by default you will be shown the groups of the module +in the first index in the list of modules. + +The third section shows a list of all students in the group of your choice. If you +haven't chosen anything, by default you will be shown the students of the group in the +first index in the list of groups. + +The purpose of the student view is to allow a TA to keep track of their students. It will +show you information such as student name, matriculation number, ratings you have given +the student and contact details such as email and telegram id. + +=== Session View +Under the sessions tab, the Session View contains the upcoming tasks that you have. -. Refer to <> for details of each command. +=== Claims View +Under the claims tab, the Claim View contains the list of all the claimable hours you +have completed so far. -[[Features]] -== Features +The purpose of this view is to allow a TA to keep track of all their claims so that +they can easily enter it into the TSS claims form at the end of the semester. + +[[Usage]] +== Usage ==== *Command Format* @@ -50,128 +100,443 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. * 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. + +*Common Parameters* + +* `PAGE_NAME` - one of the following pages: +** sessions - Sessions: list of sessions according to date +** students - Students: list of students you are teaching +** claims - Claims Form: list of claimable sessions (only sessions that have been marked as done will be included here) +* `INDEX` - The position of an item in a list +* `MATRIC_NUMBER` - the matriculation number of a student +** Must start with an ‘A’ +** Must end with an letter +** Must have 7 numbers in between the start and end letters +* `MOD_CODE` - the unique code for the module +* `NAME` - based on the command, could mean student name or module name +* `GROUP_CODE` - the unique code for the group +* `GROUP_TYPE` - Could be one of the following: +** tutorial +** lab +** recitations +** other +* `SESSION_TYPE` - Could be one of the following: +** consult - Consultations +** tutorial - Tutorials +** grading - Grading and assessments +** prep - Class preparation +** todo - other tasks and notes +** lab - Lab +* `START`, `END` - a time parameter, in the following format: HH:mm +* `DATE` - a date parameter, in the following format: dd-MM-yyyy +* `->` - indicates that the command has more parameters than specified. These missing parameters will stated in a later section of the guide. +* `./` - indicates the presence of a tag that will be specified in a later section of the guide. ==== -=== Viewing help : `help` +=== Navigation + +==== Viewing help : `help` Format: `help` -=== Adding a person: `add` +==== Changing tabs : `goto` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +TODO - NOT IMPLEMENTED YET -[TIP] -A person can have any number of tags (including 0) +==== Exiting the program : `exit` + +Exits the program. + +Format: `exit` + +=== Student View + +[[AddModule]] +==== Adding a Module `module add` + +Adds a new module to the TA-Tracker. + +When a new module is created, the Student View will show the groups +and students of the new module (which will initially be empty). + +Format: `module add m/MOD_CODE n/NAME` + +[NOTE] +==== +No module with the given module code should exist in the TA-Tracker before adding +a new module. +==== + +==== +Examples: + +* `module add m/CS2103 n/Software Engineering` ++ +Adds a module with the module code CS2103 and name 'Software Engineering' +to the TA-Tracker. +==== + +[[DeleteModule]] +==== Deleting a Module `module delete` + +Deletes a module from the TA-Tracker. + +When a module is deleted, the student view will go back to its default +setting and show the details for the first module and first group in the +TA-Tracker. If there is no module and no group, it will show empty lists. + +When a module is deleted, all groups, students and sessions associated with +the module will also be deleted. + +Format: `module delete m/MOD_CODE` + +[NOTE] +==== +A module with the given module code must exist in the TA-Tracker before +you delete it. +==== + +==== +Examples: + +* `module delete m/CS2103` ++ +Deletes the module with the module code CS2103 from the TA-Tracker. +Adds a module with the module code CS2103 and name 'Software Engineering' +==== + +[[AddGroup]] +==== Adding a Group `group add` + +Adds a new group to the TA-Tracker. + +When a new group is created, the Student View will show the groups of the module +this group belongs to and students of the new group (which will initially be an empty list). + +Format: `group add g/GROUP_CODE m/MOD_CODE t/GROUP_TYPE` + +[NOTE] +==== +[horizontal] +* A module with the given module code must exist in the TA-Tracker before +you add a group to it. + +* No group with the given group code should exist inside the module. +==== + +==== +Examples: + +* `group add g/G03 m/CS2103 t/tutorial` ++ +Adds a group with the group code G03 which is a tutorial inside the module that +has module code CS2103. +==== + +[[DeleteGroup]] +==== Deleting a Group `group delete` +Deletes a group from the TA-Tracker. + +When a group is deleted, the Student View will show the details of the first group +in the given module. If there is no group in the module, it will show the empty lists. + +Format: `group delete g/GROUP_CODE m/MOD_CODE` + +[NOTE] +==== +[horizontal] +* A module with the given module code must exist in the TA-Tracker before +you add a group to it. + +* A group with the given group code must exist inside the module. +==== + +==== +Examples: + +* `group delete g/G03 m/CS2103` ++ +Deletes the group with the group code G03 from the module that +has module code CS2103. +==== + +[[EditGroup]] +==== Editing a Group `group edit` + +TODO: FIX AFTER COMMAND HAS BEEN IMPLEMENTED + +Edits a group in the TA-Tracker. + +This command can change the group code and group type of the group. It will keep +the students inside the group intact. + +When a group is edited, the Student View will show the groups of the module +this group belongs to and students of the edited group. + +Format: `group add g/GROUP_CODE m/MOD_CODE t/GROUP_TYPE` + +[NOTE] +==== +[horizontal] +* A module with the given module code must exist in the TA-Tracker before +you add a group to it. + +* No group with the given group code should exist inside the module. +==== +==== +Examples: + +* `group add g/G03 m/CS2103 t/tutorial` ++ +Adds a group with the group code G03 which is a tutorial inside the module that +has module code CS2103. +==== + +[[AddStudent]] +==== Adding a Student `student add` + +TODO: WRITE HOW THIS WORKS PROPERLY + +Adds a new student to the TA-Tracker. + +When a new student is added, the Student View will show the groups and students +of the module the new student belongs to. + +Format: `student add m/MATRIC_NUMBER [n/NAME] [e/EMAIL] [r/RATING] [t/TAG]...` + +[NOTE] +==== +[horizontal] +`NAME`:: the name of the student +`EMAIL`:: the email address of the student +`RATING`:: a number between 1 (poor) to 5 (excellent) +`TAG`:: a remark(s) for this student +`MOD_CODE`:: A module with the given module code must exist in the TA-Tracker before +you add a group to it. +`GROUP_CODE`:: A group with the given group code should exist inside the module. +==== +==== +Examples: + +* `student add id/A0123456J n/Alice m/CS2103 g/G03` ++ +Adds the student with the matriculation number A0123456J +and the name Alice inside group G03 of module CS2103. +==== + +[[DeleteStudent]] +==== Deleting a Student `student delete` + +TODO: WRITE HOW THIS WORKS PROPERLY + +Deletes a student from the TA-Tracker. + +When a student is deleted, the Student View will show the groups of the module the +student was removed from and the students of the group the student was removed from. +If there is no student inside the group, an empty list will be shown. + +Format: `student delete id/MATRIC_NUMBER g/GROUP_CODE m/MOD_CODE` + +[NOTE] +==== +[horizontal] +`MOD_CODE`:: A module with the given module code must exist in the TA-Tracker before +you add a group to it. +`GROUP_CODE`:: A group with the given group code should exist inside the module. +`MATRIC_NUMBER`:: A student with the given matric number must exist inside the group. +==== +==== Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `delete c/student m/A0123456J g/G03 m/CS2103` ++ +Deletes the student with the matriculation number A0123456J from the group G03 inside +the module CS2103. +==== + +[[EditStudent]] +==== Editing a Student `student edit` -=== Listing all persons : `list` +TODO: WRITE HOW THIS WORKS PROPERLY -Shows a list of all persons in the address book. + -Format: `list` +Edits a student in the TA-Tracker. -=== Editing a person : `edit` +When a student is edited, the Student View will show the groups of the module the +edited student is from and the students of the group the edited student exists in. -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Edits the student with the specified matriculation number. +The CATEGORY_UID used in this case is the student’s matriculation number. + +Format: `student edit m/MATRIC_NUMBER [n/NAME] [e/EMAIL] [r/RATING] [t/TAG]…​` **** -* 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, ... +* `MATRIC_NUMBER` - see Common Parameters + * 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. +** `NAME` - the name of the student +** `EMAIL` - the email address of the student +** `RATING` - a number between 1 (poor) to 5 (excellent) +** `TAG` - other notes about this student + +* Existing fields will be updated to the input values. + +* All existing `TAG(s)` on a student will be removed, +then replaced with the new tags, +i.e adding of tags is not cumulative. + +[TIP] +You can remove all the student’s tags by typing t/ without specifying any tags after it. **** Examples: -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `edit A0123456J p/91234567 e/johndoe@example.com` +Edits student `A0123456J` to have `91234567` as their phone number, +and `johndoe@example.com` as their email address. -=== Locating persons by name: `find` +* `edit A9876543K n/Betsy Crower t/` +Edits student `A9876543K` to have `Betsy Crower` as their name. +In addition, all existing tags will be removed. -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +[[Sort]] +==== Sort `sort` -**** -* 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` -**** +TODO: ADD DETAILS ONCE IMPLEMENTED + +[[FilterStudent]] +==== Filter `filter` + +TODO: ADD DETAILS ONCE IMPLEMENTED + +=== Session View + +[[AddSession]] +==== Adding a Session `session add` +TODO - UPDATE + +Adds a new type of session for an existing module group in the program. +The `CATEGORY_UID` for this command is the SESSION_TYPE code. + +Format: `add c/session s/START e/END d/DATE [-r] m/MOD_CODE [t/SESSION_TYPE] [n/NOTES]` + +[NOTE] +==== +[horizontal] +`START` `END` `DATE`:: see Common Parameters + +`[-r]`:: makes the session recur at the same time every week + +`MOD_CODE` `SESSION_TYPE`:: see Common Parameters + +`NOTES`:: notes for this session (eg: weekly tutorials or prep sessions) + +==== +==== Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `add c/session s/14:00 e/16:00 d/19-02-2020 m/CS2103T t/consult n/with Alice and Bob` ++ +Adds a consultation session on 19 Feb 2020, +from 2pm to 4pm, +with Alice and Bob. +==== +==== Deleting a Session `session delete` -// tag::delete[] -=== Deleting a person : `delete` +TODO - UPDATE -Deletes the specified person from the address book. + -Format: `delete INDEX` +Deletes a session from the TA-Tracker. -**** -* 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, ... -**** +Format: `session delete i/SESSION_UID` +==== Examples: -* `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. +* delete c/session i/10 +Deletes the session with the identifier of 10 +==== -// end::delete[] -=== Clearing all entries : `clear` +==== Editing a Session `session edit` +TODO - ADD DETAILS -Clears all entries from the address book. + -Format: `clear` +==== Marking a Session as Done `session done` -=== Exiting the program : `exit` +Labels a session as done. +If the session is claimable, it will appear as a new claim in the TSS view. -Exits the program. + -Format: `exit` +Format: `session done INDEX` -=== Saving the data +Marks the session with the given unique session identifier as done. -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +==== +Examples: -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +* `done 25` + +Marks the session with the unique session id of 25 as done. +==== -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +==== Filter `filter` + +TODO: ADD DETAILS ONCE IMPLEMENTED + +=== Claims View + +==== Filter `filter` + +TODO: ADD DETAILS ONCE IMPLEMENTED + +==== Changing the hourly rate `set` + +TODO: EDIT ONCE IMPLEMENTED + +Sets the hourly rate for the total income and claim computation. + +Format: `set rate AMOUNT` + +[NOTE] +==== +* AMOUNT is the amount you want to change the hourly rate to. +* To specify in exact dollars, you can write it as just the number (example: 20). +* To specify in exact dollars and cents, write it as a decimal up to 2 decimal places (example: 20.05). +==== + +Examples: + +* `set rate 25` + +Sets the current hourly rate to 25$. + +== GLOSSARY == 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. +*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 TA-Tracker 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` +TODO: UPDATE ONCE ALL COMMANDS HAVE BEEN WRITTEN ABOUT + +=== Student View +* *Add Module* `module add m/MOD_CODE` +* *Delete Module* `module delete m/MOD_CODE` +* *Add Group* `group add g/GROUP_CODE m/MOD_CODE` +* *Edit Group* +* *Delete Group* `group delete g/GROUP_CODE m/MOD_CODE` +* *Add Student* +* *Delete Student* +* *Edit Student* +* *Sort* +* *Filter* + +=== Session View +* *Add Session* +* *Delete Session* +* *Edit Session* +* *Mark as Done* +* *Filter* + +=== Claims View +* *Set Rate* +* *Filter* + + diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index d1e2ae93675..236a55ef47c 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -13,13 +13,13 @@ activate ui UI_COLOR ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteStudent(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveTaTracker(taTracker) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 7790472da52..1f450af67a5 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,20 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +TaTracker *-right-> "1" UniqueStudentList +TaTracker *-right-> "1" UniqueTagList +UniqueTagList -[hidden]down- UniqueStudentList +UniqueTagList -[hidden]down- UniqueStudentList UniqueTagList *-right-> "*" Tag -UniquePersonList o-right-> Person +UniqueStudentList o-right-> Student -Person o-up-> "*" Tag +Student o-up-> "*" Tag + +Student *--> Name +Student *--> Phone +Student *--> Email +Student *--> Matric +Student *--> Rating -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address @enduml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 7f8fe407f89..5c57b909c00 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -5,10 +5,10 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits TaTracker]) :Purge redunant states; - :Save AddressBook to - addressBookStateList; + :Save TaTracker to + taTrackerStateList; else ([else]) endif stop diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml deleted file mode 100644 index 1dc2311b245..00000000000 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ /dev/null @@ -1,69 +0,0 @@ -@startuml -!include style.puml - -box Logic LOGIC_COLOR_T1 -participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR -participant ":CommandResult" as CommandResult LOGIC_COLOR -end box - -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR -end box - -[-> LogicManager : execute("delete 1") -activate LogicManager - -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser - -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser - -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser - -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser - -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand - -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand - -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser -'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser - -AddressBookParser --> LogicManager : d -deactivate AddressBookParser - -LogicManager -> DeleteCommand : execute() -activate DeleteCommand - -DeleteCommand -> Model : deletePerson(1) -activate Model - -Model --> DeleteCommand -deactivate Model - -create CommandResult -DeleteCommand -> CommandResult -activate CommandResult - -CommandResult --> DeleteCommand -deactivate CommandResult - -DeleteCommand --> LogicManager : result -deactivate DeleteCommand - -[<--LogicManager -deactivate LogicManager -@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 016ef33e2e2..622fefcca75 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -4,59 +4,121 @@ skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR +package Model { + Class HiddenModel #FFFFFF +} + package Logic { + package Parser as ParserPkg { -package Parser { -Interface Parser <> -Class AddressBookParser -Class XYZCommandParser -Class CliSyntax -Class ParserUtil -Class ArgumentMultimap -Class ArgumentTokenizer -Class Prefix -} + note as Note1 + Each ActionY modifies the UI view of TA-Tracker -package Command { -Class XYZCommand -Class CommandResult -Class "{abstract}\nCommand" as Command -} + ---- + eg. Clear, Find, Help, Filter + end note -Interface Logic <> -Class LogicManager -} + package ModelX as ModelParser { + Class ModelXCommandParser + Class ActionXModelXCommandParser + ModelXCommandParser .down.> ActionXModelXCommandParser + + note as Note2 + Each ModelX has a set of valid ActionX + + ---- + ModelX: ActionX + + Module: Add, Delete + + Group: Add, Delete + + Student: Add, Delete, Edit + + Session: Add, Delete, Edit, Done, Start, Stop + end note + } + + Class ActionYCommandParser + Class TaTrackerParser + + TaTrackerParser .down.> ModelXCommandParser + + + Class ArgumentMultimap + Class ArgumentTokenizer + Class Prefixes + Class ParserUtil + Interface Parser <> + Class Prefix + + + Prefixes -right[hidden]- Parser +' ArgumentMultimap -right[hidden]- ArgumentTokenizer +' Prefixes -left[hidden]- ParserUtil +' ArgumentTokenizer -right[hidden]- Prefixes +' ParserUtil -right[hidden]- Parser -package Model{ -Class HiddenModel #FFFFFF + ActionXModelXCommandParser ..down..|> Parser + ActionXModelXCommandParser ..down..> ArgumentMultimap + ActionXModelXCommandParser ..down..> ArgumentTokenizer + ActionXModelXCommandParser ..down..> Prefixes + ActionXModelXCommandParser ..down..> ParserUtil + + ActionYCommandParser .down.|> Parser + ActionYCommandParser .down.> ArgumentMultimap + ActionYCommandParser .down.> ArgumentTokenizer + ActionYCommandParser .down.> Prefixes + ActionYCommandParser .down.> ParserUtil + + ArgumentMultimap <.left. ArgumentTokenizer + + ArgumentTokenizer .down.> Prefix + ParserUtil .down.> Prefix + Prefixes .down.> Prefix + +' ActionXModelXCommandParser -right- ActionYCommandParser + } + + package Command as ModelCommand { + package ModelX { + Class ActionXModelXCommand + } + + Class ActionYCommand + + Class CommandResult + Class "{abstract}\nCommand" as Command + + Command .up.> CommandResult + ActionXModelXCommand -up-|> Command + + ActionYCommand -right-|> Command + } + + Interface Logic <> + Class LogicManager + + Logic .down.> CommandResult + + LogicManager .up.|> Logic + LogicManager -down-> "1" TaTrackerParser + LogicManager .down.> CommandResult + LogicManager .down.> Command + + ActionYCommandParser <.right. TaTrackerParser + + ActionXModelXCommandParser .right.> ActionXModelXCommand + ActionYCommandParser .right.> ActionYCommand + + ModelXCommandParser .left[hidden].> ActionYCommandParser + ActionYCommandParser .left[hidden].> ActionXModelXCommand } Class HiddenOutside #FFFFFF -HiddenOutside ..> Logic - -LogicManager .up.|> Logic -LogicManager -->"1" AddressBookParser -AddressBookParser .left.> XYZCommandParser: creates > - -XYZCommandParser ..> XYZCommand : creates > -XYZCommandParser ..|> Parser -XYZCommandParser ..> ArgumentMultimap -XYZCommandParser ..> ArgumentTokenizer -ArgumentTokenizer .left.> ArgumentMultimap -XYZCommandParser ..> CliSyntax -CliSyntax ..> Prefix -XYZCommandParser ..> ParserUtil -ParserUtil .down.> Prefix -ArgumentTokenizer .down.> Prefix -XYZCommand -up-|> Command -LogicManager .left.> Command : executes > - -LogicManager --> Model -Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc - -Logic ..> CommandResult -LogicManager .down.> CommandResult -Command .up.> CommandResult -CommandResult -[hidden]-> Parser +HiddenOutside .down.> Logic + +Model <-left- LogicManager +Model <.left. Command + @enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index e85a00d4107..7d82655d70e 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,23 +5,23 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <>{ -Interface ReadOnlyAddressBook <> +Interface ReadOnlyTaTracker <> Interface Model <> Interface ObservableList <> -Class AddressBook -Class ReadOnlyAddressBook +Class TaTracker +Class ReadOnlyTaTracker Class Model Class ModelManager Class UserPrefs Class ReadOnlyUserPrefs -Package Person { -Class Person -Class Address +Package Student { +Class Student Class Email +Class Matric Class Name Class Phone -Class UniquePersonList +Class UniqueStudentList } Package Tag { @@ -32,25 +32,25 @@ Class Tag Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +TaTracker .up.|> ReadOnlyTaTracker ModelManager .up.|> Model Model .right.> ObservableList -ModelManager o--> "1" AddressBook +ModelManager o--> "1" TaTracker ModelManager o-left-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList o--> "*" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +TaTracker *--> "1" UniqueStudentList +UniqueStudentList o--> "*" Student +Student *--> Name +Student *--> Phone +Student *--> Email +Student *--> Matric +Student *--> "*" Tag Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Phone -[hidden]right-> Matric +Matric -[hidden]right-> Email -ModelManager -->"1" Person : filtered list +ModelManager -->"1" Student : filtered list @enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 6adb2e156bf..25e7cd26332 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -3,22 +3,40 @@ skinparam arrowThickness 1.1 skinparam arrowColor STORAGE_COLOR skinparam classBackgroundColor STORAGE_COLOR +skinparam linetype ortho +skinparam nodesep 50 Interface Storage <> Interface UserPrefsStorage <> -Interface AddressBookStorage <> +Interface TaTrackerStorage <> Class StorageManager Class JsonUserPrefsStorage -Class JsonAddressBookStorage +Class JsonTaTrackerStorage -StorageManager .left.|> Storage -StorageManager o-right-> UserPrefsStorage -StorageManager o--> AddressBookStorage +StorageManager .up.|> Storage +StorageManager "1" o-left-> UserPrefsStorage +StorageManager "1" o-right-> TaTrackerStorage + +JsonUserPrefsStorage -[hidden]up-> UserPrefsStorage +JsonTaTrackerStorage -[hidden]up-> TaTrackerStorage + +JsonUserPrefsStorage -[hidden]right-> JsonTaTrackerStorage + +JsonUserPrefsStorage .up.|> UserPrefsStorage + +JsonTaTrackerStorage .up.|> TaTrackerStorage +JsonTaTrackerStorage .down.> JsonSerializableTaTrackerStorage + +JsonSerializableTaTrackerStorage -down-> "*" JsonAdaptedModule +JsonSerializableTaTrackerStorage -down-> "*" JsonAdaptedSession + +JsonAdaptedModule -right-> "*" JsonAdaptedSession + +JsonAdaptedModule -down-> "*" JsonAdaptedGroup +JsonAdaptedGroup -down-> "*" JsonAdaptedStudent + +JsonAdaptedSession -down-> "1" JsonAdaptedDateTime : end +JsonAdaptedSession -down-> "1" JsonAdaptedDateTime : start -JsonUserPrefsStorage .left.|> UserPrefsStorage -JsonAddressBookStorage .left.|> AddressBookStorage -JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage -JsonSerializableAddressBookStorage .right.> JsonSerializablePerson -JsonSerializablePerson .right.> JsonAdaptedTag @enduml diff --git a/docs/diagrams/SyntaxHighlighting.puml b/docs/diagrams/SyntaxHighlighting.puml new file mode 100644 index 00000000000..ddaf08fc8e9 --- /dev/null +++ b/docs/diagrams/SyntaxHighlighting.puml @@ -0,0 +1,41 @@ +@startuml +start + +:Create input buffer; +:Input character; + +while () is ([else]) + + :Read input to buffer; + :Check command in buffer; + + if () then ([no command word]) + else ([has command word]) + :Highlight command word; + :Check all command prefixes; + + while () is ([has prefix]) + :Check prefix; + + if () then ([valid prefix]) + :Highlight prefix; + :Check prefix value; + + if () then ([valid value]) + :Highlight value; + + else ([invalid value]) + endif + + else ([invalid prefix]) + endif + + endwhile ([no more prefixes]) + endif + + :Input character; + +endwhile ([enter key pressed]) + +stop +@enduml diff --git a/docs/diagrams/TssActivityDiagram.puml b/docs/diagrams/TssActivityDiagram.puml new file mode 100644 index 00000000000..fd1a17e94b1 --- /dev/null +++ b/docs/diagrams/TssActivityDiagram.puml @@ -0,0 +1,16 @@ +@startuml +start +:A DoneSessionCommand is called; + +:The duration of the session is calculated +(duration = Math.ceil(endDateTime - startDateTime)); + +:TotalHours is incremented based on the duration of the session; + +:TotalEarnings is calculated based on the rate input +by the user (TotalEarnings = TotalHours * Rate); + +:return TotalEarnings to UI; + +stop +@enduml diff --git a/docs/diagrams/TssModelClassDiagram.puml b/docs/diagrams/TssModelClassDiagram.puml new file mode 100644 index 00000000000..091f7712166 --- /dev/null +++ b/docs/diagrams/TssModelClassDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml +package Model { + +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <> { +package TSS { +class TSS +class TotalEarnings +class TotalHours +class Rate +} +} + +Class HiddenOutside #FFFFFF + +TaTracker *--> "1" TSS +TSS *-->"1" TotalEarnings +TSS *-->"1" TotalHours +TSS *-->"1" Rate + +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..34bd48020d6 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,8 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class StudentListPanel +Class StudentCard Class StatusBarFooter Class CommandBox } @@ -33,25 +33,25 @@ UiManager -down-> MainWindow MainWindow --> HelpWindow MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel +MainWindow *-down-> StudentListPanel MainWindow *-down-> StatusBarFooter -PersonListPanel -down-> PersonCard +StudentListPanel -down-> StudentCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +StudentListPanel --|> UiPart +StudentCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow -down-|> UiPart -PersonCard ..> Model +StudentCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +StudentListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..641e7c3d9eb 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title Initial state package States { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..94a9d971e99 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "delete 5" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..0307abe0959 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "add n/David" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..551674062b8 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "undo" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..09b87ceb1ca 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "list" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab2:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..4af64b5c08a 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000 title After command "clear" package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab3:AddressBook__" + class State1 as "__ab0:TaTracker__" + class State2 as "__ab1:TaTracker__" + class State3 as "__ab3:TaTracker__" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 410aab4e412..91b4d8ed5f2 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -3,42 +3,42 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR participant "u:UndoCommand" as UndoCommand LOGIC_COLOR end box box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR +participant ":VersionedTaTracker" as VersionedTaTracker MODEL_COLOR end box [-> LogicManager : execute(undo) activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser +LogicManager -> TaTrackerParser : parseCommand(undo) +activate TaTrackerParser create UndoCommand -AddressBookParser -> UndoCommand +TaTrackerParser -> UndoCommand activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand --> TaTrackerParser deactivate UndoCommand -AddressBookParser --> LogicManager : u -deactivate AddressBookParser +TaTrackerParser --> LogicManager : u +deactivate TaTrackerParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : undoTaTracker() activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook +Model -> VersionedTaTracker : undo() +activate VersionedTaTracker -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook +VersionedTaTracker -> VersionedTaTracker :resetData(ReadOnlyTaTracker) +VersionedTaTracker --> Model : +deactivate VersionedTaTracker Model --> UndoCommand deactivate Model diff --git a/docs/diagrams/find/FindCommandClassDiagram.puml b/docs/diagrams/find/FindCommandClassDiagram.puml new file mode 100644 index 00000000000..91bce84a1cb --- /dev/null +++ b/docs/diagrams/find/FindCommandClassDiagram.puml @@ -0,0 +1,29 @@ +@startuml + +!include ../style.puml +show interface members +skinparam arrowThickness 1.1 +skinparam classBackgroundColor MODEL_COLOR + +abstract class Command + +interface Parser { + T parse(String userInput) +} + +class FindCommand + +class FindKeywordDescriptor + +class FindCommandParser + +Command <|.. FindCommand + +Parser <|-- FindCommandParser + +FindKeywordDescriptor <.. FindCommand +FindKeywordDescriptor <.. FindCommandParser + + +@enduml + diff --git a/docs/diagrams/module-view/AddGroupActivityDiagram.puml b/docs/diagrams/module-view/AddGroupActivityDiagram.puml new file mode 100644 index 00000000000..dbb52f898a1 --- /dev/null +++ b/docs/diagrams/module-view/AddGroupActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +start +:AddGroupCommand object is +created with the fields group + and module; + +:The execute method of AddGroupCommand + is called; + +:Get the module from TA-Tracker's + model; + +:Add group to the module; + + +:return command result; + +stop +@enduml diff --git a/docs/diagrams/module-view/AddModuleSequenceDiagram.puml b/docs/diagrams/module-view/AddModuleSequenceDiagram.puml new file mode 100644 index 00000000000..d9e432f30d1 --- /dev/null +++ b/docs/diagrams/module-view/AddModuleSequenceDiagram.puml @@ -0,0 +1,88 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":ModuleCommandParser" as ModuleCommandParser LOGIC_COLOR +participant ":AddModuleCommandParser" as AddModuleCommandParser LOGIC_COLOR +participant "a:AddModuleCommand" as AddModuleCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Module" as Module MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("module add \nm/CS2103 n/Software Engineering") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("module add \nm/CS2103 n/Software Engineering") +activate TaTrackerParser + +create ModuleCommandParser +TaTrackerParser -> ModuleCommandParser : ModuleCommandParser() +activate ModuleCommandParser +ModuleCommandParser --> TaTrackerParser +deactivate ModuleCommandParser + +TaTrackerParser -> ModuleCommandParser : parseCommand("add m/CS2103 \nn/Software Engineering") +activate ModuleCommandParser + +create AddModuleCommandParser +ModuleCommandParser -> AddModuleCommandParser : AddModuleCommandParser() +activate AddModuleCommandParser +AddModuleCommandParser --> ModuleCommandParser +deactivate AddModuleCommandParser + +ModuleCommandParser -> AddModuleCommandParser : parseCommand("m/CS2103 \nn/Software Engineering") +activate AddModuleCommandParser + +create Module +AddModuleCommandParser -> Module : Module("CS2103", "Software Engineering") +activate Module +Module --> AddModuleCommandParser +deactivate Module + +create AddModuleCommand +AddModuleCommandParser -> AddModuleCommand : AddModuleCommand(m) +activate AddModuleCommand +AddModuleCommand --> AddModuleCommandParser +deactivate AddModuleCommand + + + +AddModuleCommandParser --> ModuleCommandParser : a +deactivate AddModuleCommandParser +AddModuleCommandParser -[hidden]-> ModuleCommandParser +destroy AddModuleCommandParser + +ModuleCommandParser --> TaTrackerParser : a +deactivate ModuleCommandParser +ModuleCommandParser -[hidden]-> TaTrackerParser +destroy ModuleCommandParser + +TaTrackerParser --> LogicManager : a +deactivate TaTrackerParser + +LogicManager -> AddModuleCommand : execute() +activate AddModuleCommand + +AddModuleCommand -> Model : addModule(m) +activate Model +Model --> AddModuleCommand +deactivate Model + +create CommandResult +AddModuleCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> AddModuleCommand +deactivate CommandResult + +AddModuleCommand --> LogicManager : result +deactivate AddModuleCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/DeleteGroupSequenceDiagram.puml b/docs/diagrams/module-view/DeleteGroupSequenceDiagram.puml new file mode 100644 index 00000000000..22301744b58 --- /dev/null +++ b/docs/diagrams/module-view/DeleteGroupSequenceDiagram.puml @@ -0,0 +1,112 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":GroupCommandParser" as GroupCommandParser LOGIC_COLOR +participant ":DeleteGroupCommandParser" as DeleteGroupCommandParser LOGIC_COLOR +participant "d:DeleteGroupCommand" as DeleteGroupCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Module" as Module MODEL_COLOR +participant "g:Group" as Group MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +participant "M:Module" as ActualModule MODEL_COLOR +participant "G:Group" as ActualGroup MODEL_COLOR +end box + +[-> LogicManager : execute("group delete \nm/CS2103 g/G03") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("group delete \nm/CS2103 g/G03") +activate TaTrackerParser + +create GroupCommandParser +TaTrackerParser -> GroupCommandParser : GroupCommandParser() +activate GroupCommandParser +GroupCommandParser --> TaTrackerParser +deactivate GroupCommandParser + +TaTrackerParser -> GroupCommandParser : parseCommand("delete \nm/CS2103 g/G03") +activate GroupCommandParser + +create DeleteGroupCommandParser +GroupCommandParser -> DeleteGroupCommandParser : DeleteGroupCommandParser() +activate DeleteGroupCommandParser +DeleteGroupCommandParser --> GroupCommandParser +deactivate DeleteGroupCommandParser + +GroupCommandParser -> DeleteGroupCommandParser : parseCommand("m/CS2103 g/G03") +activate DeleteGroupCommandParser + +create Module +DeleteGroupCommandParser -> Module : Module("CS2103", "") +activate Module +Module --> DeleteGroupCommandParser +deactivate Module + +create Group +DeleteGroupCommandParser -> Group : Group("G03", null) +activate Group +Group --> DeleteGroupCommandParser +deactivate Group + +create DeleteGroupCommand +DeleteGroupCommandParser -> DeleteGroupCommand : DeleteGroupCommand(g, m) +activate DeleteGroupCommand +DeleteGroupCommand --> DeleteGroupCommandParser +deactivate DeleteGroupCommand + +DeleteGroupCommandParser --> GroupCommandParser : d +deactivate DeleteGroupCommandParser +DeleteGroupCommandParser -[hidden]-> GroupCommandParser +destroy DeleteGroupCommandParser + +GroupCommandParser --> TaTrackerParser : d +deactivate GroupCommandParser +GroupCommandParser -[hidden]-> TaTrackerParser +destroy GroupCommandParser + +TaTrackerParser --> LogicManager : d +deactivate TaTrackerParser + +LogicManager -> DeleteGroupCommand : execute() +activate DeleteGroupCommand + +DeleteGroupCommand -> Model : hasModule(m) +activate Model +Model --> DeleteGroupCommand : true +deactivate Model + +DeleteGroupCommand -> Model : getModule(m) +activate Model +Model --> DeleteGroupCommand : M +deactivate Model + +DeleteGroupCommand -> ActualModule : hasGroup(g) +activate ActualModule +ActualModule --> DeleteGroupCommand : true +deactivate ActualModule + +DeleteGroupCommand -> ActualModule : deleteGroup(g) +activate ActualModule +ActualGroup -[hidden]-> ActualModule +destroy ActualGroup +ActualModule --> DeleteGroupCommand +deactivate ActualModule + +create CommandResult +DeleteGroupCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> DeleteGroupCommand +deactivate CommandResult + +DeleteGroupCommand --> LogicManager : result +deactivate DeleteGroupCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/ModuleModelClassDiagram.puml b/docs/diagrams/module-view/ModuleModelClassDiagram.puml new file mode 100644 index 00000000000..419fd8e7d53 --- /dev/null +++ b/docs/diagrams/module-view/ModuleModelClassDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include ../style.puml +package Model { + +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +class TaTracker + +package Module { +class Module +class UniqueModuleList +} +package Group { +class Group +class UniqueGroupList +} +package Session { +class Session +class UniqueSessionList +} +package Student { +class Student +class UniqueStudentList +} +} + +TaTracker --> "1 " UniqueModuleList + +UniqueModuleList -right-> "*" Module + +Module --> "1" UniqueGroupList +Module --> "1 " UniqueSessionList + +UniqueGroupList -left-> "*" Group +UniqueSessionList -right-> "*" Session + +Group --> "1 " UniqueStudentList +UniqueStudentList -right-> "*" Student + +@enduml + diff --git a/docs/diagrams/module-view/SortCommandsClassDiagram.puml b/docs/diagrams/module-view/SortCommandsClassDiagram.puml new file mode 100644 index 00000000000..d978893be88 --- /dev/null +++ b/docs/diagrams/module-view/SortCommandsClassDiagram.puml @@ -0,0 +1,16 @@ +@startuml +!include ../style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +Interface Command <> +class SortCommand +class SortModuleCommand +class SortGroupCommand + +SortCommand .up.|> Command +SortModuleCommand -up-|> SortCommand +SortGroupCommand -up-|> SortCommand + +@enduml diff --git a/docs/diagrams/module-view/SortGroupSequenceDiagram.puml b/docs/diagrams/module-view/SortGroupSequenceDiagram.puml new file mode 100644 index 00000000000..2566fff29bc --- /dev/null +++ b/docs/diagrams/module-view/SortGroupSequenceDiagram.puml @@ -0,0 +1,106 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":SortCommandParser" as SortCommandParser LOGIC_COLOR +participant "s:SortGroupCommand" as SortCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Module" as Module MODEL_COLOR +participant "g:Group" as Group MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +participant "M:Module" as ActualModule MODEL_COLOR +participant "G:Group" as ActualGroup MODEL_COLOR +end box + +[-> LogicManager : execute("sort \nm/CS2103 g/G03 t/alpha") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("sort \nm/CS2103 g/G03 t/alpha") +activate TaTrackerParser + +create SortCommandParser +TaTrackerParser -> SortCommandParser : SortCommandParser() +activate SortCommandParser +SortCommandParser --> TaTrackerParser +deactivate SortCommandParser + +TaTrackerParser -> SortCommandParser : parseCommand("m/CS2103 g/G03 t/alpha") +activate SortCommandParser + +create SortCommand +SortCommandParser -> SortCommand : SortCommand(G03, CS2103, "alpha") +activate SortCommand +SortCommand --> SortCommandParser +deactivate SortCommand + +SortCommandParser --> TaTrackerParser : s +deactivate SortCommandParser +SortCommandParser -[hidden]-> TaTrackerParser +destroy SortCommandParser + +TaTrackerParser --> LogicManager : s +deactivate TaTrackerParser + +LogicManager -> SortCommand : execute() +activate SortCommand + +create Module +SortCommand -> Module : Module("CS2103", "") +activate Module +Module --> SortCommand +deactivate Module + +create Group +SortCommand -> Group : Group("G03", null) +activate Group +Group --> SortCommand +deactivate Group + +SortCommand -> Model : hasModule(m) +activate Model +Model --> SortCommand : true +deactivate Model + +SortCommand -> Model : getModule(m) +activate Model +Model --> SortCommand : M +deactivate Model + +Module -[hidden]-> SortCommand +destroy Module + +SortCommand -> ActualModule : hasGroup(g) +activate ActualModule +ActualModule --> SortCommand : true +deactivate ActualModule + +SortCommand -> ActualModule : getGroup(g) +activate ActualModule +ActualModule --> SortCommand : G +deactivate ActualModule + +Group -[hidden]-> SortCommand +destroy Group + +SortCommand -> ActualGroup : sortStudentsAlphabetically() +activate ActualGroup +ActualGroup --> SortCommand +deactivate ActualGroup + +create CommandResult +SortCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> SortCommand +deactivate CommandResult + +SortCommand --> LogicManager : result +deactivate SortCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/module-view/SortParserActivityDiagram.puml b/docs/diagrams/module-view/SortParserActivityDiagram.puml new file mode 100644 index 00000000000..1b4c3370920 --- /dev/null +++ b/docs/diagrams/module-view/SortParserActivityDiagram.puml @@ -0,0 +1,34 @@ +@startuml +start +:SortCommandParser's parse + method is called; + +:ArgumentMultimap is created; + +:The sort type is extracted from + the multimap; + +if() then ([something with\n prefix module is\n present]) + :The module code is extracted + from the multimap; + +if() then ([somthing with\n prefix group is\n present]) + :The group code is extracted; + :SortGroupCommand with the parameters as + the group code and module code and type + is returned; + +else ([else]) + :SortModuleCommand with the parameters + as module code and type is returned; + +endif + +else ([else]) + :SortCommand with the parameter type + is returned; + +endif + +stop +@enduml diff --git a/docs/diagrams/student-view/AddStudentSequenceDiagram.puml b/docs/diagrams/student-view/AddStudentSequenceDiagram.puml new file mode 100644 index 00000000000..10a5d4c99c3 --- /dev/null +++ b/docs/diagrams/student-view/AddStudentSequenceDiagram.puml @@ -0,0 +1,88 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":StudentCommandParser" as StudentCommandParser LOGIC_COLOR +participant ":AddStudentCommandParser" as AddStudentCommandParser LOGIC_COLOR +participant "a:AddStudentCommand" as AddStudentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Student" as Student MODEL_COLOR +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("Student add \nn/John Doe \np/98765432 \ne/johnd@example.com \nm/A0181234G") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("Student add \nn/John Doe \np/98765432 \ne/johnd@example.com \nm/A0181234G") +activate TaTrackerParser + +create StudentCommandParser +TaTrackerParser -> StudentCommandParser : StudentCommandParser() +activate StudentCommandParser +StudentCommandParser --> TaTrackerParser +deactivate StudentCommandParser + +TaTrackerParser -> StudentCommandParser : parseCommand("add \nn/John Doe \np/98765432 \ne/johnd@example.com \nm/A0181234G") +activate StudentCommandParser + +create AddStudentCommandParser +StudentCommandParser -> AddStudentCommandParser : AddStudentCommandParser() +activate AddStudentCommandParser +AddStudentCommandParser --> StudentCommandParser +deactivate AddStudentCommandParser + +StudentCommandParser -> AddStudentCommandParser : parseCommand("\nn/John Doe \np/98765432 \ne/johnd@example.com \nm/A0181234G") +activate AddStudentCommandParser + +create Student +AddStudentCommandParser -> Student : Student(\n"John Doe", \n"98765432", \n"johnd@example.com", \n"A0181234G") +activate Student +Student --> AddStudentCommandParser +deactivate Student + +create AddStudentCommand +AddStudentCommandParser -> AddStudentCommand : AddStudentCommand(m) +activate AddStudentCommand +AddStudentCommand --> AddStudentCommandParser +deactivate AddStudentCommand + + + +AddStudentCommandParser --> StudentCommandParser : a +deactivate AddStudentCommandParser +AddStudentCommandParser -[hidden]-> StudentCommandParser +destroy AddStudentCommandParser + +StudentCommandParser --> TaTrackerParser : a +deactivate StudentCommandParser +StudentCommandParser -[hidden]-> TaTrackerParser +destroy StudentCommandParser + +TaTrackerParser --> LogicManager : a +deactivate TaTrackerParser + +LogicManager -> AddStudentCommand : execute() +activate AddStudentCommand + +AddStudentCommand -> Model : addStudent(m) +activate Model +Model --> AddStudentCommand +deactivate Model + +create CommandResult +AddStudentCommand -> CommandResult : CommandResult(SuccessMessage) +activate CommandResult +CommandResult --> AddStudentCommand +deactivate CommandResult + +AddStudentCommand --> LogicManager : result +deactivate AddStudentCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/student-view/DeleteStudentSequenceDiagram.puml b/docs/diagrams/student-view/DeleteStudentSequenceDiagram.puml new file mode 100644 index 00000000000..a7699870e0b --- /dev/null +++ b/docs/diagrams/student-view/DeleteStudentSequenceDiagram.puml @@ -0,0 +1,88 @@ +@startuml +!include ../style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TaTrackerParser" as TaTrackerParser LOGIC_COLOR +participant ":StudentCommandParser" as StudentCommandParser LOGIC_COLOR +participant ":DeleteStudentCommandParser" as DeleteStudentCommandParser LOGIC_COLOR +participant "d:DeleteStudentCommand" as DeleteStudentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("student \ndelete A0181234G") +activate LogicManager + +LogicManager -> TaTrackerParser : parseCommand("student \ndelete A0181234G") +activate TaTrackerParser + +create StudentCommandParser +TaTrackerParser -> StudentCommandParser: StudentCommandParser() +activate StudentCommandParser + +StudentCommandParser --> TaTrackerParser +deactivate StudentCommandParser + +TaTrackerParser -> StudentCommandParser : parse("delete \nA0181234G") +activate StudentCommandParser + +create DeleteStudentCommandParser +StudentCommandParser -> DeleteStudentCommandParser +activate DeleteStudentCommandParser + +DeleteStudentCommandParser --> StudentCommandParser +deactivate DeleteStudentCommandParser + +StudentCommandParser -> DeleteStudentCommandParser : parse("A0181234G") +activate DeleteStudentCommandParser + +create DeleteStudentCommand +DeleteStudentCommandParser -> DeleteStudentCommand +activate DeleteStudentCommand + +DeleteStudentCommand --> DeleteStudentCommandParser : d +deactivate DeleteStudentCommand + +DeleteStudentCommandParser --> StudentCommandParser : d +deactivate DeleteStudentCommandParser + +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteStudentCommandParser -[hidden]-> StudentCommandParser +destroy DeleteStudentCommandParser + +StudentCommandParser --> TaTrackerParser : d +deactivate StudentCommandParser + +'Hidden arrow to position the destroy marker below the end of the activation bar. +StudentCommandParser -[hidden]-> TaTrackerParser +destroy StudentCommandParser + +TaTrackerParser --> LogicManager : d +deactivate TaTrackerParser + +LogicManager -> DeleteStudentCommand : execute() +activate DeleteStudentCommand + +DeleteStudentCommand -> Model : deleteStudent(A0181234G) +activate Model + +Model --> DeleteStudentCommand +deactivate Model + +create CommandResult +DeleteStudentCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteStudentCommand +deactivate CommandResult + +DeleteStudentCommand --> LogicManager : result +deactivate DeleteStudentCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/student-view/StudentModelClassDiagram.puml b/docs/diagrams/student-view/StudentModelClassDiagram.puml new file mode 100644 index 00000000000..397d8cdd82c --- /dev/null +++ b/docs/diagrams/student-view/StudentModelClassDiagram.puml @@ -0,0 +1,39 @@ +@startuml +!include ../style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <>{ + +Package Student { +Class Student +Class Email +Class Matric +Class Name +Class Phone +Class Rating +Class UniqueStudentList +} + +Package Tag { +Class Tag +} +} + +Class HiddenOutside #FFFFFF + +TaTracker *--> "1" UniqueStudentList +UniqueStudentList o--> "*" Student +Student *-->"1" Name +Student *-->"0...1" Phone +Student *-->"0...1" Email +Student *-->"1" Matric +Student *-->"1" Rating +Student *--> "*" Tag + +Name -[hidden]right-> Phone +Phone -[hidden]right-> Matric +Matric -[hidden]right-> Email + +@enduml diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..91bb76eb527 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -2,7 +2,7 @@ !include ../style.puml Participant ":LogicManager" as logic LOGIC_COLOR -Participant ":AddressBookParser" as abp LOGIC_COLOR +Participant ":TaTrackerParser" as abp LOGIC_COLOR Participant ":EditCommandParser" as ecp LOGIC_COLOR Participant "command:EditCommand" as ec LOGIC_COLOR @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editStudentDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/AddGroupActivityDiagram.png b/docs/images/AddGroupActivityDiagram.png new file mode 100644 index 00000000000..8af4e1088a9 Binary files /dev/null and b/docs/images/AddGroupActivityDiagram.png differ diff --git a/docs/images/AddModuleSequenceDiagram.png b/docs/images/AddModuleSequenceDiagram.png new file mode 100644 index 00000000000..e60c862052a Binary files /dev/null and b/docs/images/AddModuleSequenceDiagram.png differ diff --git a/docs/images/AddStudentSequenceDiagram.png b/docs/images/AddStudentSequenceDiagram.png new file mode 100644 index 00000000000..79e2ce9c8b1 Binary files /dev/null and b/docs/images/AddStudentSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index bc7ed18ae29..c60d6ac61f6 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteGroupSequenceDiagram.png b/docs/images/DeleteGroupSequenceDiagram.png new file mode 100644 index 00000000000..13d91793de1 Binary files /dev/null and b/docs/images/DeleteGroupSequenceDiagram.png differ diff --git a/docs/images/DeleteStudentSequenceDiagram.png b/docs/images/DeleteStudentSequenceDiagram.png new file mode 100644 index 00000000000..d5714a0a1f1 Binary files /dev/null and b/docs/images/DeleteStudentSequenceDiagram.png differ diff --git a/docs/images/FindCommandActivityDiagram.png b/docs/images/FindCommandActivityDiagram.png new file mode 100644 index 00000000000..5a84a8d321c Binary files /dev/null and b/docs/images/FindCommandActivityDiagram.png differ diff --git a/docs/images/FindCommandClassDiagram.png b/docs/images/FindCommandClassDiagram.png new file mode 100644 index 00000000000..109945a9550 Binary files /dev/null and b/docs/images/FindCommandClassDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index b9e853cef12..eaaaa40557b 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..8425eadd2a5 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModuleModelClassDiagram.png b/docs/images/ModuleModelClassDiagram.png new file mode 100644 index 00000000000..2888a30b512 Binary files /dev/null and b/docs/images/ModuleModelClassDiagram.png differ diff --git a/docs/images/SortCommandsClassDiagram.png b/docs/images/SortCommandsClassDiagram.png new file mode 100644 index 00000000000..b56bb6a41a0 Binary files /dev/null and b/docs/images/SortCommandsClassDiagram.png differ diff --git a/docs/images/SortGroupSequenceDiagram.png b/docs/images/SortGroupSequenceDiagram.png new file mode 100644 index 00000000000..bf3bc77329b Binary files /dev/null and b/docs/images/SortGroupSequenceDiagram.png differ diff --git a/docs/images/SortParserActivityDiagram.png b/docs/images/SortParserActivityDiagram.png new file mode 100644 index 00000000000..069f89865d5 Binary files /dev/null and b/docs/images/SortParserActivityDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index d87c1216820..c7a4c5bae42 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StudentModelClassDiagram.png b/docs/images/StudentModelClassDiagram.png new file mode 100644 index 00000000000..383d58cd53e Binary files /dev/null and b/docs/images/StudentModelClassDiagram.png differ diff --git a/docs/images/SyntaxHighlighting.png b/docs/images/SyntaxHighlighting.png new file mode 100644 index 00000000000..b10772c73ee Binary files /dev/null and b/docs/images/SyntaxHighlighting.png differ diff --git a/docs/images/TssActivityDiagram.png b/docs/images/TssActivityDiagram.png new file mode 100644 index 00000000000..9d6506a6076 Binary files /dev/null and b/docs/images/TssActivityDiagram.png differ diff --git a/docs/images/TssModelClassDiagram.png b/docs/images/TssModelClassDiagram.png new file mode 100644 index 00000000000..69b16af5385 Binary files /dev/null and b/docs/images/TssModelClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..d67ac69b18a 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/aakanksha-rai.png b/docs/images/aakanksha-rai.png new file mode 100644 index 00000000000..80670fc5a55 Binary files /dev/null and b/docs/images/aakanksha-rai.png differ diff --git a/docs/images/chuayijing.png b/docs/images/chuayijing.png new file mode 100644 index 00000000000..b7fef168db6 Binary files /dev/null and b/docs/images/chuayijing.png differ diff --git a/docs/images/eclmist.png b/docs/images/eclmist.png new file mode 100644 index 00000000000..db19a979db5 Binary files /dev/null and b/docs/images/eclmist.png differ diff --git a/docs/images/fatin99.png b/docs/images/fatin99.png new file mode 100644 index 00000000000..599b5d3d322 Binary files /dev/null and b/docs/images/fatin99.png differ diff --git a/docs/images/potatocombat.png b/docs/images/potatocombat.png new file mode 100644 index 00000000000..e7d841dd547 Binary files /dev/null and b/docs/images/potatocombat.png differ diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc index f39e76e49b2..710585f6c7d 100644 --- a/docs/team/johndoe.adoc +++ b/docs/team/johndoe.adoc @@ -3,14 +3,13 @@ :imagesDir: ../images :stylesDir: ../stylesheets -== PROJECT: AddressBook - Level 3 +== PROJECT: TA-Tracker --- == Overview -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - +TA-Tracker is a productivity tool made for NUS Computing Teaching Assistants (TAs) who want to be able to track and manage all of their claimable hours in NUS. == Summary of contributions * *Major enhancement*: added *the ability to undo/redo previous commands* diff --git a/docs/tutorials/AddRemark.adoc b/docs/tutorials/AddRemark.adoc index ea388068303..1bdbd6c6b4e 100644 --- a/docs/tutorials/AddRemark.adoc +++ b/docs/tutorials/AddRemark.adoc @@ -38,10 +38,10 @@ We accomplish that by returning a `CommandResult` with an accompanying message. ---- package seedu.address.logic.commands; -import seedu.address.model.Model; +import tatracker.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing student in the address book. */ public class RemarkCommand extends Command { @@ -82,8 +82,8 @@ Following the convention in other commands, we add relevant messages as constant .RemarkCommand.java [source, java] ---- - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the student identified " + + "by the index number used in the last student listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -110,7 +110,7 @@ While this is not a replacement for tests, it is an obvious way to tell if our c [source, java] ---- -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; //... public class RemarkCommand extends Command { //... @@ -120,8 +120,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the student in the filtered student list to edit the remark + * @param remark of the student to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -246,12 +246,12 @@ If you are stuck, check out the sample link:https://github.com/nus-cs2103-AY1920 Now that we have all the information that we need, let's lay the groundwork for some _persistent_ changes. We achieve that by working with the `Person` model. -Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person's name). -That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the student's name). +That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a student. === Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.student`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/b7a47c50c8e5f0430d343a23d2863446b6ce9298#diff-af2f075d24dfcd333876f0fbce321f25[this]. Note how `Remark` has no constrains and thus does not require input validation. @@ -263,7 +263,7 @@ These should be relatively simple changes. == Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each student. Simply add [source, java] @@ -326,9 +326,9 @@ Just add link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/5 [source, java] .PersonCard.java ---- -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person student, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(student.getRemark().value); } ---- diff --git a/docs/tutorials/RemovingFields.adoc b/docs/tutorials/RemovingFields.adoc index 5a50b6965a6..2550a9073e6 100644 --- a/docs/tutorials/RemovingFields.adoc +++ b/docs/tutorials/RemovingFields.adoc @@ -24,7 +24,7 @@ Fortunately, the IntelliJ IDEA provides a robust refactoring tool that can ident Let's try to use it as much as we can. === Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. +The `address` field in `Person` is actually an instance of the `seedu.address.model.student.Address` class. Since removing the `Address` class will break the application, we start by identifying ``Address``'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` > `Safe Delete` through the menu. diff --git a/docs/tutorials/TracingCode.adoc b/docs/tutorials/TracingCode.adoc index 5f0aaba1741..5796463b0f8 100644 --- a/docs/tutorials/TracingCode.adoc +++ b/docs/tutorials/TracingCode.adoc @@ -49,7 +49,7 @@ However, the execution path through a GUI is often somewhat obscure due to vario used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the UI transfers control to the Logic component. According to the sequence diagram, the UI component yields control to the Logic component through a method named `execute`. Searching through the code base for `execute()` yields a promising candidate in -`seedu.address.ui.CommandBox.CommandExecutor`. +`tatracker.ui.CommandBox.CommandExecutor`. .Using the `Search for target by name` feature. `Navigate` > `Symbol`. image::Execute.png[] diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} 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/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/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/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/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/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} 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/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/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/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/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/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/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/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/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/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/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/AppParameters.java b/src/main/java/tatracker/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/tatracker/AppParameters.java index ab552c398f3..55bb401449d 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/tatracker/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package tatracker; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/tatracker/Main.java similarity index 97% rename from src/main/java/seedu/address/Main.java rename to src/main/java/tatracker/Main.java index 052a5068631..45a4f7a3402 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/tatracker/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package tatracker; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/tatracker/MainApp.java similarity index 68% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/tatracker/MainApp.java index e5cfb161b73..d2c14f51637 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/tatracker/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package tatracker; import java.io.IOException; import java.nio.file.Path; @@ -7,29 +7,30 @@ 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; +import tatracker.commons.core.Config; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.core.Notification; +import tatracker.commons.core.Version; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.commons.util.ConfigUtil; +import tatracker.commons.util.StringUtil; +import tatracker.logic.Logic; +import tatracker.logic.LogicManager; +import tatracker.model.Model; +import tatracker.model.ModelManager; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.TaTracker; +import tatracker.model.UserPrefs; +import tatracker.model.util.SampleDataUtil; +import tatracker.storage.JsonTaTrackerStorage; +import tatracker.storage.JsonUserPrefsStorage; +import tatracker.storage.Storage; +import tatracker.storage.StorageManager; +import tatracker.storage.TaTrackerStorage; +import tatracker.storage.UserPrefsStorage; +import tatracker.ui.Ui; +import tatracker.ui.UiManager; /** * Runs the application. @@ -48,7 +49,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing TaTracker ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +57,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + TaTrackerStorage taTrackerStorage = new JsonTaTrackerStorage(userPrefs.getTaTrackerFilePath()); + storage = new StorageManager(taTrackerStorage, userPrefsStorage); initLogging(config); @@ -69,25 +70,25 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s TA-Tracker and {@code userPrefs}.
+ * The data from the sample ta-tracker will be used instead if {@code storage}'s ta-tracker is not found, + * or an empty ta-tracker will be used instead if errors occur when reading {@code storage}'s ta-tracker. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional taTrackerOptional; + ReadOnlyTaTracker initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + taTrackerOptional = storage.readTaTracker(); + if (!taTrackerOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample TaTracker"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = taTrackerOptional.orElseGet(SampleDataUtil::getSampleTaTracker); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty TaTracker"); + initialData = new TaTracker(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty TaTracker"); + initialData = new TaTracker(); } return new ModelManager(initialData, userPrefs); @@ -151,7 +152,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TaTracker"); initializedPrefs = new UserPrefs(); } @@ -167,17 +168,20 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting TaTracker " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping TA-Tracker ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); } + + // Dispose the system tray icon + Notification.dispose(); } } diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/tatracker/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/tatracker/commons/core/Config.java index 91145745521..698350d8be2 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/tatracker/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/tatracker/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/tatracker/commons/core/GuiSettings.java index 5ace559ad15..d54cdd5d2e6 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/tatracker/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/tatracker/commons/core/LogsCenter.java similarity index 97% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/tatracker/commons/core/LogsCenter.java index 431e7185e76..e23c6e372f5 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/tatracker/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.io.IOException; import java.util.Arrays; @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "tatracker.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/tatracker/commons/core/Messages.java b/src/main/java/tatracker/commons/core/Messages.java new file mode 100644 index 00000000000..e5f0bd3cd6c --- /dev/null +++ b/src/main/java/tatracker/commons/core/Messages.java @@ -0,0 +1,14 @@ +package tatracker.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid"; + public static final String MESSAGE_INVALID_SESSION_DISPLAYED_INDEX = "The session index provided is invalid"; + public static final String MESSAGE_STUDENTS_LISTED_OVERVIEW = "%1$d students listed!"; + +} diff --git a/src/main/java/tatracker/commons/core/Notification.java b/src/main/java/tatracker/commons/core/Notification.java new file mode 100644 index 00000000000..8332de31061 --- /dev/null +++ b/src/main/java/tatracker/commons/core/Notification.java @@ -0,0 +1,69 @@ +package tatracker.commons.core; + +import java.awt.AWTException; +import java.awt.Image; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.awt.TrayIcon; +import java.io.IOException; + +import javax.imageio.ImageIO; + +/** + * A class that handles the displaying of System Notifications. + */ +public class Notification { + public static final String APPICON_PATH = "images/icon.png"; + private static Notification singleton; + private final TrayIcon trayIcon; + + private Notification() throws AWTException { + SystemTray tray = SystemTray.getSystemTray(); + Image trayImage; + + try { + trayImage = ImageIO.read(getClass().getClassLoader().getResource(APPICON_PATH)); + } catch (IOException e) { + System.err.println("Unable to load Application Icon for the System Tray!"); + trayImage = Toolkit.getDefaultToolkit().createImage("placeholder-icon.png"); + } + + trayIcon = new TrayIcon(trayImage, "TrayIcon"); + trayIcon.setImageAutoSize(true); + trayIcon.setToolTip("TA Tracker"); + tray.add(trayIcon); + } + + private static Notification getInstance() throws AWTException { + if (singleton == null) { + singleton = new Notification(); + } + + return singleton; + } + + private void notify(String caption, String message, TrayIcon.MessageType type) { + trayIcon.displayMessage(caption, message, type); + } + + /** + * Triggers a OS-level system notification. + * + * @param caption The title displayed in the notification + * @param message The message displayed in the notification + * @param type the type of notification (error, info, warning, etc.) + * @throws AWTException if {@code SystemTray} is not supported on the current platform. + */ + public static void sendNotification(String caption, String message, TrayIcon.MessageType type) throws AWTException { + Notification.getInstance().notify(caption, message, type); + } + + /** + * Immediately destroys the system tray icon + */ + public static void dispose() { + if (singleton != null) { + SystemTray.getSystemTray().remove(singleton.trayIcon); + } + } +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/tatracker/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/tatracker/commons/core/Version.java index e117f91b3b2..1c0eb1a854e 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/tatracker/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package tatracker.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/tatracker/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/tatracker/commons/core/index/Index.java index 19536439c09..4a39044d067 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/tatracker/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package tatracker.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/tatracker/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/tatracker/commons/exceptions/DataConversionException.java index 1f689bd8e3f..195db3caedc 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/tatracker/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package tatracker.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/tatracker/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/tatracker/commons/exceptions/IllegalValueException.java index 19124db485c..6c7cce158b4 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/tatracker/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package tatracker.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/tatracker/commons/util/AppUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/tatracker/commons/util/AppUtil.java index da90201dfd6..5a4f76e8f88 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/tatracker/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import tatracker.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/tatracker/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/tatracker/commons/util/CollectionUtil.java index eafe4dfd681..465cbbc7be1 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/tatracker/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/tatracker/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/tatracker/commons/util/ConfigUtil.java index f7f8a2bd44c..1d2de24dbc9 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/tatracker/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import tatracker.commons.core.Config; +import tatracker.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/tatracker/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/tatracker/commons/util/FileUtil.java index b1e2767cdd9..bd23f8afba5 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/tatracker/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/tatracker/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/tatracker/commons/util/JsonUtil.java index 8ef609f055d..77164b4431e 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/tatracker/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/tatracker/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/tatracker/commons/util/StringUtil.java index 61cc8c9a1cb..875e9dc414a 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/tatracker/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package tatracker.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/tatracker/logic/Logic.java b/src/main/java/tatracker/logic/Logic.java new file mode 100644 index 00000000000..a79e0a34aed --- /dev/null +++ b/src/main/java/tatracker/logic/Logic.java @@ -0,0 +1,66 @@ +package tatracker.logic; + +import java.nio.file.Path; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.GuiSettings; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; + +/** + * 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 TaTracker. + * + * @see tatracker.model.Model#getTaTracker() + */ + ReadOnlyTaTracker getTaTracker(); + + /** + * Returns the user prefs' ta-tracker file path. + */ + Path getTaTrackerFilePath(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Set the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** Returns an unmodifiable view of the filtered list of sessions. */ + ObservableList getFilteredSessionList(); + + /** Returns an unmodifiable view of the filtered list of done sessions. */ + ObservableList getFilteredDoneSessionList(); + + /** Returns an unmodifiable view of the filtered list of module. */ + ObservableList getFilteredModuleList(); + + /** Returns an unmodifiable view of the filtered list of module groups. */ + ObservableList getFilteredGroupList(); + + /** Returns an unmodifiable view of the filtered list of students. */ + ObservableList getFilteredStudentList(); +} diff --git a/src/main/java/tatracker/logic/LogicManager.java b/src/main/java/tatracker/logic/LogicManager.java new file mode 100644 index 00000000000..5b0875d103a --- /dev/null +++ b/src/main/java/tatracker/logic/LogicManager.java @@ -0,0 +1,102 @@ +package tatracker.logic; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.TaTrackerParser; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.Model; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; +import tatracker.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 TaTrackerParser taTrackerParser; + + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + this.taTrackerParser = new TaTrackerParser(); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + CommandResult commandResult; + Command command = taTrackerParser.parseCommand(commandText); + commandResult = command.execute(model); + + try { + storage.saveTaTracker(model.getTaTracker()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + + return commandResult; + } + + @Override + public ReadOnlyTaTracker getTaTracker() { + return model.getTaTracker(); + } + + @Override + public Path getTaTrackerFilePath() { + return model.getTaTrackerFilePath(); + } + + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } + + @Override + public ObservableList getFilteredSessionList() { + return model.getFilteredSessionList(); + } + + @Override + public ObservableList getFilteredDoneSessionList() { + return model.getFilteredDoneSessionList(); + } + + @Override + public ObservableList getFilteredModuleList() { + return model.getFilteredModuleList(); + } + + @Override + public ObservableList getFilteredGroupList() { + return model.getFilteredGroupList(); + } + + @Override + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/tatracker/logic/commands/ClearCommand.java similarity index 53% rename from src/main/java/seedu/address/logic/commands/ClearCommand.java rename to src/main/java/tatracker/logic/commands/ClearCommand.java index 9c86b1fa6e4..3a5e1e16cd9 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/tatracker/logic/commands/ClearCommand.java @@ -1,23 +1,23 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; +import tatracker.model.Model; +import tatracker.model.TaTracker; /** - * Clears the address book. + * Clears the TA-Tracker. */ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "TA-Tracker has been cleared!"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.setAddressBook(new AddressBook()); + model.setTaTracker(new TaTracker()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/tatracker/logic/commands/Command.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/tatracker/logic/commands/Command.java index 64f18992160..f50fdac6644 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/tatracker/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/tatracker/logic/commands/CommandResult.java similarity index 97% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/tatracker/logic/commands/CommandResult.java index 92f900b7916..ce64a385679 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/tatracker/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/tatracker/logic/commands/CommandWords.java b/src/main/java/tatracker/logic/commands/CommandWords.java new file mode 100644 index 00000000000..5d352ae6adf --- /dev/null +++ b/src/main/java/tatracker/logic/commands/CommandWords.java @@ -0,0 +1,18 @@ +package tatracker.logic.commands; + +/** + * Contains a list of common command words in TA-Tracker. + */ +public final class CommandWords { + + /* List of command words for each model in TA-Tracker. */ + public static final String STUDENT = "student"; + public static final String MODULE = "module"; + public static final String GROUP = "group"; + public static final String SESSION = "session"; + + /* List of command words for commands that are common between TA-Tracker models. */ + public static final String ADD_MODEL = "add"; + public static final String DELETE_MODEL = "delete"; + public static final String EDIT_MODEL = "edit"; +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/tatracker/logic/commands/DeleteCommand.java similarity index 55% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/tatracker/logic/commands/DeleteCommand.java index 02fd256acba..d029246299d 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/tatracker/logic/commands/DeleteCommand.java @@ -1,28 +1,28 @@ -package seedu.address.logic.commands; +package tatracker.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; +import tatracker.commons.core.Messages; +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.student.Student; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a student identified using it's displayed index from the TA-Tracker. */ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the student identified by the index number used in the displayed student 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"; + public static final String MESSAGE_DELETE_STUDENT_SUCCESS = "Deleted Student: %1$s"; private final Index targetIndex; @@ -33,15 +33,15 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredStudentList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Student studentToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteStudent(studentToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_STUDENT_SUCCESS, studentToDelete)); } @Override diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/tatracker/logic/commands/ExitCommand.java similarity index 75% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/tatracker/logic/commands/ExitCommand.java index 3dd85a8ba90..71b83cdb4b9 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/tatracker/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; -import seedu.address.model.Model; +import tatracker.model.Model; /** * Terminates the program. @@ -9,7 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting TA-Tracker as requested ..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/tatracker/logic/commands/FindCommand.java similarity index 68% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/tatracker/logic/commands/FindCommand.java index d6b19b0a0de..a9c6a896189 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/tatracker/logic/commands/FindCommand.java @@ -1,20 +1,20 @@ -package seedu.address.logic.commands; +package tatracker.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; +import tatracker.commons.core.Messages; +import tatracker.model.Model; +import tatracker.model.student.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all students in TA-Tracker whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all students 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"; @@ -28,9 +28,9 @@ public FindCommand(NameContainsKeywordsPredicate predicate) { @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.setFilteredStudentList(); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, model.getFilteredStudentList().size())); } @Override diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/tatracker/logic/commands/HelpCommand.java similarity index 88% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/tatracker/logic/commands/HelpCommand.java index bf824f91bd0..fa8727f8b6a 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/tatracker/logic/commands/HelpCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package tatracker.logic.commands; -import seedu.address.model.Model; +import tatracker.model.Model; /** * Format full help instructions for every command for display. diff --git a/src/main/java/tatracker/logic/commands/ListCommand.java b/src/main/java/tatracker/logic/commands/ListCommand.java new file mode 100644 index 00000000000..9a18a39286a --- /dev/null +++ b/src/main/java/tatracker/logic/commands/ListCommand.java @@ -0,0 +1,23 @@ +package tatracker.logic.commands; + +import static java.util.Objects.requireNonNull; + +import tatracker.model.Model; + +/** + * Lists all students in the TA-Tracker to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all students"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + //model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/tatracker/logic/commands/SortCommand.java b/src/main/java/tatracker/logic/commands/SortCommand.java new file mode 100644 index 00000000000..28e05f367d2 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/SortCommand.java @@ -0,0 +1,62 @@ +package tatracker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; + +/** + * Command to sort all students in all groups of all modules. + */ +public class SortCommand extends Command { + + public static final String COMMAND_WORD = "sort"; + + /* Example message usage. */ + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts students in" + + "all groups of all modules in the TA-Tracker. " + + "Parameters: " + + PREFIX_TYPE + "SORT TYPE " + + "Example: " + COMMAND_WORD + " " + + PREFIX_TYPE + "alphabetically" + + "\nOther variations include group code and module code."; + + public static final String MESSAGE_SUCCESS = "The modules have been sorted."; + private static final int FIRST_MODULE_INDEX = 0; + private static final int FIRST_GROUP_INDEX = 0; + + private final String type; + + public SortCommand(String type) { + this.type = type; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (type.equalsIgnoreCase("alphabetically") + || type.equalsIgnoreCase("alpha")) { + model.sortModulesAlphabetically(); + } else if (type.equalsIgnoreCase("rating asc")) { + model.sortModulesByRatingAscending(); + } else { + model.sortModulesByRatingDescending(); + } + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateGroupList(FIRST_MODULE_INDEX); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateStudentList(FIRST_GROUP_INDEX, FIRST_MODULE_INDEX); + } + } + + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } +} diff --git a/src/main/java/tatracker/logic/commands/SortGroupCommand.java b/src/main/java/tatracker/logic/commands/SortGroupCommand.java new file mode 100644 index 00000000000..33f0540c0b6 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/SortGroupCommand.java @@ -0,0 +1,88 @@ +package tatracker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; + +/** + * Sorts all students in the group. + */ +public class SortGroupCommand extends SortCommand { + + public static final String COMMAND_WORD = "sort"; + + /* Example message usage. */ + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts students in the given group. " + + "Parameters: " + + PREFIX_GROUP + "GROUP CODE " + + PREFIX_MODULE + "MODULE CODE " + + PREFIX_TYPE + "SORT TYPE " + + "Example: " + COMMAND_WORD + " " + + PREFIX_GROUP + "G06 " + + PREFIX_MODULE + "CS2100 " + + PREFIX_TYPE + "alphabetically"; + + public static final String MESSAGE_SUCCESS = "Group %s has been sorted."; + public static final String MESSAGE_INVALID_GROUP_CODE = "This group doesn't exist in the TA-Tracker"; + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with the given module code."; + + private final String groupCode; + private final String moduleCode; + private final String type; + + public SortGroupCommand(String groupCode, String moduleCode, String type) { + super(type); + this.groupCode = groupCode; + this.moduleCode = moduleCode; + this.type = type; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Module module = new Module(moduleCode, ""); + + if (!model.hasModule(module)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + module = model.getModule(module.getIdentifier()); + Group group = new Group(groupCode, null); + + if (!module.hasGroup(group)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + group = module.getGroup(groupCode); + + if (type.equalsIgnoreCase("alphabetically") + || type.equalsIgnoreCase("alpha")) { + group.sortStudentsAlphabetically(); + } else if (type.equalsIgnoreCase("rating asc")) { + group.sortStudentsByRatingAscending(); + } else { + group.sortStudentsByRatingDescending(); + } + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateFilteredGroupList(module.getIdentifier()); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateFilteredStudentList(group.getIdentifier(), module.getIdentifier()); + } + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, group)); + } +} diff --git a/src/main/java/tatracker/logic/commands/SortModuleCommand.java b/src/main/java/tatracker/logic/commands/SortModuleCommand.java new file mode 100644 index 00000000000..ef9e5bfb8d2 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/SortModuleCommand.java @@ -0,0 +1,76 @@ +package tatracker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Sorts all students of all groups in a module. + */ +public class SortModuleCommand extends SortCommand { + + public static final String COMMAND_WORD = "sort"; + + /* Example message usage. */ + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts students in" + + "all groups of the given module. " + + "Parameters: " + + PREFIX_MODULE + "MODULE CODE " + + PREFIX_TYPE + "SORT TYPE " + + "Example: " + COMMAND_WORD + " " + + PREFIX_MODULE + "CS2100 " + + PREFIX_TYPE + "alphabetically"; + + public static final String MESSAGE_SUCCESS = "Module %s has been sorted."; + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with the given module code."; + public static final int FIRST_GROUP_INDEX = 0; + + private final String moduleCode; + private String type; + + public SortModuleCommand(String moduleCode, String type) { + super(type); + this.moduleCode = moduleCode; + this.type = type; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Module module = new Module(moduleCode, ""); + + if (!model.hasModule(module)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + module = model.getModule(module.getIdentifier()); + + if (type.equalsIgnoreCase("alphabetically") + || type.equalsIgnoreCase("alpha")) { + module.sortGroupsAlphabetically(); + } else if (type.equalsIgnoreCase("rating asc")) { + module.sortGroupsByRatingAscending(); + } else { + module.sortGroupsByRatingDescending(); + } + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateFilteredGroupList(module.getIdentifier()); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.setFilteredStudentList(module.getIdentifier(), FIRST_GROUP_INDEX); + } + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, module)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/tatracker/logic/commands/exceptions/CommandException.java similarity index 80% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/tatracker/logic/commands/exceptions/CommandException.java index a16bd14f2cd..3d8668d5e8b 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/tatracker/logic/commands/exceptions/CommandException.java @@ -1,7 +1,9 @@ -package seedu.address.logic.commands.exceptions; +package tatracker.logic.commands.exceptions; + +import tatracker.logic.commands.Command; /** - * Represents an error which occurs during execution of a {@link Command}. + * Represents an error which occurs during execution of a {@link Command#execute }. */ public class CommandException extends Exception { public CommandException(String message) { diff --git a/src/main/java/tatracker/logic/commands/group/AddGroupCommand.java b/src/main/java/tatracker/logic/commands/group/AddGroupCommand.java new file mode 100644 index 00000000000..ac0b6683050 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/group/AddGroupCommand.java @@ -0,0 +1,91 @@ +package tatracker.logic.commands.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.commands.CommandWords.ADD_MODEL; +import static tatracker.logic.commands.CommandWords.GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +/** + * Adds a group to the TA-Tracker. + */ +public class AddGroupCommand extends Command { + + public static final String COMMAND_WORD = GROUP + " " + ADD_MODEL; + + /* Example message usage. */ + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a group into TA-Tracker. " + + "Parameters: " + + PREFIX_GROUP + "GROUP CODE " + + PREFIX_MODULE + "MODULE CODE " + + PREFIX_TYPE + "GROUP TYPE " + + "Example: " + COMMAND_WORD + " " + + PREFIX_GROUP + "G06 " + + PREFIX_MODULE + "CS2100 " + + PREFIX_TYPE + "lab"; + + public static final String MESSAGE_SUCCESS = "New Group added: %s"; + public static final String MESSAGE_DUPLICATE_GROUP = "This group already exists in the TA-Tracker"; + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with the given module code."; + + private final Group toAdd; + private final Module targetModule; + + /** + * Creates an addGroupCommand + */ + + public AddGroupCommand(Group group, Module module) { + requireNonNull(group); + requireNonNull(module); + toAdd = group; + targetModule = module; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module actualModule = model.getModule(targetModule.getIdentifier()); + + if (actualModule.hasGroup(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_GROUP); + } + + actualModule.addGroup(toAdd); + model.updateFilteredGroupList(actualModule.getIdentifier()); + + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof AddGroupCommand)) { + return false; // instanceof handles nulls + } + + AddGroupCommand otherCommand = (AddGroupCommand) other; + return toAdd.equals(otherCommand.toAdd) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/group/DeleteGroupCommand.java b/src/main/java/tatracker/logic/commands/group/DeleteGroupCommand.java new file mode 100644 index 00000000000..1c3e35b6905 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/group/DeleteGroupCommand.java @@ -0,0 +1,91 @@ +package tatracker.logic.commands.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.commands.CommandWords.DELETE_MODEL; +import static tatracker.logic.commands.CommandWords.GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; + +/** + * Deletes a group identified using it's group code. + */ +public class DeleteGroupCommand extends Command { + + public static final String COMMAND_WORD = GROUP + " " + DELETE_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the group identified by the group code.\n" + + "Parameters: " + PREFIX_MODULE + " MODULE_CODE " + PREFIX_GROUP + " GROUP_CODE" + + PREFIX_TYPE + " GROUP_TYPE\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_MODULE + "CS2013T " + PREFIX_GROUP + "G03 " + + PREFIX_TYPE + "lab"; + + public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Deleted Group: %1$s"; + public static final String MESSAGE_INVALID_GROUP_CODE = "There is no group with the given group code."; + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with the given module code."; + public static final int FIRST_GROUP_INDEX = 0; + public static final int FIRST_MODULE_INDEX = 0; + + private final Group group; + private final Module targetModule; + + public DeleteGroupCommand(Group group, Module module) { + this.group = group; + this.targetModule = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module actualModule = model.getModule(targetModule.getIdentifier()); + + if (!actualModule.hasGroup(group)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + Group deletedGroup = actualModule.getGroup(group.getIdentifier()); + actualModule.deleteGroup(deletedGroup); + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateGroupList(FIRST_MODULE_INDEX); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateStudentList(FIRST_GROUP_INDEX, FIRST_MODULE_INDEX); + } + } + + return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, deletedGroup)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof DeleteGroupCommand)) { + return false; // instanceof handles nulls + } + + DeleteGroupCommand otherCommand = (DeleteGroupCommand) other; + return group.equals(otherCommand.group) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/group/EditGroupCommand.java b/src/main/java/tatracker/logic/commands/group/EditGroupCommand.java new file mode 100644 index 00000000000..6619621d2b6 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/group/EditGroupCommand.java @@ -0,0 +1,97 @@ +package tatracker.logic.commands.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.commands.CommandWords.EDIT_MODEL; +import static tatracker.logic.commands.CommandWords.GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NEWGROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_NEWTYPE; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.module.Module; + +/** + * Deletes a group identified using it's group code. + */ +public class EditGroupCommand extends Command { + + public static final String COMMAND_WORD = GROUP + " " + EDIT_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits the group identified by the group code.\n" + + "Parameters: " + PREFIX_MODULE + " MODULE_CODE " + PREFIX_GROUP + "GROUP_CODE" + + PREFIX_NEWTYPE + "NEW GROUP TYPE " + PREFIX_NEWGROUP + "NEW GROUP NAME " + + "Example: " + COMMAND_WORD + " " + PREFIX_MODULE + "CS2013T " + PREFIX_GROUP + "G03 " + + PREFIX_NEWTYPE + "lab " + PREFIX_NEWGROUP + "G05"; + + public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Edited Group: %1$s"; + public static final String MESSAGE_INVALID_GROUP_CODE = "There is no group with the given group code."; + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with the given module code."; + public static final int FIRST_GROUP_INDEX = 0; + public static final int FIRST_MODULE_INDEX = 0; + + private final Group group; + private final Module targetModule; + private final String newGroupCode; + private final GroupType newGroupType; + + public EditGroupCommand(Group group, Module module, String newGroupCode, GroupType newGroupType) { + this.group = group; + this.targetModule = module; + this.newGroupCode = newGroupCode; + this.newGroupType = newGroupType; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module actualModule = model.getModule(targetModule.getIdentifier()); + + if (!actualModule.hasGroup(group)) { + throw new CommandException(MESSAGE_INVALID_GROUP_CODE); + } + + Group editedGroup = actualModule.getGroup(group.getIdentifier()); + editedGroup.setIdentifier(newGroupCode); + + if (newGroupType != null) { + editedGroup.setGroupType(newGroupType); + } + + model.updateFilteredGroupList(actualModule.getIdentifier()); + + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateFilteredStudentList(editedGroup.getIdentifier(), actualModule.getIdentifier()); + } + + return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, editedGroup)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof DeleteGroupCommand)) { + return false; // instanceof handles nulls + } + + EditGroupCommand otherCommand = (EditGroupCommand) other; + return group.equals(otherCommand.group) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/module/AddModuleCommand.java b/src/main/java/tatracker/logic/commands/module/AddModuleCommand.java new file mode 100644 index 00000000000..0c7a5e3ddb2 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/module/AddModuleCommand.java @@ -0,0 +1,66 @@ +package tatracker.logic.commands.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NAME; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Adds a module to the TA-Tracker. + */ +public class AddModuleCommand extends Command { + + public static final String COMMAND_WORD = CommandWords.MODULE + " " + CommandWords.ADD_MODEL; + public static final String MESSAGE_USAGE = COMMAND_WORD + " : Adds a module to the TA-Tracker. " + + "Parameters: " + + PREFIX_NAME + "MODULE NAME " + + PREFIX_MODULE + "MODULE CODE " + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Introduction to AI " + + PREFIX_MODULE + "CS3243 "; + + public static final String MESSAGE_SUCCESS = "New Module added: %s"; + public static final String MESSAGE_DUPLICATE_MODULE = "This module already exists in the TA-Tracker"; + public static final int FIRST_GROUP_INDEX = 0; + + private final Module toAdd; + + /** + * Creates an AddCommand to add the specified {@code Module} + */ + public AddModuleCommand(Module module) { + requireNonNull(module); + toAdd = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasModule(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_MODULE); + } + + model.addModule(toAdd); + model.updateFilteredGroupList(toAdd.getIdentifier()); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.setFilteredStudentList(toAdd.getIdentifier(), FIRST_GROUP_INDEX); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddModuleCommand // instanceof handles nulls + && toAdd.equals(((AddModuleCommand) other).toAdd)); + } +} diff --git a/src/main/java/tatracker/logic/commands/module/DeleteModuleCommand.java b/src/main/java/tatracker/logic/commands/module/DeleteModuleCommand.java new file mode 100644 index 00000000000..c2fb37992cd --- /dev/null +++ b/src/main/java/tatracker/logic/commands/module/DeleteModuleCommand.java @@ -0,0 +1,68 @@ +package tatracker.logic.commands.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.commands.CommandWords.DELETE_MODEL; +import static tatracker.logic.commands.CommandWords.MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.module.Module; + +/** + * Deletes a module identified using it's module code. + */ +public class DeleteModuleCommand extends Command { + + public static final String COMMAND_WORD = MODULE + " " + DELETE_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the module identified by the module code.\n" + + "Parameters: " + PREFIX_MODULE + " MODULE_CODE\n" + + "Example: " + COMMAND_WORD + " CS2013T"; + + public static final String MESSAGE_DELETE_MODULE_SUCCESS = "Deleted Module: %1$s"; + public static final String MESSAGE_INVALID_MODULE_CODE = "There is no module with this module code."; + + public static final int FIRST_GROUP_INDEX = 0; + public static final int FIRST_MODULE_INDEX = 0; + + private final Module module; + + public DeleteModuleCommand(Module module) { + this.module = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(module)) { + throw new CommandException(MESSAGE_INVALID_MODULE_CODE); + } + + Module moduleToDelete = model.getModule(module.getIdentifier()); + model.deleteModule(moduleToDelete); + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateGroupList(FIRST_MODULE_INDEX); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateStudentList(FIRST_GROUP_INDEX, FIRST_MODULE_INDEX); + } + } + return new CommandResult(String.format(MESSAGE_DELETE_MODULE_SUCCESS, moduleToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteModuleCommand // instanceof handles nulls + && module.equals(((DeleteModuleCommand) other).module)); // state check + } +} diff --git a/src/main/java/tatracker/logic/commands/session/AddSessionCommand.java b/src/main/java/tatracker/logic/commands/session/AddSessionCommand.java new file mode 100644 index 00000000000..40ffa189c5b --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/AddSessionCommand.java @@ -0,0 +1,74 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_DATE; +import static tatracker.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NOTES; +import static tatracker.logic.parser.CliSyntax.PREFIX_RECUR; +import static tatracker.logic.parser.CliSyntax.PREFIX_SESSION_TYPE; +import static tatracker.logic.parser.CliSyntax.PREFIX_STARTTIME; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; + +/** + * Adds a session to the TATracker. + */ +public class AddSessionCommand extends Command { + + public static final String COMMAND_WORD = CommandWords.SESSION + " " + CommandWords.ADD_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a session in TA-Tracker. " + + "Parameters: " + + "[" + PREFIX_STARTTIME + "START] " + + "[" + PREFIX_ENDTIME + "END] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_RECUR + "RECURS] " + + "[" + PREFIX_MODULE + "MODULE CODE] " + + "[" + PREFIX_SESSION_TYPE + "SESSION TYPE] " + + "[" + PREFIX_NOTES + "NOTES] " + + "Example: " + COMMAND_WORD + " " + + PREFIX_STARTTIME + "14:00 " + + PREFIX_ENDTIME + "16:00 " + + PREFIX_DATE + "19-02-2020 " + + PREFIX_MODULE + "CS2103T " + + PREFIX_SESSION_TYPE + "tutorial " + + PREFIX_NOTES + "Location: PLAB 04"; + + public static final String MESSAGE_SUCCESS = "New session added: %1$s"; + public static final String MESSAGE_DUPLICATE_SESSION = "This session already exists in the TA-Tracker"; + + private final Session toAdd; + + /** + * Creates an AddSessionCommand to add the specified {@code Session} + */ + public AddSessionCommand(Session session) { + requireNonNull(session); + toAdd = session; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasSession(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_SESSION); + } + + model.addSession(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 AddSessionCommand // instanceof handles nulls + && toAdd.equals(((AddSessionCommand) other).toAdd)); + } +} diff --git a/src/main/java/tatracker/logic/commands/session/DeleteSessionCommand.java b/src/main/java/tatracker/logic/commands/session/DeleteSessionCommand.java new file mode 100644 index 00000000000..d8c494b2cc9 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/DeleteSessionCommand.java @@ -0,0 +1,75 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.commands.CommandWords.DELETE_MODEL; +import static tatracker.logic.commands.CommandWords.SESSION; +import static tatracker.logic.parser.CliSyntax.PREFIX_DATE; +import static tatracker.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NOTES; +import static tatracker.logic.parser.CliSyntax.PREFIX_RECUR; +import static tatracker.logic.parser.CliSyntax.PREFIX_SESSION_TYPE; +import static tatracker.logic.parser.CliSyntax.PREFIX_STARTTIME; + +import java.util.List; + +import tatracker.commons.core.Messages; +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; + + +/** + * Deletes a session identified using it's index. + */ +public class DeleteSessionCommand extends Command { + + public static final String COMMAND_WORD = SESSION + " " + DELETE_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the session identified by its index.\n" + + "Parameters: " + + "index" + + "[" + PREFIX_STARTTIME + "START] " + + "[" + PREFIX_ENDTIME + "END] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_RECUR + "] " + + "[" + PREFIX_MODULE + "MODULE] " + + "[" + PREFIX_SESSION_TYPE + "SESSION_TYPE] " + + "[" + PREFIX_NOTES + "NOTES] " + + "Example: " + COMMAND_WORD + " 2" + PREFIX_DATE + "20-02-2020 "; + + public static final String MESSAGE_DELETE_SESSION_SUCCESS = "Deleted Session: %1$s"; + public static final String MESSAGE_INVALID_INDEX = "Index does not exists"; + + private final Index index; + + public DeleteSessionCommand(Index index) { + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredSessionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX); + } + + Session sessionToDelete = lastShownList.get(index.getZeroBased()); + model.deleteSession(sessionToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_SESSION_SUCCESS, sessionToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteSessionCommand // instanceof handles nulls + && index.equals(((DeleteSessionCommand) other).index)); // state check + } +} diff --git a/src/main/java/tatracker/logic/commands/session/DoneSessionCommand.java b/src/main/java/tatracker/logic/commands/session/DoneSessionCommand.java new file mode 100644 index 00000000000..3147d44ca9c --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/DoneSessionCommand.java @@ -0,0 +1,77 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import tatracker.commons.core.Messages; +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; + +/** + * Marks a session as done in TAT. + */ +public class DoneSessionCommand extends Command { + + public static final String COMMAND_WORD_DONE = "done"; + public static final String COMMAND_WORD = String.format("%s %s", CommandWords.SESSION, COMMAND_WORD_DONE); + + /* Example message usage. */ + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks a session as done in TA-Tracker. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer)"; + + public static final String MESSAGE_SUCCESS = "Session completed: %1$s"; + public static final String MESSAGE_INVALID_INDEX = "Index does not exists"; + + private final Index index; + + /** + * @param index of the session in the filtered session list to edit + */ + public DoneSessionCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredSessionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX); + } + + Session session = lastShownList.get(index.getZeroBased()); + session.done(); + model.addDoneSession(session); + model.updateFilteredDoneSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS); + model.deleteSession(session); + model.updateFilteredSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS); + return new CommandResult(String.format(MESSAGE_SUCCESS, session)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DoneSessionCommand)) { + return false; + } + + // state check + DoneSessionCommand e = (DoneSessionCommand) other; + return index.equals(e.index); + } +} diff --git a/src/main/java/tatracker/logic/commands/session/EditSessionCommand.java b/src/main/java/tatracker/logic/commands/session/EditSessionCommand.java new file mode 100644 index 00000000000..f1942f61847 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/session/EditSessionCommand.java @@ -0,0 +1,256 @@ +package tatracker.logic.commands.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_DATE; +import static tatracker.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NOTES; +import static tatracker.logic.parser.CliSyntax.PREFIX_RECUR; +import static tatracker.logic.parser.CliSyntax.PREFIX_SESSION_TYPE; +import static tatracker.logic.parser.CliSyntax.PREFIX_STARTTIME; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import tatracker.commons.core.Messages; +import tatracker.commons.core.index.Index; +import tatracker.commons.util.CollectionUtil; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; + +/** + * Edits the details of an existing session in TAT. + */ +public class EditSessionCommand extends Command { + + public static final String COMMAND_WORD = String.format("%s %s", CommandWords.SESSION, CommandWords.EDIT_MODEL); + + /* Example message usage. */ + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits an existing session in TA-Tracker. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_STARTTIME + "START] " + + "[" + PREFIX_ENDTIME + "END] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_RECUR + "] " + + "[" + PREFIX_MODULE + "MODULE CODE] " + + "[" + PREFIX_SESSION_TYPE + "SESSION TYPE] " + + "[" + PREFIX_NOTES + "NOTES] " + + "Example: " + COMMAND_WORD + " 2 " + + PREFIX_DATE + "20-02-2020 "; + + public static final String MESSAGE_SUCCESS = "Session updated: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + + private final Index index; + private final EditSessionCommand.EditSessionDescriptor editSessionDescriptor; + + /** + * @param index of the session in the filtered session list to edit + * @param editSessionDescriptor details to edit the session with + */ + public EditSessionCommand(Index index, EditSessionCommand.EditSessionDescriptor editSessionDescriptor) { + requireNonNull(index); + requireNonNull(editSessionDescriptor); + + this.index = index; + this.editSessionDescriptor = new EditSessionCommand.EditSessionDescriptor(editSessionDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredSessionList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_SESSION_DISPLAYED_INDEX); + } + + Session sessionToEdit = lastShownList.get(index.getZeroBased()); + Session editedSession = createEditedSession(sessionToEdit, editSessionDescriptor); + + // Session does not have an unique identifier like students/modules, and as such checking if + // the two objects are the same should not be done based on their field values. + // In this case, it is probably best to ignore no-edits. + /* + if (!sessionToEdit.isSameStudent(editedStudent) && model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + */ + + model.setSession(sessionToEdit, editedSession); + model.updateFilteredSessionList(Model.PREDICATE_SHOW_ALL_SESSIONS); + + return new CommandResult(String.format(MESSAGE_SUCCESS, editedSession)); + } + + /** + * Creates and returns a {@code Session} with the details of {@code sessionToEdit} + * edited with {@code editSessionDescriptor}. + */ + private static Session createEditedSession(Session sessionToEdit, + EditSessionCommand.EditSessionDescriptor editSessionDescriptor) { + assert sessionToEdit != null; + + LocalDateTime startTime = editSessionDescriptor.getStartTime().orElse(sessionToEdit.getStartDateTime()); + LocalDateTime endTime = editSessionDescriptor.getEndTime().orElse(sessionToEdit.getEndDateTime()); + boolean isRecurring = editSessionDescriptor.getIsRecurring(); + String moduleCode = editSessionDescriptor.getModuleCode().orElse(sessionToEdit.getModuleCode()); + SessionType type = editSessionDescriptor.getSessionType().orElse(sessionToEdit.getSessionType()); + String description = editSessionDescriptor.getDescription().orElse(sessionToEdit.getDescription()); + + // If date is not updated, reset the date to the original date. + // We need to do this because EditSessionCommandParser is not able to know that is the original date + // to use as default value, so an arbitrary default date is used. + if (!editSessionDescriptor.getIsDateChanged()) { + LocalDate originalDate = sessionToEdit.getDate(); + startTime = LocalDateTime.of(originalDate.getYear(), originalDate.getMonth(), originalDate.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond()); + endTime = LocalDateTime.of(originalDate.getYear(), originalDate.getMonth(), originalDate.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond()); + } + + return new Session(startTime, endTime, type, isRecurring, moduleCode, description); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditSessionCommand)) { + return false; + } + + // state check + EditSessionCommand e = (EditSessionCommand) other; + return index.equals(e.index) + && editSessionDescriptor.equals(e.editSessionDescriptor); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. + */ + public static class EditSessionDescriptor { + private LocalDateTime newStartTime; + private LocalDateTime newEndTime; + private boolean isDateChanged; + private boolean isRecurring; + private String moduleCode; + private SessionType newSessionType; + private String newDescription; + + public EditSessionDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditSessionDescriptor(EditSessionCommand.EditSessionDescriptor toCopy) { + setStartTime(toCopy.newStartTime); + setEndTime(toCopy.newEndTime); + setIsRecurring(toCopy.isRecurring); + setModuleCode(toCopy.moduleCode); + setSessionType(toCopy.newSessionType); + setDescription(toCopy.newDescription); + this.isDateChanged = toCopy.isDateChanged; + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(newStartTime, newEndTime, newSessionType, newDescription); + } + + public void setStartTime(LocalDateTime startTime) { + this.newStartTime = startTime; + } + + public Optional getStartTime() { + return Optional.ofNullable(newStartTime); + } + + public void setEndTime(LocalDateTime endTime) { + this.newEndTime = endTime; + } + + public Optional getEndTime() { + return Optional.ofNullable(newEndTime); + } + + public void setIsRecurring(boolean isRecurring) { + this.isRecurring = isRecurring; + } + + public boolean getIsRecurring() { + return this.isRecurring; + } + + public void setIsDateChanged(boolean isChanged) { + this.isDateChanged = isChanged; + } + + public boolean getIsDateChanged() { + return this.isDateChanged; + } + + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + public Optional getModuleCode() { + return Optional.ofNullable(moduleCode); + } + + public void setSessionType(SessionType sessionType) { + this.newSessionType = sessionType; + } + + public Optional getSessionType() { + return Optional.ofNullable(newSessionType); + } + + public void setDescription(String description) { + this.newDescription = description; + } + + public Optional getDescription() { + return Optional.ofNullable(newDescription); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditSessionCommand.EditSessionDescriptor)) { + return false; + } + + // state check + EditSessionCommand.EditSessionDescriptor e = (EditSessionCommand.EditSessionDescriptor) other; + + return getStartTime().equals(e.getStartTime()) + && getEndTime().equals(e.getEndTime()) + && getSessionType().equals(e.getSessionType()) + && getDescription().equals(e.getDescription()); + } + } +} diff --git a/src/main/java/tatracker/logic/commands/student/AddStudentCommand.java b/src/main/java/tatracker/logic/commands/student/AddStudentCommand.java new file mode 100644 index 00000000000..06ea92c7fac --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/AddStudentCommand.java @@ -0,0 +1,116 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MATRIC; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NAME; +import static tatracker.logic.parser.CliSyntax.PREFIX_PHONE; +import static tatracker.logic.parser.CliSyntax.PREFIX_RATING; +import static tatracker.logic.parser.CliSyntax.PREFIX_TAG; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.student.Student; + +/** + * Adds a student to the TA-Tracker. + */ +public class AddStudentCommand extends Command { + + public static final String COMMAND_WORD = CommandWords.STUDENT + " " + CommandWords.ADD_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a student to the into the given module group.\n" + + "Parameters:\n" + + PREFIX_MATRIC + "MATRIC " + + PREFIX_MODULE + "MODULE " + + PREFIX_GROUP + "GROUP " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_RATING + "RATING] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example:\n" + + COMMAND_WORD + " " + + PREFIX_MATRIC + "A0181234G " + + PREFIX_MODULE + "CS3243 " + + PREFIX_GROUP + "G06 " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_RATING + "3 " + + PREFIX_TAG + "friends " + + PREFIX_TAG + "owesMoney"; + + public static final String MESSAGE_SUCCESS = "New student added: %s\n To Module: %s\n To Group: %s"; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the TA-Tracker"; + public static final String MESSAGE_INVALID_MODULE_FORMAT = "There is no module with the given module code: %s"; + public static final String MESSAGE_INVALID_GROUP_FORMAT = + "There is no group in the module (%s) with the given group code: %s"; + + private final Student toAdd; + + private final Group targetGroup; + private final Module targetModule; + + /** + * Creates an AddStudentCommand to add the specified {@code Student} + */ + public AddStudentCommand(Student student, Group group, Module module) { + requireNonNull(student); + requireNonNull(group); + requireNonNull(module); + toAdd = student; + targetGroup = group; + targetModule = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(String.format(MESSAGE_INVALID_MODULE_FORMAT, targetModule.getIdentifier())); + } + + if (!model.hasGroup(targetGroup, targetModule)) { + throw new CommandException(String.format(MESSAGE_INVALID_GROUP_FORMAT, + targetModule.getIdentifier(), + targetGroup.getIdentifier())); + } + + if (model.hasStudent(toAdd, targetGroup, targetModule)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + model.addStudent(toAdd, targetGroup, targetModule); + + model.updateFilteredGroupList(targetModule.getIdentifier()); + model.updateFilteredStudentList(targetGroup.getIdentifier(), targetModule.getIdentifier()); + + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd, targetModule, targetGroup)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof AddStudentCommand)) { + return false; // instanceof handles nulls + } + + AddStudentCommand otherCommand = (AddStudentCommand) other; + return toAdd.equals(otherCommand.toAdd) + && targetGroup.equals(otherCommand.targetGroup) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/student/DeleteStudentCommand.java b/src/main/java/tatracker/logic/commands/student/DeleteStudentCommand.java new file mode 100644 index 00000000000..2a482e30054 --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/DeleteStudentCommand.java @@ -0,0 +1,120 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MATRIC; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.student.Matric; +import tatracker.model.student.Student; + +/** + * Deletes a student identified using it's displayed index from the TA-Tracker. + */ +public class DeleteStudentCommand extends Command { + + public static final String COMMAND_WORD = CommandWords.STUDENT + " " + CommandWords.DELETE_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the student with the given matric number from the given module group.\n" + + "Parameters:\n" + + PREFIX_MATRIC + "MATRIC " + + PREFIX_MODULE + "MODULE " + + PREFIX_GROUP + "GROUP\n" + + "Example:\n" + + COMMAND_WORD + " " + + PREFIX_MATRIC + "A0181234G " + + PREFIX_MODULE + "CS3243 " + + PREFIX_GROUP + "G06 "; + + public static final String MESSAGE_DELETE_STUDENT_SUCCESS = "Deleted Student: %1$s"; + public static final String MESSAGE_INVALID_MODULE_FORMAT = + "There is no module with the given module code: %s"; + public static final String MESSAGE_INVALID_GROUP_FORMAT = + "There is no group in the module (%s) with the given group code: %s"; + public static final String MESSAGE_INVALID_STUDENT_FORMAT = + "There is no student in the (%s) group (%s) with the given matric number: %s"; + + public static final int FIRST_GROUP_INDEX = 0; + public static final int FIRST_MODULE_INDEX = 0; + + private final Matric toDelete; + private final Group targetGroup; + private final Module targetModule; + + public DeleteStudentCommand(Matric matric, Group group, Module module) { + requireNonNull(matric); + requireNonNull(group); + requireNonNull(module); + this.toDelete = matric; + this.targetGroup = group; + this.targetModule = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModule(targetModule)) { + throw new CommandException(String.format(MESSAGE_INVALID_MODULE_FORMAT, targetModule.getIdentifier())); + } + + if (!model.hasGroup(targetGroup, targetModule)) { + throw new CommandException(String.format(MESSAGE_INVALID_GROUP_FORMAT, + targetModule.getIdentifier(), + targetGroup.getIdentifier())); + } + + Module actualModule = model.getModule(targetModule.getIdentifier()); + Group actualGroup = actualModule.getGroup(targetGroup.getIdentifier()); + + Student studentToDelete = actualGroup.getStudent(toDelete); + + // TODO: consider replacing has methods with id instead of actual objects + if (studentToDelete == null) { + throw new CommandException(String.format(MESSAGE_INVALID_STUDENT_FORMAT, + targetModule.getIdentifier(), + targetGroup.getIdentifier(), + toDelete)); + } + + model.deleteStudent(studentToDelete, targetGroup, targetModule); + + if (model.getFilteredModuleList().isEmpty()) { + model.setFilteredGroupList(); + model.setFilteredStudentList(); + } else { + model.updateGroupList(FIRST_MODULE_INDEX); + if (model.getFilteredGroupList().isEmpty()) { + model.setFilteredStudentList(); + } else { + model.updateStudentList(FIRST_GROUP_INDEX, FIRST_MODULE_INDEX); + } + } + + return new CommandResult(String.format(MESSAGE_DELETE_STUDENT_SUCCESS, studentToDelete)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; // short circuit if same object + } + + if (!(other instanceof DeleteStudentCommand)) { + return false; // instanceof handles nulls + } + + DeleteStudentCommand otherCommand = (DeleteStudentCommand) other; + return toDelete.equals(otherCommand.toDelete) + && targetGroup.equals(otherCommand.targetGroup) + && targetModule.equals(otherCommand.targetModule); + } +} diff --git a/src/main/java/tatracker/logic/commands/student/EditStudentCommand.java b/src/main/java/tatracker/logic/commands/student/EditStudentCommand.java new file mode 100644 index 00000000000..3b29983f48d --- /dev/null +++ b/src/main/java/tatracker/logic/commands/student/EditStudentCommand.java @@ -0,0 +1,245 @@ +package tatracker.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static tatracker.logic.parser.CliSyntax.PREFIX_MATRIC; +import static tatracker.logic.parser.CliSyntax.PREFIX_NAME; +import static tatracker.logic.parser.CliSyntax.PREFIX_PHONE; +import static tatracker.logic.parser.CliSyntax.PREFIX_RATING; +import static tatracker.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import tatracker.commons.core.Messages; +import tatracker.commons.core.index.Index; +import tatracker.commons.util.CollectionUtil; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.model.Model; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + +/** + * Edits the details of an existing student in the TA-Tracker. + */ +public class EditStudentCommand extends Command { + + public static final String COMMAND_WORD = CommandWords.STUDENT + " " + CommandWords.EDIT_MODEL; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the student identified " + + "by the index number used in the displayed student 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_MATRIC + "MATRIC] " + + "[" + PREFIX_RATING + "RATING] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com " + + PREFIX_MATRIC + "A0181234J"; + + public static final String MESSAGE_EDIT_STUDENT_SUCCESS = "Edited Student: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the TA-Tracker."; + + private final Index index; + private final EditStudentDescriptor editStudentDescriptor; + + /** + * @param index of the student in the filtered student list to edit + * @param editStudentDescriptor details to edit the student with + */ + public EditStudentCommand(Index index, EditStudentDescriptor editStudentDescriptor) { + requireNonNull(index); + requireNonNull(editStudentDescriptor); + + this.index = index; + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + + if (!studentToEdit.isSameStudent(editedStudent) && model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + model.setStudent(studentToEdit, editedStudent); + + return new CommandResult(String.format(MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent)); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. + */ + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Name updatedName = editStudentDescriptor.getName().orElse(studentToEdit.getName()); + Phone updatedPhone = editStudentDescriptor.getPhone().orElse(studentToEdit.getPhone()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(studentToEdit.getEmail()); + Matric updatedMatric = editStudentDescriptor.getMatric().orElse(studentToEdit.getMatric()); + Rating updatedRating = editStudentDescriptor.getRating().orElse(studentToEdit.getRating()); + Set updatedTags = editStudentDescriptor.getTags().orElse(studentToEdit.getTags()); + + return new Student(updatedMatric, updatedName, updatedPhone, updatedEmail, updatedRating, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditStudentCommand)) { + return false; + } + + // state check + EditStudentCommand e = (EditStudentCommand) other; + return index.equals(e.index) + && editStudentDescriptor.equals(e.editStudentDescriptor); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. + */ + public static class EditStudentDescriptor { + private Name name; + private Phone phone; + private Email email; + private Matric matric; + private Rating rating; + private Set tags; + + public EditStudentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditStudentDescriptor(EditStudentDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setMatric(toCopy.matric); + setRating(toCopy.rating); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, email, matric, rating, 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 setMatric(Matric matric) { + this.matric = matric; + } + + public Optional getMatric() { + return Optional.ofNullable(matric); + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + public Optional getRating() { + return Optional.ofNullable(rating); + } + + /** + * 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 EditStudentDescriptor)) { + return false; + } + + // state check + EditStudentDescriptor e = (EditStudentDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getMatric().equals(e.getMatric()) + && getRating().equals(e.getRating()) + && getTags().equals(e.getTags()); + } + } +} + diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/tatracker/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/tatracker/logic/parser/ArgumentMultimap.java index 954c8e18f8e..686d8720c68 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/tatracker/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/tatracker/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/tatracker/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..9f94790b39a 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/tatracker/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/tatracker/logic/parser/CliSyntax.java b/src/main/java/tatracker/logic/parser/CliSyntax.java new file mode 100644 index 00000000000..e0c36566289 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/CliSyntax.java @@ -0,0 +1,26 @@ +package tatracker.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class CliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_MATRIC = new Prefix("id/"); + public static final Prefix PREFIX_RATING = new Prefix("r/"); + public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_STARTTIME = new Prefix("s/"); + public static final Prefix PREFIX_ENDTIME = new Prefix("e/"); + public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_RECUR = new Prefix("-r"); + public static final Prefix PREFIX_SESSION_TYPE = new Prefix("t/"); + public static final Prefix PREFIX_NOTES = new Prefix("n/"); + public static final Prefix PREFIX_MODULE = new Prefix("m/"); + public static final Prefix PREFIX_GROUP = new Prefix("g/"); + public static final Prefix PREFIX_TYPE = new Prefix("t/"); + public static final Prefix PREFIX_NEWTYPE = new Prefix("nt/"); + public static final Prefix PREFIX_NEWGROUP = new Prefix("ng/"); +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/tatracker/logic/parser/DeleteCommandParser.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/tatracker/logic/parser/DeleteCommandParser.java index 522b93081cc..e351afa2f53 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/tatracker/logic/parser/DeleteCommandParser.java @@ -1,10 +1,10 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.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; +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.DeleteCommand; +import tatracker.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/tatracker/logic/parser/FindCommandParser.java similarity index 74% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/tatracker/logic/parser/FindCommandParser.java index 4fb71f23103..291687cd8cb 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/tatracker/logic/parser/FindCommandParser.java @@ -1,12 +1,12 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.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; +import tatracker.logic.commands.FindCommand; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.student.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/tatracker/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/tatracker/logic/parser/Parser.java index d6551ad8e3f..0b0ebc13596 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/tatracker/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import tatracker.logic.commands.Command; +import tatracker.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/tatracker/logic/parser/ParserUtil.java b/src/main/java/tatracker/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..51b30a34cba --- /dev/null +++ b/src/main/java/tatracker/logic/parser/ParserUtil.java @@ -0,0 +1,230 @@ +package tatracker.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeParseException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import tatracker.commons.core.index.Index; +import tatracker.commons.util.StringUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.GroupType; +import tatracker.model.session.SessionType; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.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."; + + private static final String MESSAGE_INVALID_DATE = "Dates should be in yyyy-MM-dd format"; + private static final String MESSAGE_INVALID_TIME = "Times should be in HH:mm format"; + + /** + * 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 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 matric} into an {@code Matric}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code matric} is invalid. + */ + public static Matric parseMatric(String matric) throws ParseException { + requireNonNull(matric); + String trimmedMatric = matric.trim(); + if (!Matric.isValidMatric(trimmedMatric)) { + throw new ParseException(Matric.MESSAGE_CONSTRAINTS); + } + return new Matric(trimmedMatric); + } + + /** + * 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; + } + + /** + * Parses and returns the given value. + */ + public static String parseValue(String value) { + requireNonNull(value); + String trimmedValue = value.trim(); + return trimmedValue; + } + + /** + * Parses a {@code String date} into a {@code LocalDate} + * + * @throws ParseException if the given {@code date} is invalid. + */ + public static LocalDate parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + try { + return LocalDate.parse(trimmedDate); + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_DATE); + } + } + + /** + * Parses a {@code String time} into a {@code LocalTime} + * + * @throws ParseException if the given {@code time} is invalid. + */ + public static LocalTime parseTime(String time) throws ParseException { + requireNonNull(time); + String trimmedTime = time.trim(); + try { + return LocalTime.parse(trimmedTime); + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_TIME); + } + } + + /** + * Parses a {@code String sessionType} into a {@code SessionType} + */ + public static SessionType parseSessionType(String sessionType) { + requireNonNull(sessionType); + String trimmedType = sessionType.trim(); + assert (trimmedType.equals(trimmedType.toLowerCase())); + switch (trimmedType) { + case "tutorial": + return SessionType.TUTORIAL; + case "lab": + return SessionType.LAB; + case "consultation": + return SessionType.CONSULTATION; + case "grading": + return SessionType.GRADING; + case "preparation": + return SessionType.PREPARATION; + default: + return SessionType.OTHER; + } + } + + /** + * Parses and returns Group Type of group. + */ + public static GroupType parseGroupType(String type) { + requireNonNull(type); + String trimmedType = type.trim(); + switch(trimmedType.toLowerCase()) { + case "lab": + return GroupType.LAB; + default: + return GroupType.TUTORIAL; + } + } + + /** + * Parses a {@code String rating} into a {@code Rating} + */ + public static Rating parseRating(String rating) throws ParseException { + requireNonNull(rating); + String trimmedRating = rating.trim(); + + if (!StringUtil.isNonZeroUnsignedInteger(trimmedRating)) { + throw new ParseException(Rating.MESSAGE_CONSTRAINTS); + } + + int parsedRating = Integer.parseUnsignedInt(trimmedRating); + + if (!Rating.isValidRating(parsedRating)) { + throw new ParseException(Rating.MESSAGE_CONSTRAINTS); + } + + return new Rating(parsedRating); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/tatracker/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/tatracker/logic/parser/Prefix.java index c859d5fa5db..9ca2cc9f3c0 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/tatracker/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/tatracker/logic/parser/SortCommandParser.java b/src/main/java/tatracker/logic/parser/SortCommandParser.java new file mode 100644 index 00000000000..808b6a61a83 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/SortCommandParser.java @@ -0,0 +1,46 @@ +package tatracker.logic.parser; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import tatracker.logic.commands.SortCommand; +import tatracker.logic.commands.SortGroupCommand; +import tatracker.logic.commands.SortModuleCommand; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortCommand object + */ +public class SortCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the SortCommand + * and returns a SortCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TYPE, + PREFIX_MODULE, PREFIX_GROUP); + + if (!argMultimap.getValue(PREFIX_TYPE).isPresent()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + String type = ParserUtil.parseValue(argMultimap.getValue(PREFIX_TYPE).get()); + + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + String moduleCode = ParserUtil.parseValue(argMultimap.getValue(PREFIX_MODULE).get()); + + if (argMultimap.getValue(PREFIX_GROUP).isPresent()) { + String groupCode = ParserUtil.parseValue(argMultimap.getValue(PREFIX_GROUP).get()); + return new SortGroupCommand(groupCode, moduleCode, type); + } else { + return new SortModuleCommand(moduleCode, type); + } + } else { + return new SortCommand(type); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/tatracker/logic/parser/TaTrackerParser.java similarity index 51% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/tatracker/logic/parser/TaTrackerParser.java index 1e466792b46..b23b943a9c5 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/tatracker/logic/parser/TaTrackerParser.java @@ -1,26 +1,32 @@ -package seedu.address.logic.parser; +package tatracker.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.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; +import tatracker.logic.commands.ClearCommand; +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.DeleteCommand; +import tatracker.logic.commands.ExitCommand; +import tatracker.logic.commands.FindCommand; +import tatracker.logic.commands.HelpCommand; +import tatracker.logic.commands.ListCommand; +//import tatracker.logic.commands.student.EditStudentCommand; +import tatracker.logic.commands.SortCommand; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.logic.parser.group.GroupCommandParser; +import tatracker.logic.parser.module.ModuleCommandParser; +import tatracker.logic.parser.session.SessionCommandParser; +//import tatracker.logic.parser.student.EditStudentCommandParser; +import tatracker.logic.parser.student.StudentCommandParser; /** * Parses user input. */ -public class AddressBookParser { +public class TaTrackerParser { /** * Used for initial separation of command word and args. @@ -44,33 +50,41 @@ public Command parseCommand(String userInput) throws ParseException { final String arguments = matcher.group("arguments"); switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case CommandWords.STUDENT: + return new StudentCommandParser().parseCommand(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case CommandWords.MODULE: + return new ModuleCommandParser().parseCommand(arguments); + + case CommandWords.GROUP: + return new GroupCommandParser().parseCommand(arguments); + + case CommandWords.SESSION: + return new SessionCommandParser().parseCommand(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 ClearCommand.COMMAND_WORD: + return new ClearCommand(); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } - } diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/tatracker/logic/parser/exceptions/ParseException.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/tatracker/logic/parser/exceptions/ParseException.java index 158a1a54c1c..1fcf1795117 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/tatracker/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package tatracker.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import tatracker.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/tatracker/logic/parser/group/AddGroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/AddGroupCommandParser.java new file mode 100644 index 00000000000..d84a3f97673 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/AddGroupCommandParser.java @@ -0,0 +1,57 @@ +package tatracker.logic.parser.group; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_TYPE; + +import java.util.stream.Stream; + +import tatracker.logic.commands.group.AddGroupCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.module.Module; + +/** + * Parses input arguments and creates a new AddGroupCommand object + */ +public class AddGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddGroupCommand + * and returns an AddGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_GROUP, PREFIX_MODULE, PREFIX_TYPE); + + if (!arePrefixesPresent(argMultimap, PREFIX_GROUP, PREFIX_MODULE, PREFIX_TYPE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE)); + } + + String groupCode = argMultimap.getValue(PREFIX_GROUP).get(); + String moduleCode = argMultimap.getValue(PREFIX_MODULE).get(); + GroupType groupType = ParserUtil.parseGroupType(argMultimap.getValue(PREFIX_TYPE).get()); + + Group group = new Group(groupCode, groupType); + Module module = new Module(moduleCode); + + return new AddGroupCommand(group, module); + } + + /** + * 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/tatracker/logic/parser/group/DeleteGroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/DeleteGroupCommandParser.java new file mode 100644 index 00000000000..e964bdb3b58 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/DeleteGroupCommandParser.java @@ -0,0 +1,54 @@ +package tatracker.logic.parser.group; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.stream.Stream; + +import tatracker.logic.commands.group.DeleteGroupCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.module.Module; + +/** + * Parses input arguments and creates a new DeleteGroupCommand object + */ +public class DeleteGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteGroupCommand + * and returns an DeleteGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_GROUP, PREFIX_MODULE); + + if (!arePrefixesPresent(argMultimap, PREFIX_GROUP, PREFIX_MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE)); + } + + String groupCode = argMultimap.getValue(PREFIX_GROUP).get(); + String moduleCode = argMultimap.getValue(PREFIX_MODULE).get(); + + Group group = new Group(groupCode); + Module module = new Module(moduleCode); + + return new DeleteGroupCommand(group, module); + } + + /** + * 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/tatracker/logic/parser/group/EditGroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/EditGroupCommandParser.java new file mode 100644 index 00000000000..9797d5d509d --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/EditGroupCommandParser.java @@ -0,0 +1,71 @@ +package tatracker.logic.parser.group; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NEWGROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_NEWTYPE; + +import java.util.stream.Stream; + +import tatracker.logic.commands.group.EditGroupCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.module.Module; + +/** + * Parses input arguments and creates a new AddGroupCommand object + */ +public class EditGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddGroupCommand + * and returns an AddGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_GROUP, PREFIX_MODULE, PREFIX_NEWTYPE, PREFIX_NEWGROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_GROUP, PREFIX_MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGroupCommand.MESSAGE_USAGE)); + } + + String groupCode = argMultimap.getValue(PREFIX_GROUP).get(); + String moduleCode = argMultimap.getValue(PREFIX_MODULE).get(); + + Group group = new Group(groupCode); + Module module = new Module(moduleCode); + + String newGroupCode; + if (argMultimap.getValue(PREFIX_NEWGROUP).isPresent()) { + newGroupCode = ParserUtil.parseValue(argMultimap.getValue(PREFIX_NEWGROUP).get()); + } else { + newGroupCode = groupCode; + } + + GroupType newGroupType; + if (argMultimap.getValue(PREFIX_NEWTYPE).isPresent()) { + newGroupType = ParserUtil.parseGroupType(argMultimap.getValue(PREFIX_NEWTYPE).get()); + } else { + newGroupType = null; + } + + return new EditGroupCommand(group, module, newGroupCode, newGroupType); + } + + /** + * 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/tatracker/logic/parser/group/GroupCommandParser.java b/src/main/java/tatracker/logic/parser/group/GroupCommandParser.java new file mode 100644 index 00000000000..f45c6e641b7 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/group/GroupCommandParser.java @@ -0,0 +1,56 @@ +package tatracker.logic.parser.group; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.HelpCommand; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Group models. + */ +public class GroupCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + private static final String UNIMPLEMENTED_CODE_FORMAT = "%s not yet implemented!"; + + /** + * 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 CommandWords.ADD_MODEL: + return new AddGroupCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteGroupCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditGroupCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/module/AddModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/AddModuleCommandParser.java new file mode 100644 index 00000000000..48220756da8 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/AddModuleCommandParser.java @@ -0,0 +1,53 @@ +package tatracker.logic.parser.module; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import tatracker.logic.commands.module.AddModuleCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.module.Module; + +/** + * Parses input arguments and creates a new AddModuleCommand object + */ +public class AddModuleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddModuleCommand + * and returns an AddModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddModuleCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_NAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddModuleCommand.MESSAGE_USAGE)); + } + + // No need to parse trimmed strings + String moduleCode = argMultimap.getValue(PREFIX_MODULE).get(); + String name = argMultimap.getValue(PREFIX_NAME).get(); + + Module module = new Module(moduleCode, name); + + return new AddModuleCommand(module); + } + + /** + * 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/tatracker/logic/parser/module/DeleteModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/DeleteModuleCommandParser.java new file mode 100644 index 00000000000..f84a0412f76 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/DeleteModuleCommandParser.java @@ -0,0 +1,50 @@ +package tatracker.logic.parser.module; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.stream.Stream; + +import tatracker.logic.commands.module.DeleteModuleCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.module.Module; + +/** + * Parses input arguments and creates a new DeleteModuleCommand object + */ +public class DeleteModuleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteModuleCommand + * and returns an DeleteModuleCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteModuleCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE); + + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteModuleCommand.MESSAGE_USAGE)); + } + + String moduleCode = argMultimap.getValue(PREFIX_MODULE).get(); + + Module module = new Module(moduleCode); + + return new DeleteModuleCommand(module); + } + + /** + * 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/tatracker/logic/parser/module/ModuleCommandParser.java b/src/main/java/tatracker/logic/parser/module/ModuleCommandParser.java new file mode 100644 index 00000000000..2c921d0fc10 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/module/ModuleCommandParser.java @@ -0,0 +1,57 @@ +package tatracker.logic.parser.module; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.HelpCommand; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Module models. + */ +public class ModuleCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + private static final String UNIMPLEMENTED_CODE_FORMAT = "%s not yet implemented!"; + + /** + * 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 CommandWords.ADD_MODEL: + return new AddModuleCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteModuleCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + // return new EditGroupCommandParser().parse(arguments); + throw new ParseException(String.format(UNIMPLEMENTED_CODE_FORMAT, "Edit group commands")); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/AddSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/AddSessionCommandParser.java new file mode 100644 index 00000000000..47cedd527bd --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/AddSessionCommandParser.java @@ -0,0 +1,89 @@ +package tatracker.logic.parser.session; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_DATE; +import static tatracker.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NOTES; +import static tatracker.logic.parser.CliSyntax.PREFIX_RECUR; +import static tatracker.logic.parser.CliSyntax.PREFIX_SESSION_TYPE; +import static tatracker.logic.parser.CliSyntax.PREFIX_STARTTIME; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import tatracker.logic.commands.session.AddSessionCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.session.Session; + +/* + * === BUGS === + * TODO: No error when end time is after start time. + * + * TODO: Sessions cannot have dates that are earlier than the current date. + * Earlier dates are replaced by the current date. + */ + +/** + * Parses input arguments and creates a new AddSessionCommand object + */ +public class AddSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddSessionCommand + * and returns an AddSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddSessionCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_STARTTIME, PREFIX_ENDTIME, + PREFIX_DATE, PREFIX_RECUR, PREFIX_MODULE, PREFIX_SESSION_TYPE, PREFIX_NOTES); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddSessionCommand.MESSAGE_USAGE)); + } + + LocalDate date = LocalDate.now(); + Session sessionToAdd = new Session(); + + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()); + } + + if (argMultimap.getValue(PREFIX_STARTTIME).isPresent()) { + LocalTime startTime = ParserUtil.parseTime(argMultimap.getValue(PREFIX_STARTTIME).get()); + sessionToAdd.setStartDateTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond())); + } + + if (argMultimap.getValue(PREFIX_ENDTIME).isPresent()) { + LocalTime endTime = ParserUtil.parseTime(argMultimap.getValue(PREFIX_ENDTIME).get()); + sessionToAdd.setEndDateTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond())); + } + + if (argMultimap.getValue(PREFIX_RECUR).isPresent()) { + sessionToAdd.setRecurring(argMultimap.getValue(PREFIX_RECUR).isPresent()); + } + + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + sessionToAdd.setModuleCode(ParserUtil.parseValue(argMultimap.getValue(PREFIX_MODULE).get())); + } + + if (argMultimap.getValue(PREFIX_SESSION_TYPE).isPresent()) { + sessionToAdd.setType( + ParserUtil.parseSessionType(argMultimap.getValue(PREFIX_SESSION_TYPE).get())); + } + + if (argMultimap.getValue(PREFIX_NOTES).isPresent()) { + sessionToAdd.setDescription(ParserUtil.parseValue(argMultimap.getValue(PREFIX_NOTES).get())); + } + + return new AddSessionCommand(sessionToAdd); + } +} diff --git a/src/main/java/tatracker/logic/parser/session/DeleteSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/DeleteSessionCommandParser.java new file mode 100644 index 00000000000..05362e2204c --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/DeleteSessionCommandParser.java @@ -0,0 +1,32 @@ +package tatracker.logic.parser.session; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.session.DeleteSessionCommand; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteSessionCommand object + */ +public class DeleteSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteSessionCommand + * and returns an DeleteSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteSessionCommand parse(String args) throws ParseException { + + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteSessionCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteSessionCommand.MESSAGE_INVALID_INDEX), pe); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/DoneSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/DoneSessionCommandParser.java new file mode 100644 index 00000000000..822dc1fb286 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/DoneSessionCommandParser.java @@ -0,0 +1,31 @@ +package tatracker.logic.parser.session; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.session.DoneSessionCommand; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DoneSessionCommand object + */ +public class DoneSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DoneSessionCommand + * and returns an DoneSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DoneSessionCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DoneSessionCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DoneSessionCommand.MESSAGE_INVALID_INDEX), pe); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/session/EditSessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/EditSessionCommandParser.java new file mode 100644 index 00000000000..a5fba7ed62d --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/EditSessionCommandParser.java @@ -0,0 +1,102 @@ +package tatracker.logic.parser.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_DATE; +import static tatracker.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NOTES; +import static tatracker.logic.parser.CliSyntax.PREFIX_RECUR; +import static tatracker.logic.parser.CliSyntax.PREFIX_SESSION_TYPE; +import static tatracker.logic.parser.CliSyntax.PREFIX_STARTTIME; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.session.EditSessionCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; + +/* + * === BUGS === + * TODO: No error when end time is after start time. + * + * TODO: Sessions cannot have dates that are earlier than the current date. + * Earlier dates are replaced by the current date. + */ + +/** + * Parses input arguments and creates a new EditSessionCommand object + */ +public class EditSessionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditSessionCommand + * and returns an AddSessionCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditSessionCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_STARTTIME, PREFIX_ENDTIME, + PREFIX_DATE, PREFIX_RECUR, PREFIX_MODULE, PREFIX_SESSION_TYPE, PREFIX_NOTES); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditSessionCommand.MESSAGE_USAGE), + pe); + } + + EditSessionCommand.EditSessionDescriptor editSessionDescriptor = new EditSessionCommand.EditSessionDescriptor(); + + LocalDate date = LocalDate.now(); // Arbitrary default value. Will be overwritten by EditSessionCommand + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()); + editSessionDescriptor.setIsDateChanged(true); + } + + if (argMultimap.getValue(PREFIX_STARTTIME).isPresent()) { + LocalTime startTime = ParserUtil.parseTime(argMultimap.getValue(PREFIX_STARTTIME).get()); + editSessionDescriptor.setStartTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + startTime.getHour(), startTime.getMinute(), startTime.getSecond())); + } + + if (argMultimap.getValue(PREFIX_ENDTIME).isPresent()) { + LocalTime endTime = ParserUtil.parseTime(argMultimap.getValue(PREFIX_ENDTIME).get()); + editSessionDescriptor.setEndTime(LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond())); + } + + if (argMultimap.getValue(PREFIX_RECUR).isPresent()) { + editSessionDescriptor.setIsRecurring(argMultimap.getValue(PREFIX_RECUR).isPresent()); + } + + if (argMultimap.getValue(PREFIX_MODULE).isPresent()) { + editSessionDescriptor.setModuleCode(ParserUtil.parseValue(argMultimap.getValue(PREFIX_MODULE).get())); + } + + if (argMultimap.getValue(PREFIX_SESSION_TYPE).isPresent()) { + editSessionDescriptor.setSessionType( + ParserUtil.parseSessionType(argMultimap.getValue(PREFIX_SESSION_TYPE).get())); + } + + if (argMultimap.getValue(PREFIX_NOTES).isPresent()) { + editSessionDescriptor.setDescription(ParserUtil.parseValue(argMultimap.getValue(PREFIX_NOTES).get())); + } + + // TODO: Check if editing should be allowed if there are no fields + if (!editSessionDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditSessionCommand.MESSAGE_NOT_EDITED); + } + + return new EditSessionCommand(index, editSessionDescriptor); + } +} diff --git a/src/main/java/tatracker/logic/parser/session/SessionCommandParser.java b/src/main/java/tatracker/logic/parser/session/SessionCommandParser.java new file mode 100644 index 00000000000..69cbf69c0c2 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/session/SessionCommandParser.java @@ -0,0 +1,60 @@ +package tatracker.logic.parser.session; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.HelpCommand; +import tatracker.logic.commands.session.DoneSessionCommand; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Session model. + */ +public class SessionCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + private static final String UNIMPLEMENTED_CODE_FORMAT = "%s not yet implemented!"; + + /** + * 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 CommandWords.ADD_MODEL: + return new AddSessionCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteSessionCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditSessionCommandParser().parse(arguments); + + case DoneSessionCommand.COMMAND_WORD_DONE: + return new DoneSessionCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/tatracker/logic/parser/student/AddStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/AddStudentCommandParser.java new file mode 100644 index 00000000000..f7579d42b72 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/AddStudentCommandParser.java @@ -0,0 +1,96 @@ +package tatracker.logic.parser.student; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MATRIC; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; +import static tatracker.logic.parser.CliSyntax.PREFIX_NAME; +import static tatracker.logic.parser.CliSyntax.PREFIX_PHONE; +import static tatracker.logic.parser.CliSyntax.PREFIX_RATING; +import static tatracker.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import tatracker.logic.commands.student.AddStudentCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddStudentCommand object + */ +public class AddStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddStudentCommand + * and returns an AddStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddStudentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MATRIC, PREFIX_MODULE, PREFIX_GROUP, + PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_RATING, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_MATRIC, PREFIX_MODULE, PREFIX_GROUP, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddStudentCommand.MESSAGE_USAGE)); + } + + // ==== Identity fields ==== + + Matric matric = ParserUtil.parseMatric(argMultimap.getValue(PREFIX_MATRIC).get()); + + Module module = new Module(argMultimap.getValue(PREFIX_MODULE).get()); + Group group = new Group(argMultimap.getValue(PREFIX_GROUP).get()); + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + + // ==== Optional fields ==== + + Phone phone = new Phone(); + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + + Email email = new Email(); + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + } + + Rating rating = new Rating(); + if (argMultimap.getValue(PREFIX_RATING).isPresent()) { + rating = ParserUtil.parseRating(argMultimap.getValue(PREFIX_RATING).get()); + } + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + // ==== Build Student ==== + + Student student = new Student(matric, name, phone, email, rating, tagList); + + return new AddStudentCommand(student, group, module); + } + + /** + * 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/tatracker/logic/parser/student/DeleteStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/DeleteStudentCommandParser.java new file mode 100644 index 00000000000..9228177f05c --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/DeleteStudentCommandParser.java @@ -0,0 +1,60 @@ +package tatracker.logic.parser.student; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_GROUP; +import static tatracker.logic.parser.CliSyntax.PREFIX_MATRIC; +import static tatracker.logic.parser.CliSyntax.PREFIX_MODULE; + +import java.util.stream.Stream; + +import tatracker.logic.commands.student.DeleteStudentCommand; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.Prefix; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.student.Matric; + +/** + * Parses input arguments and creates a new DeleteStudentCommand object + */ +public class DeleteStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteStudentCommand + * and returns a DeleteStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteStudentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MATRIC, PREFIX_GROUP, PREFIX_MODULE); + + if (!arePrefixesPresent(argMultimap, PREFIX_MATRIC, PREFIX_GROUP, PREFIX_MODULE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteStudentCommand.MESSAGE_USAGE)); + } + + Matric matric = ParserUtil.parseMatric(argMultimap.getValue(PREFIX_MATRIC).get()); + + String groupCode = argMultimap.getValue(PREFIX_GROUP).get(); + String moduleCode = argMultimap.getValue(PREFIX_MODULE).get(); + + Group group = new Group(groupCode); + Module module = new Module(moduleCode); + + return new DeleteStudentCommand(matric, group, module); + } + + /** + * 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/tatracker/logic/parser/student/EditStudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/EditStudentCommandParser.java new file mode 100644 index 00000000000..2931b696015 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/EditStudentCommandParser.java @@ -0,0 +1,93 @@ +package tatracker.logic.parser.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static tatracker.logic.parser.CliSyntax.PREFIX_MATRIC; +import static tatracker.logic.parser.CliSyntax.PREFIX_NAME; +import static tatracker.logic.parser.CliSyntax.PREFIX_PHONE; +import static tatracker.logic.parser.CliSyntax.PREFIX_RATING; +import static tatracker.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import tatracker.commons.core.index.Index; +import tatracker.logic.commands.student.EditStudentCommand; +import tatracker.logic.commands.student.EditStudentCommand.EditStudentDescriptor; +import tatracker.logic.parser.ArgumentMultimap; +import tatracker.logic.parser.ArgumentTokenizer; +import tatracker.logic.parser.Parser; +import tatracker.logic.parser.ParserUtil; +import tatracker.logic.parser.exceptions.ParseException; +import tatracker.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditStudentCommand object + */ +public class EditStudentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditStudentCommand + * and returns an EditStudentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditStudentCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer + .tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_MATRIC, + PREFIX_RATING, PREFIX_TAG); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditStudentCommand.MESSAGE_USAGE), + pe); + } + + EditStudentDescriptor editStudentDescriptor = new EditStudentDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editStudentDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editStudentDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editStudentDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_MATRIC).isPresent()) { + editStudentDescriptor.setMatric(ParserUtil.parseMatric(argMultimap.getValue(PREFIX_MATRIC).get())); + } + if (argMultimap.getValue(PREFIX_RATING).isPresent()) { + editStudentDescriptor.setRating(ParserUtil.parseRating(argMultimap.getValue(PREFIX_RATING).get())); + } + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editStudentDescriptor::setTags); + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditStudentCommand.MESSAGE_NOT_EDITED); + } + + return new EditStudentCommand(index, editStudentDescriptor); + } + + /** + * 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/tatracker/logic/parser/student/StudentCommandParser.java b/src/main/java/tatracker/logic/parser/student/StudentCommandParser.java new file mode 100644 index 00000000000..81d9e004054 --- /dev/null +++ b/src/main/java/tatracker/logic/parser/student/StudentCommandParser.java @@ -0,0 +1,56 @@ +package tatracker.logic.parser.student; + +import static tatracker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static tatracker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import tatracker.logic.commands.Command; +import tatracker.logic.commands.CommandWords; +import tatracker.logic.commands.HelpCommand; +import tatracker.logic.parser.exceptions.ParseException; + +/** + * Parses user input into commands that interact with Student models. + */ +public class StudentCommandParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + private static final String UNIMPLEMENTED_CODE_FORMAT = "%s not yet implemented!"; + + /** + * 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 CommandWords.ADD_MODEL: + return new AddStudentCommandParser().parse(arguments); + + case CommandWords.DELETE_MODEL: + return new DeleteStudentCommandParser().parse(arguments); + + case CommandWords.EDIT_MODEL: + return new EditStudentCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/tatracker/model/Model.java b/src/main/java/tatracker/model/Model.java new file mode 100644 index 00000000000..e8bb5aa2495 --- /dev/null +++ b/src/main/java/tatracker/model/Model.java @@ -0,0 +1,322 @@ +package tatracker.model; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; + +import tatracker.commons.core.GuiSettings; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; + +/** + * The API of the Model component. + */ +public interface Model { + // TODO: Remove interface constants (aim for pure interface). + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; + + /** {@code Predicates} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_MODULES = unused -> true; + + /** {@code Predicates} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_SESSIONS = unused -> true; + + // ======== TaTracker Methods ============================================== + + /** + * Returns the TaTracker. + */ + ReadOnlyTaTracker getTaTracker(); + + /** + * Replaces TaTracker data with the data in {@code taTracker}. + */ + void setTaTracker(ReadOnlyTaTracker taTracker); + + // ======== User Prefs Methods ============================================= + + /** + * Returns the User Prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Replaces the data in User Prefs with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the TaTracker file path in the User Prefs. + */ + Path getTaTrackerFilePath(); + + /** + * Replaces the TaTracker file path in the User Prefs. + */ + void setTaTrackerFilePath(Path taTrackerFilePath); + + /** + * Returns the GUI settings in the User Prefs. + */ + GuiSettings getGuiSettings(); + + /** + * Replaces the GUI settings in the User Prefs. + */ + void setGuiSettings(GuiSettings guiSettings); + + // ======== Session Methods ================================================ + + /** + * Returns true if a given session with the same identity as {@code session} + * exists in TaTracker. + */ + boolean hasSession(Session session); + + /** + * Adds the given session into the TaTracker. + * @param session session to add, which must not already exist in the TaTracker. + */ + void addSession(Session session); + + /** + * Deletes the given session {@code target} from the TaTracker. + * @param target session to delete, which must exist in the TaTracker. + */ + void deleteSession(Session target); + + /** + * Replaces the given session {@code target} in the TaTracker with {@code editedSession}. + * @param target session to edit, which must exist in the TaTracker. + * @param editedSession the edited session {@code target}. + * The identity of {@code editedSession} must be the same as {@code target}. + */ + void setSession(Session target, Session editedSession); + + /** Returns an unmodifiable view of the filtered session list */ + ObservableList getFilteredSessionList(); + + /** + * Updates the filter of the filtered session list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredSessionList(Predicate predicate); + + // ======== Done Session Methods ================================================= + + /** + * Adds the given session to the list of completed sessions. + * The session must exist in the ta-tracker. + */ + void addDoneSession(Session session); + + // ======== Module Methods ================================================= + + /** + * Returns the TaTracker module with the given module identifier. + */ + Module getModule(String moduleId); + + /** Returns an unmodifiable view of the filtered done session list */ + ObservableList getFilteredDoneSessionList(); + + /** + * Updates the filter of the filtered done session list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredDoneSessionList(Predicate predicate); + + /** + * Returns true if a given module with the same identity as {@code module} + * exists in TaTracker. + */ + boolean hasModule(Module module); + + /** + * Adds the given module into the TaTracker. + * @param module module to add, which must not already exist in the TaTracker. + */ + void addModule(Module module); + + /** + * Deletes the given module {@code target} from the TaTracker. + * @param target module to delete, which must exist in the TaTracker. + */ + void deleteModule(Module target); + + /** + * Sorts the modules by rating in ascending order. + */ + void sortModulesByRatingAscending(); + + /** + * Sorts the modules by rating in descending order. + */ + void sortModulesByRatingDescending(); + + /** + * Sorts all the students of all groups in all the modules alphabetically. + */ + void sortModulesAlphabetically(); + + + /** + * Replaces the given module {@code target} in the TaTracker with {@code editedModule}. + * @param target module to edit, which must exist in the TaTracker. + * @param editedModule the edited module {@code target}. + * The identity of {@code editedModule} must be the same as {@code target}. + */ + void setModule(Module target, Module editedModule); + + /** Returns an unmodifiable view of the filtered module list */ + ObservableList getFilteredModuleList(); + + /** + * Updates the filter of the filtered module list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredModuleList(Predicate predicate); + + // ======== Group Methods ================================================== + + /** + * Returns true if a given group with the same identity as {@code group} + * exists in a module that is in TaTracker. + * @param targetModule module that contains {@code group}. + */ + boolean hasGroup(Group group, Module targetModule); + + /** + * Adds the given group into a module that is in TaTracker. + * @param group group to add, which must not already exist in the TaTracker module. + * @param targetModule module to add {@code group} into, which must exist in the TaTracker. + */ + void addGroup(Group group, Module targetModule); + + /** + * Deletes the given group {@code target} from a module that is in TaTracker. + * @param target group to delete, which must exist in the TaTracker module. + * @param targetModule module to delete group {@code target} from, which must exist in the TaTracker. + */ + void deleteGroup(Group target, Module targetModule); + + /** + * Replaces the given group {@code target} in a TaTracker module with {@code editedGroup}. + * @param target group to edit, which must exist in the TaTracker module. + * @param editedGroup the edited group {@code target}. + * The identity of {@code editedGroup} must be the same as {@code target}. + * @param targetModule module with the group to edit, which must exist in the TaTracker. + */ + void setGroup(Group target, Group editedGroup, Module targetModule); + + /** Returns an unmodifiable view of the filtered group list */ + ObservableList getFilteredGroupList(); + + /** + * Updates the filtered group list to show the groups in module with the given + * module code. + */ + void updateFilteredGroupList(String moduleCode); + + /** + * Sets the filtered group list to be an empty list. + */ + void setFilteredGroupList(); + + /** + * Updates the group list to show the groups in the module with the given index. + */ + void updateGroupList(int index); + + // ======== Student Methods ================================================ + + /** + * Returns true if a student with the same identity as {@code student} exists in the TaTracker. + */ + boolean hasStudent(Student student); + + /** + * Returns true if a given student with the same identity as {@code student} + * exists in a module group that is in TaTracker. + * @param targetGroup group to check if {@code student} is enrolled in. + * @param targetModule module that contains {@code group}. + */ + boolean hasStudent(Student student, Group targetGroup, Module targetModule); + + /** + * Adds the given student. + * {@code student} must not already exist in the ta-tracker. + */ + void addStudent(Student student); + + /** + * Adds the given student into a module group that is in TaTracker. + * @param student student to add, which must not already exist in the TaTracker module group. + * @param targetGroup group to add {@code student} into, which must exist in the TaTracker module. + * @param targetModule module to add {@code student} into, which must exist in the TaTracker. + */ + void addStudent(Student student, Group targetGroup, Module targetModule); + + /** + * Deletes the given student. + * The student must exist in the ta-tracker. + */ + void deleteStudent(Student target); + + /** + * Deletes the given student {@code target} from a module group that is in TaTracker. + * @param target student to delete, which must exist in the TaTracker module group. + * @param targetGroup group to delete student {@code target} from, which must exist in the TaTracker module. + * @param targetModule module to delete student {@code target} from, which must exist in the TaTracker. + */ + void deleteStudent(Student target, Group targetGroup, Module targetModule); + + /** + * Replaces the given student {@code target} with {@code editedStudent}. + * {@code target} must exist in the ta-tracker. + * The student identity of {@code editedStudent} must not be the same as another existing student in the tracker. + */ + void setStudent(Student target, Student editedStudent); + + /** + * Replaces the given student {@code target} in a TaTracker module group with {@code editedStudent}. + * @param target student to edit, which must exist in the TaTracker module group. + * @param editedStudent the edited student {@code target}. + * The identity of {@code editedStudent} must be the same as {@code target}. + * @param targetGroup group with the student to edit, which must exist in the TaTracker module. + * @param targetModule module with the student to edit, which must exist in the TaTracker. + */ + void setStudent(Student target, Student editedStudent, Group targetGroup, Module targetModule); + + /** + * Sets the student list to be of group of index groupIndex in the module of index moduleIndex. + */ + void updateStudentList(int moduleIndex, int groupIndex); + + + // TODO: Student filter methods. Javadoc comments should mention students are inside group -> inside module + + /** Returns an unmodifiable view of the filtered student list */ + ObservableList getFilteredStudentList(); + + /** + * Updates the currently shown student list to show students of the given group + * from the given module. + */ + void updateFilteredStudentList(String groupCode, String moduleCode); + + /** + * Sets the filtered student list to be an empty list. + */ + void setFilteredStudentList(); + + /** + * Sets the filtered student list to be an that of given index group in given module. + */ + void setFilteredStudentList(String moduleCode, int groupIndex); +} diff --git a/src/main/java/tatracker/model/ModelManager.java b/src/main/java/tatracker/model/ModelManager.java new file mode 100644 index 00000000000..c276e03b310 --- /dev/null +++ b/src/main/java/tatracker/model/ModelManager.java @@ -0,0 +1,377 @@ +package tatracker.model; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; + +/** + * Represents the in-memory model of the ta-tracker data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final TaTracker taTracker; + private final UserPrefs userPrefs; + private final FilteredList filteredSessions; + private final FilteredList filteredDoneSessions; + private final FilteredList filteredModules; + + private long totalHours = 0; + private int rate; + private long totalEarnings; + + /** + * Initializes a ModelManager with the given taTracker and userPrefs. + */ + public ModelManager(ReadOnlyTaTracker taTracker, ReadOnlyUserPrefs userPrefs) { + super(); // TODO: Super gets interface constants. + requireAllNonNull(taTracker, userPrefs); + + logger.fine("Initializing with ta-tracker: " + taTracker + " and user prefs " + userPrefs); + + this.taTracker = new TaTracker(taTracker); + this.userPrefs = new UserPrefs(userPrefs); + filteredSessions = new FilteredList<>(this.taTracker.getSessionList()); + filteredDoneSessions = new FilteredList<>(this.taTracker.getDoneSessionList()); + filteredModules = new FilteredList<>(this.taTracker.getModuleList()); + } + + public ModelManager() { + this(new TaTracker(), new UserPrefs()); + } + + // ======== TaTracker ====================================================== + + @Override + public ReadOnlyTaTracker getTaTracker() { + return taTracker; + } + + @Override + public void setTaTracker(ReadOnlyTaTracker taTracker) { + this.taTracker.resetData(taTracker); + } + + // ======== UserPrefs ====================================================== + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + @Override + public Path getTaTrackerFilePath() { + return userPrefs.getTaTrackerFilePath(); + } + + @Override + public void setTaTrackerFilePath(Path taTrackerFilePath) { + requireNonNull(taTrackerFilePath); + userPrefs.setTaTrackerFilePath(taTrackerFilePath); + } + + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + // ======== Session Methods ================================================ + + @Override + public boolean hasSession(Session session) { + requireNonNull(session); + return taTracker.hasSession(session); + } + + @Override + public void addSession(Session session) { + taTracker.addSession(session); + updateFilteredSessionList(PREDICATE_SHOW_ALL_SESSIONS); + } + + @Override + public void deleteSession(Session target) { + taTracker.removeSession(target); + } + + @Override + public void setSession(Session target, Session editedSession) { + requireAllNonNull(target, editedSession); + taTracker.setSession(target, editedSession); + } + + @Override + public ObservableList getFilteredSessionList() { + return filteredSessions; + } + + @Override + public void updateFilteredSessionList(Predicate predicate) { + requireNonNull(predicate); + filteredSessions.setPredicate(predicate); + } + + // ======== Done Session Methods ================================================= + + @Override + public void addDoneSession(Session session) { + taTracker.addDoneSession(session); + totalHours += Math.ceil(Duration.between + (session.getEndDateTime(), session.getStartDateTime()) + .toHours()); + updateFilteredDoneSessionList(PREDICATE_SHOW_ALL_SESSIONS); + } + + /** + * Returns an unmodifiable view of the list of {@code Session} backed by the internal list of + * {@code versionedTaTracker} + */ + @Override + public ObservableList getFilteredDoneSessionList() { + return filteredDoneSessions; + } + + @Override + public void updateFilteredDoneSessionList(Predicate predicate) { + requireNonNull(predicate); + filteredDoneSessions.setPredicate(predicate); + } + + // ======== Module Methods ================================================= + + public Module getModule(String code) { + requireNonNull(code); + return taTracker.getModule(code); + } + + @Override + public boolean hasModule(Module module) { + requireNonNull(module); + return taTracker.hasModule(module); + } + + @Override + public void addModule(Module module) { + requireNonNull(module); + taTracker.addModule(module); + } + + @Override + public void deleteModule(Module module) { + requireNonNull(module); + taTracker.deleteModule(module); + } + + @Override + public void setModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + taTracker.setModule(target, editedModule); + } + + @Override + public void sortModulesAlphabetically() { + taTracker.sortModulesAlphabetically(); + } + + @Override + public void sortModulesByRatingAscending() { + taTracker.sortModulesByRatingAscending(); + } + + @Override + public void sortModulesByRatingDescending() { + taTracker.sortModulesByRatingDescending(); + } + + + @Override + public ObservableList getFilteredModuleList() { + return filteredModules; + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + requireNonNull(predicate); + filteredModules.setPredicate(predicate); + } + + // ======== Group Methods ================================================== + + @Override + public boolean hasGroup(Group group, Module targetModule) { + requireNonNull(group); + return taTracker.hasGroup(group, targetModule); + } + + + //=========== Filtered Student List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Student} backed by the internal list of + * {@code versionedTaTracker} + */ + @Override + public void addGroup(Group group, Module targetModule) { + requireNonNull(group); + taTracker.addGroup(group, targetModule); + } + + @Override + public void deleteGroup(Group target, Module targetModule) { + requireNonNull(target); + taTracker.removeGroup(target, targetModule); + } + + @Override + public void setGroup(Group target, Group editedGroup, Module targetModule) { + requireAllNonNull(target, editedGroup); + taTracker.setGroup(target, editedGroup, targetModule); + } + + @Override + public ObservableList getFilteredGroupList() { + return taTracker.getCurrentlyShownGroupList(); + } + + @Override + public void updateFilteredGroupList(String moduleCode) { + taTracker.updateCurrentlyShownGroups(moduleCode); + } + + @Override + public void setFilteredGroupList() { + taTracker.setCurrentlyShownGroups(new ArrayList()); + } + + @Override + public void updateGroupList(int moduleIndex) { + taTracker.setCurrentlyShownGroups(moduleIndex); + } + + // ======== Student Methods ================================================ + + @Override + public boolean hasStudent(Student student) { + requireNonNull(student); + return taTracker.hasStudent(student); + } + + @Override + public boolean hasStudent(Student student, Group targetGroup, Module targetModule) { + requireNonNull(student); + return taTracker.hasStudent(student, targetGroup, targetModule); + } + + @Override + public void addStudent(Student student) { + taTracker.addStudent(student); + } + + @Override + public void addStudent(Student student, Group targetGroup, Module targetModule) { + requireNonNull(student); + taTracker.addStudent(student, targetGroup, targetModule); + } + + @Override + public void deleteStudent(Student target) { + taTracker.removeStudent(target); + } + + @Override + public void deleteStudent(Student target, Group targetGroup, Module targetModule) { + requireNonNull(target); + taTracker.deleteStudent(target, targetGroup, targetModule); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + taTracker.setStudent(target, editedStudent); + } + + @Override + public void setStudent(Student target, Student editedStudent, Group targetGroup, Module targetModule) { + requireAllNonNull(target, editedStudent); + taTracker.setStudent(target, editedStudent, targetGroup, targetModule); + } + + @Override + public void updateStudentList(int moduleIndex, int groupIndex) { + taTracker.setCurrentlyShownStudents(moduleIndex, groupIndex); + } + + /** + * TODO: Review filter functions. + */ + + @Override + public ObservableList getFilteredStudentList() { + return taTracker.getCurrentlyShownStudentList(); + } + + @Override + public void setFilteredStudentList() { + taTracker.setCurrentlyShownStudents(new ArrayList()); + } + + @Override + public void setFilteredStudentList(String moduleCode, int groupIndex) { + taTracker.setCurrentlyShownStudents(moduleCode, groupIndex); + } + + @Override + public void updateFilteredStudentList(String groupCode, String moduleCode) { + taTracker.updateCurrentlyShownStudents(groupCode, moduleCode); + } + + + // ======== Others Methods ================================================= + + @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 taTracker.equals(other.taTracker) + && userPrefs.equals(other.userPrefs) + && filteredSessions.equals(other.filteredSessions) + && filteredModules.equals(other.filteredModules); + } +} diff --git a/src/main/java/tatracker/model/ReadOnlyTaTracker.java b/src/main/java/tatracker/model/ReadOnlyTaTracker.java new file mode 100644 index 00000000000..8772b9b7cde --- /dev/null +++ b/src/main/java/tatracker/model/ReadOnlyTaTracker.java @@ -0,0 +1,43 @@ +package tatracker.model; + +import javafx.collections.ObservableList; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; +import tatracker.model.student.Student; + +/** + * Unmodifiable view of a ta-tracker. + */ +public interface ReadOnlyTaTracker { + + /** + * Returns an unmodifiable view of the student list. + * This list will not contain any duplicate student. + */ + ObservableList getCurrentlyShownStudentList(); + + /** + * Returns an unmodifiable view of the modules list. + * This list will not contain any duplicate modules. + */ + ObservableList getModuleList(); + + /** + * Returns an unmodifiable view of the groups list. + * This list will not contain any duplicate groups. + */ + ObservableList getCurrentlyShownGroupList(); + + /** + * Returns an unmodifiable view of the sessions list. + * This list will not contain any duplicate sessions. + */ + ObservableList getSessionList(); + + /** + * Returns an unmodifiable view of the done sessions list. + * This list will not contain any duplicate sessions. + */ + ObservableList getDoneSessionList(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/tatracker/model/ReadOnlyUserPrefs.java similarity index 57% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/tatracker/model/ReadOnlyUserPrefs.java index befd58a4c73..10472029592 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/tatracker/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package tatracker.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import tatracker.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getTaTrackerFilePath(); } diff --git a/src/main/java/tatracker/model/TaTracker.java b/src/main/java/tatracker/model/TaTracker.java new file mode 100644 index 00000000000..55343cc857c --- /dev/null +++ b/src/main/java/tatracker/model/TaTracker.java @@ -0,0 +1,486 @@ +package tatracker.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; + +import tatracker.model.group.Group; +import tatracker.model.group.GroupNotFoundException; +import tatracker.model.group.UniqueGroupList; +import tatracker.model.module.Module; +import tatracker.model.module.ModuleNotFoundException; +import tatracker.model.module.UniqueModuleList; +import tatracker.model.session.Session; +import tatracker.model.session.UniqueDoneSessionList; +import tatracker.model.session.UniqueSessionList; +import tatracker.model.student.Student; +import tatracker.model.student.UniqueStudentList; + +/** + * Wraps all data at the ta-tracker level + * Duplicates are not allowed (by .isSameSession comparison) + * Duplicates are not allowed (by .isSameStudent comparison) + */ +public class TaTracker implements ReadOnlyTaTracker { + + private final UniqueSessionList sessions; + private final UniqueDoneSessionList doneSessions; + private final UniqueModuleList modules; + private final UniqueGroupList currentlyShownGroups; + private final UniqueStudentList currentlyShownStudents; + + public TaTracker() { + sessions = new UniqueSessionList(); + doneSessions = new UniqueDoneSessionList(); + modules = new UniqueModuleList(); + currentlyShownGroups = new UniqueGroupList(); + currentlyShownStudents = new UniqueStudentList(); + } + + /** + * Creates a TaTracker using the Students in the {@code toBeCopied} + */ + public TaTracker(ReadOnlyTaTracker toBeCopied) { + this(); + resetData(toBeCopied); + } + + /** + * Resets the existing data of this {@code TaTracker} with {@code newData}. + */ + public void resetData(ReadOnlyTaTracker newData) { + requireNonNull(newData); + + setSessions(newData.getSessionList()); + setDoneSessions(newData.getDoneSessionList()); + setModules(newData.getModuleList()); + setCurrentlyShownGroups(newData.getCurrentlyShownGroupList()); + setCurrentlyShownStudents(newData.getCurrentlyShownStudentList()); + } + + // ======== Session Methods ================================================ + + /** + * Returns true if a session with the same identity as {@code session} exists in the ta-tracker. + */ + public boolean hasSession(Session session) { + requireNonNull(session); + return sessions.contains(session); + } + + /** + * Adds a session to the ta-tracker. + * The session must not already exist in the ta-tracker. + */ + public void addSession(Session s) { + sessions.add(s); + } + + /** + * Returns true if a session with the same identity as {@code session} exists in the ta-tracker. + * Removes {@code session} from this {@code TaTracker}. + * {@code session} must exist in the ta-tracker. + */ + public void removeSession(Session session) { + sessions.remove(session); + } + + /** + * Replaces the given session {@code target} in the list with {@code editedSession}. + * {@code target} must exist in the ta-tracker. + * The session identity of {@code editedSession} must not be the same as another + * existing session in the ta-tracker. + */ + public void setSession(Session target, Session editedSession) { + requireNonNull(editedSession); + + sessions.setSession(target, editedSession); + } + + /** + * Replaces the contents of the session list with {@code sessions}. + * {@code sessions} must not contain duplicate sessions. + */ + public void setSessions(List sessions) { + this.sessions.setSessions(sessions); + } + + @Override + public ObservableList getSessionList() { + return sessions.asUnmodifiableObservableList(); + } + + // ======== Done Session Methods ================================================= + + public void addDoneSession(Session s) { + doneSessions.add(s); + } + + /** + * Replaces the contents of the donesession list with {@code donesessions}. + * {@code donesessions} must not contain duplicate donesessions. + */ + public void setDoneSessions(List donesessions) { + this.doneSessions.setSessions(donesessions); + } + + @Override + public ObservableList getDoneSessionList() { + return doneSessions.asUnmodifiableObservableList(); + } + + // ======== Module Methods ================================================= + + /** + * Returns module from TATracker. + */ + public Module getModule(String moduleId) { + return modules.getModule(moduleId); + } + + /** + * Returns true if a module with the same module code exists in the TATracker. + */ + public boolean hasModule(Module module) { + requireNonNull(module); + return modules.contains(module); + } + + /** + * Adds a module to the TATracker. + */ + public void addModule(Module module) { + modules.add(module); + } + + /** + * Removes module with same module code from TA-Tracker. + */ + public void deleteModule(Module module) { + for (Session session : sessions) { + if (session.getModuleCode().equals(module.getIdentifier())) { + sessions.remove(session); + } + } + modules.remove(module); + } + + /** + * Removes {@code key} from this {@code TaTracker}. + * {@code key} must exist in the ta-tracker. + */ + public void removeModule(Module key) { + modules.remove(key); + } + + /** + * Replaces the given module {@code target} in the list with {@code editedModule}. + * {@code target} must exist in the ta-tracker. + * The module identity of {@code editedModule} must not be the same as another existing module in the tracker. + */ + public void setModule(Module target, Module editedModule) { + requireNonNull(editedModule); + + modules.setModule(target, editedModule); + } + + /** + * Sorts modules alphabetically. + */ + public void sortModulesAlphabetically() { + for (int i = 0; i < modules.size(); ++i) { + modules.get(i).sortGroupsAlphabetically(); + } + } + + /** + * Sorts modules by rating in ascending order. + */ + public void sortModulesByRatingAscending() { + for (int i = 0; i < modules.size(); ++i) { + modules.get(i).sortGroupsByRatingAscending(); + } + } + + /** + * Sorts modules alphabetically. + */ + public void sortModulesByRatingDescending() { + for (int i = 0; i < modules.size(); ++i) { + modules.get(i).sortGroupsByRatingDescending(); + } + } + + /** + * Replaces the contents of the modules list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setModules(List modules) { + this.modules.setModules(modules); + } + + @Override + public ObservableList getModuleList() { + return modules.asUnmodifiableObservableList(); + } + + // ======== Group Methods ================================================== + + /** + * Returns true if a group with the same group code exists in the TATracker. + */ + public boolean hasGroup(Group group, Module targetModule) { + requireNonNull(group); + + if (!hasModule(targetModule)) { + return false; + } + + Module module = getModule(targetModule.getIdentifier()); + return module.hasGroup(group); + } + + /** + * Adds a group to the TATracker. + */ + public void addGroup(Group group, Module targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + Module module = getModule(targetModule.getIdentifier()); + module.addGroup(group); + } + + /** + * Removes {@code key} from this {@code TaTracker}. + * {@code key} must exist in the ta-tracker. + */ + public void removeGroup(Group group, Module targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + Module module = getModule(targetModule.getIdentifier()); + module.deleteGroup(group); + } + + /** + * Replaces the given group {@code target} in the list with {@code editedGroup}. + * {@code target} must exist in the ta-tracker. + * The group identity of {@code editedGroup} must not be the same as another existing group in the tracker. + */ + public void setGroup(Group target, Group editedGroup, Module targetModule) { + requireNonNull(editedGroup); + + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + Module module = getModule(targetModule.getIdentifier()); + module.setGroup(target, editedGroup); + } + + /** + * Replaces the contents of the group list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setCurrentlyShownGroups(List groups) { + this.currentlyShownGroups.setGroups(groups); + } + + /** + * Replaces the contents of the group list with the groups at the given + * module index. + */ + public void setCurrentlyShownGroups(int n) { + setCurrentlyShownGroups(((modules.get(n)).getGroupList())); + } + + /** + * Updates the currently shown groups to be that of the currently shown module + * code. + */ + public void updateCurrentlyShownGroups(String moduleCode) { + setCurrentlyShownGroups((modules.getModule(moduleCode)).getGroupList()); + } + + @Override + public ObservableList getCurrentlyShownGroupList() { + return currentlyShownGroups.asUnmodifiableObservableList(); + } + + // ======== Student Methods ================================================ + + /** + * Returns true if a given student with the same identity as {@code student} + * exists in a module group that is in TaTracker. + * @param targetGroup group to check if {@code student} is enrolled in. + * @param targetModule module that contains {@code group}. + */ + public boolean hasStudent(Student student, Group targetGroup, Module targetModule) { + requireNonNull(student); + + if (!hasGroup(targetGroup, targetModule)) { + return false; + } + + Module module = getModule(targetModule.getIdentifier()); + Group group = module.getGroup(targetGroup.getIdentifier()); + return group.hasStudent(student); + } + + /** + * Returns true if a student with the same identity as {@code student} exists in the ta-tracker. + */ + public boolean hasStudent(Student student) { + requireNonNull(student); + return currentlyShownStudents.contains(student); + } + + /** + * Adds the given student into a module group that is in TaTracker. + * @param student student to add, which must not already exist in the TaTracker module group. + * @param targetGroup group to add {@code student} into, which must exist in the TaTracker module. + * @param targetModule module to add {@code student} into, which must exist in the TaTracker. + */ + public void addStudent(Student student, Group targetGroup, Module targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + if (!hasGroup(targetGroup, targetModule)) { + throw new GroupNotFoundException(); + } + + Module module = getModule(targetModule.getIdentifier()); + Group group = module.getGroup(targetGroup.getIdentifier()); + group.addStudent(student); + } + + /** + * Adds a student to the ta-tracker. + * The student must not already exist in the ta-tracker. + */ + public void addStudent(Student p) { + currentlyShownStudents.add(p); + } + + /** + * Deletes the given student {@code target} from a module group that is in TaTracker. + * @param target student to delete, which must exist in the TaTracker module group. + * @param targetGroup group to delete student {@code target} from, which must exist in the TaTracker module. + * @param targetModule module to delete student {@code target} from, which must exist in the TaTracker. + */ + public void deleteStudent(Student target, Group targetGroup, Module targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + if (!hasGroup(targetGroup, targetModule)) { + throw new GroupNotFoundException(); + } + + Module module = getModule(targetModule.getIdentifier()); + Group group = module.getGroup(targetGroup.getIdentifier()); + group.deleteStudent(target); + } + + /** + * Removes {@code key} from this {@code TaTracker}. + * {@code key} must exist in the ta-tracker. + */ + public void removeStudent(Student key) { + currentlyShownStudents.remove(key); + } + + /** + * Replaces the given student {@code target} in a TaTracker module group with {@code editedStudent}. + * @param target student to edit, which must exist in the TaTracker module group. + * @param editedStudent the edited student {@code target}. + * The identity of {@code editedStudent} must be the same as {@code target}. + * @param targetGroup group with the student to edit, which must exist in the TaTracker module. + * @param targetModule module with the student to edit, which must exist in the TaTracker. + */ + public void setStudent(Student target, Student editedStudent, Group targetGroup, Module targetModule) { + if (!hasModule(targetModule)) { + throw new ModuleNotFoundException(); + } + + if (!hasGroup(targetGroup, targetModule)) { + throw new GroupNotFoundException(); + } + + Module module = getModule(targetModule.getIdentifier()); + Group group = module.getGroup(targetGroup.getIdentifier()); + group.setStudent(target, editedStudent); + } + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the ta-tracker. + * The student identity of {@code editedStudent} must not be the same as another existing student in the tracker. + */ + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); + + currentlyShownStudents.setStudent(target, editedStudent); + } + + /** + * Replaces the contents of the student list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setCurrentlyShownStudents(List students) { + this.currentlyShownStudents.setStudents(students); + } + + /** + * Replaces the contents of the student list with the students at the given + * group index of the given module. + */ + public void setCurrentlyShownStudents(String moduleCode, int n) { + setCurrentlyShownStudents(((modules.getModule(moduleCode).get(n)).getStudentList())); + } + + /** + * Replaces the contents of the student list with the students at the given + * group index of the given module. + */ + public void setCurrentlyShownStudents(int i, int n) { + setCurrentlyShownStudents(((modules.get(i).get(n)).getStudentList())); + } + + /** + * Updates the currently shown groups to be that of the currently shown module + * code. + */ + public void updateCurrentlyShownStudents(String groupCode, String moduleCode) { + setCurrentlyShownStudents(((modules.getModule(moduleCode)).getGroup(groupCode)).getStudentList()); + } + + @Override + public ObservableList getCurrentlyShownStudentList() { + return currentlyShownStudents.asUnmodifiableObservableList(); + } + + // ======== Utility Methods ================================================ + + @Override + public int hashCode() { + return modules.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaTracker // instanceof handles nulls + && modules.equals(((TaTracker) other).modules)); + } + + @Override + public String toString() { + return modules.asUnmodifiableObservableList().size() + " students"; + // TODO: refine later + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/tatracker/model/UserPrefs.java similarity index 70% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/tatracker/model/UserPrefs.java index 25a5fd6eab9..872e5358ad6 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/tatracker/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package tatracker.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import tatracker.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path taTrackerFilePath = Paths.get("data" , "tatracker.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setTaTrackerFilePath(newUserPrefs.getTaTrackerFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getTaTrackerFilePath() { + return taTrackerFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setTaTrackerFilePath(Path taTrackerFilePath) { + requireNonNull(taTrackerFilePath); + this.taTrackerFilePath = taTrackerFilePath; } @Override @@ -68,19 +68,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && taTrackerFilePath.equals(o.taTrackerFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, taTrackerFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + taTrackerFilePath); return sb.toString(); } diff --git a/src/main/java/tatracker/model/group/DuplicateGroupException.java b/src/main/java/tatracker/model/group/DuplicateGroupException.java new file mode 100644 index 00000000000..6d8604af43a --- /dev/null +++ b/src/main/java/tatracker/model/group/DuplicateGroupException.java @@ -0,0 +1,12 @@ +package tatracker.model.group; + +/** + * Signals that the operation will result in duplicate Groups + * (Groups are considered duplicates if they have the same + * identifiers). + */ +public class DuplicateGroupException extends RuntimeException { + public DuplicateGroupException() { + super("Operation would result in duplicate groups"); + } +} diff --git a/src/main/java/tatracker/model/group/Group.java b/src/main/java/tatracker/model/group/Group.java new file mode 100644 index 00000000000..030570ebb8f --- /dev/null +++ b/src/main/java/tatracker/model/group/Group.java @@ -0,0 +1,167 @@ +package tatracker.model.group; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import javafx.collections.ObservableList; + +import tatracker.model.student.Matric; +import tatracker.model.student.Student; +import tatracker.model.student.UniqueStudentList; + +/** + * Represents a group in TAT. + * A group is anything that would include a + * group of students such as a lab or tutorial. + */ +public class Group { + + private static final GroupType DEFAULT_GROUP_TYPE = GroupType.TUTORIAL; + + private String identifier; + private GroupType groupType; + private final UniqueStudentList students; + + /** + * Constructs a group object with a default group type. + */ + public Group(String identifier) { + this(identifier, DEFAULT_GROUP_TYPE); + } + + /** + * Constructs a group object. + * + * @param identifier identifies the group. + * For example, the tutorial code for a tutorial, etc. + */ + public Group(String identifier, GroupType groupType) { + this.identifier = identifier; + this.groupType = groupType; + this.students = new UniqueStudentList(); + } + + /** + * Sorts students alphabetically. + */ + public void sortStudentsAlphabetically() { + students.sortAlphabetically(); + } + + /** + * Sorts students by rating in ascending order. + */ + public void sortStudentsByRatingAscending() { + students.sortByRatingAscending(); + } + + /** + * Sorts students by rating in descending order. + */ + public void sortStudentsByRatingDescending() { + students.sortByRatingDescending(); + } + + /** + * Returns the group identifier. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Updates the group code. + */ + public void setIdentifier(String newIdentifier) { + this.identifier = newIdentifier; + } + + /** + * Updates the group type. + */ + public void setGroupType(GroupType newGroupType) { + this.groupType = newGroupType; + } + + /** + * Returns the group type of this group. + * For example, if it is a tutorial or lab. + */ + public GroupType getGroupType() { + return groupType; + } + + /** + * Returns the student list. + */ + public ObservableList getStudentList() { + return students.asUnmodifiableObservableList(); + } + + public boolean hasStudent(Student student) { + return students.contains(student); + } + + /** + * Returns the student enrolled in this module with the given + * matriculation number (the student id). + * Returns null if no such student exists. + */ + public Student getStudent(Matric studentId) { + return students.get(studentId); + } + + /** + * Adds a student to the list of enrolled students. + */ + public void addStudent(Student student) { + students.add(student); + } + + /** + * Deletes the given student from the list of enrolled students, + * if it exists. + */ + public void deleteStudent(Student student) { + students.remove(student); + } + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list of enrolled students. + * The student identity of {@code editedStudent} must not be the same as another existing student in the group. + */ + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); + students.setStudent(target, editedStudent); + } + + /** + * Returns true if both groups have the same identifiers. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Group)) { + return false; + } + + Group otherGroup = (Group) other; + return this.identifier.equals(otherGroup.identifier); + } + + @Override + public int hashCode() { + return Objects.hash(identifier); + } + + //TODO: edit once Student is made + @Override + public String toString() { + return String.format("%s", identifier); + } +} diff --git a/src/main/java/tatracker/model/group/GroupNotFoundException.java b/src/main/java/tatracker/model/group/GroupNotFoundException.java new file mode 100644 index 00000000000..0437411c23a --- /dev/null +++ b/src/main/java/tatracker/model/group/GroupNotFoundException.java @@ -0,0 +1,6 @@ +package tatracker.model.group; + +/** + * Signals that the operation is unable to find the specified group. + */ +public class GroupNotFoundException extends RuntimeException {} diff --git a/src/main/java/tatracker/model/group/GroupType.java b/src/main/java/tatracker/model/group/GroupType.java new file mode 100644 index 00000000000..0f4f4da9aa2 --- /dev/null +++ b/src/main/java/tatracker/model/group/GroupType.java @@ -0,0 +1,22 @@ +package tatracker.model.group; + +/** + * Represents a group type. + * Can be a lab or a tutorial. + */ +public enum GroupType { + LAB("Lab"), + TUTORIAL("Tutorial"), + RECITATION("Recitation"), + OTHER("Other"); + + private String typeName; + GroupType(String typeName) { + this.typeName = typeName; + } + + @Override + public String toString() { + return typeName; + } +} diff --git a/src/main/java/tatracker/model/group/UniqueGroupList.java b/src/main/java/tatracker/model/group/UniqueGroupList.java new file mode 100644 index 00000000000..2919b55782e --- /dev/null +++ b/src/main/java/tatracker/model/group/UniqueGroupList.java @@ -0,0 +1,155 @@ +package tatracker.model.group; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of groups that enforces uniqueness between its elements and does not allow nulls. + * A group is considered unique by comparing using {@code Group#equals(Object)}. As such, adding and updating of + * groups uses Group#equals(Object) for equality so as to ensure that the group being added or updated is + * unique in terms of identity in the UniqueGroupList. + * + * Supports a minimal set of list operations. + * + * @see Group#equals(Object) + */ +public class UniqueGroupList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + public int size() { + return internalList.size(); + } + + /** + * Returns true if the list contains an equivalent group as the given argument. + */ + public boolean contains(Group toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + public Group get(int n) { + return internalList.get(n); + } + + /** + * Returns the group in this list with the given group id. + * Returns null if no such group exists. + */ + public Group get(String groupId) { + for (Group group : internalList) { + if (group.getIdentifier().equals(groupId)) { + return group; + } + } + return null; // Did not find a group with the given group id + } + + /** + * Adds a group to the list. + * The group must not already exist in the list. + */ + public void add(Group toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGroupException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent group from the list. + * The group must exist in the list. + */ + public void remove(Group toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new GroupNotFoundException(); + } + } + + /** + * Replaces the group {@code target} in the list with {@code editedGroup}. + * {@code target} must exist in the list. + * The group identity of {@code editedGroup} must not be the same as another existing group in the list. + */ + public void setGroup(Group target, Group editedGroup) { + requireAllNonNull(target, editedGroup); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GroupNotFoundException(); + } + + if (!target.equals(editedGroup) && contains(editedGroup)) { + throw new DuplicateGroupException(); + } + + internalList.set(index, editedGroup); + } + + public void setGroups(UniqueGroupList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setGroups(List groups) { + requireAllNonNull(groups); + if (!groupsAreUnique(groups)) { + throw new DuplicateGroupException(); + } + + internalList.setAll(groups); + } + + /** + * 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 UniqueGroupList // instanceof handles nulls + && internalList.equals(((UniqueGroupList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean groupsAreUnique(List groups) { + for (int i = 0; i < groups.size() - 1; i++) { + for (int j = i + 1; j < groups.size(); j++) { + if (groups.get(i).equals(groups.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/module/DuplicateModuleException.java b/src/main/java/tatracker/model/module/DuplicateModuleException.java new file mode 100644 index 00000000000..010b317a8b3 --- /dev/null +++ b/src/main/java/tatracker/model/module/DuplicateModuleException.java @@ -0,0 +1,12 @@ +package tatracker.model.module; + +/** + * Signals that the operation will result in duplicate Modules + * (Modules are considered duplicates if they have the same + * identifiers). + */ +public class DuplicateModuleException extends RuntimeException { + public DuplicateModuleException() { + super("Operation would result in duplicate modules"); + } +} diff --git a/src/main/java/tatracker/model/module/Module.java b/src/main/java/tatracker/model/module/Module.java new file mode 100644 index 00000000000..5d155b5cbd0 --- /dev/null +++ b/src/main/java/tatracker/model/module/Module.java @@ -0,0 +1,188 @@ +package tatracker.model.module; + +import java.util.Objects; + +import javafx.collections.ObservableList; + +import tatracker.model.group.Group; +import tatracker.model.group.UniqueGroupList; +import tatracker.model.session.Session; +import tatracker.model.session.UniqueSessionList; + +/** + * Represents a module in the TAT. + */ +public class Module { + private static final String DEFAULT_NAME = ""; + + private final String identifier; + private final String name; + private final UniqueGroupList groups; + private final UniqueSessionList doneSessions; + + /** + * Constructs a module object with no name. + */ + public Module(String identifier) { + this(identifier, DEFAULT_NAME); + } + + /** + * Constructs a module object. + * + * @param identifier identifies the module. + * Usually equal to the module code. + * @param name the name of the module. + */ + public Module(String identifier, String name) { + this.identifier = identifier; + this.name = name; + this.groups = new UniqueGroupList(); + this.doneSessions = new UniqueSessionList(); + } + + /** + * Returns the module identifier. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Returns module name. + */ + public String getName() { + return name; + } + + /** + * Returns module at index n. + */ + public Group get(int n) { + return groups.get(n); + } + + /** + * Returns the group list. + */ + public ObservableList getGroupList() { + return groups.asUnmodifiableObservableList(); + } + + /** + * Returns the unique group list. + */ + public UniqueGroupList getUniqueGroupList() { + return groups; + } + + /** + * Returns the session list. + */ + public ObservableList getSessionList() { + return doneSessions.asUnmodifiableObservableList(); + } + + public boolean hasGroup(Group group) { + return groups.contains(group); + } + + /** + * Adds a group to the list of module groups. + */ + public void addGroup(Group group) { + groups.add(group); + } + + /** + * Returns the group in this module with the given group id. + * Returns null if no such group exists. + */ + public Group getGroup(String groupId) { + return groups.get(groupId); + } + + /** + * Deletes the given group from the list of module groups, + * if it exists. + */ + public void deleteGroup(Group group) { + groups.remove(group); + } + + /** + * Replaces the given group {@code target} in the list with {@code editedGroup}. + * {@code target} must exist in the list of groups. + * The group identity of {@code editedGroup} must not be the same as another existing group in the module. + */ + public void setGroup(Group target, Group editedGroup) { + groups.setGroup(target, editedGroup); + } + + public boolean hasDoneSession(Session session) { + return doneSessions.contains(session); + } + + /** + * Sorts students in the groups alphabetically. + */ + public void sortGroupsAlphabetically() { + for (int i = 0; i < groups.size(); ++i) { + groups.get(i).sortStudentsAlphabetically(); + } + } + + /** + * Sorts the students in the groups by rating in ascending order. + */ + public void sortGroupsByRatingAscending() { + for (int i = 0; i < groups.size(); ++i) { + groups.get(i).sortStudentsByRatingAscending(); + } + } + + /** + * Sorts the students in the groups by rating in descending order. + */ + public void sortGroupsByRatingDescending() { + for (int i = 0; i < groups.size(); ++i) { + groups.get(i).sortStudentsByRatingDescending(); + } + } + + + /** + * Adds a done session to the list of done sessions for this module. + */ + public void addDoneSession(Session session) { + doneSessions.add(session); + } + + /** + * Returns true if both modules have the same identifiers. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Module)) { + return false; + } + + Module otherModule = (Module) other; + return this.identifier.equals(otherModule.identifier); + } + + @Override + public int hashCode() { + return Objects.hash(identifier); + } + + @Override + public String toString() { + return String.format("%s (%s)", name, identifier); + } + +} diff --git a/src/main/java/tatracker/model/module/ModuleNotFoundException.java b/src/main/java/tatracker/model/module/ModuleNotFoundException.java new file mode 100644 index 00000000000..e3ea0b81035 --- /dev/null +++ b/src/main/java/tatracker/model/module/ModuleNotFoundException.java @@ -0,0 +1,6 @@ +package tatracker.model.module; + +/** + * Signals that the operation is unable to find the specified module. + */ +public class ModuleNotFoundException extends RuntimeException {} diff --git a/src/main/java/tatracker/model/module/UniqueModuleList.java b/src/main/java/tatracker/model/module/UniqueModuleList.java new file mode 100644 index 00000000000..efb60110cec --- /dev/null +++ b/src/main/java/tatracker/model/module/UniqueModuleList.java @@ -0,0 +1,167 @@ +package tatracker.model.module; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of modules that enforces uniqueness between its elements and does not allow nulls. + * A module is considered unique by comparing using {@code Module#equals(Object)}. As such, adding and updating of + * groups uses Module#equals(Object) for equality so as to ensure that the module being added or updated is + * unique in terms of identity in the UniqueModuleList. + * + * Supports a minimal set of list operations. + * + * @see Module#equals(Object) + */ +public class UniqueModuleList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent module as the given argument. + */ + public boolean contains(Module toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + public int size() { + return internalList.size(); + } + + public Module get(int n) { + return internalList.get(n); + } + + /** + * Adds a module to the list. + * The module must not already exist in the list. + */ + public void add(Module toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateModuleException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the module {@code target} in the list with {@code editedModule}. + * {@code target} must exist in the list. + * The module identity of {@code editedModule} must not be the same as another existing module in the list. + */ + public void setModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ModuleNotFoundException(); + } + + if (!target.equals(editedModule) && contains(editedModule)) { + throw new DuplicateModuleException(); + } + + internalList.set(index, editedModule); + } + + public Module getModule(Module module) { + for (int i = 0; i < internalList.size(); i++) { + if (module.getIdentifier().equals(internalList.get(i).getIdentifier())) { + return internalList.get(i); + } + } + return null; + } + + /** + * Gets module with given module code. + * Returns null if no such module exists. + */ + public Module getModule(String code) { + Module module = null; + for (int i = 0; i < this.size(); ++i) { + module = this.get(i); + if (module.getIdentifier().equals(code)) { + break; + } + module = null; + } + return module; + } + + /** + * Removes the equivalent module from the list. + * The module must exist in the list. + */ + public void remove(Module toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ModuleNotFoundException(); + } + } + + public void setModules(UniqueModuleList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setModules(List modules) { + requireAllNonNull(modules); + if (!modulesAreUnique(modules)) { + throw new DuplicateModuleException(); + } + + internalList.setAll(modules); + } + + /** + * 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 UniqueModuleList // instanceof handles nulls + && internalList.equals(((UniqueModuleList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean modulesAreUnique(List modules) { + for (int i = 0; i < modules.size() - 1; i++) { + for (int j = i + 1; j < modules.size(); j++) { + if (modules.get(i).equals(modules.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/session/Session.java b/src/main/java/tatracker/model/session/Session.java new file mode 100644 index 00000000000..7f0122b1e58 --- /dev/null +++ b/src/main/java/tatracker/model/session/Session.java @@ -0,0 +1,216 @@ +package tatracker.model.session; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Represents a session in TAT. + * A session is any claimable duty that has a start and end time. + * Guarantees: Date, Start Time and End Time are not null. + */ +public class Session implements Comparable { + + /** For converting date times to strings. Example: "2020-03-03 14:00" */ + private static final DateTimeFormatter FORMAT_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; + private String moduleCode; + private SessionType type; + private String description; + private boolean isRecurring; + private boolean isDone; + + /** + * Default Constructor for Session. + * Creates a session object with default values. + */ + public Session() { + this.startDateTime = LocalDateTime.now(); + this.endDateTime = LocalDateTime.now(); + this.isRecurring = false; + this.moduleCode = ""; + this.type = SessionType.OTHER; + this.description = "Default Session"; + this.isDone = false; + } + + /** + * Constructs a Session object. + * The session's end time should be strictly after the session's start time. + */ + public Session(LocalDateTime start, LocalDateTime end, SessionType type, boolean isRecurring, String moduleCode, + String description) throws IllegalArgumentException { + + if (start.compareTo(end) > 0) { + throw new IllegalArgumentException("[Session] Start time is set to after end time!"); + } + + this.startDateTime = start; + this.endDateTime = end; + this.isRecurring = isRecurring; + this.moduleCode = moduleCode; + this.type = type; + this.description = description; + this.isDone = false; + } + + /** + * Returns true if both sessions of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two sessions. + */ + public boolean isSameSession(Session s) { + return false; + } + + /** + * Returns the date when the session will take place. + */ + public LocalDate getDate() { + return this.startDateTime.toLocalDate(); + } + + /** + * Returns the start time of the session. + */ + public LocalDateTime getStartDateTime() { + return this.startDateTime; + } + + /** + * Sets the start time of the session. + */ + public void setStartDateTime(LocalDateTime startDateTime) { + this.startDateTime = startDateTime; + } + + /** + * Returns the end time of the session. + */ + public LocalDateTime getEndDateTime() { + return this.endDateTime; + } + + /** + * Sets the end time of the session. + */ + public void setEndDateTime(LocalDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + /** + * Returns true if session will recur every week; false otherwise. + */ + public boolean getIsRecurring() { + return this.isRecurring; + } + + /** + * Returns true if session is already completed; false otherwise. + */ + public boolean getIsDone() { + return this.isDone; + } + + /** + * Marks the session as done. + */ + public void done() { + this.isDone = true; + } + + /** + * Sets whether the session is a recurring session. + */ + public void setRecurring(boolean recurring) { + isRecurring = recurring; + } + + /** + * Returns the module code associated with this session. + */ + public String getModuleCode() { + return this.moduleCode; + } + + /** + * Sets the module code associated with this session. + */ + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + /** + * Returns the type of session. + */ + public SessionType getSessionType() { + return this.type; + } + + /** + * Sets the type of session. + */ + public void setType(SessionType type) { + this.type = type; + } + + /** + * Returns the description of the session. + */ + public String getDescription() { + return this.description; + } + + /** + * Sets the description of the session. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Returns the duration of the session. + */ + public Duration getSessionDuration() { + return Duration.between(this.startDateTime, this.endDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Type: ").append(type) + .append(" Start: ").append(startDateTime.format(FORMAT_DATE_TIME)) + .append(" End: ").append(endDateTime.format(FORMAT_DATE_TIME)) + .append(" Module Code: ").append(moduleCode) + .append(" Description: ").append(description) + .append(" Recurs: ").append(isRecurring); + return builder.toString(); + } + + /** + * Returns true if both sessions have the same identity and data fields. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Session)) { + return false; + } + + Session otherSession = (Session) other; + return isSameSession(otherSession); + } + + /** + * Compare Sessions based on the session that will occur first. + */ + @Override + public int compareTo(Session other) { + return getDate().compareTo(other.getDate()); + } +} diff --git a/src/main/java/tatracker/model/session/SessionType.java b/src/main/java/tatracker/model/session/SessionType.java new file mode 100644 index 00000000000..70159b6d018 --- /dev/null +++ b/src/main/java/tatracker/model/session/SessionType.java @@ -0,0 +1,24 @@ +package tatracker.model.session; + +/** + * Represents a session type. Session types follows the same specifications as the TSS. + * Example session types include: Tutorial, Grading, Consultation, etc. + */ +public enum SessionType { + TUTORIAL ("Tutorial"), + LAB ("Lab"), + CONSULTATION ("Consultation"), + GRADING ("Grading"), + PREPARATION ("Preparation"), + OTHER ("Other"); + + private String typeName; + SessionType(String typeName) { + this.typeName = typeName; + } + + @Override + public String toString() { + return typeName; + } +} diff --git a/src/main/java/tatracker/model/session/UniqueDoneSessionList.java b/src/main/java/tatracker/model/session/UniqueDoneSessionList.java new file mode 100644 index 00000000000..a2429bb46eb --- /dev/null +++ b/src/main/java/tatracker/model/session/UniqueDoneSessionList.java @@ -0,0 +1,167 @@ +package tatracker.model.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import tatracker.model.session.exceptions.DuplicateSessionException; +import tatracker.model.session.exceptions.SessionNotFoundException; + +/** + * A list of done sessions that enforces uniqueness between its elements and does not allow nulls. + * A done session is considered unique by comparing using {@code Session#isSameSession(Session)}. + * + * Supports a minimal set of list operations. + * + * @see Session#isSameSession(Session) + */ +public class UniqueDoneSessionList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent session as the given argument. + */ + public boolean contains(Session toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameSession); + } + + /** + * Returns the size of the UniqueDoneSessionList. + */ + public int size() { + return internalList.size(); + } + + /** + * Returns the session at the given index. + */ + public Session get(int n) { + return internalList.get(n); + } + + /** + * Adds a session to the list. + * The session must not already exist in the list. + */ + public void add(Session toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateSessionException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the session {@code target} in the list with {@code editedSession}. + * {@code target} must exist in the list. + * The session identity of {@code editedSession} must not be the same as another existing session in the list. + */ + public void setSession(Session target, Session editedSession) { + requireAllNonNull(target, editedSession); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new SessionNotFoundException(); + } + + if (!target.isSameSession(editedSession) && contains(editedSession)) { + throw new DuplicateSessionException(); + } + + internalList.set(index, editedSession); + } + + /** + * Removes the equivalent session from the list. + * The session must exist in the list. + */ + public void remove(Session toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new SessionNotFoundException(); + } + } + + /** + * Removes the session of the given index from the list. + * The session must exist in the list. + */ + public void remove(int n) { + requireNonNull(n); + if (n < 0 || n > internalList.size()) { + throw new SessionNotFoundException(); + } + + internalList.remove(n); + } + + public void setSessions(UniqueDoneSessionList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code sessions}. + * {@code sessions} must not contain duplicate sessions. + */ + public void setSessions(List sessions) { + requireAllNonNull(sessions); + if (!sessionsAreUnique(sessions)) { + throw new DuplicateSessionException(); + } + + internalList.setAll(sessions); + } + + /** + * Returns the session list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + + FXCollections.sort(internalList, Comparator.comparing(Session::getDate) + .thenComparing(Session::getStartDateTime) + .thenComparing(Session::getEndDateTime)); + 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 UniqueDoneSessionList // instanceof handles nulls + && internalList.equals(((UniqueDoneSessionList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code sessions} contains only unique sessions. + */ + private boolean sessionsAreUnique(List sessions) { + for (int i = 0; i < sessions.size() - 1; i++) { + for (int j = i + 1; j < sessions.size(); j++) { + if (sessions.get(i).isSameSession(sessions.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/session/UniqueSessionList.java b/src/main/java/tatracker/model/session/UniqueSessionList.java new file mode 100644 index 00000000000..dadb746e976 --- /dev/null +++ b/src/main/java/tatracker/model/session/UniqueSessionList.java @@ -0,0 +1,172 @@ +package tatracker.model.session; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import tatracker.model.session.exceptions.DuplicateSessionException; +import tatracker.model.session.exceptions.SessionNotFoundException; + +/** + * A list of sessions that enforces uniqueness between its elements and does not allow nulls. + * A session is considered unique by comparing using {@code Session#isSameSession(Session)}. + * As such, adding and updating of sessions uses Session#isSameSession(Session) + * for equality so as to ensure that the session being added or updated is + * unique in terms of identity in the UniqueSessionList. However, + * the removal of a session uses Session#equals(Object) so as to ensure that + * the session with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Session#isSameSession(Session) + */ +public class UniqueSessionList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent session as the given argument. + */ + public boolean contains(Session toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameSession); + } + + /** + * Returns the size of the UniqueSessionList. + */ + public int size() { + return internalList.size(); + } + + /** + * Returns the session at the given index. + */ + public Session get(int n) { + return internalList.get(n); + } + + /** + * Adds a session to the list. + * The session must not already exist in the list. + */ + public void add(Session toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateSessionException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the session {@code target} in the list with {@code editedSession}. + * {@code target} must exist in the list. + * The session identity of {@code editedSession} must not be the same as another existing session in the list. + */ + public void setSession(Session target, Session editedSession) { + requireAllNonNull(target, editedSession); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new SessionNotFoundException(); + } + + if (!target.isSameSession(editedSession) && contains(editedSession)) { + throw new DuplicateSessionException(); + } + + internalList.set(index, editedSession); + } + + /** + * Removes the equivalent session from the list. + * The session must exist in the list. + */ + public void remove(Session toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new SessionNotFoundException(); + } + } + + /** + * Removes the session of the given index from the list. + * The session must exist in the list. + */ + public void remove(int n) { + requireNonNull(n); + if (n < 0 || n > internalList.size()) { + throw new SessionNotFoundException(); + } + + internalList.remove(n); + } + + public void setSessions(UniqueSessionList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code sessions}. + * {@code sessions} must not contain duplicate sessions. + */ + public void setSessions(List sessions) { + requireAllNonNull(sessions); + if (!sessionsAreUnique(sessions)) { + throw new DuplicateSessionException(); + } + + internalList.setAll(sessions); + } + + /** + * Returns the session list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + + FXCollections.sort(internalList, Comparator.comparing(Session::getDate) + .thenComparing(Session::getStartDateTime) + .thenComparing(Session::getEndDateTime)); + 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 UniqueSessionList // instanceof handles nulls + && internalList.equals(((UniqueSessionList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code sessions} contains only unique sessions. + */ + private boolean sessionsAreUnique(List sessions) { + for (int i = 0; i < sessions.size() - 1; i++) { + for (int j = i + 1; j < sessions.size(); j++) { + if (sessions.get(i).isSameSession(sessions.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/session/exceptions/DuplicateSessionException.java b/src/main/java/tatracker/model/session/exceptions/DuplicateSessionException.java new file mode 100644 index 00000000000..086e8e966cd --- /dev/null +++ b/src/main/java/tatracker/model/session/exceptions/DuplicateSessionException.java @@ -0,0 +1,11 @@ +package tatracker.model.session.exceptions; + +/** + * Signals that the operation will result in duplicate {@code Session} + * (Session are considered duplicates if they have the same identity). + */ +public class DuplicateSessionException extends RuntimeException { + public DuplicateSessionException() { + super("Operation would result in duplicate session"); + } +} diff --git a/src/main/java/tatracker/model/session/exceptions/SessionNotFoundException.java b/src/main/java/tatracker/model/session/exceptions/SessionNotFoundException.java new file mode 100644 index 00000000000..0ee1a7be197 --- /dev/null +++ b/src/main/java/tatracker/model/session/exceptions/SessionNotFoundException.java @@ -0,0 +1,8 @@ +package tatracker.model.session.exceptions; + + +/** + * Signals that the operation is unable to find the specified {@code Session}. + */ +public class SessionNotFoundException extends RuntimeException {} + diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/tatracker/model/student/Email.java similarity index 86% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/tatracker/model/student/Email.java index a5bbe0b6a5f..319a4c1ef0b 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/tatracker/model/student/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package tatracker.model.student; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Student's email in the TA-Tracker. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { @@ -27,8 +27,17 @@ public class Email { public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; + private static final String DEFAULT_VALUE = ""; + public final String value; + /** + * Constructs an empty {@code Email}. + */ + public Email() { + this(DEFAULT_VALUE); + } + /** * Constructs an {@code Email}. * @@ -44,7 +53,7 @@ public Email(String email) { * Returns if a given string is a valid email. */ public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); + return test.isEmpty() || test.matches(VALIDATION_REGEX); } @Override diff --git a/src/main/java/tatracker/model/student/Matric.java b/src/main/java/tatracker/model/student/Matric.java new file mode 100644 index 00000000000..cdc554e3341 --- /dev/null +++ b/src/main/java/tatracker/model/student/Matric.java @@ -0,0 +1,59 @@ +package tatracker.model.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.AppUtil.checkArgument; + +/** + * Represents a Student's matric number in the TA-Tracker. + * Guarantees: immutable; is valid as declared in {@link #isValidMatric(String)} + */ +public class Matric { + + public static final String MESSAGE_CONSTRAINTS = + "Matric numbers should start with an \"A\" followed by 7 digits and one final capital letter," + + "and it should not be blank"; + + /* + * matric number must start with an "A" followed by 7 digits and one final capital letter + */ + public static final String VALIDATION_REGEX = "A" + "\\d{7}" + "[A-Z]"; + + public final String value; + + /** + * Constructs a {@code Matric}. + * + * @param matric A valid matric. + */ + public Matric(String matric) { + requireNonNull(matric); + checkArgument(isValidMatric(matric), MESSAGE_CONSTRAINTS); + value = matric; + } + + /** + * Returns true if a given string is a valid matric number. + */ + public static boolean isValidMatric(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 Matric // instanceof handles nulls + && value.equals(((Matric) 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/tatracker/model/student/Name.java similarity index 86% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/tatracker/model/student/Name.java index 79244d71cf7..bc54b25a47d 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/tatracker/model/student/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package tatracker.model.student; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Student's name in the TA-Tracker. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -13,7 +13,7 @@ public class Name { "Names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/tatracker/model/student/NameContainsKeywordsPredicate.java similarity index 73% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/tatracker/model/student/NameContainsKeywordsPredicate.java index c9b5868427c..fb966efa726 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/tatracker/model/student/NameContainsKeywordsPredicate.java @@ -1,14 +1,14 @@ -package seedu.address.model.person; +package tatracker.model.student; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import tatracker.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Student}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Student student) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(student.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/tatracker/model/student/Phone.java similarity index 76% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/tatracker/model/student/Phone.java index 872c76b382f..389495e10ac 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/tatracker/model/student/Phone.java @@ -1,20 +1,29 @@ -package seedu.address.model.person; +package tatracker.model.student; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Student's phone number in the TA-Tracker. * 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,}"; + + private static final String DEFAULT_VALUE = ""; + public final String value; + /** + * Constructs an empty {@code Phone}. + */ + public Phone() { + this(DEFAULT_VALUE); + } + /** * Constructs a {@code Phone}. * @@ -30,7 +39,7 @@ public Phone(String phone) { * Returns true if a given string is a valid phone number. */ public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); + return test.isEmpty() || test.matches(VALIDATION_REGEX); } @Override diff --git a/src/main/java/tatracker/model/student/Rating.java b/src/main/java/tatracker/model/student/Rating.java new file mode 100644 index 00000000000..2799261e1b9 --- /dev/null +++ b/src/main/java/tatracker/model/student/Rating.java @@ -0,0 +1,66 @@ +package tatracker.model.student; + +import static tatracker.commons.util.AppUtil.checkArgument; + +/** + * Represents a Rating in the TA-Tracker. A Rating is an integer on a scale from 1 - 5, + * where 1 represents the poorest rating, and 5 represents the best rating. + * Guarantees: immutable; rating is valid as declared in {@link #isValidRating(int)} + */ +public class Rating { + + public static final String MESSAGE_CONSTRAINTS = "Ratings should be a number" + + " between 1 (POOR) to 5 (EXCELLENT) inclusive"; + + private static final int DEFAULT_VALUE = 3; + + private static final int MIN_RATING = 1; + private static final int MAX_RATING = 5; + + public final int value; + + /** + * Constructs a default {@code Rating} (The default rating is 3 for AVERAGE). + */ + public Rating() { + this.value = DEFAULT_VALUE; + } + + /** + * Constructs a {@code Rating}. + * + * @param value A valid rating on a scale from 1 (POOR) to 5 (EXCELLENT). + */ + public Rating(int value) { + checkArgument(isValidRating(value), MESSAGE_CONSTRAINTS); + this.value = value; + } + + /** + * Returns true if a given number is a valid rating. + */ + public static boolean isValidRating(int test) { + return MIN_RATING <= test && test <= MAX_RATING; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Rating // instanceof handles nulls + && value == ((Rating) other).value); // state check + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + + /** + * Format state as text for viewing. + */ + @Override + public String toString() { + return String.format("%d", value); + } + +} diff --git a/src/main/java/tatracker/model/student/Student.java b/src/main/java/tatracker/model/student/Student.java new file mode 100644 index 00000000000..59e913a7574 --- /dev/null +++ b/src/main/java/tatracker/model/student/Student.java @@ -0,0 +1,137 @@ +package tatracker.model.student; + +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import tatracker.model.tag.Tag; + +/** + * Represents a Student in the Ta-Tracker. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Student { + + // Identity fields + private final Matric matric; + private final Name name; + + // Optional fields + private final Phone phone; + private final Email email; + private final Rating rating; + private final Set tags; + + /** + * Every field must be present and not null. + */ + public Student(Matric matric, Name name, Phone phone, Email email, Rating rating, Set tags) { + requireAllNonNull(matric, name, phone, email, rating, tags); + this.matric = matric; + this.name = name; + + this.phone = phone; + this.email = email; + this.rating = rating; + + this.tags = new HashSet<>(); + this.tags.addAll(tags); + } + + public Matric getMatric() { + return matric; + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + public Rating getRating() { + return rating; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns true if both students of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two students. + */ + public boolean isSameStudent(Student otherStudent) { + if (otherStudent == this) { + return true; + } + + return otherStudent != null + && otherStudent.getName().equals(getName()) + && otherStudent.getMatric().equals(getMatric()); + } + + /** + * Returns true if both students have the same identity and data fields. + * This defines a stronger notion of equality between two students. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Student)) { + return false; + } + + Student otherStudent = (Student) other; + return otherStudent.getName().equals(getName()) + && otherStudent.getPhone().equals(getPhone()) + && otherStudent.getEmail().equals(getEmail()) + && otherStudent.getMatric().equals(getMatric()) + && otherStudent.getRating().equals(getRating()) + && otherStudent.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, matric, rating, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + // Append identity fields + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Matric: ") + .append(getMatric()); + + // Append optional fields + builder.append(" Rating: ") + .append(getRating()); + + // Append Tags + builder.append(" Tags: "); + getTags().forEach(builder::append); + + return builder.toString(); + } +} diff --git a/src/main/java/tatracker/model/student/UniqueStudentList.java b/src/main/java/tatracker/model/student/UniqueStudentList.java new file mode 100644 index 00000000000..a959cd5d6c8 --- /dev/null +++ b/src/main/java/tatracker/model/student/UniqueStudentList.java @@ -0,0 +1,208 @@ +package tatracker.model.student; + +import static java.util.Objects.requireNonNull; +import static tatracker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import tatracker.model.student.exceptions.DuplicateStudentException; +import tatracker.model.student.exceptions.StudentNotFoundException; + +/** + * A list of students that enforces uniqueness between its elements and does not allow nulls. + * A student is considered unique by comparing using {@code Student#isSameStudent(Student)}. As such, adding and + * updating of students uses Student#isSameStudent(Student) for equality so as to ensure that the student being added or + * updated is unique in terms of identity in the UniqueStudentList. However, the removal of a student uses + * Student#equals(Object) so as to ensure that the student with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Student#isSameStudent(Student) + */ +public class UniqueStudentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + private Comparator alphabetically = new Comparator() { + @Override + public int compare(Student student, Student other) { + return (student.getName().toString()).compareTo(other.getName().toString()); + } + }; + private Comparator ratingAscending = new Comparator() { + @Override + public int compare(Student student, Student other) { + return (student.getRating().toString()).compareTo(other.getRating().toString()); + } + }; + private Comparator ratingDescending = new Comparator() { + @Override + public int compare(Student student, Student other) { + return (-1) * (student.getRating().toString()).compareTo(other.getRating().toString()); + } + }; + + public int size() { + return internalList.size(); + } + + /** + * Returns true if the list contains an equivalent student as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameStudent); + } + + public Student get(int n) { + return internalList.get(n); + } + + /** + * Returns the student in this list with the given student + * matriculation number (the student id). + * Returns null if no such student exists. + */ + public Student get(Matric studentId) { + for (Student student : internalList) { + if (student.getMatric().equals(studentId)) { + return student; + } + } + return null; // Did not find a student with the given student id + } + + /** + * Adds a student to the list. + * The student must not already exist in the list. + */ + public void add(Student toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateStudentException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent student from the list. + * The student must exist in the list. + */ + public void remove(Student toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new StudentNotFoundException(); + } + } + + /** + * Replaces the student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list. + * The student identity of {@code editedStudent} must not be the same as another existing student in the list. + */ + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new StudentNotFoundException(); + } + + if (!target.isSameStudent(editedStudent) && contains(editedStudent)) { + throw new DuplicateStudentException(); + } + + internalList.set(index, editedStudent); + } + + public void setStudents(UniqueStudentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setStudents(List students) { + requireAllNonNull(students); + if (!studentsAreUnique(students)) { + throw new DuplicateStudentException(); + } + + internalList.setAll(students); + } + + /** + * returns alphabetical comparator. + */ + public Comparator getAlphabetically() { + return alphabetically; + } + + /** + * Sorts the students alphabetically. + */ + public void sortAlphabetically() { + FXCollections.sort(internalList, alphabetically); + } + + /** + * Sorts the students by rating in ascending order. + */ + public void sortByRatingAscending() { + FXCollections.sort(internalList, ratingAscending); + } + + /** + * Sorts the students by rating in descending order. + */ + public void sortByRatingDescending() { + FXCollections.sort(internalList, ratingDescending); + } + + + /** + * 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 UniqueStudentList // instanceof handles nulls + && internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean studentsAreUnique(List students) { + for (int i = 0; i < students.size() - 1; i++) { + for (int j = i + 1; j < students.size(); j++) { + if (students.get(i).isSameStudent(students.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/tatracker/model/student/exceptions/DuplicateStudentException.java b/src/main/java/tatracker/model/student/exceptions/DuplicateStudentException.java new file mode 100644 index 00000000000..d34761f5d2b --- /dev/null +++ b/src/main/java/tatracker/model/student/exceptions/DuplicateStudentException.java @@ -0,0 +1,11 @@ +package tatracker.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Student (Student are considered duplicates if they have the same + * identity). + */ +public class DuplicateStudentException extends RuntimeException { + public DuplicateStudentException() { + super("Operation would result in duplicate students"); + } +} diff --git a/src/main/java/tatracker/model/student/exceptions/StudentNotFoundException.java b/src/main/java/tatracker/model/student/exceptions/StudentNotFoundException.java new file mode 100644 index 00000000000..35b5a121d81 --- /dev/null +++ b/src/main/java/tatracker/model/student/exceptions/StudentNotFoundException.java @@ -0,0 +1,6 @@ +package tatracker.model.student.exceptions; + +/** + * Signals that the operation is unable to find the specified student. + */ +public class StudentNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/tatracker/model/tag/Tag.java similarity index 90% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/tatracker/model/tag/Tag.java index b0ea7e7dad7..e1753291e7b 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/tatracker/model/tag/Tag.java @@ -1,10 +1,10 @@ -package seedu.address.model.tag; +package tatracker.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static tatracker.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the TA-Tracker. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { diff --git a/src/main/java/tatracker/model/util/SampleDataUtil.java b/src/main/java/tatracker/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..d30444d9454 --- /dev/null +++ b/src/main/java/tatracker/model/util/SampleDataUtil.java @@ -0,0 +1,85 @@ +package tatracker.model.util; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.TaTracker; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + +/** + * Contains utility methods for populating {@code TaTracker} with sample data. + */ +public class SampleDataUtil { + public static Student[] getSampleStudents() { + return new Student[] { + new Student(new Matric("A0187945J"), + new Name("Alex Yeoh"), + new Phone("87438807"), + new Email("alexyeoh@example.com"), + new Rating(4), + getTagSet("friends")), + new Student(new Matric("A0181137L"), + new Name("Bernice Yu"), + new Phone("99272758"), + new Email("berniceyu@example.com"), + new Rating(1), + getTagSet("colleagues", "friends")), + new Student(new Matric("A0187565N"), + new Name("Charlotte Oliveiro"), + new Phone("93210283"), + new Email("charlotte@example.com"), + new Rating(5), + getTagSet("neighbours")), + new Student(new Matric("A0186153P"), + new Name("David Li"), + new Phone("91031282"), + new Email("lidavid@example.com"), + new Rating(3), + getTagSet("family")), + new Student(new Matric("A0180474R"), + new Name("Irfan Ibrahim"), + new Phone("92492021"), + new Email("irfan@example.com"), + new Rating(2), + getTagSet("classmates")), + new Student(new Matric("A0187613T"), + new Name("Roy Balakrishnan"), + new Phone("92624417"), + new Email("royb@example.com"), + new Rating(4), + getTagSet("colleagues")), + new Student(new Matric("A0195558H"), + new Name("Jeffry Lum"), + new Phone("65162727"), + new Email("Jeffry@u.nus.edu"), + new Rating(5), + getTagSet("tutors")) + }; + } + + public static ReadOnlyTaTracker getSampleTaTracker() { + TaTracker sampleAb = new TaTracker(); + for (Student sampleStudent : getSampleStudents()) { + sampleAb.addStudent(sampleStudent); + } + 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/tatracker/storage/JsonAdaptedGroup.java b/src/main/java/tatracker/storage/JsonAdaptedGroup.java new file mode 100644 index 00000000000..bb5cce7c2ad --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedGroup.java @@ -0,0 +1,88 @@ +package tatracker.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 tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.group.Group; +import tatracker.model.group.GroupType; +import tatracker.model.student.Student; + +/** + * Jackson-friendly version of {@link Group}. + */ +class JsonAdaptedGroup { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Group's %s field is missing!"; + public static final String MESSAGE_DUPLICATE_STUDENTS = "Student list contains duplicate student(s)."; + + private final String id; + private final String type; + private final List students = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedGroup} with the given group details. + */ + @JsonCreator + public JsonAdaptedGroup(@JsonProperty("id") String id, + @JsonProperty("type") String type, + @JsonProperty("students") List students) { + this.id = id; + this.type = type; + if (students != null) { + this.students.addAll(students); + } + } + + /** + * Converts a given {@code Group} into this class for Jackson use. + */ + public JsonAdaptedGroup(Group source) { + id = source.getIdentifier(); + type = source.getGroupType().name(); + students.addAll(source.getStudentList() + .stream() + .map(JsonAdaptedStudent::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted group object into the model's {@code Group} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted group. + */ + public Group toModelType() throws IllegalValueException { + // ==== ID ==== + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Group id")); + } + + // ==== Type ==== + if (type == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Group type")); + } + // TODO: is valid group type + final GroupType modelGroupType = GroupType.valueOf(type); + + // ==== Students ==== + final List modelStudents = new ArrayList<>(); + for (JsonAdaptedStudent jsonAdaptedStudent : students) { + Student student = jsonAdaptedStudent.toModelType(); + if (modelStudents.contains(student)) { + throw new IllegalArgumentException(MESSAGE_DUPLICATE_STUDENTS); + } + modelStudents.add(student); + } + + // ==== Build ==== + Group group = new Group(id, modelGroupType); + modelStudents.forEach(group::addStudent); + + return group; + } + +} diff --git a/src/main/java/tatracker/storage/JsonAdaptedModule.java b/src/main/java/tatracker/storage/JsonAdaptedModule.java new file mode 100644 index 00000000000..5a4bd9b6398 --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedModule.java @@ -0,0 +1,105 @@ +package tatracker.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 tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.group.Group; +import tatracker.model.module.Module; +import tatracker.model.session.Session; + +/** + * Jackson-friendly version of {@link Module}. + */ +class JsonAdaptedModule { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Module's %s field is missing!"; + public static final String MESSAGE_DUPLICATE_SESSIONS = "Session list contains duplicate session(s)."; + public static final String MESSAGE_DUPLICATE_GROUPS = "Group list contains duplicate group(s)."; + + private final String id; + private final String name; + private final List sessions = new ArrayList<>(); + private final List groups = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedModule} with the given module details. + */ + @JsonCreator + public JsonAdaptedModule(@JsonProperty("id") String id, + @JsonProperty("name") String name, + @JsonProperty("sessions") List sessions, + @JsonProperty("groups") List groups) { + this.id = id; + this.name = name; + if (sessions != null) { + this.sessions.addAll(sessions); + } + if (groups != null) { + this.groups.addAll(groups); + } + } + + /** + * Converts a given {@code Module} into this class for Jackson use. + */ + public JsonAdaptedModule(Module source) { + id = source.getIdentifier(); + name = source.getName(); + sessions.addAll(source.getSessionList().stream() + .map(JsonAdaptedSession::new) + .collect(Collectors.toList())); + groups.addAll(source.getGroupList().stream() + .map(JsonAdaptedGroup::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted module object into the model's {@code Module} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted module. + */ + public Module toModelType() throws IllegalValueException { + // ==== ID ==== + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Module id")); + } + + // ==== Name ==== + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Module name")); + } + + // ==== Done Sessions ==== + final List modelDoneSessions = new ArrayList<>(); + for (JsonAdaptedSession jsonAdaptedSession : sessions) { + Session doneSession = jsonAdaptedSession.toModelType(); + if (modelDoneSessions.contains(doneSession)) { + throw new IllegalArgumentException(MESSAGE_DUPLICATE_SESSIONS); + } + modelDoneSessions.add(doneSession); + } + + // ==== Groups ==== + final List modelGroups = new ArrayList<>(); + for (JsonAdaptedGroup jsonAdaptedGroup : groups) { + Group group = jsonAdaptedGroup.toModelType(); + if (modelGroups.contains(group)) { + throw new IllegalArgumentException(MESSAGE_DUPLICATE_GROUPS); + } + modelGroups.add(group); + } + + // ==== Build ==== + Module module = new Module(id, name); + modelDoneSessions.forEach(module::addDoneSession); + modelGroups.forEach(module::addGroup); + + return module; + } + +} diff --git a/src/main/java/tatracker/storage/JsonAdaptedSession.java b/src/main/java/tatracker/storage/JsonAdaptedSession.java new file mode 100644 index 00000000000..c2770dfc81b --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedSession.java @@ -0,0 +1,128 @@ +package tatracker.storage; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.session.Session; +import tatracker.model.session.SessionType; + +/** + * Jackson-friendly version of {@link Session}. + */ +class JsonAdaptedSession { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Session's %s field is missing!"; + + private static final String MESSAGE_INVALID_DATE = "Dates should be in yyyy-MM-dd format"; + private static final String MESSAGE_INVALID_TIME = "Times should be in HH:mm format"; + private static final String MESSAGE_INVALID_DATE_TIME = + "Session's %s date time is invalid!\n" + + MESSAGE_INVALID_DATE + "\n" + + MESSAGE_INVALID_TIME; + + private String startDateTime; + private String endDateTime; + private String type; + private String description; + private String moduleId; + private boolean isDone; + private boolean isRecurring; + + /** + * Constructs a {@code JsonAdaptedModule} with the given module details. + */ + @JsonCreator + public JsonAdaptedSession(@JsonProperty("start") String startDateTime, + @JsonProperty("end") String endDateTime, + @JsonProperty("type") String type, + @JsonProperty("description") String description, + @JsonProperty("moduleId") String moduleId, + @JsonProperty("isDone") boolean isDone, + @JsonProperty("isRecurring") boolean isRecurring) { + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + + this.type = type; + this.description = description; + this.moduleId = moduleId; + + this.isDone = isDone; + this.isRecurring = isRecurring; + } + + /** + * Converts a given {@code Module} into this class for Jackson use. + */ + public JsonAdaptedSession(Session source) { + startDateTime = source.getStartDateTime().toString(); + endDateTime = source.getEndDateTime().toString(); + + type = source.getSessionType().name(); + description = source.getDescription(); + moduleId = source.getModuleCode(); + + isDone = source.getIsDone(); + isRecurring = source.getIsRecurring(); + } + + /** + * Converts this Jackson-friendly adapted module object into the model's {@code Module} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted module. + */ + public Session toModelType() throws IllegalValueException { + // ==== Start Date Time ==== + if (startDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "start date time")); + } + + final LocalDateTime modelStartDateTime; + try { + modelStartDateTime = LocalDateTime.parse(startDateTime); + } catch (DateTimeParseException dtpe) { + throw new IllegalValueException(String.format(MESSAGE_INVALID_DATE_TIME, "start")); + } + + // ==== End Date Time ==== + if (endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "end date time")); + } + + final LocalDateTime modelEndDateTime; + try { + modelEndDateTime = LocalDateTime.parse(endDateTime); + } catch (DateTimeParseException dtpe) { + throw new IllegalValueException(String.format(MESSAGE_INVALID_DATE_TIME, "end")); + } + + // ==== Type ==== + if (type == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Session type")); + } + final SessionType modelSessionType = SessionType.valueOf(type); + + // ==== Description ==== + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "description")); + } + + // ==== Module Id ==== + if (moduleId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Module id")); + } + + Session session = new Session(modelStartDateTime, modelEndDateTime, modelSessionType, + isRecurring, moduleId, description); + + if (isDone) { + session.done(); + } + + return session; + } + +} diff --git a/src/main/java/tatracker/storage/JsonAdaptedStudent.java b/src/main/java/tatracker/storage/JsonAdaptedStudent.java new file mode 100644 index 00000000000..8eb196f8915 --- /dev/null +++ b/src/main/java/tatracker/storage/JsonAdaptedStudent.java @@ -0,0 +1,126 @@ +package tatracker.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 tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.student.Email; +import tatracker.model.student.Matric; +import tatracker.model.student.Name; +import tatracker.model.student.Phone; +import tatracker.model.student.Rating; +import tatracker.model.student.Student; +import tatracker.model.tag.Tag; + +/** + * Jackson-friendly version of {@link Student}. + */ +class JsonAdaptedStudent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Student's %s field is missing!"; + + private final String matric; + private final String name; + private final String phone; + private final String email; + private final int rating; + private final List tagged = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedStudent} with the given student details. + */ + @JsonCreator + public JsonAdaptedStudent(@JsonProperty("matric") String matric, + @JsonProperty("name") String name, + @JsonProperty("phone") String phone, + @JsonProperty("email") String email, + @JsonProperty("rating") int rating, + @JsonProperty("tagged") List tagged) { + this.matric = matric; + this.name = name; + this.phone = phone; + this.email = email; + this.rating = rating; + if (tagged != null) { + this.tagged.addAll(tagged); + } + } + + /** + * Converts a given {@code Student} into this class for Jackson use. + */ + public JsonAdaptedStudent(Student source) { + matric = source.getMatric().value; + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + rating = source.getRating().value; + tagged.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted student object into the model's {@code Student} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted student. + */ + public Student toModelType() throws IllegalValueException { + // ==== Matric ==== + if (matric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Matric.class.getSimpleName())); + } + if (!Matric.isValidMatric(matric)) { + throw new IllegalValueException(Matric.MESSAGE_CONSTRAINTS); + } + final Matric modelMatric = new Matric(matric); + + // ==== Name ==== + 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); + + // ==== Phone ==== + 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); + + // ==== Email ==== + 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); + + // ==== Rating ==== + if (!Rating.isValidRating(rating)) { + throw new IllegalValueException(Rating.MESSAGE_CONSTRAINTS); + } + final Rating modelRating = new Rating(rating); + + // ==== Tags ==== + final Set modelTags = new HashSet<>(); + for (JsonAdaptedTag tag : tagged) { + modelTags.add(tag.toModelType()); + } + + return new Student(modelMatric, modelName, modelPhone, modelEmail, modelRating, modelTags); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/tatracker/storage/JsonAdaptedTag.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/tatracker/storage/JsonAdaptedTag.java index 0df22bdb754..28b923c5ca6 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/tatracker/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package tatracker.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; +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.tag.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/tatracker/storage/JsonSerializableTaTracker.java b/src/main/java/tatracker/storage/JsonSerializableTaTracker.java new file mode 100644 index 00000000000..efdc1aba3da --- /dev/null +++ b/src/main/java/tatracker/storage/JsonSerializableTaTracker.java @@ -0,0 +1,89 @@ +package tatracker.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 tatracker.commons.exceptions.IllegalValueException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.TaTracker; +import tatracker.model.module.Module; +import tatracker.model.session.Session; + +/** + * An Immutable TaTracker that is serializable to JSON format. + */ +@JsonRootName(value = "tatracker") +class JsonSerializableTaTracker { + + public static final String MESSAGE_DUPLICATE_SESSIONS = "Session list contains duplicate session(s)."; + public static final String MESSAGE_DUPLICATE_MODULES = "Module list contains duplicate module(s)."; + + private final List sessions = new ArrayList<>(); + private final List modules = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableTaTracker} with the given lists. + */ + @JsonCreator + public JsonSerializableTaTracker(@JsonProperty("sessions") List sessions, + @JsonProperty("modules") List modules) { + this.sessions.addAll(sessions); + this.modules.addAll(modules); + } + + /** + * Converts a given {@code ReadOnlyTaTracker} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableTaTracker}. + */ + public JsonSerializableTaTracker(ReadOnlyTaTracker source) { + sessions.addAll(source.getSessionList() + .stream() + .map(JsonAdaptedSession::new) + .collect(Collectors.toList())); + + modules.addAll(source.getModuleList() + .stream() + .map(JsonAdaptedModule::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Ta Tracker into the model's {@code TaTracker} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public TaTracker toModelType() throws IllegalValueException { + // ==== Sessions ==== + final List modelSessions = new ArrayList<>(); + for (JsonAdaptedSession jsonAdaptedSession : sessions) { + Session session = jsonAdaptedSession.toModelType(); + if (modelSessions.contains(session)) { + throw new IllegalArgumentException(MESSAGE_DUPLICATE_SESSIONS); + } + modelSessions.add(session); + } + + // ==== Modules ==== + final List modelModules = new ArrayList<>(); + for (JsonAdaptedModule jsonAdaptedModule : modules) { + Module module = jsonAdaptedModule.toModelType(); + if (modelModules.contains(module)) { + throw new IllegalArgumentException(MESSAGE_DUPLICATE_MODULES); + } + modelModules.add(module); + } + + // ==== Build ==== + TaTracker taTracker = new TaTracker(); + modelSessions.forEach(taTracker::addSession); + modelModules.forEach(taTracker::addModule); + + return taTracker; + } +} diff --git a/src/main/java/tatracker/storage/JsonTaTrackerStorage.java b/src/main/java/tatracker/storage/JsonTaTrackerStorage.java new file mode 100644 index 00000000000..a26d464375d --- /dev/null +++ b/src/main/java/tatracker/storage/JsonTaTrackerStorage.java @@ -0,0 +1,80 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.commons.exceptions.IllegalValueException; +import tatracker.commons.util.FileUtil; +import tatracker.commons.util.JsonUtil; +import tatracker.model.ReadOnlyTaTracker; + +/** + * A class to access TaTracker data stored as a json file on the hard disk. + */ +public class JsonTaTrackerStorage implements TaTrackerStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonTaTrackerStorage.class); + + private Path filePath; + + public JsonTaTrackerStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getTaTrackerFilePath() { + return filePath; + } + + @Override + public Optional readTaTracker() throws DataConversionException { + return readTaTracker(filePath); + } + + /** + * Similar to {@link #readTaTracker()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readTaTracker(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonTaTracker = JsonUtil.readJsonFile( + filePath, JsonSerializableTaTracker.class); + if (!jsonTaTracker.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonTaTracker.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException { + saveTaTracker(taTracker, filePath); + } + + /** + * Similar to {@link #saveTaTracker(ReadOnlyTaTracker)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveTaTracker(ReadOnlyTaTracker taTracker, Path filePath) throws IOException { + requireNonNull(taTracker); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableTaTracker(taTracker), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/tatracker/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/tatracker/storage/JsonUserPrefsStorage.java index bc2bbad84aa..ef25a1a98db 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/tatracker/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package tatracker.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; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.commons.util.JsonUtil; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/tatracker/storage/Storage.java b/src/main/java/tatracker/storage/Storage.java new file mode 100644 index 00000000000..b31a3c65048 --- /dev/null +++ b/src/main/java/tatracker/storage/Storage.java @@ -0,0 +1,32 @@ +package tatracker.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends TaTrackerStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getTaTrackerFilePath(); + + @Override + Optional readTaTracker() throws DataConversionException, IOException; + + @Override + void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException; + +} diff --git a/src/main/java/tatracker/storage/StorageManager.java b/src/main/java/tatracker/storage/StorageManager.java new file mode 100644 index 00000000000..28d555dd83f --- /dev/null +++ b/src/main/java/tatracker/storage/StorageManager.java @@ -0,0 +1,77 @@ +package tatracker.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import tatracker.commons.core.LogsCenter; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; + +/** + * Manages storage of TaTracker data in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private TaTrackerStorage taTrackerStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(TaTrackerStorage taTrackerStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.taTrackerStorage = taTrackerStorage; + 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); + } + + + // ================ TaTracker methods ============================== + + @Override + public Path getTaTrackerFilePath() { + return taTrackerStorage.getTaTrackerFilePath(); + } + + @Override + public Optional readTaTracker() throws DataConversionException, IOException { + return readTaTracker(taTrackerStorage.getTaTrackerFilePath()); + } + + @Override + public Optional readTaTracker(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return taTrackerStorage.readTaTracker(filePath); + } + + @Override + public void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException { + saveTaTracker(taTracker, taTrackerStorage.getTaTrackerFilePath()); + } + + @Override + public void saveTaTracker(ReadOnlyTaTracker taTracker, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + taTrackerStorage.saveTaTracker(taTracker, filePath); + } + +} diff --git a/src/main/java/tatracker/storage/TaTrackerStorage.java b/src/main/java/tatracker/storage/TaTrackerStorage.java new file mode 100644 index 00000000000..d656f5a570f --- /dev/null +++ b/src/main/java/tatracker/storage/TaTrackerStorage.java @@ -0,0 +1,46 @@ +package tatracker.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyTaTracker; +import tatracker.model.TaTracker; + +/** + * Represents a storage for {@link TaTracker}. + */ +public interface TaTrackerStorage { + + /** + * Returns the file path of the data file. + */ + Path getTaTrackerFilePath(); + + /** + * Returns TaTracker data as a {@link ReadOnlyTaTracker}. + * 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 readTaTracker() throws DataConversionException, IOException; + + /** + * @see #getTaTrackerFilePath() + */ + Optional readTaTracker(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTaTracker} to the storage. + * @param taTracker cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTaTracker(ReadOnlyTaTracker taTracker) throws IOException; + + /** + * @see #saveTaTracker(ReadOnlyTaTracker) + */ + void saveTaTracker(ReadOnlyTaTracker taTracker, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/tatracker/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/tatracker/storage/UserPrefsStorage.java index 29eef178dbc..a1c42e99b6d 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/tatracker/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package tatracker.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; +import tatracker.commons.exceptions.DataConversionException; +import tatracker.model.ReadOnlyUserPrefs; +import tatracker.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link tatracker.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link tatracker.model.ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/tatracker/ui/ClaimsListPanel.java b/src/main/java/tatracker/ui/ClaimsListPanel.java new file mode 100644 index 00000000000..b76aa7c2b38 --- /dev/null +++ b/src/main/java/tatracker/ui/ClaimsListPanel.java @@ -0,0 +1,45 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.model.session.Session; + +/** + * Panel containing the list of sessions. + */ +public class ClaimsListPanel extends UiPart { + private static final String FXML = "ClaimsListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ClaimsListPanel.class); + + @FXML + private ListView claimsListView; + + public ClaimsListPanel(ObservableList claimsList) { + super(FXML); + claimsListView.setItems(claimsList); + claimsListView.setCellFactory(listView -> new ClaimsListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Session} using a {@code ClaimsCard}. + */ + class ClaimsListViewCell extends ListCell { + @Override + protected void updateItem(Session claims, boolean empty) { + super.updateItem(claims, empty); + + if (empty || claims == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new SessionCard(claims, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/tatracker/ui/CommandBox.java similarity index 88% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/tatracker/ui/CommandBox.java index 7d76e691f52..9073c9fab60 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/tatracker/ui/CommandBox.java @@ -1,12 +1,12 @@ -package seedu.address.ui; +package tatracker.ui; import javafx.collections.ObservableList; 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.parser.exceptions.ParseException; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -69,7 +69,7 @@ public interface CommandExecutor { /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see tatracker.logic.Logic#execute(String) */ CommandResult execute(String commandText) throws CommandException, ParseException; } diff --git a/src/main/java/tatracker/ui/GroupCard.java b/src/main/java/tatracker/ui/GroupCard.java new file mode 100644 index 00000000000..73acf0b8d4f --- /dev/null +++ b/src/main/java/tatracker/ui/GroupCard.java @@ -0,0 +1,60 @@ +package tatracker.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import tatracker.model.group.Group; + +/** + * An UI component that displays information of a {@code Group}. + */ +public class GroupCard extends UiPart { + + private static final String FXML = "GroupListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Group group; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label type; + + public GroupCard(Group group, int displayedIndex) { + super(FXML); + this.group = group; + id.setText(displayedIndex + ". "); + name.setText(group.getIdentifier()); + type.setText(group.getGroupType().toString()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupCard)) { + return false; + } + + // state check + GroupCard card = (GroupCard) other; + return id.getText().equals(card.id.getText()) + && group.equals(card.group); + } +} diff --git a/src/main/java/tatracker/ui/GroupListPanel.java b/src/main/java/tatracker/ui/GroupListPanel.java new file mode 100644 index 00000000000..c41655d6482 --- /dev/null +++ b/src/main/java/tatracker/ui/GroupListPanel.java @@ -0,0 +1,45 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.model.group.Group; + +/** + * Panel containing the list of groups. + */ +public class GroupListPanel extends UiPart { + private static final String FXML = "GroupListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(GroupListPanel.class); + + @FXML + private ListView groupListView; + + public GroupListPanel(ObservableList groupList) { + super(FXML); + groupListView.setItems(groupList); + groupListView.setCellFactory(listView -> new GroupListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Group} using a {@code GroupCard}. + */ + class GroupListViewCell extends ListCell { + @Override + protected void updateItem(Group group, boolean empty) { + super.updateItem(group, empty); + + if (empty || group == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new GroupCard(group, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/tatracker/ui/HelpWindow.java similarity index 92% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/tatracker/ui/HelpWindow.java index 9a665915949..b1147cc15fe 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/tatracker/ui/HelpWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.util.logging.Logger; @@ -8,14 +8,14 @@ import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import tatracker.commons.core.LogsCenter; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay1920s2-cs2103t-w17-4.github.io/main/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/tatracker/ui/MainWindow.java similarity index 68% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/tatracker/ui/MainWindow.java index 90bbf11de97..d6a74782ed5 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/tatracker/ui/MainWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.util.logging.Logger; @@ -10,12 +10,12 @@ 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; +import tatracker.commons.core.GuiSettings; +import tatracker.commons.core.LogsCenter; +import tatracker.logic.Logic; +import tatracker.logic.commands.CommandResult; +import tatracker.logic.commands.exceptions.CommandException; +import tatracker.logic.parser.exceptions.ParseException; /** * The Main Window. Provides the basic application layout containing @@ -31,7 +31,12 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private StudentListPanel studentListPanel; + private GroupListPanel groupListPanel; + private ModuleListPanel moduleListPanel; + private ModuleListPanelCopy moduleListPanelCopy; + private SessionListPanel sessionListPanel; + private ClaimsListPanel claimsListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -42,7 +47,22 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane studentListPanelPlaceholder; + + @FXML + private StackPane groupListPanelPlaceholder; + + @FXML + private StackPane moduleListPanelPlaceholder; + + @FXML + private StackPane moduleListPanelPlaceholderCopy; + + @FXML + private StackPane sessionListPanelPlaceholder; + + @FXML + private StackPane claimsListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -107,13 +127,28 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + studentListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); + + groupListPanel = new GroupListPanel(logic.getFilteredGroupList()); + groupListPanelPlaceholder.getChildren().add(groupListPanel.getRoot()); + + moduleListPanel = new ModuleListPanel(logic.getFilteredModuleList()); + moduleListPanelPlaceholder.getChildren().add(moduleListPanel.getRoot()); + + moduleListPanelCopy = new ModuleListPanelCopy(logic.getFilteredModuleList()); + moduleListPanelPlaceholderCopy.getChildren().add(moduleListPanelCopy.getRoot()); + + sessionListPanel = new SessionListPanel(logic.getFilteredSessionList()); + sessionListPanelPlaceholder.getChildren().add(sessionListPanel.getRoot()); + + claimsListPanel = new ClaimsListPanel(logic.getFilteredDoneSessionList()); + claimsListPanelPlaceholder.getChildren().add(claimsListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getTaTrackerFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); @@ -160,14 +195,34 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public StudentListPanel getStudentListPanel() { + return studentListPanel; + } + + public GroupListPanel getGroupListPanel() { + return groupListPanel; + } + + public ModuleListPanel getModuleListPanel() { + return moduleListPanel; + } + + public ModuleListPanelCopy getModuleListPanelCopy() { + return moduleListPanelCopy; + } + + public SessionListPanel getSessionListPanel() { + return sessionListPanel; + } + + public ClaimsListPanel getClaimsListPanel() { + return claimsListPanel; } /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see tatracker.logic.Logic#execute(String) */ private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { diff --git a/src/main/java/tatracker/ui/ModuleCard.java b/src/main/java/tatracker/ui/ModuleCard.java new file mode 100644 index 00000000000..7e37c7af2f0 --- /dev/null +++ b/src/main/java/tatracker/ui/ModuleCard.java @@ -0,0 +1,60 @@ +package tatracker.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import tatracker.model.module.Module; + +/** + * An UI component that displays information of a {@code Module}. + */ +public class ModuleCard extends UiPart { + + private static final String FXML = "ModuleListCard.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 Module module; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label identifier; + + public ModuleCard(Module module, int displayedIndex) { + super(FXML); + this.module = module; + id.setText(displayedIndex + ". "); + name.setText(module.getName()); + identifier.setText(module.getIdentifier()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ModuleCard)) { + return false; + } + + // state check + ModuleCard card = (ModuleCard) other; + return id.getText().equals(card.id.getText()) + && module.equals(card.module); + } +} diff --git a/src/main/java/tatracker/ui/ModuleListPanel.java b/src/main/java/tatracker/ui/ModuleListPanel.java new file mode 100644 index 00000000000..5cd9362f381 --- /dev/null +++ b/src/main/java/tatracker/ui/ModuleListPanel.java @@ -0,0 +1,46 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.model.module.Module; + +/** + * Panel containing the list of modules. + */ +public class ModuleListPanel extends UiPart { + private static final String FXML = "ModuleListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ModuleListPanel.class); + + @FXML + private ListView moduleListView; + + public ModuleListPanel(ObservableList moduleList) { + super(FXML); + moduleListView.setItems(moduleList); + moduleListView.setCellFactory(listView -> new ModuleListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Module} using a {@code ModuleCard}. + */ + class ModuleListViewCell extends ListCell { + @Override + protected void updateItem(Module module, boolean empty) { + super.updateItem(module, empty); + + if (empty || module == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ModuleCard(module, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/tatracker/ui/ModuleListPanelCopy.java b/src/main/java/tatracker/ui/ModuleListPanelCopy.java new file mode 100644 index 00000000000..c9956fb985a --- /dev/null +++ b/src/main/java/tatracker/ui/ModuleListPanelCopy.java @@ -0,0 +1,46 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.model.module.Module; + +/** + * Panel containing the list of modules. + */ +public class ModuleListPanelCopy extends UiPart { + private static final String FXML = "ModuleListPanelCopy.fxml"; + private final Logger logger = LogsCenter.getLogger(ModuleListPanelCopy.class); + + @FXML + private ListView moduleListViewCopy; + + public ModuleListPanelCopy(ObservableList moduleListCopy) { + super(FXML); + moduleListViewCopy.setItems(moduleListCopy); + moduleListViewCopy.setCellFactory(listView -> new ModuleListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Module} using a {@code ModuleCard}. + */ + class ModuleListViewCell extends ListCell { + @Override + protected void updateItem(Module module, boolean empty) { + super.updateItem(module, empty); + + if (empty || module == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ModuleCard(module, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/tatracker/ui/ResultDisplay.java similarity index 95% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/tatracker/ui/ResultDisplay.java index 7d98e84eedf..9ac9665ea63 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/tatracker/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/tatracker/ui/SessionCard.java b/src/main/java/tatracker/ui/SessionCard.java new file mode 100644 index 00000000000..11e7a057fa8 --- /dev/null +++ b/src/main/java/tatracker/ui/SessionCard.java @@ -0,0 +1,75 @@ +package tatracker.ui; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import tatracker.model.session.Session; + +/** + * An UI component that displays information of a {@code Session}. + */ +public class SessionCard extends UiPart { + + private static final String FXML = "SessionListCard.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 Session session; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label type; + @FXML + private Label date; + @FXML + private Label time; + @FXML + private Label module; + @FXML + private Label description; + + private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("hh:mma"); + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MMM d"); + + public SessionCard(Session session, int displayedIndex) { + super(FXML); + this.session = session; + id.setText(displayedIndex + ". "); + type.setText(session.getSessionType().toString()); + date.setText(session.getStartDateTime().format(dateFormat)); + time.setText(session.getStartDateTime().format(timeFormat) + " - " + + session.getEndDateTime().format(timeFormat)); + module.setText(session.getModuleCode()); + description.setText(session.getDescription()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SessionCard)) { + return false; + } + + // state check + SessionCard card = (SessionCard) other; + return id.getText().equals(card.id.getText()) + && session.equals(card.session); + } +} diff --git a/src/main/java/tatracker/ui/SessionListPanel.java b/src/main/java/tatracker/ui/SessionListPanel.java new file mode 100644 index 00000000000..753dd7374c0 --- /dev/null +++ b/src/main/java/tatracker/ui/SessionListPanel.java @@ -0,0 +1,45 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.model.session.Session; + +/** + * Panel containing the list of sessions. + */ +public class SessionListPanel extends UiPart { + private static final String FXML = "SessionListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(SessionListPanel.class); + + @FXML + private ListView sessionListView; + + public SessionListPanel(ObservableList sessionList) { + super(FXML); + sessionListView.setItems(sessionList); + sessionListView.setCellFactory(listView -> new SessionListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Session} using a {@code SessionCard}. + */ + class SessionListViewCell extends ListCell { + @Override + protected void updateItem(Session session, boolean empty) { + super.updateItem(session, empty); + + if (empty || session == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new SessionCard(session, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/tatracker/ui/StatusBarFooter.java similarity index 95% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/tatracker/ui/StatusBarFooter.java index 7e17911323f..510a53f8fd8 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/tatracker/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/tatracker/ui/StudentCard.java similarity index 63% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/tatracker/ui/StudentCard.java index 0684b088868..d626273e532 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/tatracker/ui/StudentCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.util.Comparator; @@ -7,14 +7,14 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import tatracker.model.student.Student; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Student}. */ -public class PersonCard extends UiPart { +public class StudentCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "StudentListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,7 +24,7 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Student student; @FXML private HBox cardPane; @@ -35,21 +35,21 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; - @FXML private Label email; @FXML + private Label matric; + @FXML private FlowPane tags; - public PersonCard(Person person, int displayedIndex) { + public StudentCard(Student student, int displayedIndex) { super(FXML); - this.person = person; + this.student = student; 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() + name.setText(student.getName().fullName); + phone.setText(student.getPhone().value); + email.setText(student.getEmail().value); + matric.setText(student.getMatric().value); + student.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @@ -62,13 +62,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof StudentCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + StudentCard card = (StudentCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && student.equals(card.student); } } diff --git a/src/main/java/tatracker/ui/StudentListPanel.java b/src/main/java/tatracker/ui/StudentListPanel.java new file mode 100644 index 00000000000..98d3f71aeff --- /dev/null +++ b/src/main/java/tatracker/ui/StudentListPanel.java @@ -0,0 +1,46 @@ +package tatracker.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 tatracker.commons.core.LogsCenter; +import tatracker.model.student.Student; + +/** + * Panel containing the list of students. + */ +public class StudentListPanel extends UiPart { + private static final String FXML = "StudentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + public StudentListPanel(ObservableList studentList) { + super(FXML); + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentCard}. + */ + class StudentListViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + + if (empty || student == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentCard(student, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/tatracker/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/tatracker/ui/Ui.java index 17aa0b494fe..62f32eedf80 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/tatracker/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/tatracker/ui/UiManager.java similarity index 91% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/tatracker/ui/UiManager.java index 876621d79b9..a1fb2dfdca2 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/tatracker/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import java.util.logging.Logger; @@ -7,10 +7,10 @@ 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; +import tatracker.MainApp; +import tatracker.commons.core.LogsCenter; +import tatracker.commons.util.StringUtil; +import tatracker.logic.Logic; /** * The manager of the UI component. @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/icon.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/tatracker/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/tatracker/ui/UiPart.java index fc820e01a9c..be489f6d9b7 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/tatracker/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package tatracker.ui; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import tatracker.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. diff --git a/src/main/resources/images/icon.png b/src/main/resources/images/icon.png new file mode 100644 index 00000000000..48f4cf80f0e Binary files /dev/null and b/src/main/resources/images/icon.png differ diff --git a/src/main/resources/images/tracker.png b/src/main/resources/images/tracker.png new file mode 100644 index 00000000000..f73b63dc943 Binary files /dev/null and b/src/main/resources/images/tracker.png differ diff --git a/src/main/resources/view/ClaimsListPanel.fxml b/src/main/resources/view/ClaimsListPanel.fxml new file mode 100644 index 00000000000..4d49860b128 --- /dev/null +++ b/src/main/resources/view/ClaimsListPanel.fxml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..3189f516bf7 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -30,15 +30,33 @@ } .tab-pane { + -fx-background-color: derive(#1d1d1d, 20%); -fx-padding: 0 0 0 1; } .tab-pane .tab-header-area { + -fx-background-color: derive(#1d1d1d, 20%); -fx-padding: 0 0 0 0; -fx-min-height: 0; -fx-max-height: 0; } +.tab-pane .tab-header-area .tab { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.tab-pane .tab-header-area .tab-header-background { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-opacity: 0.9; +} + +.tab-pane .tab-header-area .tab .tab-label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + .table-view { -fx-base: #1d1d1d; -fx-control-inner-background: #1d1d1d; @@ -328,7 +346,7 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #studentListPanel, #studentWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } diff --git a/src/main/resources/view/GroupListCard.fxml b/src/main/resources/view/GroupListCard.fxml new file mode 100644 index 00000000000..a7c0b2db423 --- /dev/null +++ b/src/main/resources/view/GroupListCard.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/GroupListPanel.fxml similarity index 77% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/GroupListPanel.fxml index 8836d323cc5..bb39c0736a6 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/GroupListPanel.fxml @@ -4,5 +4,5 @@ - + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..96005acf196 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,15 +6,17 @@ - + + + + - - + title="TA-Tracker App" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> + @@ -31,29 +33,58 @@ + + + + + - + - + - + - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ModuleListCard.fxml b/src/main/resources/view/ModuleListCard.fxml new file mode 100644 index 00000000000..3ee1c52df0f --- /dev/null +++ b/src/main/resources/view/ModuleListCard.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ModuleListPanel.fxml b/src/main/resources/view/ModuleListPanel.fxml new file mode 100644 index 00000000000..31cac3dd31b --- /dev/null +++ b/src/main/resources/view/ModuleListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/ModuleListPanelCopy.fxml b/src/main/resources/view/ModuleListPanelCopy.fxml new file mode 100644 index 00000000000..4de062b7f33 --- /dev/null +++ b/src/main/resources/view/ModuleListPanelCopy.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/SessionListCard.fxml b/src/main/resources/view/SessionListCard.fxml new file mode 100644 index 00000000000..8ce1ddfb246 --- /dev/null +++ b/src/main/resources/view/SessionListCard.fxml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/SessionListPanel.fxml b/src/main/resources/view/SessionListPanel.fxml new file mode 100644 index 00000000000..8939b94edfe --- /dev/null +++ b/src/main/resources/view/SessionListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/StudentListCard.fxml similarity index 94% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/StudentListCard.fxml index f08ea32ad55..e92d4c611a5 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/StudentListCard.fxml @@ -28,8 +28,8 @@