diff --git a/.gitignore b/.gitignore
index 5e59b862ba4..2d92e5e3006 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,11 +2,13 @@
/.gradle/
/build/
src/main/resources/docs/
+src/main/main.iml
# IDEA files
/.idea/
/out/
/*.iml
+*.swp
# Storage/log files
/data/
diff --git a/.travis.yml b/.travis.yml
index 924a42eb8da..01ada018b8b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,7 +15,7 @@ deploy:
branch: master
before_cache:
- - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
+ - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
diff --git a/README.adoc b/README.adoc
index e36efe534bb..aae81592200 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,11 +1,10 @@
-= Address Book (Level 3)
+= iGrad
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-F09-3/main[image:https://travis-ci.org/AY1920S2-CS2103T-F09-3/main.svg?branch=master[Build Status]]
+https://ci.appveyor.com/project/AY1920S2-CS2103T-F09-3/main[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]]
+https://coveralls.io/github/AY1920S2-CS2103T-F09-3/main?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]]
+https://www.codacy.com/app/AY1920S2-CS2103T-F09-3/main?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]]
ifdef::env-github[]
image::docs/images/Ui.png[width="600"]
@@ -15,9 +14,24 @@ 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.
+Sick of having tracking your university progress the manual way?
+Start getting rid of your Excel sheets and scribbled down notes and download _iGrad_ right now!
+
+What is _iGrad_?
+
+_iGrad_ is the app to track your university progress, for students who are frustrated with the
+limited features the university provides, by students who are frustrated by the limited
+features the university provides.
+
+_iGrad_ offers users the ability to create custom courses and graduation requirements,
+ensuring the *maximum* degree of flexibility when planning and keeping track of your progress
+
+_iGrad_ also retrieves data directly from https://nusmods.com[NUS Mods], ensuring that module
+information is always up to date.
+
+_iGrad_ calculates your CAP at every step, ensuring you never have to use a CAP calculator again
+
+Finally, the _iGrad_ team is always open to feedback and suggestions from the public will always be followed up on.
== Site Map
@@ -29,8 +43,4 @@ endif::[]
== Acknowledgements
-* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by
-_Marco Jakob_.
-* Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5]
-
-== Licence : link:LICENSE[MIT]
+This project was built on top of Address Book 4 by the https://se-education.org/[SE-Education team]
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..33e401de2ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,436 @@
+# iGrad
+> A handy guide to help you make the most of your application
+
+## Table of Contents
+
+1. [Introduction](#Introduction)
+2. [Features](#Features)
+
+ 2.1 [Course Builder](#courseInfo-builder)
+
+ 2.2 [Modular Credits Tracker](#modular-credits-mcs-tracker)
+
+ 2.3 [Cumulative Average Point Tracker](#cumulative-average-point-cap-tracker)
+
+3. [Components](#Components)
+
+ 3.1 [Course](#courses)
+
+ 3.2 [Requirements](#requirements)
+
+ 3.3 [Modules](#modules)
+
+4. [Walkthrough](#walkthrough)
+5. [Command List](#command-list)
+
+ 5.1.[`help`](#help)
+
+ 5.2 [`courseInfo`](#courseInfo)
+
+ 5.3 [`requirement`](#requirements)
+
+ 5.4 [`module`](#module)
+
+ 5.5 [`assign`](#assign)
+
+ 5.6 [`exam`](#exam)
+
+ 5.7 [`achieve`](#achieve)
+
+ 5.8 [`export`](#export)
+
+ 5.9 [`exit`](#exit)
+
+6. [Frequently Asked Questions (FAQ)](#faqs)
+7. [Cheat Sheet](#cheat-sheet)
+8. [Glossary](#glossary)
+
+---
+
+## Introduction
+
+Sick of having tracking your university progress the manual way?
+Start getting rid of your Excel sheets and scribbled down notes and download *iGrad* right now!
+
+What is *iGrad*?
+
+*iGrad* is the app to track your university progress, for students who are frustrated with the
+limited features the university provides, by students who are frustrated by the limited
+features the university provides.
+
+*iGrad* offers users the ability to create custom courses and graduation requirements,
+ensuring the **maximum** degree of flexibility when planning and keeping track of your progress
+
+*iGrad* also retrieves data directly from [NUS Mods](https://nusmods.com), ensuring that module
+information is always up to date.
+
+*iGrad* calculates your CAP at every step, ensuring you never have to use a CAP calculator again
+
+Finally, the *iGrad* team is always open to feedback and suggestions from the public will always be followed up on.
+
+## Features
+
+#### Course Builder
+iGrad was built with every NUS student in mind. Our custom courseInfo builder allows you to build
+the courseInfo of your dreams.
+
+#### Modular Credits (MCs) Tracker
+We are sick of counting our MCs at the beginning of every semester too. Easily see how many MCs you
+have left in order to apply for graduation.
+
+#### Cumulative Average Point (CAP) Tracker
+No more googling CAP calculators. iGrad's CAP tracker keeps track of your CAP at every step and
+even offers predictive services so you know how well you have to do
+in order to achieve your dream Cumulative Point Average (CAP).
+
+## Components
+
+**Figure 1**
+
+
+#### Courses
+A courseInfo is simply a group of requirements. It is also how we keep track of your overall CAP
+and MCs.
+
+#### Requirements
+A requirement consists of at least one module. Fulfill all modules within a requirement to
+complete it.
+
+#### Modules
+A module is the building block of all other components. Mark your modules as done and give it
+a grade. You can also add optional memos to help you remember why
+you took the module.
+
+## Walkthrough
+
+#### 1. Start up the application
+
+Double-click the .jar file to get started right away!
+
+#### 2. Enter your courseInfo details
+
+
+
+#### 3. Key in your graduation requirements
+
+
+
+#### 4. Assign your modules
+
+#### 5. Mark a module as done
+
+
+
+#### 6. Key in a memo
+
+#### 7. Track your MCs
+
+#### 8. View your CAP
+
+
+
+#### 9. Run batch commands
+
+#### 10. Export your data
+
+
+
+## Command List
+
+#### `help`
+
+Displays a help message to the user. Lists all possible commands
+and provides a link to the user guide online.
+
+Command Format
+
+ help
+
+Constraints
+
+:warning: NIL
+
+Expected Outcome
+
+:white_check_mark: A help message should be displayed
+
+---
+
+#### `courseInfo`
+
+Creates a courseInfo. (TODO: Add in modify and remove)
+
+Command Format
+
+
+Command Sample
+
+ /*
+ * Creating a courseInfo named "Computer Science"
+ */
+ courseInfo add n/Computer Science
+
+Constraints
+
+:warning: You can only have one courseInfo at a time
+
+Expected Outcome
+
+:white_check_mark: You should be able to see the courseInfo title in the
+top panel
+ courseInfo add n/COURSE_NAME
+
+---
+
+#### `requirement`
+
+Creates a graduation requirement. (TODO: Add in modify and remove)
+
+Command Format
+
+ requirement add n/REQUIREMENT_NAME u/NO_OF_MCS
+
+Command Sample
+
+ /**
+ * Creating a requirement named "Unrestricted Electives"
+ * which requires 32 MCs to fulfill
+ */
+ requirement add n/Unrestricted Electives u/32
+
+Constraints
+
+:warning: The number of MCs needed to fulfill the requirement is needed
+
+:warning: Requirement names have to be unique
+
+Expected Outcome
+
+:white_check_mark: You should be able to see the requirement title in the
+main panel
+
+---
+
+#### `module`
+
+Creates a module with semester, grade or memo notes information. (TODO: Add in modify and remove)
+
+Command Format
+
+ /**
+ * At least one option must be specified.
+ * SEMESTER is specified in format Y_S_ ( e.g. Y1S2 - Year 1 Semester 2 )
+ */
+ module add n/MODULE_CODE [n/MODULE_TITLE] [u/MCs] [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]
+
+Command Sample
+
+ /**
+ * Tags CS1101 with "Y1S2" and grade "A+"
+ */
+ module add n/CS1101 s/Y1S2 g/A+
+
+ /**
+ * Tags ST2234 with "Y2S1" and gives it a memo "pretty easy module"
+ */
+ module add n/ST2334 s/Y2S1 m/pretty easy module
+
+Constraints
+
+:warning: The module has to be assigned
+
+Expected Outcome
+
+:white_check_mark: The tags (if any) should appear under their respective column headers
+
+---
+
+#### `assign`
+
+Assigns a module to a graduation requirement. If there is good internet
+connectivity, the module will be validated with NUSMods and its description
+will be auto-filled.
+
+Command Format
+
+ /**
+ * DESCRIPTION is optional
+ */
+ assign REQUIREMENT_NAME n/MODULE_CODE
+
+Command Sample
+
+ /**
+ * Assigns module "LAJ1201" (Japanese 1)
+ * to requirement "Unrestricted Electives"
+ */
+ assign Unrestricted Electives n/LAJ1201
+
+Constraints
+
+:warning: A module cannot be assigned if there are not enough MCs left under
+a graduation requirement
+
+:warning: The module code and title have to be unique
+
+Expected Outcome
+
+:white_check_mark: The module will be displayed in the main panel
+
+
+---
+
+#### `exam`
+
+View your examination results.
+
+Command Format
+
+ /**
+ * SEMESTER is optional.
+ * If not specified, displays results for all tags.
+ */
+ exam s/SEMESTER
+
+Command Sample
+
+ /**
+ * Displays exam results for Year 3 Semester 2
+ */
+ exam s/Y3S2
+
+Constraints
+
+:warning: NIL
+
+Expected Outcome
+
+:white_check_mark: You should be able to view your exam results
+
+---
+
+#### `achieve`
+
+Calculates the average grade needed to achieve the CAP you desire/
+
+Command Format
+
+ achieve c/DESIRED_CAP
+
+Command Sample
+
+ /**
+ * Calculates the avergae grade needed
+ * to achieve a CAP of 4.50
+ */
+ achieve c/4.50
+
+Constraints
+
+:warning: NIL
+
+Expected Outcome
+
+:white_check_mark: You should be able to view the average grade needed to achieve
+the CAP you desire
+
+---
+#### `export`
+
+Exports all data in a text file. If information is sufficient,
+this file can be submitted to NUS as a study plan.
+
+Command Format
+
+ export
+
+Constraints
+
+:warning: NIL
+
+Expected Outcome
+
+:white_check_mark: A text file "study_plan.txt" should be generated in
+the same folder as the iGrad application.
+
+---
+
+#### `exit`
+
+Exits the program
+
+Command Format
+
+ exit
+
+Constraints
+
+:warning: NIL
+
+Expected Outcome
+
+:white_check_mark: The application should exit.
+
+ ## FAQs
+
+*I'm not an NUS student. Can I still use iGrad?*
+
+As long as your university follows a similar [hierachical structure](#fig-1)!
+However, we will be unable to provide features such as validation from NUS Mods.
+
+## Cheat Sheet
+
+> This segment contains all the commands detailed in this guide in a consolidated list
+
+`help`
+
+`courseInfo add n/COURSE_NAME`
+
+`courseInfo modify n/NEW_COURSE_NAME`
+
+`courseInfo remove`
+
+`requirement add n/REQUIREMENT_NAME u/NO_OF_MCS`
+
+`requirement modify REQUIREMENT_NAME [n/REQUIREMENT_NAME] [u/NO_OF_MCS]`
+
+`requirement remove REQUIREMENT_NAME`
+
+`module add n/MODULE_CODE [n/MODULE_TITLE] [u/MCs] [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]`
+
+`module modify MODULE_CODE [n/MODULE_CODE] [n/MODULE_TITLE] [u/MCs] [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]`
+
+`module remove MODULE_CODE`
+
+`assign REQUIREMENT_NAME n/MODULE_CODE`
+
+`exam s/SEMESTER`
+
+`achieve c/DESIRED_CAP`
+
+`export`
+
+`exit`
+
+ ## Glossary
+
+| | |
+| ------------- |------------- |
+|Course |A courseInfo is the entire programme of studies required to complete a university degree |
+|Graduation requirement |Requirements specified by the university in order for a student to graduate |
+|Module |Each module of study has a unique module code consisting of a two- or three-letter prefix that generally denotes the discipline, and four digits, the first of which indicates the level of the module |
+|Cumulative Average Point (CAP) |The Cumulative Average Point (CAP) is the weighted average grade point of the letter grades of all the modules taken by the students. |
+|Semester |A semester is a part of the academic year. Each semester typically lasts 13 weeks in NUS. |
+|Modular Credits (MCs) |A modular credit (MC) is a unit of the effort, stated in terms of time, expected of a typical student in managing his/her workload. |
+|NUS Mods |A timetabling application built for NUS students, by NUS students. Much like this iGrad! |
+| | |
+
+**Handy Links**
+
+[NUS - Modular System](http://www.nus.edu.sg/registrar/academic-information-policies/graduate/modular-system)
+
+[NUS - Degree Requirements](http://www.nus.edu.sg/registrar/academic-information-policies/undergraduate-students/degree-requirements)
+
+[NUS - Grading System and Regulations](http://www.nus.edu.sg/nusbulletin/yong-siew-toh-conservatory-of-music/undergraduate-education/degree-requirements/grading-system-and-regulations/)
+
+[NUS - Academic Calendar](http://www.nus.edu.sg/registrar/calendar)
diff --git a/build.gradle b/build.gradle
index 93029ef8262..b5e1bd1346d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@ plugins {
}
// Specifies the entry point of the application
-mainClassName = 'seedu.address.Main'
+mainClassName = 'igrad.Main'
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -44,7 +44,7 @@ test {
dependencies {
String jUnitVersion = '5.4.0'
String javaFxVersion = '11'
-
+ compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: '2.8.8'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
@@ -67,7 +67,7 @@ dependencies {
}
shadowJar {
- archiveName = 'addressbook.jar'
+ archiveName = 'igrad.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': 'iGrad',
+ 'site-githuburl': 'https://github.com/AY1920S2-CS2103T-F09-3/main',
'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project)
]
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index dabcd1ff4e3..f2f96550d7e 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -1,8 +1,8 @@
+ "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
+ "https://checkstyle.org/dtds/suppressions_1_2.dtd">
diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc
index 458e6134f45..94c6af57b5d 100644
--- a/docs/AboutUs.adoc
+++ b/docs/AboutUs.adoc
@@ -4,53 +4,48 @@
: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} +
+iGrad was developed by the https://github.com/AY1920S2-CS2103T-F09-3 team.
+
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]] [<>]
+=== Daryl Goh
+image::dargohzy.png[width="150", align="left"]
+{empty}[https://github.com/dargohzy[github]]
-Role: Project Advisor
+Role: Frontend Developer
'''
-=== John Roe
-image::lejolly.jpg[width="150", align="left"]
-{empty}[http://github.com/lejolly[github]] [<>]
+=== Nathanael Seen
+image::nathanaelseen.png[width="150", align="left"]
+{empty}[https://github.com/nathanaelseen[github]]
-Role: Team Lead +
-Responsibilities: UI
+Role: Chief Technology Officer
'''
-=== Johnny Doe
-image::yijinl.jpg[width="150", align="left"]
-{empty}[http://github.com/yijinl[github]] [<>]
+=== Teri
+image::teriaiw.png[width="150", align="left"]
+{empty}[http://github.com/teriaiw[github]]
-Role: Developer +
-Responsibilities: Data
+Role: UI/UX
'''
-=== Johnny Roe
-image::m133225.jpg[width="150", align="left"]
-{empty}[http://github.com/m133225[github]] [<>]
+=== Wayne Wee
+image::waynewee.png[width="150", align="left"]
+{empty}[http://github.com/waynewee[github]]
-Role: Developer +
-Responsibilities: Dev Ops + Threading
+Role: Marketing
'''
-=== Benson Meier
-image::yl_coder.jpg[width="150", align="left"]
-{empty}[http://github.com/yl-coder[github]] [<>]
+=== Yijie
+image::yjskrs.png[width="150", align="left"]
+{empty}[http://github.com/yjskrs[github]]
-Role: Developer +
-Responsibilities: UI
+Role: Project Manager
'''
diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc
index 81be279ef6d..d19a3a54365 100644
--- a/docs/ContactUs.adoc
+++ b/docs/ContactUs.adoc
@@ -2,6 +2,5 @@
: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-F09-3/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve.
+* *Email us* : You can also reach us at `e0202795 [at] comp.nus.edu.sg`
diff --git a/docs/DevOps.adoc b/docs/DevOps.adoc
index 2aa5a6bc0c1..3bc56fd313a 100644
--- a/docs/DevOps.adoc
+++ b/docs/DevOps.adoc
@@ -1,7 +1,7 @@
= AddressBook Level 3 - Dev Ops
:site-section: DeveloperGuide
:toc:
-:toc-title:
+:toc-name:
:toc-placement: preamble
:sectnums:
:imagesDir: images
@@ -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-F09-3/main/tree/master
== Build Automation
@@ -34,14 +34,14 @@ When a pull request has changes to asciidoc files, you can use https://www.netli
Here are the steps to create a new release.
-. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`].
+. Update the version number in link:{repoURL}/src/main/java/igrad/MainApp.java[`MainApp.java`].
. Generate a JAR file <>.
. Tag the repo with the version number. e.g. `v0.1`
. https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created.
== Managing Dependencies
-A project often depends on third-party libraries. For example, Address Book depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
+A project often depends on third-party libraries. For example, Course Book depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
[loweralpha]
. Include those libraries in the repo (this bloats the repo size)
diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc
index 3d65905a853..6e6160dc07b 100644
--- a/docs/DeveloperGuide.adoc
+++ b/docs/DeveloperGuide.adoc
@@ -1,7 +1,7 @@
-= AddressBook Level 3 - Developer Guide
+= iGrad - Developer Guide
:site-section: DeveloperGuide
:toc:
-:toc-title:
+:toc-name:
:toc-placement: preamble
:sectnums:
:imagesDir: images
@@ -14,7 +14,7 @@ ifdef::env-github[]
endif::[]
:repoURL: https://github.com/se-edu/addressbook-level3/tree/master
-By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT`
+By: `AY1920S2-CS2103T-F09-3` Since: `Feb 2020` Licence: `MIT`
== Setting up
@@ -28,13 +28,15 @@ Refer to the guide <>.
.Architecture Diagram
image::ArchitectureDiagram.png[]
-The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component.
+The *_Architecture Diagram_* given above explains the high-level design of the App.
+Given below is a quick overview of each component.
[TIP]
The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder.
Refer to the <> to learn how to create and edit diagrams.
-`Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for,
+`Main` has two classes called link:{repoURL}/src/main/java/igrad/Main.java[`Main`] and link:{repoURL}/src/main/java/igrad/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.
@@ -44,19 +46,20 @@ The following class plays an important role at the architecture level:
* `LogsCenter` : Used by many classes to write log messages to the App's log file.
-The rest of the App consists of four components.
+The rest of the App consists of five components.
* <>: The UI of the App.
* <>: The command executor.
* <>: Holds the data of the App in-memory.
* <>: Reads data from, and writes data to, the hard disk.
+* <>: Interacts with an external Application Programming Interface (API) to obtain data for the App.
-Each of the four components
+Each of the first 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[]
@@ -66,7 +69,7 @@ image::LogicClassDiagram.png[]
The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
-.Component interactions for `delete 1` command
+.Component interactions for `module delete CS1010` command
image::ArchitectureSequenceDiagram.png[]
The sections below give more details of each component.
@@ -77,11 +80,14 @@ 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/igrad/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`, `ModuleListPanel`, `StatusBar` etc.
+All these, including the `MainWindow` (excluding `AvatarImage`), 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/igrad/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`].
The `UI` component,
@@ -96,20 +102,20 @@ The `UI` component,
image::LogicClassDiagram.png[]
*API* :
-link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`]
+link:{repoURL}/src/main/java/igrad/logic/Logic.java[`Logic.java`]
-. `Logic` uses the `AddressBookParser` class to parse the user command.
-. This results in a `Command` object which is executed by the `LogicManager`.
-. The command execution can affect the `Model` (e.g. adding a person).
-. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`.
-. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user.
+. `Logic` uses the `CourseBookParser` class to parse the user command.
+. This results in a `Command` object which is executed by the `LogicManager`.
+. The command execution can affect the `Model` (e.g. adding a module).
+. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`.
+. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user.
Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call.
.Interactions Inside the Logic Component for the `delete 1` Command
image::DeleteSequenceDiagram.png[]
-NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+NOTE: The lifeline for `ModuleDeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
[[Design-Model]]
=== Model component
@@ -117,17 +123,20 @@ 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/igrad/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 Course 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.
+* exposes an unmodifiable `ObservableList` that can be 'observed'.
* 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 `Course Book`, which `Module` can reference.
+This would allow `Course Book` to only require one `Tag` object per unique `Tag`, instead of each `Module` needing their own `Tag` object.
+An example of how such a model may look like is given below. +
+
image:BetterModelClassDiagram.png[]
@@ -137,59 +146,179 @@ 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/igrad/memo/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 Course Book data in json format and read it back.
[[Design-Commons]]
=== Common classes
-Classes used by multiple components are in the `seedu.addressbook.commons` package.
+Classes used by multiple components are in the `iGrad.commons` package.
== Implementation
This section describes some noteworthy details on how certain features are implemented.
-// tag::undoredo[]
+=== Course Feature
+
+A `CourseBook` contains only one course information.
+
+==== Implementation
+
+.Structure of CourseInfo Class
+image::CourseInfoClassDiagram.png[]
+A course consists of three information: Name, CAP, and SemsLeft. One course should only have one name,
+and one CAP.
+
+The course related commands that can be called are:
+
+* `course edit` - edits name of existing course in the course book
+* `course delete` - deletes existing course and all information in the course book
+* `course achieve` - calculates CAP needed to maintain each semester to achieve overall CAP targer
+
+Here is how the courseInfo class updates when name of course is edited:
+
+.Sequence Diagram when editing course name.
+image::CourseEditSequenceDiagram.png[]
+When a user edits name of a course, the user has to specify the prefix `/n` for name.
+Then the application proceeds to do the following steps:
+
+Step 1: The CourseEditCommandParser is called to parse the CourseEditCommand with the n/ prefix.
+
+Step 2: The CourseEditCommand is executed to set the new course name to the model.
+
+Step 3: The new course name is set in the course book.
+
+=== Adding Modules from NUS Mods
+
+==== Implementation
+
+The automatic filling in of module details on addition of a new module is facilitated by `NusModsRequester`.
+It creates a new instance of `GetRequestManager` in order to make a request to link:https://nusmods.com/[NUSMods].
+Upon receiving a response, it creates an instance of `JsonParsedModule`.
+
+`JsonParsedModule` parses the JSON object given in the response of the initial request and stores the following values:
+
+* `title` e.g. Software Engineering
+* `moduleCode` e.g. CS2103T
+* `credits` e.g. 4
+* `description` e.g. This module introduces the necessary conceptual and analytical tools for systematic and rigorous development of software systems...
+
+The above process is initiated by `AddAutoCommandParser` which then converts a `JsonParsedModule` instance to a `Module`.
+
+The sequence diagram below provide further insight into its execution:
+
+image::AutoAddSequenceDiagram.png[]
+
+As NUSMods might not always have the latest data for the current academic year, a follow up request is made when an empty response is received.
+
+The following activity diagram illustrates this:
+
+image::AutoAddActivityDiagram.png[]
+
+==== Design Considerations
+
+As with all network requests, this feature might not work as intended in certain circumstances. Possible cases are:
+
+[start=1]
+1. High Network Congestion
+2. Poor Network Connection
+3. NUSMods Offline
+
+In such situations, the user would be prompted to manually fill in the necessary details for a module. The compulsory
+and optional fields are shown in the class diagram below:
+
+image::ModuleClassDiagram.png[]
+
+**Pros**
+
+1. Time taken to add a module with accurate `moduleCode`, `title` and `credits` is reduced by a factor of **0.85** (statistic derived from user trials).
+
+**Cons**
+
+1. Successful addition of module is not always guaranteed.
+
+=== Requirements Feature
+Within a course, there are multiple requirements to be tracked.
+
+==== Implementation
+.The Requirement class
+
+.Structure of the Requirement class.
+image::RequirementClassDiagram.png[]
+A requirement consists of three components: title, credits and unique module list.
+The unique module list implies that each requirement stores modules assigned to that requirement.
+Multiple requirements can exist in the course book at any one time.
+
+The requirement-related commands that can be called are:
+
+* `requirement add` - adds a new requirement to the course book
+* `requirement edit` - edits an existing requirement in the course book
+* `requirement delete` - deletes an existing requirement from the course book
+* `assign` - assigns a module to the requirement
+
+Here is how the requirement class updates when a requirement is added:
+
+.Sequence Diagram when adding a requirement.
+image::RequirementAddSequenceDiagram.png[]
+
+When the user adds a requirement, the user has to specify two prefixes: `n/` for title and `u/` for credits value (number of credits needed to fulfill for the requirement).
+Then, the application proceeds to do the following steps
+
+Step 1: The RequirementAddCommandParser is called to parse the RequirementAddCommand with the `n/` and the `u/` prefixes into a new requirement.
+
+Step 2: The RequirementAddCommand is executed to add the new requirement to the model. In this step, the following check is performed:
+
+* Check if a requirement with the same title already exists in the course book.
+
+Step 3: The new requirement is added to the course book.
+
=== [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 `VersionedCourseBook`.
+It extends `CourseBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`.
Additionally, it implements the following operations:
-* `VersionedAddressBook#commit()` -- Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history.
+* `VersionedCourseBook#commit()` -- Saves the current course book state in its history.
+* `VersionedCourseBook#undo()` -- Restores the previous course book state from its history.
+* `VersionedCourseBook#redo()` -- Restores a previously undone course book 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#commitCourseBook()`, `Model#undoCourseBook()` and `Model#redoCourseBook()` 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 `VersionedCourseBook` will be initialized with the initial course book state, and the `currentStatePointer` pointing to that single memo book 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.
+<<<<<<< HEAD Step 2. The user executes `delete 5` command to delete the 5th module in the course book.
+The `delete` command calls `Model#commitCourseBook()`, causing the modified state of the course book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted course book 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 module.
+The `add` command also calls `Model#commitCourseBook()`, causing another modified address course book state to be saved into the `addressBookStateList`.
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#commitCourseBook()`, so the course book state will not be saved into the `addressBookStateList`.
-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 module was a mistake, and decides to undo that action by executing the `undo` command.
+The `undo` command will call `Model#undoCourseBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous course book state, and restores the course book 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 course book state, then there are no previous course book states to restore.
+The `undo` command uses `Model#canUndoCourseBook()` 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 +326,23 @@ 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#redoCourseBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the course book 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 `addressBookStateList.size() - 1`, pointing to the latest course book state, then there are no undone course book states to restore.
+The `redo` command uses `Model#canRedoCourseBook()` 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 course book, such as `list`, will usually not call `Model#commitCourseBook()`, `Model#undoCourseBook()` or `Model#redoCourseBook()`.
+Thus, the `addressBookStateList` 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#commitCourseBook()`.
+Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all course 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.
image::UndoRedoState5.png[]
@@ -218,21 +354,23 @@ image::CommitActivityDiagram.png[]
===== Aspect: How undo & redo executes
-* **Alternative 1 (current choice):** Saves the entire address book.
+* **Alternative 1 (current choice):** Saves the entire course book.
** Pros: Easy to implement.
** Cons: May have performance issues in terms of memory usage.
* **Alternative 2:** Individual command knows how to undo/redo by itself.
-** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
+** Pros: Will use less memory (e.g. for `delete`, just save the module 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 course book states.
** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
-** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`.
+** Cons: Logic is duplicated twice.
+For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedCourseBook`.
* **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.
+** 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::dataencryption[]
@@ -244,7 +382,8 @@ _{Explain here how the data encryption feature will be implemented}_
=== Logging
-We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations.
+We are using `java.util.logging` package for logging.
+The `LogsCenter` class is used to manage the logging levels and logging destinations.
* The logging level can be controlled using the `logLevel` setting in the configuration file (See <>)
* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level
@@ -279,86 +418,190 @@ Refer to the guide <>.
*Target user profile*:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
+* is a NUS undergraduate
+* prefers desktop apps over other types
* can type fast
* prefers typing over mouse input
* is reasonably comfortable using CLI apps
-*Value proposition*: manage contacts faster than a typical mouse/GUI driven app
+*Value proposition*: convenient course requirements tracker for NUS undergraduates
[appendix]
== User Stories
-Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*`
+*Priorities*:
+
+* High (must have) - `* * *`
+* Medium (nice to have) - `* *`
+* Low (unlikely to have) - `*`
[width="59%",cols="22%,<23%,<25%,<30%",options="header",]
|=======================================================================
-|Priority |As a ... |I want to ... |So that I can...
-|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App
+|Priority |As a ... |I want to ... |So that I can ...
+|`* * *` |first-time user |create a course |
+
+|`* * *` |student |create a graduation requirement |
+
+|`* * *` |student |input modules under a graduation requirement |keep track of when a graduation requirement is fulfilled
+
+|`* * *` |careless user |change the graduation requirements which I assigned to a course |amend any mistakes made when entering data
-|`* * *` |user |add a new person |
+|`* * *` |fickle user |change the modules which I assigned to a graduation requirement |change my study plan
-|`* * *` |user |delete a person |remove entries that I no longer need
+|`* * *` |fickle user |have the option to defer adding modules to a graduation requirement |delay making up my mind on which modules I wish to take
-|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list
+|`* * *` |basic user |see information regarding the course I created, including graduation requirements, modules and gaps (e.g. modules that are unassigned) that need to be filled |
-|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident
+|`* * *` |user |see the latest updated information about any module |make informed decisions
-|`*` |user with many persons in the address book |sort persons by name |locate a person easily
+|`* * *` |basic user |mark when a module is completed |
+
+|`* * *` |basic user |input the grades of a module |
+
+|`* *` |basic user |retrieve my CAP of any semester at a command |stay updated about my results
+
+|`* *` |user |input my desired CAP and have the program calculate what grades I need to achieve |find out how well I need to do in following semesters
+
+|`* *` |user |group modules by graduation requirement |view by requirement
+
+|`* *` |user |group modules by semester |view by semester
+
+|`*` |user who wants to take notes |record notes for each module |record why I took it
+
+|`*` |picky user |customize display settings |customize to my needs
+
+|`*` |advanced command line user |use familiar linux commands |navigate more easily
+
+|`*` |advanced command line user |I want certain keys to do the same things as they would in the terminal (e.g. arrowUp cycles through command history)
|=======================================================================
-_{More to be added}_
+_{All user stories can be viewed from our wiki page and from our issues tracker.}_
[appendix]
== Use Cases
-(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise)
+(For all use cases below, the *System* is `iGrad` and the *Actor* is the `user`, unless specified otherwise)
[discrete]
-=== Use case: Delete person
+=== Use case: U01 - Create Course
-*MSS*
+*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. iGrad starts up.
+2. User requests to create a course.
+3. iGrad creates the course.
+
Use case ends.
-*Extensions*
+*Extensions*:
[none]
-* 2a. The list is empty.
+* 2a.
+The course name is not provided.
+** 2a1. iGrad prompts user for course name.
+** 2a2. User enters a course name.
++
+Steps 2a1-2a2 are repeated until the a non-empty course name is provided.
++
+Use case resumes at step 3.
+
+[discrete]
+=== Use case: U02 - Create Requirement
+
+*MSS*:
+
+1. User requests to create a course.
+2. iGrad creates course (UC01).
+3. User requests to create a requirement.
+4. iGrad creates the requirement.
+
Use case ends.
-* 3a. The given index is invalid.
+*Extensions*:
+
+[none]
+* 3a.
+The requirement title is not provided.
+** 3a1. iGrad prompts user for requirement title.
+** 3a2. User enters a requirement title.
++
+Steps 3a1-3a2 are repeated until the a non-empty requirement title is provided.
++
+Use case resumes at step 4.
+
+[discrete]
+=== Use case: U03 - Create Module
+
+*MSS*:
+
+1. User requests to create a module by providing a module code.
+2. iGrad creates the module with its data pulled from NUSMods.
+
+Use case ends.
+
+*Extensions*:
+
+[none]
+* 1a.
+Module data fails to get pulled due to network error.
+** 1a1. iGrad takes from its local module data copy.
++
+Use case ends.
+
+[none]
+* 1b.
+Module data does not exist on NUSMods.
+** 1b1. iGrad creates a empty module with only the module code.
++
+Use case ends.
+
+[discrete]
+=== Use case: U04 - Assign Module to Requirement
+
+*MSS*:
+
+1. User requests to assign a module to a requirement by specifying its module code.
+2. iGrad assigns module to requirement.
++
+Use case ends.
+
+*Extensions*:
+
[none]
-** 3a1. AddressBook shows an error message.
+* 1a.
+Module does not exist in system.
+** 1a1. iGrad creates the module (UC03).
+
Use case resumes at step 2.
+[none]
+* 1b.
+Module has already been assigned to the requirement.
+** 1b1. iGrad generates a warning and stops the assignment.
++
+Use case ends.
+
_{More to be added}_
[appendix]
== Non Functional Requirements
-. Should work on any <> as long as it has Java `11` or above installed.
-. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+. Should work on any <> as long as it has Java `11` or above installed.
+. Should be able to hold up to 100 modules without a noticeable sluggishness in performance (i.e. should take less than 1 second to load)
+. A user with above 70 wpm 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.
+. The interface should be intuitive enough such that a user who has never seen the user guide is able to use the basic features.
_{More to be added}_
[appendix]
== Glossary
-[[mainstream-os]] Mainstream OS::
+[[mainstream-os]]
+Mainstream OS::
Windows, Linux, Unix, OS-X
-[[private-contact-detail]] Private contact detail::
+[[private-contact-detail]]
+Private contact detail::
A contact detail that is not meant to be shared with others
[appendix]
@@ -393,24 +636,26 @@ These instructions only provide a starting point for testers to work on; testers
.. Download the jar file and copy into an empty folder
.. Double-click the jar file +
Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-
. Saving window preferences
.. Resize the window to an optimum size. Move the window to a different location. Close the window.
.. Re-launch the app by double-clicking the jar file. +
Expected: The most recent window size and location is retained.
-
_{ more test cases ... }_
-=== Deleting a person
+=== Deleting a module
-. Deleting a person while all persons are listed
+. Deleting a module while all modules are listed
-.. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+.. Prerequisites: List all modules using the `list` command. Multiple modules 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.
+ Expected: First module is deleted from the list.
+Details of the deleted module 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 module 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.
@@ -421,5 +666,4 @@ _{ more test cases ... }_
. Dealing with missing/corrupted data files
.. _{explain how to simulate a missing/corrupted file and the expected behavior}_
-
_{ more test cases ... }_
diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc
index ad90ac87bda..ac0e0e1ed93 100644
--- a/docs/Documentation.adoc
+++ b/docs/Documentation.adoc
@@ -1,7 +1,7 @@
= AddressBook Level 3 - Documentation
:site-section: DeveloperGuide
:toc:
-:toc-title:
+:toc-name:
:toc-placement: preamble
:sectnums:
:imagesDir: images
diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc
index 436c1777617..490921f741d 100644
--- a/docs/LearningOutcomes.adoc
+++ b/docs/LearningOutcomes.adoc
@@ -1,7 +1,7 @@
= Learning Outcomes
:site-section: LearningOutcomes
:toc: macro
-:toc-title:
+:toc-name:
:toclevels: 1
:imagesDir: images
:stylesDir: stylesheets
@@ -33,9 +33,9 @@ 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 modules 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.
+Assume that CourseBook confirms the change with the user before carrying out the operation.
== Use Non Functional Requirements `[LO-NFR]`
@@ -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 module 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..a46b52eeb64 100644
--- a/docs/SettingUp.adoc
+++ b/docs/SettingUp.adoc
@@ -1,7 +1,7 @@
= AddressBook Level 3 - Setting Up
:site-section: DeveloperGuide
:toc:
-:toc-title:
+:toc-name:
:toc-placement: preamble
:sectnums:
:imagesDir: images
@@ -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 `igrad.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..52b5aaeadca 100644
--- a/docs/Testing.adoc
+++ b/docs/Testing.adoc
@@ -1,7 +1,7 @@
= AddressBook Level 3 - Testing
:site-section: DeveloperGuide
:toc:
-:toc-title:
+:toc-name:
:toc-placement: preamble
:sectnums:
:imagesDir: images
@@ -35,12 +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. `igrad.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. `igrad.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. `igrad.logic.LogicManagerTest`
== Troubleshooting Testing
**Problem: Keyboard and mouse movements are not simulated on macOS Mojave, resulting in GUI Tests failure.**
diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc
index 4e5d297a19f..043f8255452 100644
--- a/docs/UserGuide.adoc
+++ b/docs/UserGuide.adoc
@@ -1,4 +1,4 @@
-= AddressBook Level 3 - User Guide
+= iGrad - User Guide
:site-section: UserGuide
:toc:
:toc-title:
@@ -11,167 +11,419 @@
ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
+:important-caption: :heavy_exclamation_mark:
+:caution-caption: :fire:
+:warning-caption: :warning:
+:icons: :font:
endif::[]
-:repoURL: https://github.com/se-edu/addressbook-level3
+:repoURL: https://github.com/AY1920S2-CS2103T-F09-3/main
-By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT`
+By: `Team-iGrad` Since: `Feb 2020`
== 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!
+Sick of having tracking your university progress the manual way?
+Start getting rid of your Excel sheets and scribbled down notes and download _iGrad_ right now!
-== Quick Start
+What is _iGrad_?
-. 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.
-. 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:
+_iGrad_ is the app to track your university progress, for students who are frustrated with the
+limited features the university provides, by students who are frustrated by the limited
+features the university provides.
-* *`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
+_iGrad_ offers users the ability to create custom courses and graduation requirements,
+ensuring the *maximum* degree of flexibility when planning and keeping track of your progress
-. Refer to <> for details of each command.
+_iGrad_ also retrieves data directly from https://nusmods.com[NUS Mods], ensuring that module
+information is always up to date.
+
+_iGrad_ calculates your CAP at every step, ensuring you never have to use a CAP calculator again
+
+Finally, the _iGrad_ team is always open to feedback and suggestions from the public which
+will always be followed up on.
+
+== How to use this guide
+
+You may proceed to <> if you would like to learn how to use each of the commands, and <>
+if you would just like a list of all the commands for easy reference.
+
+The figure below explains the symbols used throughout the user guide.
+
+WARNING: Specifies the constraints of the command or situation.
+
+NOTE: Specifies expected command outcomes.
+
+TIP: Specifies extra tips you can use when navigating through our app.
-[[Features]]
== Features
+____
+This segment highlights the key features of iGrad.
+____
+
+==== Course Builder
+
+iGrad was built with every NUS student in mind. Our custom course builder allows you to build
+the course of your dreams.
+
+==== Modular Credits (MCs) Tracker
+
+We are sick of counting our MCs at the beginning of every semester. Easily see how many MCs you
+have left in order to apply for graduation.
+
+==== Cumulative Average Point (CAP) Tracker
+
+No more googling CAP calculators. iGrad's CAP tracker keeps track of your CAP at every step and
+even offers predictive services so you know how well you have to do
+in order to achieve your dream CAP.
+
+== Components
+____
+This segment contains details on the structure of iGrad.
+____
+
+image:https://user-images.githubusercontent.com/34233605/75425925-9774ff80-597e-11ea-87f5-228f95b5c84f.png[]
+
+==== Course
+
+A course is simply a group of requirements. It is also how we keep track of your overall CAP
+and MCs.
+
+==== Requirements
+
+A requirement consists of at least one module. Fulfill all modules within a requirement to
+complete it.
+
+==== Modules
+
+A module is the building block of all other components. Mark your modules as done and give it
+a grade. You can also add optional memos to help you remember why
+you took the module.
+
+== Walkthrough
+
+1. Start up the application
+
+2. Enter your course details
+
+3. Key in your graduation requirements
+
+4. Add modules to the tracker
+
+5. Assign your modules
+
+6. Key in additional details for your modules
+
+7. Mark a module as done and assign a grade to it
+
+8. Track your MCs
+
+9. View your CAP
+
+10. Export your data
+
+== Command List
+____
+This segment contains a list of commands with examples that you can use to make full use of iGrad.
+____
+
+Take note of the following when using our commands:
+
+WARNING: Commands with fields wrapped within square brackets (i.e. []) require at least one of these fields to be specified
+when using the command.
+This means that you need just specify one of these fields while others may be optional based on your usage.
+
+
+==== help
+
+Displays a help message to the user. Lists all possible commands
+and provides a link to the user guide online.
+
+Command Format
+
+----
+help
+----
+
+Expected Outcome
+[NOTE]
+A popup for the list of all commands as well as the link to the user guide is shown.
+`INSERT POPUP PHOTO`
+
+'''
+
+==== course
+
+Add, edit or delete your course. Find out how much CAP you need to maintain
+each semester to achieve your desired CAP.
+
+Command Format
+
+----
+course add n/COURSE_NAME
+
+course edit [n/COURSE_NAME] [u/MCs]
+
+course delete n/COURSE_NAME
+
+course achieve c/DESIRED_CAP s/SEMESTERS_LEFT
+----
+
+Command Sample
+Creating a course named "Computer Science"
+----
+course add n/Computer Science
+----
+
+
+Renaming your current course to "Information Systems"
+----
+course edit n/Information Systems
+----
+
+Removing your current course
+----
+course delete
+----
+
+Calculating the average grade needed to achieve a CAP of 4.50 with
+2 semesters left
+----
+course achieve c/4.50 s/2
+----
+
+Constraints
+[WARNING]
+====
+1. `(all)`: You can only have at most one course at any one time
+2. `course delete`: Removing a course deletes all data from the system (including modules, requirements, etc)
+====
+
+Expected Outcome
+[NOTE]
+You should be able to see the added and/or modified course name in the
+top panel. For delete course, all data would be reset
+
+'''
+
+==== requirement
+
+Add, edit or delete a graduation requirement.
+
+Command Format
+
+----
+requirement add n/REQUIREMENT_TITLE u/MCS_REQUIRED
+
+requirement edit REQUIREMENT_CODE [n/NEW_REQUIREMENT_TITLE] [u/NEW_MCS_REQUIRED]
+
+requirement delete REQUIREMENT_CODE
+
+requirement assign REQUIREMENT_CODE [n/MODULE_CODE ...]
+----
+
+Command Sample
+
+Adding a requirement named "Unrestricted Electives" which requires 32 MCs.
+----
+requirement add n/Unrestricted Electives u/32
+----
+
+Renaming requirement "Unrestricted Electives" to "Maths and Sciences", and changing the number of MCs required to 20.
+
+----
+requirement edit Unrestricted Electives n/Maths and Sciences u/32
+----
+
+Renaming requirement "Unrestricted Electives" to "Maths and Sciences".
+----
+requirement edit Unrestricted Electives n/Maths and Sciences
+----
+
+Changing number of required MCs for requirement "Unrestricted Electives" to 20.
+----
+requirement edit Unrestricted Electives u/20
+----
+
+Removing requirement named "Unrestricted Electives".
+----
+requirement delete Unrestricted Electives
+----
+
+Assigning modules to requirement.
+----
+requirement assign UE0 n/CS1101S n/CS1231S n/CS2030S n/CS2040S
+----
+
+Constraints
+[WARNING]
====
-*Command Format*
+1. None.
+====
+
+Expected Outcome
+[NOTE]
+You should be able to see the requirement created and/or edited in the main panel.
+
+'''
+
+==== module
+
+Add, edit or delete a module. You would be able to tag (edit) a module with information such as; semester, grade or memo notes information.
+
+Command Format
+
+----
+module add n/MODULE_CODE t/MODULE_TITLE u/MCs [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]
+
+module edit MODULE_CODE [n/MODULE_CODE] [n/MODULE_TITLE] [u/MCs] [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]
+
+module delete MODULE_CODE
+
+module done MODULE_CODE g/GRADE
+----
+Note:
+[WARNING]
+SEMESTER is specified in format Y_S_ ( e.g. Y1S2 - Year 1 Semester 2 )
+
+
+Command Sample
+
+Adding the module, CS2103T, with title "Software Engineering" and MCs "4".
+----
+module add n/CS2103T t/Software Engineering u/4
+----
-* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
-* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
-* Items with `…` after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
-* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+Tagging the module, CS1101, with "Y1S2" and grade "A+", and also renaming it to CS1101S.
+----
+module edit n/CS1101 n/CS1101S s/Y1S2 g/A+
+----
+
+Tagging the module, ST2234, with "Y2S1" and giving it a memo "pretty easy module".
+----
+module edit n/ST2334 s/Y2S1 m/pretty easy module
+----
+
+Removing a module named "CS1101S".
+----
+module delete n/CS1101S
+----
+
+Assigning grade 'A' to the module 'CS2103T'.
+----
+module done CS2103T g/A
+----
+
+Constraints
+[WARNING]
+====
+1. `(module edit)` There must be enough MCs left under a graduation requirement (category) for all modules.
+2. `(all)` The module code (e.g, CS1101S, ST2334), have to be unique
====
-=== Viewing help : `help`
+Expected Outcome
+[NOTE]
+You should be able to see the modified module details reflected in the main panel
+
+'''
+
+==== export
+
+Exports all data in a (prettified) text file. If information is sufficient,
+this file can be submitted to NUS as a study plan.
+
+Command Format
+
+----
+export
+----
+
+Expected Outcome
+[NOTE]
+A text file "study_plan.txt" should be generated in the same folder as the iGrad application.
+
+'''
-Format: `help`
+==== exit
-=== Adding a person: `add`
+Exits the program
-Adds a person to the address book +
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...`
+Command Format
-[TIP]
-A person can have any number of tags (including 0)
+----
+exit
+----
-Examples:
+Expected Outcome
+[NOTE]
+The application should exit
-* `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`
+## FAQs
-=== Listing all persons : `list`
+_I'm not an NUS student. Can I still use iGrad?_
-Shows a list of all persons in the address book. +
-Format: `list`
+As long as your university follows a similar <>!
+However, we will be unable to provide features such as validation from NUSMods.
-=== Editing a person : `edit`
+== Cheat Sheet
-Edits an existing person in the address book. +
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [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, ...
-* 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.
-****
+This segment contains all the commands detailed in this guide in a consolidated list.
-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.
+`help`
-=== Locating persons by name: `find`
+`course add n/COURSE_NAME`
-Finds persons whose names contain any of the given keywords. +
-Format: `find KEYWORD [MORE_KEYWORDS]`
+`course edit [n/COURSE_NAME] [u/MCs]`
-****
-* 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`
-****
+`course delete COURSE_NAME`
-Examples:
+`course achieve c/DESIRED_CAP`
-* `find John` +
-Returns `john` and `John Doe`
-* `find Betsy Tim John` +
-Returns any person having names `Betsy`, `Tim`, or `John`
+`requirement add n/REQUIREMENT_TITLE u/MCS_REQUIRED`
-// tag::delete[]
-=== Deleting a person : `delete`
+`requirement edit REQUIREMENT_CODE [n/REQUIREMENT_TITLE] [u/MCS_REQUIRED]`
-Deletes the specified person from the address book. +
-Format: `delete INDEX`
+`requirement delete REQUIREMENT_CODE`
-****
-* 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, ...
-****
+`requirement assign REQUIREMENT_CODE [n/MODULE_CODE ...]`
-Examples:
+`module add n/MODULE_CODE n/MODULE_TITLE u/MCs [n/DESCRIPTION] [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]`
-* `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.
+`module edit MODULE_CODE [n/MODULE_CODE] [n/MODULE_TITLE] [n/DESCRIPTION] [u/MCs] [s/SEMESTER] [g/GRADE] [m/MEMO_NOTES]`
-// end::delete[]
-=== Clearing all entries : `clear`
+`module delete MODULE_CODE`
-Clears all entries from the address book. +
-Format: `clear`
+`module done MODULE_CODE g/GRADE`
-=== Exiting the program : `exit`
+`undo`
-Exits the program. +
-Format: `exit`
+`export`
-=== Saving the data
+`exit`
-Address book data are saved in the hard disk automatically after any command that changes the data. +
-There is no need to save manually.
+## Glossary
-// tag::dataencryption[]
-=== Encrypting data files `[coming in v2.0]`
+|===
+|Terms |Definition
-_{explain how the user can enable/disable data encryption}_
-// end::dataencryption[]
+|Course |A course is the entire programme of studies required to complete a university degree
+|Graduation requirement |Requirements specified by the university in order for a student to graduate
+|Module |Each module of study has a unique module code consisting of a two- or three-letter prefix that generally denotes the discipline, and four digits, the first of which indicates the level of the module
+|Cumulative Average Point (CAP) |The Cumulative Average Point (CAP) is the weighted average grade point of the letter grades of all the modules taken by the students.
+|Semester |A semester is a part of the academic year. Each semester typically lasts 13 weeks in NUS.
+|Modular Credits (MCs) |A modular credit (MC) is a unit of the effort, stated in terms of time, expected of a typical student in managing his/her workload.
+|NUSMods |A timetabling application built for NUS students, by NUS students. Much like this iGrad!
+| |
+|===
-== FAQ
+*Handy Links*
-*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.
+http://www.nus.edu.sg/registrar/academic-information-policies/undergraduate-students/degree-requirements[NUS - Degree Requirements]
-== Command Summary
+http://www.nus.edu.sg/registrar/academic-information-policies/undergraduate-students/modular-system[NUS - Modular System, Grading and Regulations]
-* *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`
+http://www.nus.edu.sg/registrar/calendar[NUS - Academic Calendar]
diff --git a/docs/UsingAppVeyor.adoc b/docs/UsingAppVeyor.adoc
index 12a7a89ac68..043200c3b90 100644
--- a/docs/UsingAppVeyor.adoc
+++ b/docs/UsingAppVeyor.adoc
@@ -91,4 +91,4 @@ The asciidoc code should look similar to:
https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]]
----
+
-Copy and paste the asciidoc code to your `README.adoc` file.
+Copy and paste the asciidoc code to your `README.md` file.
diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml
index d021b3992ed..c6c3e12be89 100644
--- a/docs/diagrams/ArchitectureDiagram.puml
+++ b/docs/diagrams/ArchitectureDiagram.puml
@@ -9,8 +9,10 @@ Package " "<>{
Class Logic LOGIC_COLOR
Class Storage STORAGE_COLOR
Class Model MODEL_COLOR
+ Class Services SERVICES_COLOR
Class Main MODEL_COLOR_T1
Class Commons LOGIC_COLOR_T2
+ Class Services SERVICES_COLOR_T1
Class "Log Center" as Logs UI_COLOR_T2
Class Hidden #FFFFFF
Class HiddenUI #FFFFFF
@@ -29,6 +31,7 @@ Main -left-> HiddenModel
UI -> Logic
UI -right-> Model
Logic -> Storage
+Logic -down-> Services
Logic -down-> Model
Logs -right- Commons
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index d1e2ae93675..34305e2c287 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -7,19 +7,19 @@ Participant ":Logic" as logic LOGIC_COLOR
Participant ":Model" as model MODEL_COLOR
Participant ":Storage" as storage STORAGE_COLOR
-user -[USER_COLOR]> ui : "delete 1"
+user -[USER_COLOR]> ui : "module delete CS1010"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("module delete CS1010")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteModule(p)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
deactivate model
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
+logic -[LOGIC_COLOR]> storage : saveCourseBook(courseBook)
activate storage STORAGE_COLOR
storage -[STORAGE_COLOR]> storage : Save to file
diff --git a/docs/diagrams/AutoAddActivityDiagram.puml b/docs/diagrams/AutoAddActivityDiagram.puml
new file mode 100644
index 00000000000..15eefac9a15
--- /dev/null
+++ b/docs/diagrams/AutoAddActivityDiagram.puml
@@ -0,0 +1,17 @@
+@startuml
+start
+:User executes command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+:NusModsRequester makes a get request\nto NUSMods for current academic year;
+
+if () then([receive valid response])
+else([else])
+:NusModsRequester makes a get request\nto NUSMods for previous academic year;
+
+endif
+:create Module with information in response;
+stop
+@enduml
diff --git a/docs/diagrams/AutoAddSequenceDiagram.puml b/docs/diagrams/AutoAddSequenceDiagram.puml
new file mode 100644
index 00000000000..8fa377fb7ef
--- /dev/null
+++ b/docs/diagrams/AutoAddSequenceDiagram.puml
@@ -0,0 +1,97 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":CourseBookParser" as CourseBookParser LOGIC_COLOR
+participant "a:ModuleAddAutoCommand" as ModuleAddAutoCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Coursebook" as Coursebook MODEL_COLOR
+participant ":AddAutoCommandParser" as AddAutoCommandParser MODEL_COLOR
+participant ":Module" as Module MODEL_COLOR
+end box
+
+box Services SERVICES_COLOR_T1
+participant ":NusModsRequester" as NusModsRequester SERVICES_COLOR
+participant ":GetRequestManager" as GetRequestManager SERVICES_COLOR
+participant ":JsonParsedModule" as JsonParsedModule SERVICES_COLOR
+end box
+
+[-> LogicManager : execute(auto add)
+activate LogicManager
+
+LogicManager -> CourseBookParser : parseCommand(auto add)
+activate CourseBookParser
+
+CourseBookParser -> AddAutoCommandParser:parse()
+
+activate AddAutoCommandParser
+
+AddAutoCommandParser -> NusModsRequester:getModule()
+
+activate NusModsRequester
+
+NusModsRequester -> GetRequestManager
+
+activate GetRequestManager
+
+GetRequestManager -> GetRequestManager: makeRequest()
+
+GetRequestManager --> NusModsRequester
+
+deactivate GetRequestManager
+
+NusModsRequester -> JsonParsedModule:initJsonParsedModule
+
+activate JsonParsedModule
+
+JsonParsedModule --> NusModsRequester:
+
+deactivate JsonParsedModule
+
+deactivate GetRequestManager
+NusModsRequester --> AddAutoCommandParser: jsonParsedModule
+
+deactivate NusModsRequester
+
+AddAutoCommandParser -> Module
+
+activate Module
+
+Module --> AddAutoCommandParser
+
+deactivate Module
+
+AddAutoCommandParser --> CourseBookParser: addAutoCommand(module)
+
+deactivate AddAutoCommandParser
+
+CourseBookParser --> LogicManager : commandResult
+deactivate CourseBookParser
+
+LogicManager -> ModuleAddAutoCommand : execute()
+activate ModuleAddAutoCommand
+
+ModuleAddAutoCommand -> Model : addModule()
+activate Model
+
+Model -> Coursebook : addModule()
+activate Coursebook
+
+Coursebook --> Model :
+deactivate Coursebook
+
+Model --> ModuleAddAutoCommand
+deactivate Model
+
+ModuleAddAutoCommand --> LogicManager : result
+deactivate ModuleAddAutoCommand
+ModuleAddAutoCommand -[hidden]-> LogicManager : result
+destroy ModuleAddAutoCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 7790472da52..539de4ccaef 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -4,8 +4,8 @@ skinparam arrowThickness 1.1
skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
-AddressBook *-right-> "1" UniquePersonList
-AddressBook *-right-> "1" UniqueTagList
+CourseBook *-right-> "1" UniquePersonList
+CourseBook *-right-> "1" UniqueTagList
UniqueTagList -[hidden]down- UniquePersonList
UniqueTagList -[hidden]down- UniquePersonList
diff --git a/docs/diagrams/CourseEditSequenceDiagram.puml b/docs/diagrams/CourseEditSequenceDiagram.puml
new file mode 100644
index 00000000000..7685a112f87
--- /dev/null
+++ b/docs/diagrams/CourseEditSequenceDiagram.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":CourseBookParser" as CourseBookParser LOGIC_COLOR
+participant ":CourseEditCommandParser" as CourseEditCommandParser LOGIC_COLOR
+participant "r:CourseEditCommand" as CourseEditCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":CourseBook" as CourseBook MODEL_COLOR
+end box
+
+[-> LogicManager : execute(edit course)
+activate LogicManager
+
+LogicManager -> CourseBookParser : parseCommand(edit course)
+activate CourseBookParser
+
+create CourseEditCommandParser
+CourseBookParser -> CourseEditCommandParser : new CourseEditCommandParser(\nname)
+activate CourseEditCommandParser
+
+create CourseEditCommand
+CourseEditCommandParser -> CourseEditCommand : new CourseEditCommand(\nname)
+activate CourseEditCommand
+
+CourseEditCommand --> CourseEditCommandParser
+CourseEditCommandParser --> CourseBookParser : r
+
+CourseBookParser --> LogicManager : r
+deactivate CourseBookParser
+
+LogicManager -> CourseEditCommand : execute()
+activate CourseEditCommand
+
+CourseEditCommand -> Model : setCourseInfo()
+activate Model
+
+Model -> CourseBook : setCourseInfo()
+activate CourseBook
+
+CourseBook --> Model
+deactivate CourseBook
+
+Model --> CourseEditCommand
+deactivate Model
+
+CourseEditCommand --> LogicManager : result
+deactivate CourseEditCommand
+CourseEditCommand -[hidden]-> LogicManager : result
+destroy CourseEditCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/CourseInfoClassDiagram.puml b/docs/diagrams/CourseInfoClassDiagram.puml
new file mode 100644
index 00000000000..580ff804287
--- /dev/null
+++ b/docs/diagrams/CourseInfoClassDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+class CourseInfo {
+ +getName()
+ +getCap()
+ +equals()
+ +hashCode()
+ +toString()
+}
+class Name {}
+
+CourseInfo *-- "1" Name
+CourseInfo *-- "1" CAP
+CourseInfo *-- "1" SemsLeft
+
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 1dc2311b245..56fb0e08cf1 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -3,7 +3,7 @@
box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":CourseBookParser" as CourseBookParser LOGIC_COLOR
participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
@@ -16,17 +16,17 @@ end box
[-> LogicManager : execute("delete 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
+LogicManager -> CourseBookParser : parseCommand("delete 1")
+activate CourseBookParser
create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
+CourseBookParser -> DeleteCommandParser
activate DeleteCommandParser
-DeleteCommandParser --> AddressBookParser
+DeleteCommandParser --> CourseBookParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+CourseBookParser -> DeleteCommandParser : parse("1")
activate DeleteCommandParser
create DeleteCommand
@@ -36,14 +36,14 @@ activate DeleteCommand
DeleteCommand --> DeleteCommandParser : d
deactivate DeleteCommand
-DeleteCommandParser --> AddressBookParser : d
+DeleteCommandParser --> CourseBookParser : d
deactivate DeleteCommandParser
'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
+DeleteCommandParser -[hidden]-> CourseBookParser
destroy DeleteCommandParser
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
+CourseBookParser --> LogicManager : d
+deactivate CourseBookParser
LogicManager -> DeleteCommand : execute()
activate DeleteCommand
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index 016ef33e2e2..5df23198c2d 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -8,7 +8,7 @@ package Logic {
package Parser {
Interface Parser <>
-Class AddressBookParser
+Class CourseBookParser
Class XYZCommandParser
Class CliSyntax
Class ParserUtil
@@ -35,8 +35,8 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Logic
LogicManager .up.|> Logic
-LogicManager -->"1" AddressBookParser
-AddressBookParser .left.> XYZCommandParser: creates >
+LogicManager -->"1" CourseBookParser
+CourseBookParser .left.> XYZCommandParser: creates >
XYZCommandParser ..> XYZCommand : creates >
XYZCommandParser ..|> Parser
@@ -53,7 +53,7 @@ LogicManager .left.> Command : executes >
LogicManager --> Model
Command .right.> Model
-note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
+note right of XYZCommand: XYZCommand = CourseAddCommand, \nExportCommand, etc
Logic ..> CommandResult
LogicManager .down.> CommandResult
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index e85a00d4107..50911e233ff 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,23 +5,33 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
-Interface ReadOnlyAddressBook <>
+Interface ReadOnlyCourseBook <>
Interface Model <>
Interface ObservableList <>
-Class AddressBook
-Class ReadOnlyAddressBook
+Class CourseBook
+Class ReadOnlyCourseBook
Class Model
Class ModelManager
Class UserPrefs
Class ReadOnlyUserPrefs
-Package Person {
-Class Person
-Class Address
-Class Email
+Package Requirement {
+Class Requirement
Class Name
-Class Phone
-Class UniquePersonList
+Class "Credits" as c1
+Class UniqueRequirementList
+}
+
+Package Module {
+Class Module
+Class "Credits" as c2
+Class Description
+Class Grade
+Class Memo
+Class ModuleCode
+Class Semester
+Class Title
+Class UniqueModuleList
}
Package Tag {
@@ -32,25 +42,41 @@ Class Tag
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+CourseBook .up.|> ReadOnlyCourseBook
ModelManager .up.|> Model
Model .right.> ObservableList
-ModelManager o--> "1" AddressBook
+ModelManager o-down-> "1" CourseBook
ModelManager o-left-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
-UniquePersonList o--> "*" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
+CourseBook ---> "1" UniqueRequirementList
+CourseBook *--> "1" UniqueModuleList
+
+UniqueRequirementList o--> "*" Requirement
+Requirement *--> "1" Name
+Requirement *--> "1" c1
+Requirement *--> "1" UniqueModuleList
+
+UniqueModuleList o--> "*" Module
+Module *--> "1" Title
+Module *--> "1" ModuleCode
+Module *--> "1" c2
+Module *--> "1" Memo
+Module *--> "1" Description
+Module *--> "1" Semester
+Module *--> "1" Grade
+Module *--> "*" Tag
+
+
+Title -[hidden]right-> ModuleCode
+ModuleCode -[hidden]right-> c2
+c2 -[hidden]right-> Memo
+Memo -[hidden]right-> Description
+Description -[hidden]right-> Semester
+Semester -[hidden]right-> Grade
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
-ModelManager -->"1" Person : filtered list
+ModelManager -->"1" Module : filtered list
+ModelManager -->"1" Requirement : filtered list
@enduml
diff --git a/docs/diagrams/ModuleClassDiagram.puml b/docs/diagrams/ModuleClassDiagram.puml
new file mode 100644
index 00000000000..f2570ba0efa
--- /dev/null
+++ b/docs/diagrams/ModuleClassDiagram.puml
@@ -0,0 +1,26 @@
+@startuml
+class Module {
+ +isDone()
+ +isSameModule()
+ +equals()
+ +hashCode()
+ +toString()
+}
+class Title {}
+class ModuleCode {}
+class Credits {}
+
+class Memo {}
+class Description {}
+class Semester {}
+class Grade {}
+
+Module *-- "1" Title
+Module *-- "1" ModuleCode
+Module *-- "1" Credits
+
+Module o-- "1" Memo
+Module o-- "1" Description
+Module o-- "1" Semester
+Module o-- "1" Grade
+@enduml
diff --git a/docs/diagrams/RequirementAddSequenceDiagram.puml b/docs/diagrams/RequirementAddSequenceDiagram.puml
new file mode 100644
index 00000000000..dd234d270f8
--- /dev/null
+++ b/docs/diagrams/RequirementAddSequenceDiagram.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":CourseBookParser" as CourseBookParser LOGIC_COLOR
+participant ":RequirementAddCommandParser" as RequirementAddCommandParser LOGIC_COLOR
+participant "r:RequirementAddCommand" as RequirementAddCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":CourseBook" as CourseBook MODEL_COLOR
+end box
+
+[-> LogicManager : execute(add requirement)
+activate LogicManager
+
+LogicManager -> CourseBookParser : parseCommand(add requirement)
+activate CourseBookParser
+
+create RequirementAddCommandParser
+CourseBookParser -> RequirementAddCommandParser : new RequirementAddCommandParser(\nname, credits)
+activate RequirementAddCommandParser
+
+create RequirementAddCommand
+RequirementAddCommandParser -> RequirementAddCommand : new RequirementAddCommand(\nrequirement)
+activate RequirementAddCommand
+
+RequirementAddCommand --> RequirementAddCommandParser
+RequirementAddCommandParser --> CourseBookParser : r
+
+CourseBookParser --> LogicManager : r
+deactivate CourseBookParser
+
+LogicManager -> RequirementAddCommand : execute()
+activate RequirementAddCommand
+
+RequirementAddCommand -> Model : addRequirement()
+activate Model
+
+Model -> CourseBook : addRequirement()
+activate CourseBook
+
+CourseBook --> Model
+deactivate CourseBook
+
+Model --> RequirementAddCommand
+deactivate Model
+
+RequirementAddCommand --> LogicManager : result
+deactivate RequirementAddCommand
+RequirementAddCommand -[hidden]-> LogicManager : result
+destroy RequirementAddCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/RequirementClassDiagram.puml b/docs/diagrams/RequirementClassDiagram.puml
new file mode 100644
index 00000000000..d645fbd5f48
--- /dev/null
+++ b/docs/diagrams/RequirementClassDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+class Requirement {
+ +isFulfilled()
+ +equals()
+ +hashCode()
+ +toString()
+}
+class Name {}
+class Credits {}
+class UniqueModuleList {}
+
+Requirement *-- "1" Name
+Requirement *-- "1" Credits
+Requirement *-- "1" UniqueModuleList
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 6adb2e156bf..b8e1527658b 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -6,19 +6,22 @@ skinparam classBackgroundColor STORAGE_COLOR
Interface Storage <>
Interface UserPrefsStorage <>
-Interface AddressBookStorage <>
+Interface CourseBookStorage <>
Class StorageManager
Class JsonUserPrefsStorage
-Class JsonAddressBookStorage
+Class JsonCourseBookStorage
StorageManager .left.|> Storage
StorageManager o-right-> UserPrefsStorage
-StorageManager o--> AddressBookStorage
+StorageManager o--> CourseBookStorage
JsonUserPrefsStorage .left.|> UserPrefsStorage
-JsonAddressBookStorage .left.|> AddressBookStorage
-JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage
-JsonSerializableAddressBookStorage .right.> JsonSerializablePerson
-JsonSerializablePerson .right.> JsonAdaptedTag
+JsonCourseBookStorage .left.|> CourseBookStorage
+JsonCourseBookStorage .down.> JsonSerializableCourseBookStorage
+
+JsonSerializableCourseBookStorage .down.> JsonAdaptedCourseInfo
+JsonSerializableCourseBookStorage .down.> JsonAdaptedRequirement
+JsonSerializableCourseBookStorage .down.> JsonAdaptedModule
+JsonAdaptedModule .right.> JsonAdaptedTag
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 92746f9fcf7..8fff92e0a00 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -11,10 +11,16 @@ Class UiManager
Class MainWindow
Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
-Class StatusBarFooter
+Class CommandReceivedPanel
+Class ProgressSidePanel
+Class RequirementListPanel
+Class StatusBar
Class CommandBox
+Class ModuleCard
+Class AvatarImage
+Class ModuleListPanel
+Class RequirementCard
+Class AvatarSelectionPanel
}
package Model <> {
@@ -33,28 +39,39 @@ UiManager -down-> MainWindow
MainWindow --> HelpWindow
MainWindow *-down-> CommandBox
MainWindow *-down-> ResultDisplay
-MainWindow *-down-> PersonListPanel
-MainWindow *-down-> StatusBarFooter
+MainWindow *-down-> StatusBar
+MainWindow *-down-> ProgressSidePanel
+MainWindow *-down-> CommandReceivedPanel
+MainWindow *-down-> RequirementListPanel
+MainWindow *-down-> ModuleListPanel
-PersonListPanel -down-> PersonCard
+ModuleListPanel -down-> ModuleCard
+RequirementListPanel -down-> RequirementCard
+AvatarSelectionPanel ..> AvatarImage
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
-StatusBarFooter --|> UiPart
-HelpWindow -down-|> UiPart
+ModuleListPanel --|> UiPart
+ModuleCard --|> UiPart
+StatusBar --|> UiPart
+CommandReceivedPanel --|> UiPart
+ProgressSidePanel --|> UiPart
+RequirementListPanel --|> UiPart
+RequirementCard --|> UiPart
-PersonCard ..> Model
UiManager -right-> Logic
+UiManager -left-> Model
+MainWindow ..> Model
+MainWindow ..> AvatarSelectionPanel
MainWindow -left-> Logic
-PersonListPanel -[hidden]left- HelpWindow
+ProgressSidePanel -[hidden]left- CommandReceivedPanel
+CommandReceivedPanel -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
-ResultDisplay -[hidden]left- StatusBarFooter
+ResultDisplay -[hidden]left- StatusBar
MainWindow -[hidden]-|> UiPart
@enduml
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
index 96e30744d24..1aef778bd35 100644
--- a/docs/diagrams/UndoRedoState0.puml
+++ b/docs/diagrams/UndoRedoState0.puml
@@ -17,4 +17,4 @@ hide State3
class Pointer as "Current State" #FFFFF
Pointer -up-> State1
-@end
+@enduml
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
index 01fcb9b2b96..994bd794c83 100644
--- a/docs/diagrams/UndoRedoState1.puml
+++ b/docs/diagrams/UndoRedoState1.puml
@@ -1,5 +1,6 @@
@startuml
!include style.puml
+
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
@@ -19,4 +20,4 @@ hide State3
class Pointer as "Current State" #FFFFF
Pointer -up-> State2
-@end
+@enduml
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
index 410aab4e412..054794925cc 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 ":CourseBookParser" as CourseBookParser 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 ":VersionedCourseBook" as VersionedCourseBook MODEL_COLOR
end box
[-> LogicManager : execute(undo)
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand(undo)
-activate AddressBookParser
+LogicManager -> CourseBookParser : parseCommand(undo)
+activate CourseBookParser
create UndoCommand
-AddressBookParser -> UndoCommand
+CourseBookParser -> UndoCommand
activate UndoCommand
-UndoCommand --> AddressBookParser
-deactivate UndoCommand
+UndoCommand --> CourseBookParser
+deactivate UndoCommandCourse
-AddressBookParser --> LogicManager : u
-deactivate AddressBookParser
+CourseBookParser --> LogicManager : u
+deactivate CourseBookParser
LogicManager -> UndoCommand : execute()
activate UndoCommand
-UndoCommand -> Model : undoAddressBook()
+UndoCommand -> Model : undoCourseBook()
activate Model
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
+Model -> VersionedCourseBook : undo()
+activate VersionedCourseBook
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
+VersionedCourseBook -> VersionedCourseBook :resetData(ReadOnlyCourseBook)
+VersionedCourseBook --> Model :
+deactivate VersionedCourseBook
Model --> UndoCommand
deactivate Model
diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml
index fad8b0adeaa..0c1a726d10a 100644
--- a/docs/diagrams/style.puml
+++ b/docs/diagrams/style.puml
@@ -31,6 +31,9 @@
!define STORAGE_COLOR_T3 #806600
!define STORAGE_COLOR_T2 #544400
+!define SERVICES_COLOR #f995c6
+!define SERVICES_COLOR_T1 #ff34b3
+
!define USER_COLOR #000000
skinparam BackgroundColor #FFFFFFF
diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml
index fdcbe1c0ccc..1bdf8bedd1e 100644
--- a/docs/diagrams/tracing/LogicSequenceDiagram.puml
+++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml
@@ -13,7 +13,7 @@ create ecp
abp -> ecp
abp -> ecp ++: parse(arguments)
create ec
-ecp -> ec ++: index, editPersonDescriptor
+ecp -> ec ++: index, editModuleDescriptor
ec --> ecp --
ecp --> abp --: command
abp --> logic --: command
diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png
index aa2d337d932..400f9166fee 100644
Binary files a/docs/images/ArchitectureDiagram.png and b/docs/images/ArchitectureDiagram.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index aa198138f8f..27803bca047 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/AutoAddActivityDiagram.png b/docs/images/AutoAddActivityDiagram.png
new file mode 100644
index 00000000000..002183f84d6
Binary files /dev/null and b/docs/images/AutoAddActivityDiagram.png differ
diff --git a/docs/images/AutoAddSequenceDiagram.png b/docs/images/AutoAddSequenceDiagram.png
new file mode 100644
index 00000000000..03c21d16982
Binary files /dev/null and b/docs/images/AutoAddSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index bc7ed18ae29..890a778ae59 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/CourseEditSequenceDiagram.png b/docs/images/CourseEditSequenceDiagram.png
new file mode 100644
index 00000000000..069def10bb4
Binary files /dev/null and b/docs/images/CourseEditSequenceDiagram.png differ
diff --git a/docs/images/CourseInfoClassDiagram.png b/docs/images/CourseInfoClassDiagram.png
new file mode 100644
index 00000000000..e19f72a7a38
Binary files /dev/null and b/docs/images/CourseInfoClassDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index fa327b39618..0f616e5425e 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index b9e853cef12..2d31433b380 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..b6961cafaf3 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ModuleClassDiagram.png b/docs/images/ModuleClassDiagram.png
new file mode 100644
index 00000000000..6d950956e25
Binary files /dev/null and b/docs/images/ModuleClassDiagram.png differ
diff --git a/docs/images/RequirementAddSequenceDiagram.png b/docs/images/RequirementAddSequenceDiagram.png
new file mode 100644
index 00000000000..16467942ed5
Binary files /dev/null and b/docs/images/RequirementAddSequenceDiagram.png differ
diff --git a/docs/images/RequirementClassDiagram.png b/docs/images/RequirementClassDiagram.png
new file mode 100644
index 00000000000..0e29ed8d9f6
Binary files /dev/null and b/docs/images/RequirementClassDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index d87c1216820..0ccbee6b51c 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..75773f9f369 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 7b4b3dbea45..f937f98f4fb 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/dargohzy.png b/docs/images/dargohzy.png
new file mode 100644
index 00000000000..0a434e40d4c
Binary files /dev/null and b/docs/images/dargohzy.png differ
diff --git a/docs/images/nathanaelseen.png b/docs/images/nathanaelseen.png
new file mode 100644
index 00000000000..5d19d8ed2ed
Binary files /dev/null and b/docs/images/nathanaelseen.png differ
diff --git a/docs/images/teriaiw.png b/docs/images/teriaiw.png
new file mode 100644
index 00000000000..8ebc367c254
Binary files /dev/null and b/docs/images/teriaiw.png differ
diff --git a/docs/images/waynewee.png b/docs/images/waynewee.png
new file mode 100644
index 00000000000..99c5f697a06
Binary files /dev/null and b/docs/images/waynewee.png differ
diff --git a/docs/images/yjskrs.png b/docs/images/yjskrs.png
new file mode 100644
index 00000000000..1c24f7f71f1
Binary files /dev/null and b/docs/images/yjskrs.png differ
diff --git a/docs/index.adoc b/docs/index.adoc
index a65ae663288..a16ebd90b4f 100644
--- a/docs/index.adoc
+++ b/docs/index.adoc
@@ -1,2 +1,2 @@
:stylesDir: stylesheets
-include::../README.adoc[]
+include::../README.md[]
diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc
index f39e76e49b2..cefd021b1fe 100644
--- a/docs/team/johndoe.adoc
+++ b/docs/team/johndoe.adoc
@@ -9,7 +9,7 @@
== 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.
+AddressBook - Level 3 is a desktop courseInfo 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.
== Summary of contributions
diff --git a/docs/templates/_header.html.slim b/docs/templates/_header.html.slim
index 3c2d5aed43c..c8268f89010 100644
--- a/docs/templates/_header.html.slim
+++ b/docs/templates/_header.html.slim
@@ -49,7 +49,7 @@
#header
- if has_header?
- unless notitle
- h1 =header.title
+ h1 =header.name
- if [:author, :revnumber, :revdate, :revremark].any? {|a| attr? a }
.details
- if attr? :author
diff --git a/docs/templates/_toc.html.slim b/docs/templates/_toc.html.slim
index f0ad719fe0f..c6be07abd65 100644
--- a/docs/templates/_toc.html.slim
+++ b/docs/templates/_toc.html.slim
@@ -1,5 +1,5 @@
/ NOTE: You must restart the gradle daemon after modifying any template file for the changes to take effect.
#toc class=(attr 'toc-class', 'toc')
- #toctitle =(attr 'toc-title')
+ #toctitle =(attr 'toc-name')
/ Renders block_outline.html.
= converter.convert document, 'outline'
diff --git a/docs/templates/document.html.slim b/docs/templates/document.html.slim
index 3e1961d4afa..7fcc439eb3c 100644
--- a/docs/templates/document.html.slim
+++ b/docs/templates/document.html.slim
@@ -12,7 +12,7 @@ html lang=(attr :lang, 'en' unless attr? :nolang)
= html_meta_if 'copyright', (attr :copyright)
= html_meta_if 'description', (attr :description)
= html_meta_if 'keywords', (attr :keywords)
- title=((doctitle sanitize: true) || (attr 'untitled-label'))
+ name=((doctitle sanitize: true) || (attr 'untitled-label'))
= styles_and_scripts
- unless (docinfo_content = docinfo).empty?
=docinfo_content
diff --git a/docs/tutorials/AddRemark.adoc b/docs/tutorials/AddRemark.adoc
index ea388068303..4d8511c699d 100644
--- a/docs/tutorials/AddRemark.adoc
+++ b/docs/tutorials/AddRemark.adoc
@@ -16,7 +16,7 @@ toc::[]
In this tutorial, we'll walk you through the implementation of a new command -- `remark`.
-This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required.
+This command allows users of the CourseBook application to add optional remarks to people in their courseInfo book and edit it if required.
The command should have the format of `remark INDEX r/REMARK`.
An example of the command is `remark 2 r/Likes baseball`.
@@ -28,7 +28,7 @@ Looking in the `logic.command` package, you will notice that each existing comma
All the commands inherit from the abstract class `Command` which means that they must override `execute()`.
Each `Command` returns an instance of `CommandResult` upon success and `CommandResult#feedbackToUser` is printed to the `ResultDisplay`.
-Let's start by creating a new `RemarkCommand` class in the `src/main/java/seedu/address/logic/command` directory.
+Let's start by creating a new `RemarkCommand` class in the `src/main/java/igrad/logic/command` directory.
For now, let's keep `RemarkCommand` as simple as possible and print some output.
We accomplish that by returning a `CommandResult` with an accompanying message.
@@ -36,12 +36,12 @@ We accomplish that by returning a `CommandResult` with an accompanying message.
.RemarkCommand.java
[source, java]
----
-package seedu.address.logic.commands;
+package igrad.logic.commands;
-import seedu.address.model.Model;
+import igrad.model.Model;
/**
- * Changes the remark of an existing person in the address book.
+ * Changes the remark of an existing module in the courseInfo 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 module identified "
+ + "by the index number used in the last module 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 igrad.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 module in the filtered module list to edit the remark
+ * @param remark of the module to be updated to
*/
public RemarkCommand(Index index, String remark) {
requireAllNonNull(index, remark);
@@ -160,8 +160,8 @@ Your code should look something like link:https://github.com/nus-cs2103-AY1920S1
Now let's move on to writing a parser that will extract the index and remark from the input provided by the user.
-Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package.
-The class must implement the `Parser` interface.
+Create a `RemarkCommandParser` class in the `igrad.logic.parser` package.
+The class must extend the `Parser` interface.
.The relationship between Parser and RemarkCommandParser
image::ParserInterface.png[]
@@ -245,13 +245,13 @@ If you are stuck, check out the sample link:https://github.com/nus-cs2103-AY1920
== Add `Remark` to the model
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.
+We achieve that by working with the `Module` model.
+Each field in a Module is implemented as a separate class (e.g. a `Name` object represents the module'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 module.
=== 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 `igrad.model.module`. Since a `Remark` is a field that is similar to `Module`, 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,17 +263,17 @@ 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 module.
Simply add
[source, java]
-.PersonCard.java
+.ModuleCard.java
```
@FXML
private Label remark;
```
-to link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/2758455583f0101ed918a318fc75679270843a0d#diff-0c6b6abcfac8c205e075294f25e851fe[`seedu.address.ui.PersonCard`].
+to link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/2758455583f0101ed918a318fc75679270843a0d#diff-0c6b6abcfac8c205e075294f25e851fe[`igrad.ui.ModuleCard`].
`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML.
It might sound like Greek to you right now, don't worry -- we will get back to it later.
@@ -313,22 +313,22 @@ Let's update `JsonAdaptedPerson` to work with our new `Person`!
While the changes to code may be minimal, the test data will have to be updated as well.
-WARNING: You must delete AddressBook's storage file located at `/data/addressbook.json` before running it!
-Not doing so will cause AddressBook to default to an empty address book!
+WARNING: You must delete CourseBook's storage file located at `/data/coursebook.json` before running it!
+Not doing so will cause CourseBook to default to an empty courseInfo book!
Check out link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/ce4f9b70f524d2395948861d80d57fda9ae6e82e#diff-07708562699e2436c717f3330bafae1e[this commit] to see what the changes entail.
== Finalizing the UI
-Now that we have finalized the `Person` class and its dependencies, we can now bind the `Remark` field to the UI.
+Now that we have finalized the `Module` class and its dependencies, we can now bind the `Remark` field to the UI.
Just add link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/56d5cb662c31dd38b02f6a5301ba6ab3c667d6a3#diff-0c6b6abcfac8c205e075294f25e851fe[this one line of code!]
[source, java]
-.PersonCard.java
+.ModuleCard.java
----
-public PersonCard(Person person, int displayedIndex) {
+public ModuleCard(Module module, int displayedIndex) {
//...
- remark.setText(person.getRemark().value);
+ remark.setText(module.getRemark().value);
}
----
diff --git a/docs/tutorials/RemovingFields.adoc b/docs/tutorials/RemovingFields.adoc
index 5a50b6965a6..234383bdb60 100644
--- a/docs/tutorials/RemovingFields.adoc
+++ b/docs/tutorials/RemovingFields.adoc
@@ -13,10 +13,10 @@ endif::[]
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
== Introduction
-When working on Address Book, you will most likely find that some features and fields that are no longer necessary.
-In scenarios like this, you can consider refactoring the existing `Person` model to suit your use case.
+When working on Course Book, you will most likely find that some features and fields that are no longer necessary.
+In scenarios like this, you can consider refactoring the existing `Module` model to suit your use case.
-In this tutorial, we'll do exactly just that and remove the `address` field from `Person`.
+In this tutorial, we'll do exactly just that and remove the `memo` field from `Module`.
== Safely deleting `Address`
@@ -24,68 +24,68 @@ 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.
-Since removing the `Address` class will break the application, we start by identifying ``Address``'s usages.
-This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis.
-Right-click the `Address` class and select `Refactor` > `Safe Delete` through the menu.
+The `memo` field in `Module` is actually an instance of the `igrad.model.module.Memo` class.
+Since removing the `Memo` class will break the application, we start by identifying ``Memo``'s usages.
+This allows us to see code that depends on `Memo` to function properly and edit them on a case-by-case basis.
+Right-click the `Memo` class and select `Refactor` > `Safe Delete` through the menu.
.Usages detected
image::UnsafeDelete.png[width=787px. height=238px]
Choose to `View Usages` and you should be presented with a list of `Safe Delete Conflicts`.
-These conflicts describe locations in which the `Address` class is used.
+These conflicts describe locations in which the `Memo` class is used.
.List of conflicts
image::SafeDeleteConflicts.png[width=955, height=640px]
-Remove usages of `Address` by performing ``Safe Delete``s on each entry.
-You will need to exercise discretion when removing usages of `Address`.
+Remove usages of `Memo` by performing ``Safe Delete``s on each entry.
+You will need to exercise discretion when removing usages of `Memo`.
Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well.
-Other usages like in `EditPersonDescriptor` may require more careful inspection.
+Other usages like in `EditModuleDescriptor` may require more careful inspection.
-Let's try removing references to `Address` in `EditPersonDescriptor`.
+Let's try removing references to `memo` in `EditModuleDescriptor`.
-. Safe delete the field `address` in `EditPersonDescriptor`
+. Safe delete the field `memo` in `EditModuleDescriptor`
. Select `Yes` when prompted to remove getters and setters
. Select `View Usages` again image:UnsafeDeleteOnField.png[width=1145px, height=583px]
-. Remove the usages of `address` and select `Do refactor` when you are done.
+. Remove the usages of `memo` and select `Do refactor` when you are done.
+
TIP: Removing usages may result in errors.
Exercise discretion and fix them.
-For example, removing the `address` field from the `Person` class will require you to modify its constructor.
+For example, removing the `memo` field from the `Module` class will require you to modify its constructor.
-. Repeat the steps for the remaining usages of `Address`
+. Repeat the steps for the remaining usages of `Memo`
After you are done, verify that the application still works by compiling and running it again.
=== Manual refactoring
-Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify.
-You can find them by searching for instances of the word `address` in your code (`Edit` > `Find` > `Find in path`).
+Unfortunately, there are usages of `Memo` that IntelliJ IDEA cannot identify.
+You can find them by searching for instances of the word `memo` in your code (`Edit` > `Find` > `Find in path`).
Places of interest to look out for would be resources used by the application.
`main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data.
-For example, there is a `$address` in each `PersonCard` that has not been removed nor identified.
+For example, there is a `$memo` in each `ModuleCard` that has not been removed nor identified.
-image::$address.png[width=1090px, height=890px]
+image::$memo.png[width=1090px, height=890px]
-A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
+A quick look at the `ModuleCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
-.PersonCard.java
+.ModuleCard.java
[source, java]
----
...
@FXML
-private Label address;
+private Label memo;
...
----
-.PersonCard.fxml
+.ModuleCard.fxml
[source, xml]
----
...
-
+
...
----
@@ -97,21 +97,21 @@ Fix any remaining errors until the tests all pass.
== Tidying up
At this point, your application is working as intended and all your tests are passing.
-What's left to do is to clean up references to `Address` in test data and documentation.
+What's left to do is to clean up references to `Memo` in test data and documentation.
In `src/test/data/`, data meant for testing purposes are stored.
-While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
+While keeping the `memo` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
-.invalidPersonAddressBook.json
+.invalidModuleCourseBook.json
[source, json]
```
{
- "persons": [ {
+ "modules": [ {
"name": "Person with invalid name field: Ha!ns Mu@ster",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "memo": "4th street"
} ]
}
```
-You can go through each individual `json` file and manually remove the `address` field.
+You can go through each individual `json` file and manually remove the `memo` field.
diff --git a/docs/tutorials/TracingCode.adoc b/docs/tutorials/TracingCode.adoc
index 5f0aaba1741..758f0a091d4 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`.
+`igrad.ui.CommandBox.CommandExecutor`.
.Using the `Search for target by name` feature. `Navigate` > `Symbol`.
image::Execute.png[]
@@ -75,7 +75,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
[TIP]
.Taking notes
====
-Over the course of the debugging session, you will encounter every major component in the application. Try to jot down
+Over the courseInfo of the debugging session, you will encounter every major component in the application. Try to jot down
what happens inside the component and where the execution transfers to another component.
====
@@ -251,8 +251,8 @@ handled?
. What components will you have to modify to perform the following enhancements to the application?
.. Make command words case-insensitive
.. Allow `delete` to remove more than one index at a time
-.. Save the address book in the CSV format instead
+.. Save the courseInfo book in the CSV format instead
.. Add a new command
-.. Add a new field to `Person`
-.. Add a new entity to the address book
+.. Add a new field to `Module`
+.. Add a new entity to the courseInfo book
****
diff --git a/quotes.json b/quotes.json
new file mode 100644
index 00000000000..892873d1b28
--- /dev/null
+++ b/quotes.json
@@ -0,0 +1,82 @@
+{
+ "quotes": [
+ {
+ "value":"Believe you can and you’re halfway there."
+ },
+ {
+ "value":"You have to expect things of yourself before you can do them."
+ },
+ {
+ "value":"It always seems impossible until it’s done."
+ },
+ {
+ "value":"Don’t let what you cannot do interfere with what you can do."
+ },
+ {
+ "value":"Start where you are. Use what you have. Do what you can."
+ },
+ {
+ "value":"The secret of success is to do the common things uncommonly well."
+ },
+ {
+ "value":"Strive for progress, not perfection."
+ },
+ {
+ "value":"I find that the harder I work, the more luck I seem to have."
+ },
+ {
+ "value":"Success is the sum of small efforts, repeated day in and day out."
+ },
+ {
+ "value":"Don’t wish it were easier; wish you were better."
+ },
+ {
+ "value":"The secret to getting ahead is getting started."
+ },
+ {
+ "value":"You don’t have to be great to start, but you have to start to be great."
+ },
+ {
+ "value":"The expert in everything was once a beginner."
+ },
+ {
+ "value":"There are no shortcuts to any place worth going."
+ },
+ {
+ "value":"Push yourself, because no one else is going to do it for you."
+ },
+ {
+ "value":"There is no substitute for hard work."
+ },
+ {
+ "value":"The difference between ordinary and extraordinary is that little “extra.”"
+ },
+ {
+ "value":"You don’t always get what you wish for; you get what you work for."
+ },
+ {
+ "value":"The only place where success comes before work is in the dictionary."
+ },
+ {
+ "value":"There are no traffic jams on the extra mile."
+ },
+ {
+ "value":"If you’re going through hell, keep going."
+ },
+ {
+ "value":"Don’t let your victories go to your head, or your failures go to your heart."
+ },
+ {
+ "value":"Failure is the opportunity to begin again more intelligently."
+ },
+ {
+ "value":"You don’t drown by falling in the water; you drown by staying there."
+ },
+ {
+ "value":"It’s not going to be easy, but it’s going to be worth it."
+ },
+ {
+ "value":"I’ve failed over and over and over again in my life. And that is why I succeed."
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/igrad/AppParameters.java
similarity index 93%
rename from src/main/java/seedu/address/AppParameters.java
rename to src/main/java/igrad/AppParameters.java
index ab552c398f3..fb846eded57 100644
--- a/src/main/java/seedu/address/AppParameters.java
+++ b/src/main/java/igrad/AppParameters.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package igrad;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -6,9 +6,9 @@
import java.util.Objects;
import java.util.logging.Logger;
+import igrad.commons.core.LogsCenter;
+import igrad.commons.util.FileUtil;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.FileUtil;
/**
* Represents the parsed command-line parameters given to the application.
@@ -18,14 +18,6 @@ public class AppParameters {
private Path configPath;
- public Path getConfigPath() {
- return configPath;
- }
-
- public void setConfigPath(Path configPath) {
- this.configPath = configPath;
- }
-
/**
* Parses the application command-line parameters.
*/
@@ -43,6 +35,14 @@ public static AppParameters parse(Application.Parameters parameters) {
return appParameters;
}
+ public Path getConfigPath() {
+ return configPath;
+ }
+
+ public void setConfigPath(Path configPath) {
+ this.configPath = configPath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/igrad/Main.java
similarity index 84%
rename from src/main/java/seedu/address/Main.java
rename to src/main/java/igrad/Main.java
index 052a5068631..b2d3f74e8b4 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/igrad/Main.java
@@ -1,20 +1,20 @@
-package seedu.address;
+package igrad;
import javafx.application.Application;
/**
* The main entry point to the application.
- *
+ *
* This is a workaround for the following error when MainApp is made the
* entry point of the application:
- *
- * Error: JavaFX runtime components are missing, and are required to run this application
- *
+ *
+ * Error: JavaFX runtime components are missing, and are required to run this application
+ *
* The reason is that MainApp extends Application. In that case, the
* LauncherHelper will check for the javafx.graphics module to be present
* as a named module. We don't use JavaFX via the module system so it can't
* find the javafx.graphics module, and so the launch is aborted.
- *
+ *
* By having a separate main class (Main) that doesn't extend Application
* to be the entry point of the application, we avoid this issue.
*/
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/igrad/MainApp.java
similarity index 60%
rename from src/main/java/seedu/address/MainApp.java
rename to src/main/java/igrad/MainApp.java
index e5cfb161b73..3c645a4f050 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/igrad/MainApp.java
@@ -1,35 +1,35 @@
-package seedu.address;
+package igrad;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.logging.Logger;
+import igrad.commons.core.Config;
+import igrad.commons.core.LogsCenter;
+import igrad.commons.core.Version;
+import igrad.commons.exceptions.DataConversionException;
+import igrad.commons.util.ConfigUtil;
+import igrad.commons.util.StringUtil;
+import igrad.logic.Logic;
+import igrad.logic.LogicManager;
+import igrad.model.CourseBook;
+import igrad.model.Model;
+import igrad.model.ModelManager;
+import igrad.model.ReadOnlyCourseBook;
+import igrad.model.ReadOnlyUserPrefs;
+import igrad.model.UserPrefs;
+import igrad.model.util.SampleDataUtil;
+import igrad.storage.CourseBookStorage;
+import igrad.storage.JsonCourseBookStorage;
+import igrad.storage.JsonUserPrefsStorage;
+import igrad.storage.Storage;
+import igrad.storage.StorageManager;
+import igrad.storage.UserPrefsStorage;
+import igrad.ui.Ui;
+import igrad.ui.UiManager;
import javafx.application.Application;
import javafx.stage.Stage;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.core.Version;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.util.ConfigUtil;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
-import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
-import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
-import seedu.address.storage.UserPrefsStorage;
-import seedu.address.ui.Ui;
-import seedu.address.ui.UiManager;
/**
* Runs the application.
@@ -48,16 +48,19 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing iGrad ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
config = initConfig(appParameters.getConfigPath());
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
- UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ ReadOnlyUserPrefs userPrefs = initPrefs(userPrefsStorage);
+ CourseBookStorage courseBookStorage = new JsonCourseBookStorage(
+ userPrefs.getCourseBookFilePath(),
+ userPrefs.getBackupCourseBookFilePath()
+ );
+ storage = new StorageManager(courseBookStorage, userPrefsStorage);
initLogging(config);
@@ -65,34 +68,74 @@ public void init() throws Exception {
logic = new LogicManager(model, storage);
- ui = new UiManager(logic);
+ ui = new UiManager(logic, model);
}
/**
- * 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 course book and {@code userPrefs}.
+ * The data from the sample course book will be used instead if {@code storage}'s course book is not found,
+ * or an empty course book will be used instead if errors occur when reading {@code storage}'s course book.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional courseBookOptional;
+ Optional userPrefsOptional;
+ ReadOnlyCourseBook initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ courseBookOptional = storage.readCourseBook();
+
+ if (courseBookOptional.isEmpty()) {
+ logger.info("CourseBook Data file not found. Will be starting with an empty CourseBook");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+
+ initialData = courseBookOptional.orElseGet(SampleDataUtil::getEmptyCourseBook);
} 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 CourseBook");
+ initialData = new CourseBook();
} 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 CourseBook");
+ initialData = new CourseBook();
}
return new ModelManager(initialData, userPrefs);
}
+ /**
+ * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path,
+ * or a new {@code UserPrefs} with default configuration if errors occur when
+ * reading from the file.
+ */
+ protected ReadOnlyUserPrefs initPrefs(UserPrefsStorage storage) {
+ Path prefsFilePath = storage.getUserPrefsFilePath();
+ logger.info("Using prefs file : " + prefsFilePath);
+
+ UserPrefs initializedPrefs;
+ try {
+ Optional userPrefsOptional = storage.readUserPrefs();
+
+ if (!userPrefsOptional.isPresent()) {
+ logger.info("UserPrefs Data file not found. Will be starting with a sample UserPrefs");
+ }
+
+ initializedPrefs = userPrefsOptional.orElseGet(SampleDataUtil::getSampleUserPrefs);
+ } catch (DataConversionException e) {
+ logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. "
+ + "Using default user prefs");
+ initializedPrefs = new UserPrefs();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the file. Will be starting with an empty UserPrefs");
+ initializedPrefs = new UserPrefs();
+ }
+
+ //Update prefs file in case it was missing to begin with or there are new/unused fields
+ try {
+ storage.saveUserPrefs(initializedPrefs);
+ } catch (IOException e) {
+ logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
+ }
+
+ return initializedPrefs;
+ }
+
private void initLogging(Config config) {
LogsCenter.init(config);
}
@@ -120,7 +163,7 @@ protected Config initConfig(Path configFilePath) {
initializedConfig = configOptional.orElse(new Config());
} catch (DataConversionException e) {
logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. "
- + "Using default config properties");
+ + "Using default config properties");
initializedConfig = new Config();
}
@@ -133,47 +176,16 @@ protected Config initConfig(Path configFilePath) {
return initializedConfig;
}
- /**
- * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path,
- * or a new {@code UserPrefs} with default configuration if errors occur when
- * reading from the file.
- */
- protected UserPrefs initPrefs(UserPrefsStorage storage) {
- Path prefsFilePath = storage.getUserPrefsFilePath();
- logger.info("Using prefs file : " + prefsFilePath);
-
- UserPrefs initializedPrefs;
- try {
- Optional prefsOptional = storage.readUserPrefs();
- initializedPrefs = prefsOptional.orElse(new UserPrefs());
- } catch (DataConversionException e) {
- logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. "
- + "Using default user prefs");
- initializedPrefs = new UserPrefs();
- } catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initializedPrefs = new UserPrefs();
- }
-
- //Update prefs file in case it was missing to begin with or there are new/unused fields
- try {
- storage.saveUserPrefs(initializedPrefs);
- } catch (IOException e) {
- logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
- }
-
- return initializedPrefs;
- }
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting iGrad " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping iGrad ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/igrad/commons/core/Config.java
similarity index 90%
rename from src/main/java/seedu/address/commons/core/Config.java
rename to src/main/java/igrad/commons/core/Config.java
index 91145745521..4694557849e 100644
--- a/src/main/java/seedu/address/commons/core/Config.java
+++ b/src/main/java/igrad/commons/core/Config.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package igrad.commons.core;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -6,7 +6,7 @@
import java.util.logging.Level;
/**
- * Config values used by the app
+ * Config values used by the app.
*/
public class Config {
@@ -44,7 +44,7 @@ public boolean equals(Object other) {
Config o = (Config) other;
return Objects.equals(logLevel, o.logLevel)
- && Objects.equals(userPrefsFilePath, o.userPrefsFilePath);
+ && Objects.equals(userPrefsFilePath, o.userPrefsFilePath);
}
@Override
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/igrad/commons/core/GuiSettings.java
similarity index 86%
rename from src/main/java/seedu/address/commons/core/GuiSettings.java
rename to src/main/java/igrad/commons/core/GuiSettings.java
index 5ace559ad15..bd4ff3ec4d7 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/igrad/commons/core/GuiSettings.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package igrad.commons.core;
import java.awt.Point;
import java.io.Serializable;
@@ -10,8 +10,8 @@
*/
public class GuiSettings implements Serializable {
- private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+ private static final double DEFAULT_HEIGHT = 633;
+ private static final double DEFAULT_WIDTH = 1100;
private final double windowWidth;
private final double windowHeight;
@@ -53,8 +53,8 @@ public boolean equals(Object other) {
GuiSettings o = (GuiSettings) other;
return windowWidth == o.windowWidth
- && windowHeight == o.windowHeight
- && Objects.equals(windowCoordinates, o.windowCoordinates);
+ && windowHeight == o.windowHeight
+ && Objects.equals(windowCoordinates, o.windowCoordinates);
}
@Override
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/igrad/commons/core/LogsCenter.java
similarity index 92%
rename from src/main/java/seedu/address/commons/core/LogsCenter.java
rename to src/main/java/igrad/commons/core/LogsCenter.java
index 431e7185e76..ec6b31ec5c4 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/igrad/commons/core/LogsCenter.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package igrad.commons.core;
import java.io.IOException;
import java.util.Arrays;
@@ -12,17 +12,17 @@
* Configures and manages loggers and handlers, including their logging level
* Named {@link Logger}s can be obtained from this class
* These loggers have been configured to output messages to the console and a {@code .log} file by default,
- * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log
- * file reaches 5MB big, up to a maximum of 5 files.
+ * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log
+ * file reaches 5MB big, up to a maximum of 5 files.
*/
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 = "coursebook.log";
private static Level currentLogLevel = Level.INFO;
- private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
private static FileHandler fileHandler;
private static ConsoleHandler consoleHandler;
+ private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
/**
* Initializes with a custom log level (specified in the {@code config} object)
@@ -75,7 +75,7 @@ private static void addConsoleHandler(Logger logger) {
*/
private static void removeHandlers(Logger logger) {
Arrays.stream(logger.getHandlers())
- .forEach(logger::removeHandler);
+ .forEach(logger::removeHandler);
}
/**
@@ -95,6 +95,7 @@ private static void addFileHandler(Logger logger) {
/**
* Creates a {@code FileHandler} for the log file.
+ *
* @throws IOException if there are problems opening the file.
*/
private static FileHandler createFileHandler() throws IOException {
diff --git a/src/main/java/igrad/commons/core/Messages.java b/src/main/java/igrad/commons/core/Messages.java
new file mode 100644
index 00000000000..82223bdb2b8
--- /dev/null
+++ b/src/main/java/igrad/commons/core/Messages.java
@@ -0,0 +1,39 @@
+package igrad.commons.core;
+
+/**
+ * Container for generic and global user visible messages.
+ */
+public class Messages {
+
+ public static final String MESSAGE_UNKNOWN_COMMAND = "I don't know this command, "
+ + "you may key in `help` to get a list of commands!";
+ public static final String MESSAGE_UNKNOWN_COURSE_COMMAND = "I don't know this course command, "
+ + "you might want to try:\n"
+ + "course add ...\n"
+ + "course edit ...\n"
+ + "course achieve ...\n"
+ + "course delete\n";
+ public static final String MESSAGE_UNKNOWN_REQUIREMENT_COMMAND = "I don't know this requirement command, "
+ + "you might want to try:\n"
+ + "requirement add ...\n"
+ + "requirement edit ...\n"
+ + "requirement delete ...\n"
+ + "requirement assign ...\n";
+ public static final String MESSAGE_UNKNOWN_MODULE_COMMAND = "I don't know this module command, "
+ + "you might want to try:\n"
+ + "module add ...\n"
+ + "module edit ...\n"
+ + "module done ...\n"
+ + "module delete\n";
+ public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format!\n%1$s";
+ public static final String MESSAGE_COURSE_NOT_SET = "You need to set a course first! Use this command:\n"
+ + "course add n/COURSE_NAME";
+ public static final String MESSAGE_COURSE_ALREADY_SET = "Course has been set! Only one course can be added.";
+ public static final String MESSAGE_SPECIFIER_NOT_SPECIFIED = "Please provide a non-empty specifier.\n%1$s";
+ public static final String MESSAGE_SPECIFIER_INVALID = "Please enter a valid specifier.\n%1$s";
+
+ public static final String MESSAGE_REQUEST_FAILED = "ERROR: Request failed for %s\n";
+
+ public static final String MESSAGE_ADD_COURSE = "You don't have a course! Enter your course in the format: 'course add n/'";
+ public static final String MESSAGE_WELCOME_BACK = "Welcome back! Hope your studies are going well!";
+}
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/igrad/commons/core/Version.java
similarity index 91%
rename from src/main/java/seedu/address/commons/core/Version.java
rename to src/main/java/igrad/commons/core/Version.java
index e117f91b3b2..d37dcd2b2b4 100644
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ b/src/main/java/igrad/commons/core/Version.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package igrad.commons.core;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -7,7 +7,7 @@
import com.fasterxml.jackson.annotation.JsonValue;
/**
- * Represents a version with major, minor and patch number
+ * Represents a version with major, minor and patch number.
*/
public class Version implements Comparable {
@@ -29,24 +29,9 @@ public Version(int major, int minor, int patch, boolean isEarlyAccess) {
this.isEarlyAccess = isEarlyAccess;
}
- public int getMajor() {
- return major;
- }
-
- public int getMinor() {
- return minor;
- }
-
- public int getPatch() {
- return patch;
- }
-
- public boolean isEarlyAccess() {
- return isEarlyAccess;
- }
-
/**
* Parses a version number string in the format V1.2.3.
+ *
* @param versionString version number string
* @return a Version object
*/
@@ -59,9 +44,25 @@ public static Version fromString(String versionString) throws IllegalArgumentExc
}
return new Version(Integer.parseInt(versionMatcher.group(1)),
- Integer.parseInt(versionMatcher.group(2)),
- Integer.parseInt(versionMatcher.group(3)),
- versionMatcher.group(4) == null ? false : true);
+ Integer.parseInt(versionMatcher.group(2)),
+ Integer.parseInt(versionMatcher.group(3)),
+ versionMatcher.group(4) != null);
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
+ public int getPatch() {
+ return patch;
+ }
+
+ public boolean isEarlyAccess() {
+ return isEarlyAccess;
}
@JsonValue
diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/igrad/commons/exceptions/DataConversionException.java
similarity index 79%
rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java
rename to src/main/java/igrad/commons/exceptions/DataConversionException.java
index 1f689bd8e3f..d45d49d21a2 100644
--- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java
+++ b/src/main/java/igrad/commons/exceptions/DataConversionException.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.exceptions;
+package igrad.commons.exceptions;
/**
- * Represents an error during conversion of data from one format to another
+ * Represents an error during conversion of data from one format to another.
*/
public class DataConversionException extends Exception {
public DataConversionException(Exception cause) {
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/igrad/commons/exceptions/IllegalValueException.java
similarity index 86%
rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
rename to src/main/java/igrad/commons/exceptions/IllegalValueException.java
index 19124db485c..1247a68b2e3 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/igrad/commons/exceptions/IllegalValueException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package igrad.commons.exceptions;
/**
* Signals that some given data does not fulfill some constraints.
@@ -13,7 +13,7 @@ public IllegalValueException(String message) {
/**
* @param message should contain relevant information on the failed constraint(s)
- * @param cause of the main exception
+ * @param cause of the main exception
*/
public IllegalValueException(String message, Throwable cause) {
super(message, cause);
diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/igrad/commons/util/AppUtil.java
similarity index 89%
rename from src/main/java/seedu/address/commons/util/AppUtil.java
rename to src/main/java/igrad/commons/util/AppUtil.java
index da90201dfd6..0f6c6e8b8b8 100644
--- a/src/main/java/seedu/address/commons/util/AppUtil.java
+++ b/src/main/java/igrad/commons/util/AppUtil.java
@@ -1,12 +1,12 @@
-package seedu.address.commons.util;
+package igrad.commons.util;
import static java.util.Objects.requireNonNull;
+import igrad.MainApp;
import javafx.scene.image.Image;
-import seedu.address.MainApp;
/**
- * A container for App specific utility functions
+ * A container for App specific utility functions.
*/
public class AppUtil {
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/igrad/commons/util/CollectionUtil.java
similarity index 91%
rename from src/main/java/seedu/address/commons/util/CollectionUtil.java
rename to src/main/java/igrad/commons/util/CollectionUtil.java
index eafe4dfd681..d3d2e6b893b 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/igrad/commons/util/CollectionUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package igrad.commons.util;
import static java.util.Objects.requireNonNull;
@@ -12,7 +12,9 @@
*/
public class CollectionUtil {
- /** @see #requireAllNonNull(Collection) */
+ /**
+ * @see #requireAllNonNull(Collection)
+ */
public static void requireAllNonNull(Object... items) {
requireNonNull(items);
Stream.of(items).forEach(Objects::requireNonNull);
diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/igrad/commons/util/ConfigUtil.java
similarity index 77%
rename from src/main/java/seedu/address/commons/util/ConfigUtil.java
rename to src/main/java/igrad/commons/util/ConfigUtil.java
index f7f8a2bd44c..bb93e391740 100644
--- a/src/main/java/seedu/address/commons/util/ConfigUtil.java
+++ b/src/main/java/igrad/commons/util/ConfigUtil.java
@@ -1,11 +1,11 @@
-package seedu.address.commons.util;
+package igrad.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 igrad.commons.core.Config;
+import igrad.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/igrad/commons/util/FileUtil.java
similarity index 98%
rename from src/main/java/seedu/address/commons/util/FileUtil.java
rename to src/main/java/igrad/commons/util/FileUtil.java
index b1e2767cdd9..9bdfb258027 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/igrad/commons/util/FileUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package igrad.commons.util;
import java.io.IOException;
import java.nio.file.Files;
@@ -20,6 +20,7 @@ public static boolean isFileExists(Path file) {
/**
* Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)},
* otherwise returns false.
+ *
* @param path A string representing the file path. Cannot be null.
*/
public static boolean isValidPath(String path) {
@@ -33,6 +34,7 @@ public static boolean isValidPath(String path) {
/**
* Creates a file if it does not exist along with its missing parent directories.
+ *
* @throws IOException if the file or directory cannot be created.
*/
public static void createIfMissing(Path file) throws IOException {
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/igrad/commons/util/JsonUtil.java
similarity index 82%
rename from src/main/java/seedu/address/commons/util/JsonUtil.java
rename to src/main/java/igrad/commons/util/JsonUtil.java
index 8ef609f055d..9a4029d39e4 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/igrad/commons/util/JsonUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package igrad.commons.util;
import static java.util.Objects.requireNonNull;
@@ -19,9 +19,10 @@
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
+import igrad.commons.core.LogsCenter;
+import igrad.commons.exceptions.DataConversionException;
/**
* Converts a Java object instance to JSON and vice versa
@@ -31,32 +32,34 @@ public class JsonUtil {
private static final Logger logger = LogsCenter.getLogger(JsonUtil.class);
private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules()
- .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
- .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
- .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
- .registerModule(new SimpleModule("SimpleModule")
- .addSerializer(Level.class, new ToStringSerializer())
- .addDeserializer(Level.class, new LevelDeserializer(Level.class)));
+ .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
+ .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
+ .registerModule(new Jdk8Module())
+ .registerModule(new SimpleModule("SimpleModule")
+ .addSerializer(Level.class, new ToStringSerializer())
+ .addDeserializer(Level.class, new LevelDeserializer(Level.class)));
static void serializeObjectToJsonFile(Path jsonFile, T objectToSerialize) throws IOException {
FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize));
}
static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObjectToDeserialize)
- throws IOException {
+ throws IOException {
return fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize);
}
/**
* Returns the Json object from the given file or {@code Optional.empty()} object if the file is not found.
* If any values are missing from the file, default values will be used, as long as the file is a valid json file.
- * @param filePath cannot be null.
+ *
+ * @param filePath cannot be null.
* @param classOfObjectToDeserialize Json file has to correspond to the structure in the class given here.
* @throws DataConversionException if the file format is not as expected.
*/
public static Optional readJsonFile(
- Path filePath, Class classOfObjectToDeserialize) throws DataConversionException {
+ Path filePath, Class classOfObjectToDeserialize) throws DataConversionException {
requireNonNull(filePath);
if (!Files.exists(filePath)) {
@@ -79,6 +82,7 @@ public static Optional readJsonFile(
/**
* Saves the Json object to the specified file.
* Overwrites existing file if it exists, creates a new file if it doesn't.
+ *
* @param jsonFile cannot be null
* @param filePath cannot be null
* @throws IOException if there was an error during writing to the file
@@ -93,6 +97,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio
/**
* Converts a given string representation of a JSON data to instance of a class
+ *
* @param The generic type to create an instance of
* @return The instance of T with the specified values in the JSON string
*/
@@ -102,8 +107,9 @@ public static T fromJsonString(String json, Class instanceClass) throws I
/**
* Converts a given instance of a class into its JSON data string representation
+ *
* @param instance The T object to be converted into the JSON string
- * @param The generic type to create an instance of
+ * @param The generic type to create an instance of
* @return JSON data representation of the given class instance, in string
*/
public static String toJsonString(T instance) throws JsonProcessingException {
@@ -128,7 +134,6 @@ protected Level _deserialize(String value, DeserializationContext ctxt) {
* Gets the logging level that matches loggingLevelString
*
* Returns null if there are no matches
- *
*/
private Level getLoggingLevel(String loggingLevelString) {
return Level.parse(loggingLevelString);
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/igrad/commons/util/StringUtil.java
similarity index 87%
rename from src/main/java/seedu/address/commons/util/StringUtil.java
rename to src/main/java/igrad/commons/util/StringUtil.java
index 61cc8c9a1cb..69886e314fd 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/igrad/commons/util/StringUtil.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.util;
+package igrad.commons.util;
+import static igrad.commons.util.AppUtil.checkArgument;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -14,14 +14,15 @@ public class StringUtil {
/**
* Returns true if the {@code sentence} contains the {@code word}.
- * Ignores case, but a full word match is required.
- * examples:
+ * Ignores case, but a full word match is required.
+ * examples:
* containsWordIgnoreCase("ABc def", "abc") == true
* containsWordIgnoreCase("ABc def", "DEF") == true
* containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
*
+ *
* @param sentence cannot be null
- * @param word cannot be null, cannot be empty, must be a single word
+ * @param word cannot be null, cannot be empty, must be a single word
*/
public static boolean containsWordIgnoreCase(String sentence, String word) {
requireNonNull(sentence);
@@ -35,7 +36,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
return Arrays.stream(wordsInPreppedSentence)
- .anyMatch(preppedWord::equalsIgnoreCase);
+ .anyMatch(preppedWord::equalsIgnoreCase);
}
/**
@@ -53,6 +54,7 @@ public static String getDetails(Throwable t) {
* e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input
* e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters)
+ *
* @throws NullPointerException if {@code s} is null.
*/
public static boolean isNonZeroUnsignedInteger(String s) {
diff --git a/src/main/java/igrad/csvwriter/CsvWriter.java b/src/main/java/igrad/csvwriter/CsvWriter.java
new file mode 100644
index 00000000000..eac01b4c64a
--- /dev/null
+++ b/src/main/java/igrad/csvwriter/CsvWriter.java
@@ -0,0 +1,94 @@
+package igrad.csvwriter;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+import igrad.model.module.Module;
+
+/**
+ * Writes the data stored as a human-readable CSV file.
+ */
+public class CsvWriter {
+
+ private static final String fileName = "study_plan.csv";
+ private FileWriter csvWriter;
+ private List sortedList;
+
+ public CsvWriter(List sortedList) throws IOException {
+ csvWriter = new FileWriter(fileName);
+
+ this.sortedList = sortedList;
+ }
+
+ /**
+ * Writes to CSV
+ */
+ public void write() throws IOException {
+ writeHeaders();
+ writeBody();
+ closeWriter();
+ }
+
+ private void closeWriter() throws IOException {
+ csvWriter.flush();
+ csvWriter.close();
+ }
+
+ private void appendNewLine() throws IOException {
+ csvWriter.append("\n");
+ }
+
+ private void append(String text) throws IOException {
+ csvWriter.append(text);
+ csvWriter.append(",");
+ }
+
+ /**
+ * Writes each module as a line. Separates modules taken in different semesters
+ * by a new line.
+ */
+ private void writeBody() throws IOException {
+
+ for (int i = 0; i < sortedList.size(); i++) {
+ Module module = sortedList.get(i);
+
+ if (module.getSemester().isPresent()) {
+ append(module.getSemester().toString());
+ }
+ append(module.getModuleCode().toString());
+ append(module.getTitle().toString());
+ append(module.getCredits().toString());
+ appendNewLine();
+
+ if (i < sortedList.size() - 1) {
+ Module nextModule = sortedList.get(i + 1);
+ if (!nextModule.getSemester().equals(module.getSemester())) {
+ appendNewLine();
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Writes the headers of the CSV file.
+ */
+ private void writeHeaders() throws IOException {
+
+ String[] headers = {
+ "Semester",
+ "Module Code",
+ "Module Title",
+ "MCs"
+ };
+
+ for (String header : headers) {
+ append(header);
+ }
+
+ appendNewLine();
+
+ }
+
+}
diff --git a/src/main/java/igrad/csvwriter/exceptions/InvalidDataException.java b/src/main/java/igrad/csvwriter/exceptions/InvalidDataException.java
new file mode 100644
index 00000000000..71d5d8cfb7f
--- /dev/null
+++ b/src/main/java/igrad/csvwriter/exceptions/InvalidDataException.java
@@ -0,0 +1,14 @@
+package igrad.csvwriter.exceptions;
+
+import java.io.IOException;
+
+/**
+ * Signals that one or more of the required fields are not available
+ */
+public class InvalidDataException extends IOException {
+
+ public InvalidDataException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/src/main/java/igrad/logic/Logic.java b/src/main/java/igrad/logic/Logic.java
new file mode 100644
index 00000000000..a71a12ccfe8
--- /dev/null
+++ b/src/main/java/igrad/logic/Logic.java
@@ -0,0 +1,65 @@
+package igrad.logic;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import igrad.commons.core.GuiSettings;
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.logic.parser.exceptions.ParseException;
+import igrad.model.Model;
+import igrad.model.ReadOnlyCourseBook;
+import igrad.model.module.Module;
+import igrad.model.requirement.Requirement;
+import igrad.services.exceptions.ServiceException;
+import javafx.collections.ObservableList;
+
+/**
+ * API of the Logic component.
+ */
+public interface Logic {
+ /**
+ * Execute an 'Avatar' command and returns the result.
+ *
+ * @param commandText The command as entered by the user.
+ * @return the result of the command execution.
+ * @throws ParseException If error occurs during parsing avatar name (i.e, it is not valid).
+ */
+ CommandResult executeAvatar(String commandText) throws ParseException, CommandException;
+
+ /**
+ * 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, IOException, ServiceException;
+
+ /**
+ * Returns the CourseBook.
+ *
+ * @see Model#getCourseBook()
+ */
+ ReadOnlyCourseBook getCourseBook();
+
+ ObservableList getFilteredModuleList();
+
+ ObservableList getRequirementList();
+
+ /**
+ * Returns the user prefs' course book file path.
+ */
+ Path getCourseBookFilePath();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Set the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+}
diff --git a/src/main/java/igrad/logic/LogicManager.java b/src/main/java/igrad/logic/LogicManager.java
new file mode 100644
index 00000000000..9414548b498
--- /dev/null
+++ b/src/main/java/igrad/logic/LogicManager.java
@@ -0,0 +1,129 @@
+package igrad.logic;
+
+import static igrad.commons.core.Messages.MESSAGE_COURSE_NOT_SET;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.logging.Logger;
+
+import igrad.commons.core.GuiSettings;
+import igrad.commons.core.LogsCenter;
+import igrad.logic.commands.Command;
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.SelectAvatarCommand;
+import igrad.logic.commands.UndoCommand;
+import igrad.logic.commands.course.CourseAddCommand;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.logic.parser.CourseBookParser;
+import igrad.logic.parser.exceptions.ParseException;
+import igrad.model.Model;
+import igrad.model.ReadOnlyCourseBook;
+import igrad.model.module.Module;
+import igrad.model.requirement.Requirement;
+import igrad.services.exceptions.ServiceException;
+import igrad.storage.Storage;
+import javafx.collections.ObservableList;
+
+/**
+ * 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 CourseBookParser courseBookParser;
+
+ public LogicManager(Model model, Storage storage) {
+ this.model = model;
+ this.storage = storage;
+ courseBookParser = new CourseBookParser();
+ }
+
+ @Override
+ public CommandResult executeAvatar(String avatarName) throws ParseException, CommandException {
+ CommandResult commandResult;
+
+
+ SelectAvatarCommand selectAvatarCommand = courseBookParser.parseAvatarName(avatarName);
+ commandResult = selectAvatarCommand.execute(model);
+
+ try {
+ // Saves to UserPref data file to save new Avatar, after successful Avatar command execution
+ storage.saveUserPrefs(model.getUserPrefs());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
+ @Override
+ public CommandResult execute(String commandText) throws CommandException,
+ ParseException, IOException, ServiceException {
+
+ logger.info("----------------[USER COMMAND][" + commandText + "]");
+
+ CommandResult commandResult;
+ Command command = courseBookParser.parseCommand(commandText);
+
+ /*
+ * If user has not selected her course name, and she is trying to execute any other
+ * command except course add n/course_name, prevent her from doing so.
+ */
+ if (!model.isCourseNameSet() && !(command instanceof CourseAddCommand)) {
+ throw new CommandException(MESSAGE_COURSE_NOT_SET);
+ }
+
+ if (!(command instanceof UndoCommand)) {
+ try {
+ // First, load current state into backup
+ storage.saveBackupCourseBook(model.getCourseBook());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+ }
+
+ commandResult = command.execute(model);
+
+ try {
+ // Saves to data file after every command
+ storage.saveCourseBook(model.getCourseBook());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
+ @Override
+ public ReadOnlyCourseBook getCourseBook() {
+ return model.getCourseBook();
+ }
+
+ @Override
+ public ObservableList getFilteredModuleList() {
+ return model.getFilteredModuleList();
+ }
+
+ @Override
+ public ObservableList getRequirementList() {
+ return model.getRequirementList();
+ }
+
+ @Override
+ public Path getCourseBookFilePath() {
+ return model.getCourseBookFilePath();
+ }
+
+ @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/Command.java b/src/main/java/igrad/logic/commands/Command.java
similarity index 76%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/igrad/logic/commands/Command.java
index 64f18992160..186fb60fb03 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/igrad/logic/commands/Command.java
@@ -1,12 +1,13 @@
-package seedu.address.logic.commands;
+package igrad.logic.commands;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
/**
* Represents a command with hidden internal logic and the ability to be executed.
*/
public abstract class Command {
+ public static final String SPACE = " ";
/**
* Executes the command and returns the result message.
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/igrad/logic/commands/CommandResult.java
similarity index 72%
rename from src/main/java/seedu/address/logic/commands/CommandResult.java
rename to src/main/java/igrad/logic/commands/CommandResult.java
index 92f900b7916..499ff9e113b 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/igrad/logic/commands/CommandResult.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands;
+package igrad.logic.commands;
import static java.util.Objects.requireNonNull;
@@ -11,19 +11,30 @@ public class CommandResult {
private final String feedbackToUser;
- /** Help information should be shown to the user. */
+ /**
+ * Help information should be shown to the user.
+ */
private final boolean showHelp;
- /** The application should exit. */
+ /**
+ * The application should exit.
+ */
private final boolean exit;
+
+ /**
+ * Course information (name) updated on the UI panel.
+ */
+ private final boolean courseEdit;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean courseEdit) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.courseEdit = courseEdit;
}
/**
@@ -31,7 +42,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, false, false, false);
}
public String getFeedbackToUser() {
@@ -46,6 +57,10 @@ public boolean isExit() {
return exit;
}
+ public boolean isCourseEdit() {
+ return courseEdit;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -59,8 +74,8 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
- && showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && showHelp == otherCommandResult.showHelp
+ && exit == otherCommandResult.exit;
}
@Override
diff --git a/src/main/java/igrad/logic/commands/CommandUtil.java b/src/main/java/igrad/logic/commands/CommandUtil.java
new file mode 100644
index 00000000000..42e7951d1ee
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/CommandUtil.java
@@ -0,0 +1,37 @@
+package igrad.logic.commands;
+
+import java.util.Optional;
+
+import igrad.model.Model;
+import igrad.model.course.Cap;
+import igrad.model.course.CourseInfo;
+import igrad.model.course.Name;
+
+/**
+ * Contains general purpose utility methods used by some of the commands.
+ */
+public class CommandUtil {
+ /**
+ * Retrieves the latest {@code CourseInfo} fields, based on the most updated {@code Requirement}
+ * list and {@code Module} list in {@code Model}
+ */
+ public static CourseInfo retrieveLatestCourseInfo(CourseInfo course, Model model) {
+ // Copy over all the old values of course
+ Optional currentName = course.getName();
+
+ // Now we actually go to our model and recompute cap based on updated module list in model (coursebook)
+ Optional updatedCap = CourseInfo.computeCap(model.getFilteredModuleList(),
+ model.getRequirementList());
+
+ /*
+ * Now given that we've updated a new module to requirement (as done), we've to update (recompute)
+ * creditsFulfilled and creditsRequired
+ */
+ Optional updatedCredits = CourseInfo.computeCredits(
+ model.getRequirementList());
+
+ CourseInfo latestCourseInfo = new CourseInfo(currentName, updatedCap, updatedCredits);
+
+ return latestCourseInfo;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/igrad/logic/commands/ExitCommand.java
similarity index 73%
rename from src/main/java/seedu/address/logic/commands/ExitCommand.java
rename to src/main/java/igrad/logic/commands/ExitCommand.java
index 3dd85a8ba90..23c876d052e 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/igrad/logic/commands/ExitCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package igrad.logic.commands;
-import seedu.address.model.Model;
+import igrad.model.Model;
/**
* Terminates the program.
@@ -9,11 +9,11 @@ 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 iGrad as requested ...";
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false);
}
}
diff --git a/src/main/java/igrad/logic/commands/ExportCommand.java b/src/main/java/igrad/logic/commands/ExportCommand.java
new file mode 100644
index 00000000000..7898bcf368a
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/ExportCommand.java
@@ -0,0 +1,41 @@
+package igrad.logic.commands;
+
+import java.io.IOException;
+import java.util.List;
+
+import igrad.csvwriter.CsvWriter;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.module.Module;
+import igrad.model.module.sorters.SortBySemester;
+
+/**
+ * Format full help instructions for every command for display.
+ */
+public class ExportCommand extends Command {
+
+ public static final String COMMAND_WORD = "export";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exports data to a CSV file.\n"
+ + "Example: " + COMMAND_WORD;
+
+ public static final String SHOWING_EXPORT_MESSAGE = "I've exported your data to a CSV file."
+ + " You can find it in the same folder as this app's executable!";
+ public static final String EXPORT_ERROR_MESSAGE = "Unable to export data to CSV file."
+ + " Please ensure that you do not have the file open and "
+ + "each module is tagged to a semester.";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+
+ try {
+ List sortedModuleList = model.getSortedModuleList(new SortBySemester());
+ CsvWriter csvWriter = new CsvWriter(sortedModuleList);
+ csvWriter.write();
+ } catch (IOException | NumberFormatException e) {
+ throw new CommandException(EXPORT_ERROR_MESSAGE);
+ }
+
+ return new CommandResult(SHOWING_EXPORT_MESSAGE);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/igrad/logic/commands/HelpCommand.java
similarity index 80%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/igrad/logic/commands/HelpCommand.java
index bf824f91bd0..63b3ac20d1f 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/igrad/logic/commands/HelpCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package igrad.logic.commands;
-import seedu.address.model.Model;
+import igrad.model.Model;
/**
* Format full help instructions for every command for display.
@@ -10,12 +10,12 @@ public class HelpCommand extends Command {
public static final String COMMAND_WORD = "help";
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
- + "Example: " + COMMAND_WORD;
+ + "Example: " + COMMAND_WORD;
public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
@Override
public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false);
}
}
diff --git a/src/main/java/igrad/logic/commands/SelectAvatarCommand.java b/src/main/java/igrad/logic/commands/SelectAvatarCommand.java
new file mode 100644
index 00000000000..bc214f9353b
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/SelectAvatarCommand.java
@@ -0,0 +1,53 @@
+package igrad.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import igrad.commons.core.Messages;
+import igrad.model.Model;
+import igrad.model.avatar.Avatar;
+
+/**
+ * Adds a module to the course book.
+ */
+public class SelectAvatarCommand extends Command {
+
+ public static final String MESSAGE_SUCCESS = "You've chosen a guide!";
+ private final Avatar toAdd;
+
+ /**
+ * Creates an SelectAvatarCommand for Avatar selection
+ */
+ public SelectAvatarCommand(Avatar avatar) {
+ requireNonNull(avatar);
+ toAdd = avatar;
+ }
+
+ /**
+ * Generates the actual success message for the command replacing the placeholders in MESSAGE_SUCCESS
+ * with the actual Avatar name. (This method also capitalises the first letter of the Avatar name)
+ *
+ * @return String the (command) success message
+ */
+ private String generateSuccessMessage() {
+ String avatarName = toAdd.getName();
+ String capitaliseAvatarName = avatarName.substring(0, 1).toUpperCase() + avatarName.substring(1);
+
+ return String.format(MESSAGE_SUCCESS + Messages.MESSAGE_ADD_COURSE, capitaliseAvatarName);
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.setAvatar(toAdd);
+
+ return new CommandResult(String.format(generateSuccessMessage(), toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SelectAvatarCommand // instanceof handles nulls
+ && toAdd.equals(((SelectAvatarCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/UndoCommand.java b/src/main/java/igrad/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..5d1e8c67b28
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/UndoCommand.java
@@ -0,0 +1,54 @@
+package igrad.logic.commands;
+
+import java.util.Optional;
+
+import igrad.commons.exceptions.DataConversionException;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.ReadOnlyCourseBook;
+import igrad.storage.JsonCourseBookStorage;
+
+/**
+ * Undoes the previous action taken.
+ */
+public class UndoCommand extends Command {
+
+ public static final String COMMAND_WORD = "undo";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Undoes latest action.\n"
+ + "Example: " + COMMAND_WORD;
+
+ public static final String MESSAGE_SUCCESS = "Undid last command.";
+ public static final String MESSAGE_ERROR = "Unable to undo the last comamand.";
+ public static final String MESSAGE_NO_ACTION = "Nothing to undo";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+
+ JsonCourseBookStorage courseBookStorage = new JsonCourseBookStorage(
+ model.getCourseBookFilePath(),
+ model.getBackupCourseBookFilePath()
+ );
+
+ try {
+ Optional backupCourseBook = courseBookStorage.readBackupCourseBook();
+ Optional courseBook = courseBookStorage.readCourseBook();
+
+ if (courseBook.equals(backupCourseBook)) {
+ throw new CommandException(MESSAGE_NO_ACTION);
+ } else {
+ if (backupCourseBook.isPresent()) {
+ model.setCourseBook(backupCourseBook.get());
+ } else {
+ throw new CommandException(MESSAGE_ERROR);
+ }
+ }
+
+
+ } catch (DataConversionException e) {
+ throw new CommandException(MESSAGE_ERROR);
+ }
+
+ return new CommandResult(MESSAGE_SUCCESS, false, false, false);
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/course/CourseAchieveCommand.java b/src/main/java/igrad/logic/commands/course/CourseAchieveCommand.java
new file mode 100644
index 00000000000..34234893e4d
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/course/CourseAchieveCommand.java
@@ -0,0 +1,46 @@
+package igrad.logic.commands.course;
+
+import static igrad.logic.parser.CliSyntax.PREFIX_CAP;
+import static igrad.logic.parser.CliSyntax.PREFIX_SEMESTER;
+import static java.util.Objects.requireNonNull;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.Cap;
+
+/**
+ * Adds a course to the application (there can only be one such course).
+ */
+public class CourseAchieveCommand extends CourseCommand {
+ public static final String COURSE_ACHIEVE_COMMAND_WORD = COURSE_COMMAND_WORD + SPACE + "achieve";
+
+ public static final String MESSAGE_COURSE_ACHIEVE_SUCCESS = "You need to maintain an average CAP (per sem) "
+ + "of: %1$s";
+ public static final String MESSAGE_ACHIEVED_CAP_NOT_CALCULATED = "Please enter desired CAP";
+ public static final String MESSAGE_SEMS_LEFT_NEEDED = "Please enter semesters left";
+ public static final String MESSAGE_COURSE_ACHIEVE_DETAILS = COURSE_ACHIEVE_COMMAND_WORD + ": Calculates average "
+ + "CAP needed per sem"
+ + " to achieve desired CAP\n";
+ public static final String MESSAGE_COURSE_ACHIEVE_USAGE = "Parameter(s): " + PREFIX_CAP + "DESIRED CAP "
+ + PREFIX_SEMESTER + "SEMESTERS LEFT";
+ public static final String MESSAGE_COURSE_ACHIEVE_HELP = MESSAGE_COURSE_ACHIEVE_DETAILS
+ + MESSAGE_COURSE_ACHIEVE_USAGE;
+
+ private final Cap capToAchieve;
+ private final int semsLeft;
+
+ public CourseAchieveCommand(Cap capToAchieve, int semsLeft) {
+ this.capToAchieve = capToAchieve;
+ this.semsLeft = semsLeft;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Cap estimatedCap = model.computeEstimatedCap(capToAchieve, semsLeft);
+
+ return new CommandResult(String.format(MESSAGE_COURSE_ACHIEVE_SUCCESS, estimatedCap));
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/course/CourseAddCommand.java b/src/main/java/igrad/logic/commands/course/CourseAddCommand.java
new file mode 100644
index 00000000000..86a9ba76175
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/course/CourseAddCommand.java
@@ -0,0 +1,64 @@
+package igrad.logic.commands.course;
+
+import static igrad.commons.core.Messages.MESSAGE_COURSE_ALREADY_SET;
+import static igrad.logic.parser.CliSyntax.PREFIX_NAME;
+import static java.util.Objects.requireNonNull;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.logic.commands.module.ModuleAddCommand;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+
+/**
+ * Adds a course to the application (there can only be one such course).
+ */
+public class CourseAddCommand extends CourseCommand {
+ public static final String COURSE_ADD_COMMAND_WORD = COURSE_COMMAND_WORD + SPACE + "add";
+ public static final String MESSAGE_COURSE_ADD_DETAILS = COURSE_ADD_COMMAND_WORD
+ + ": Adds a course with relevant details specified.\n";
+
+ public static final String MESSAGE_COURSE_ADD_USAGE = "Parameter(s): "
+ + PREFIX_NAME + "COURSE_NAME\n"
+ + "Example: " + COURSE_ADD_COMMAND_WORD + " "
+ + PREFIX_NAME + "Bachelor of Computing (Honours) in Computer Science ";
+
+ public static final String MESSAGE_COURSE_ADD_HELP = MESSAGE_COURSE_ADD_DETAILS + MESSAGE_COURSE_ADD_USAGE;
+
+ public static final String MESSAGE_COURSE_ADD_SUCCESS = "New course added: %1$s";
+ private final CourseInfo toAdd;
+
+ /**
+ * Creates an CourseInfo to add the Course Book
+ */
+ public CourseAddCommand(CourseInfo courseInfo) {
+ requireNonNull(courseInfo);
+ toAdd = courseInfo;
+ }
+
+ /**
+ * Executes the command and returns the result message.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return feedback message of the operation result for display
+ * @throws CommandException If an error occurs during command execution.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.isCourseNameSet()) {
+ throw new CommandException(MESSAGE_COURSE_ALREADY_SET);
+ }
+
+ model.addCourseInfo(toAdd);
+ return new CommandResult(String.format(MESSAGE_COURSE_ADD_SUCCESS, toAdd), false, false, true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ModuleAddCommand // instanceof handles nulls
+ && toAdd.equals(((CourseAddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/course/CourseCommand.java b/src/main/java/igrad/logic/commands/course/CourseCommand.java
new file mode 100644
index 00000000000..9fe749c766c
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/course/CourseCommand.java
@@ -0,0 +1,12 @@
+package igrad.logic.commands.course;
+
+import igrad.logic.commands.Command;
+
+/**
+ * Represents a generic course command.
+ */
+public abstract class CourseCommand extends Command {
+ public static final String COURSE_COMMAND_WORD = "course";
+ public static final String MESSAGE_COURSE_NON_EXISTENT =
+ "Course does not exist. Please enter an existing course.";
+}
diff --git a/src/main/java/igrad/logic/commands/course/CourseDeleteCommand.java b/src/main/java/igrad/logic/commands/course/CourseDeleteCommand.java
new file mode 100644
index 00000000000..b0c6d068266
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/course/CourseDeleteCommand.java
@@ -0,0 +1,38 @@
+package igrad.logic.commands.course;
+
+import static java.util.Objects.requireNonNull;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.ReadOnlyCourseBook;
+import igrad.model.course.CourseInfo;
+
+/**
+ * Deletes the existing {@code Course} (and all data within it, e.g, {@code Module}, {@code Requirement}).
+ */
+public class CourseDeleteCommand extends CourseCommand {
+
+ public static final String COURSE_DELETE_COMMAND_WORD = COURSE_COMMAND_WORD + SPACE + "delete";
+
+ public static final String MESSAGE_COURSE_DELETE_SUCCESS = "Deleted Course: %1$s\nAll data cleared!";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ ReadOnlyCourseBook courseBookToDelete = model.getCourseBook();
+
+ CourseInfo oldCourseInfo = model.getCourseInfo();
+
+ model.resetCourseBook(courseBookToDelete);
+
+ return new CommandResult(String.format(MESSAGE_COURSE_DELETE_SUCCESS, oldCourseInfo));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CourseDeleteCommand); // instanceof handles nulls
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/course/CourseEditCommand.java b/src/main/java/igrad/logic/commands/course/CourseEditCommand.java
new file mode 100644
index 00000000000..f400d05b277
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/course/CourseEditCommand.java
@@ -0,0 +1,144 @@
+package igrad.logic.commands.course;
+
+import static igrad.logic.parser.CliSyntax.PREFIX_NAME;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.Cap;
+import igrad.model.course.CourseInfo;
+import igrad.model.course.Credits;
+import igrad.model.course.Name;
+
+/**
+ * Edits the details of an existing module in the course book.
+ */
+public class CourseEditCommand extends CourseCommand {
+
+ public static final String COURSE_EDIT_COMMAND_WORD = COURSE_COMMAND_WORD + SPACE + "edit";
+ public static final String MESSAGE_COURSE_EDIT_SUCCESS = "Edited Course: %1$s";
+ public static final String MESSAGE_EDIT_COURSE_SAME_PARAMETERS = "Please change the name of the course";
+ public static final String MESSAGE_COURSE_NOT_EDITED = "Course name must be provided.";
+ public static final String MESSAGE_COURSE_EDIT_DETAILS = COURSE_EDIT_COMMAND_WORD + ": Edits the name of Course\n";
+ public static final String MESSAGE_COURSE_EDIT_USAGE = "Parameter(s): " + PREFIX_NAME + "COURSE NAME";
+ public static final String MESSAGE_COURSE_EDIT_HELP = MESSAGE_COURSE_EDIT_DETAILS + MESSAGE_COURSE_EDIT_USAGE;
+
+ private EditCourseDescriptor editCourseDescriptor;
+
+ /**
+ * @param editCourseDescriptor details (course name) to edit the course with
+ * (Note: course is special unlike module and requirement as there is only
+ * one course in the course book, hence we don't need a 'Name'/'ModuleCode', or any
+ * kind of identifier to identify the course we want to edit)
+ */
+ public CourseEditCommand(EditCourseDescriptor editCourseDescriptor) {
+ requireNonNull(editCourseDescriptor);
+
+ this.editCourseDescriptor = new EditCourseDescriptor(editCourseDescriptor);
+ }
+
+ /**
+ * Creates and returns a {@code CourseInfo} with the details of {@code courseInfoToEdit}
+ * edited with {@code editCourseDescriptor}.
+ */
+ private static CourseInfo createEditedCourseInfo(CourseInfo courseInfoToEdit,
+ CourseEditCommand.EditCourseDescriptor editCourseDescriptor) {
+ /*
+ * Just copy everything from the original {@code courseInfoToEdit} to our new {@code CourseInfo}.
+ * But for course name, we retrieve the updated value from the editCourseDescriptor here.
+ */
+ Optional updatedName = editCourseDescriptor.getName();
+ Optional cap = courseInfoToEdit.getCap();
+ Optional credits = courseInfoToEdit.getCredits();
+
+ return new CourseInfo(updatedName, cap, credits);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ CourseInfo courseToEdit = model.getCourseInfo();
+
+ // The course name has to first be set, else we can't proceed to even edit it.
+ courseToEdit.getName().orElseThrow(() -> new CommandException(MESSAGE_COURSE_NON_EXISTENT));
+
+ if (editCourseDescriptor.getName().isEmpty()) {
+ throw new CommandException(MESSAGE_COURSE_NOT_EDITED);
+ }
+
+ if (courseToEdit.getName().equals(editCourseDescriptor.getName())) {
+ throw new CommandException(MESSAGE_EDIT_COURSE_SAME_PARAMETERS);
+ }
+
+ CourseInfo editedCourse = createEditedCourseInfo(courseToEdit, editCourseDescriptor);
+
+ model.setCourseInfo(editedCourse);
+ return new CommandResult(String.format(MESSAGE_COURSE_EDIT_SUCCESS, editedCourse));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditCourseDescriptor)) {
+ return false;
+ }
+
+ // state check
+ CourseEditCommand e = (CourseEditCommand) other;
+
+ return editCourseDescriptor.equals(e.editCourseDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the module with. Each non-empty field value will replace the
+ * corresponding field value of the module.
+ */
+ public static class EditCourseDescriptor {
+ private Optional name;
+
+ public EditCourseDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ */
+ public EditCourseDescriptor(EditCourseDescriptor toCopy) {
+ setName(toCopy.name);
+ }
+
+ public Optional getName() {
+ return name;
+ }
+
+ public void setName(Optional name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditCourseDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditCourseDescriptor e = (EditCourseDescriptor) other;
+
+ return getName().equals(e.getName());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/igrad/logic/commands/exceptions/CommandException.java
similarity index 89%
rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
rename to src/main/java/igrad/logic/commands/exceptions/CommandException.java
index a16bd14f2cd..ea575e74c69 100644
--- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
+++ b/src/main/java/igrad/logic/commands/exceptions/CommandException.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands.exceptions;
+package igrad.logic.commands.exceptions;
/**
* Represents an error which occurs during execution of a {@link Command}.
diff --git a/src/main/java/igrad/logic/commands/module/ModuleAddAutoCommand.java b/src/main/java/igrad/logic/commands/module/ModuleAddAutoCommand.java
new file mode 100644
index 00000000000..b1a33b7c421
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/module/ModuleAddAutoCommand.java
@@ -0,0 +1,117 @@
+package igrad.logic.commands.module;
+
+import static igrad.logic.parser.CliSyntax.PREFIX_CREDITS;
+import static igrad.logic.parser.CliSyntax.PREFIX_MEMO;
+import static igrad.logic.parser.CliSyntax.PREFIX_MODULE_CODE;
+import static igrad.logic.parser.CliSyntax.PREFIX_SEMESTER;
+import static igrad.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+
+import igrad.logic.commands.CommandResult;
+import igrad.model.Model;
+import igrad.model.module.Module;
+import igrad.model.module.ModuleCode;
+
+/**
+ * Adds a module to the course book.
+ */
+public class ModuleAddAutoCommand extends ModuleCommand {
+
+ public static final String COMMAND_WORD = MODULE_COMMAND_WORD + SPACE + "add";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a module. "
+ + "Parameter(s): "
+ + PREFIX_TITLE + "MODULE TITLE "
+ + PREFIX_MODULE_CODE + "MODULE CODE "
+ + PREFIX_CREDITS + "CREDITS "
+ + PREFIX_MEMO + "MEMO "
+ + "[" + PREFIX_SEMESTER + "SEMESTER]...\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Software Engineering "
+ + PREFIX_MODULE_CODE + "CS2103T "
+ + PREFIX_CREDITS + "4 "
+ + PREFIX_MEMO + "Hard module. Good teachers. "
+ + PREFIX_SEMESTER + "Y2S2 ";
+
+ public static final String MESSAGE_COMPLETE = "%d module(s) added through NUSMods API.\n";
+ public static final String MESSAGE_SUCCESS = "Added module: %s\n";
+ public static final String MESSAGE_DUPLICATE_MODULE = "ERROR: Duplicate detected: %s\n";
+ public static final String MESSAGE_PREREQUISITE_NOT_PRESENT =
+ "WARNING: Prerequisite not found!\n";
+ public static final String MESSAGE_PRECLUSION_PRESENT =
+ "WARNING: Preclusion found!\n";
+
+ private final ArrayList toAddList;
+ private final String messageAdditional;
+
+ /**
+ * Creates an ModuleAddCommand to add the specified {@code Module}
+ */
+ public ModuleAddAutoCommand(ArrayList modules, String messageAdditional) {
+ requireNonNull(modules);
+
+ toAddList = modules;
+ this.messageAdditional = messageAdditional;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ StringBuilder message = new StringBuilder();
+
+ int modulesAdded = 0;
+
+ for (Module module : toAddList) {
+
+ if (model.hasModule(module)) {
+ message.append(String.format(MESSAGE_DUPLICATE_MODULE, module.toString()));
+ } else {
+ message.append(String.format(MESSAGE_SUCCESS, module.toString()));
+
+ if (!model.hasModulePrerequisites(module)) {
+ message.append(MESSAGE_PREREQUISITE_NOT_PRESENT);
+ }
+
+ if (model.hasModulePreclusions(module)) {
+ message.append(MESSAGE_PRECLUSION_PRESENT);
+ }
+
+ model.addModule(module);
+ modulesAdded++;
+ }
+ }
+
+ message.append(String.format(MESSAGE_COMPLETE, modulesAdded));
+ message.append(messageAdditional);
+
+ return new CommandResult(message.toString());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ModuleAddAutoCommand // instanceof handles nulls
+ && toAddList.equals(((ModuleAddAutoCommand) other).toAddList));
+ }
+
+ /**
+ * Formats the exception message for when a preclusion module is present in the model.
+ */
+ private String formatPreclusionExceptionMessage(ModuleCode moduleCode) {
+ String moduleCodeString = "(" + moduleCode.toString() + ")";
+
+ return String.format(MESSAGE_PRECLUSION_PRESENT, moduleCodeString);
+ }
+
+ /**
+ * Formats the exception message for when a prerequisite module is not present in the model.
+ */
+ private String formatPrerequisiteExceptionMessage(ModuleCode moduleCode) {
+ String moduleCodeString = "(" + moduleCode.toString() + ")";
+
+ return String.format(MESSAGE_PREREQUISITE_NOT_PRESENT, moduleCodeString);
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/module/ModuleAddCommand.java b/src/main/java/igrad/logic/commands/module/ModuleAddCommand.java
new file mode 100644
index 00000000000..b0195815cdd
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/module/ModuleAddCommand.java
@@ -0,0 +1,74 @@
+package igrad.logic.commands.module;
+
+import static igrad.logic.parser.CliSyntax.PREFIX_CREDITS;
+import static igrad.logic.parser.CliSyntax.PREFIX_MEMO;
+import static igrad.logic.parser.CliSyntax.PREFIX_MODULE_CODE;
+import static igrad.logic.parser.CliSyntax.PREFIX_SEMESTER;
+import static igrad.logic.parser.CliSyntax.PREFIX_TAG;
+import static igrad.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.module.Module;
+
+/**
+ * Adds a module to the course book.
+ */
+public class ModuleAddCommand extends ModuleCommand {
+
+ public static final String MODULE_ADD_COMMAND_WORD = MODULE_COMMAND_WORD + SPACE + "add";
+
+ public static final String MESSAGE_MODULE_ADD_DETAILS = MODULE_ADD_COMMAND_WORD
+ + ": Adds a module with relevant details specified.\n";
+
+ public static final String MESSAGE_MODULE_ADD_USAGE = "Parameter(s): "
+ + PREFIX_MODULE_CODE + "MODULE_CODE "
+ + PREFIX_TITLE + " TITLE "
+ + PREFIX_CREDITS + "CREDITS "
+ + "[" + PREFIX_MEMO + "MEMO] "
+ + "[" + PREFIX_SEMESTER + "SEMESTER] "
+ + "[" + PREFIX_TAG + "TAGS]...\n"
+ + "Example: " + MODULE_ADD_COMMAND_WORD + " "
+ + PREFIX_MODULE_CODE + "CS2103T "
+ + PREFIX_TITLE + "Software Engineering "
+ + PREFIX_CREDITS + "4 "
+ + PREFIX_SEMESTER + "Y2S2";
+
+ public static final String MESSAGE_MODULE_ADD_HELP = MESSAGE_MODULE_ADD_DETAILS + MESSAGE_MODULE_ADD_USAGE;
+
+ public static final String MESSAGE_MODULE_ADD_SUCCESS = "New module added:\n%1$s";
+ public static final String MESSAGE_DUPLICATE_MODULE = "This module already exists in the course book";
+ public static final String MESSAGE_MODULE_NOT_ADDED = "Added module must be provided with at least these "
+ + "argument(s) " + PREFIX_MODULE_CODE + "MODULE_CODE " + PREFIX_TITLE + "TITLE " + PREFIX_CREDITS + "CREDITS ";
+
+ private final Module toAdd;
+
+ /**
+ * Creates an ModuleAddCommand to add the specified {@code Module}
+ */
+ public ModuleAddCommand(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);
+ return new CommandResult(String.format(MESSAGE_MODULE_ADD_SUCCESS, toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ModuleAddCommand // instanceof handles nulls
+ && toAdd.equals(((ModuleAddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/module/ModuleCommand.java b/src/main/java/igrad/logic/commands/module/ModuleCommand.java
new file mode 100644
index 00000000000..185c60d7e7f
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/module/ModuleCommand.java
@@ -0,0 +1,12 @@
+package igrad.logic.commands.module;
+
+import igrad.logic.commands.Command;
+
+/**
+ * Represents a generic module command.
+ */
+public abstract class ModuleCommand extends Command {
+ public static final String MODULE_COMMAND_WORD = "module";
+
+ public static final String MESSAGE_MODULE_NON_EXISTENT = "The module code provided is invalid";
+}
diff --git a/src/main/java/igrad/logic/commands/module/ModuleDeleteCommand.java b/src/main/java/igrad/logic/commands/module/ModuleDeleteCommand.java
new file mode 100644
index 00000000000..dc0e23805ea
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/module/ModuleDeleteCommand.java
@@ -0,0 +1,112 @@
+package igrad.logic.commands.module;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Optional;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.module.Module;
+import igrad.model.module.ModuleCode;
+import igrad.model.requirement.Requirement;
+
+/**
+ * Deletes a {@code Module} identified using it's displayed index from the course book.
+ */
+public class ModuleDeleteCommand extends ModuleCommand {
+
+ public static final String MODULE_DELETE_COMMAND_WORD = MODULE_COMMAND_WORD + SPACE + "delete";
+
+ public static final String MESSAGE_MODULE_DELETE_SUCCESS = "Deleted Module:\n%1$s";
+
+ private final ModuleCode moduleCode;
+
+ public ModuleDeleteCommand(ModuleCode moduleCode) {
+ this.moduleCode = moduleCode;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredModuleList();
+
+ Optional moduleToDeleteOpt = Optional.empty();
+
+ for (Module module : lastShownList) {
+ if (module.getModuleCode().equals(moduleCode)) {
+ moduleToDeleteOpt = Optional.of(module);
+ }
+ }
+
+ if (moduleToDeleteOpt.isEmpty()) {
+ throw new CommandException(MESSAGE_MODULE_NON_EXISTENT);
+ }
+
+ Module moduleToDelete = moduleToDeleteOpt.get();
+
+ model.deleteModule(moduleToDelete);
+
+
+ List requirementsToUpdate = model.getRequirementsWithModule(moduleToDelete);
+
+ /*
+ * Given that this module has been deleted in the modules list, there are two things we need
+ * to do, first is to delete the copies of this modules existing in the modules list of all
+ * requirements containing that module. And the second is that we need to update the
+ * creditsFulfilled of all requirements (which consists of that module).
+ *
+ * The code below does both of these, for each related Requirement.
+ */
+ requirementsToUpdate.stream()
+ .forEach(requirementToEdit -> {
+ // Copy over all the old values of requirementToEdit
+ igrad.model.requirement.RequirementCode requirementCode = requirementToEdit.getRequirementCode();
+ igrad.model.requirement.Title title = requirementToEdit.getTitle();
+
+ /*
+ * Now given that we've delete a module from a requirement, we've to update (recompute)
+ * creditsFulfilled in the relevant Requirements, but since Requirement constructor already does
+ * it for us, based on the module list passed in, we don't have to do anything here, just
+ * propagate the old credits value.
+ */
+ igrad.model.requirement.Credits credits = requirementToEdit.getCredits();
+
+ // Deletes from the existing requirement; requirementToEdit, the moduleToDelete
+ requirementToEdit.removeModule(moduleToDelete);
+
+ // Get the most update module list (now with the new module replaced)
+ List modules = requirementToEdit.getModuleList();
+
+ // Finally, create a new Requirement with all the updated information (details).
+ Requirement editedRequirement = new Requirement(requirementCode, title, credits, modules);
+
+ // Update the current Requirement in the model (coursebook) with this latest version.
+ model.setRequirement(requirementToEdit, editedRequirement);
+ });
+
+ /*
+ * Now that we've deleted a module in the system, we need to update CourseInfo, specifically its cap,
+ * and the Credits (creditsFulfilled) property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(String.format(MESSAGE_MODULE_DELETE_SUCCESS, moduleToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ModuleDeleteCommand // instanceof handles nulls
+ && moduleCode.equals(((ModuleDeleteCommand) other).moduleCode)); // state check
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/module/ModuleDoneCommand.java b/src/main/java/igrad/logic/commands/module/ModuleDoneCommand.java
new file mode 100644
index 00000000000..b17c354413e
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/module/ModuleDoneCommand.java
@@ -0,0 +1,214 @@
+package igrad.logic.commands.module;
+
+import static igrad.commons.util.CollectionUtil.requireAllNonNull;
+import static igrad.logic.parser.CliSyntax.PREFIX_GRADE;
+import static igrad.logic.parser.CliSyntax.PREFIX_MODULE_CODE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.module.Credits;
+import igrad.model.module.Description;
+import igrad.model.module.Grade;
+import igrad.model.module.Memo;
+import igrad.model.module.Module;
+import igrad.model.module.ModuleCode;
+import igrad.model.module.Semester;
+import igrad.model.module.Title;
+import igrad.model.requirement.Requirement;
+import igrad.model.tag.Tag;
+
+/**
+ * Marks the module as done, with a specified grade.
+ */
+public class ModuleDoneCommand extends ModuleCommand {
+ public static final String MODULE_DONE_COMMAND_WORD = MODULE_COMMAND_WORD + SPACE + "done";
+
+ public static final String MESSAGE_MODULE_DONE_DETAILS = MODULE_DONE_COMMAND_WORD + ": Marks a module as done "
+ + "(with a grade) of the module identified by its module code. Existing module (grade) will be overwritten "
+ + "by the input values.\n";
+
+ public static final String MESSAGE_MODULE_DONE_USAGE = "Parameter(s): MODULE CODE "
+ + PREFIX_GRADE + "GRADE\n"
+ + "Example: " + MODULE_DONE_COMMAND_WORD + " "
+ + PREFIX_MODULE_CODE + "CS2103T "
+ + PREFIX_GRADE + "A+";
+
+ public static final String MESSAGE_MODULE_DONE_HELP = MESSAGE_MODULE_DONE_DETAILS + MESSAGE_MODULE_DONE_USAGE;
+
+ public static final String MESSAGE_MODULE_NOT_EDITED = "Grade must be provided.";
+
+ public static final String MESSAGE_MODULE_DONE_SUCCESS = "Marked Module as done:\n%1$s";
+
+ private ModuleCode moduleCode;
+ private EditModuleGradeDescriptor editModuleGradeDescriptor;
+
+ /**
+ * @param moduleCode of the module in the filtered module list to edit
+ * @param editModuleGradeDescriptor details (grade) to edit the module with
+ */
+ public ModuleDoneCommand(ModuleCode moduleCode, EditModuleGradeDescriptor editModuleGradeDescriptor) {
+ requireAllNonNull(moduleCode, editModuleGradeDescriptor);
+
+ this.moduleCode = moduleCode;
+ this.editModuleGradeDescriptor = new EditModuleGradeDescriptor(editModuleGradeDescriptor);
+ }
+
+ /**
+ * Creates and returns a {@code Module} with the details of {@code moduleToEdit}
+ * edited with {@code editModuleGradeDescriptor}.
+ */
+ private static Module createEditedModule(Module moduleToEdit,
+ ModuleDoneCommand.EditModuleGradeDescriptor editModuleGradeDescriptor) {
+ assert moduleToEdit != null;
+
+ // Just copy everything from the original {@code moduleToEdit} to our new {@code Module}
+ ModuleCode moduleCode = moduleToEdit.getModuleCode();
+ Title updatedTitle = moduleToEdit.getTitle();
+ Credits updatedCredits = moduleToEdit.getCredits();
+ Optional updatedMemo = moduleToEdit.getMemo();
+ Optional updatedSemester = moduleToEdit.getSemester();
+ Optional updatedDescription = moduleToEdit.getDescription();
+ Set updatedTags = moduleToEdit.getTags();
+
+ /*
+ * But for Grade, It's compulsory for Grade to be optionally edited/updated. This should have already been
+ * guaranteed through the validations in the ModuleDoneCommandParser
+ */
+ Optional updatedGrade = editModuleGradeDescriptor.getGrade();
+
+ return new Module(updatedTitle, moduleCode, updatedCredits, updatedMemo, updatedSemester,
+ updatedDescription, updatedGrade, updatedTags);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // Retrieve the module we want to mark a grade done with
+ Module moduleToEdit = model.getModuleByModuleCode(moduleCode)
+ .orElseThrow(() -> new CommandException(MESSAGE_MODULE_NON_EXISTENT));
+
+ // Create a new module based on the edited grade.
+ Module editedModule = createEditedModule(moduleToEdit, editModuleGradeDescriptor);
+
+ // Update the module in our model
+ model.setModule(moduleToEdit, editedModule);
+
+
+
+ List requirementsToUpdate = model.getRequirementsWithModule(editedModule);
+
+ /*
+ * Given that this module has been updated in the modules list, there are two things we need
+ * to do, first is to update the copies of this modules existing in the modules list of all
+ * requirements containing that module. And the second is that we need to update the
+ * creditsFulfilled of all requirements (which consists of that module).
+ *
+ * The code below does both of these, for each related Requirement.
+ */
+ requirementsToUpdate.stream()
+ .forEach(requirementToEdit -> {
+ // Copy over all the old values of requirementToEdit
+ igrad.model.requirement.RequirementCode requirementCode = requirementToEdit.getRequirementCode();
+ igrad.model.requirement.Title title = requirementToEdit.getTitle();
+
+ /*
+ * Now given that we've marked a module in a requirement as done, we've to update (recompute)
+ * creditsFulfilled in the relevant Requirements, but since Requirement constructor already does
+ * it for us, based on the module list passed in, we don't have to do anything here, just
+ * propagate the old credits value.
+ */
+ igrad.model.requirement.Credits credits = requirementToEdit.getCredits();
+
+ // Updates the existing requirement; requirementToEdit with the editedModule
+ requirementToEdit.setModule(moduleToEdit, editedModule);
+
+ // Get the most update module list (now with the new module replaced)
+ List modules = requirementToEdit.getModuleList();
+
+ // Finally, create a new Requirement with all the updated information (details).
+ Requirement editedRequirement = new Requirement(requirementCode, title, credits, modules);
+
+ // Update the current Requirement in the model (coursebook) with this latest version.
+ model.setRequirement(requirementToEdit, editedRequirement);
+ });
+
+ /*
+ * Now that we've deleted a module in the system, we need to update CourseInfo, specifically its cap,
+ * and the Credits (creditsFulfilled) property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(String.format(MESSAGE_MODULE_DONE_SUCCESS, editedModule));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ /*
+ * TODO (Teri): Please take a look at how ModuleEditCommand.java
+ * implements this, and fill it up!
+ */
+
+ return false;
+ }
+
+ /**
+ * Stores the grade to edit the module with, and its used in the module done command to mark (edit)
+ * a module with a grade. Each non-empty field value will replace the
+ * corresponding field value of the module.
+ */
+ public static class EditModuleGradeDescriptor {
+ private Optional grade;
+
+ public EditModuleGradeDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ */
+ public EditModuleGradeDescriptor(ModuleDoneCommand.EditModuleGradeDescriptor toCopy) {
+ setGrade(toCopy.grade);
+ }
+
+ public Optional getGrade() {
+ return grade;
+ }
+
+ public void setGrade(Optional grade) {
+ this.grade = grade;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ModuleDoneCommand.EditModuleGradeDescriptor)) {
+ return false;
+ }
+
+ // state check
+ ModuleDoneCommand.EditModuleGradeDescriptor e = (ModuleDoneCommand.EditModuleGradeDescriptor) other;
+
+ return getGrade().equals(e.getGrade());
+ }
+ }
+
+}
diff --git a/src/main/java/igrad/logic/commands/module/ModuleEditCommand.java b/src/main/java/igrad/logic/commands/module/ModuleEditCommand.java
new file mode 100644
index 00000000000..3bc0a00e0a2
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/module/ModuleEditCommand.java
@@ -0,0 +1,335 @@
+package igrad.logic.commands.module;
+
+import static igrad.commons.util.CollectionUtil.requireAllNonNull;
+import static igrad.logic.parser.CliSyntax.PREFIX_CREDITS;
+import static igrad.logic.parser.CliSyntax.PREFIX_MEMO;
+import static igrad.logic.parser.CliSyntax.PREFIX_MODULE_CODE;
+import static igrad.logic.parser.CliSyntax.PREFIX_SEMESTER;
+import static igrad.logic.parser.CliSyntax.PREFIX_TAG;
+import static igrad.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import igrad.commons.util.CollectionUtil;
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.module.Credits;
+import igrad.model.module.Description;
+import igrad.model.module.Grade;
+import igrad.model.module.Memo;
+import igrad.model.module.Module;
+import igrad.model.module.ModuleCode;
+import igrad.model.module.Semester;
+import igrad.model.module.Title;
+import igrad.model.requirement.Requirement;
+import igrad.model.tag.Tag;
+
+/**
+ * Edits the details (course name) of the existing module.
+ */
+public class ModuleEditCommand extends ModuleCommand {
+
+ public static final String MODULE_EDIT_COMMAND_WORD = MODULE_COMMAND_WORD + SPACE + "edit";
+
+ public static final String MESSAGE_MODULE_EDIT_DETAILS = MODULE_EDIT_COMMAND_WORD + ": Edits the details of the "
+ + "module identified by its module code. Existing module will be overwritten by the input values.\n";
+
+ public static final String MESSAGE_MODULE_EDIT_USAGE = "Parameter(s): MODULE CODE "
+ + "[" + PREFIX_MODULE_CODE + "MODULE_CODE] "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_CREDITS + "CREDITS] "
+ + "[" + PREFIX_MEMO + "MEMO] "
+ + "[" + PREFIX_SEMESTER + "SEMESTER] "
+ + "[" + PREFIX_TAG + "TAGS]...\n"
+ + "Example: " + MODULE_EDIT_COMMAND_WORD + " CS2040 "
+ + PREFIX_MODULE_CODE + "CS2040S "
+ + PREFIX_CREDITS + "4";
+
+ public static final String MESSAGE_MODULE_EDIT_HELP = MESSAGE_MODULE_EDIT_DETAILS + MESSAGE_MODULE_EDIT_USAGE;
+
+ public static final String MESSAGE_MODULE_EDIT_SUCCESS = "Edited Module:\n%1$s";
+ public static final String MESSAGE_MODULE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_MODULE = "This module already exists in the course book.";
+
+ protected final ModuleCode moduleCode;
+ protected final EditModuleDescriptor editModuleDescriptor;
+
+ /**
+ * @param moduleCode of the module in the filtered module list to edit
+ * @param editModuleDescriptor details to edit the module with
+ */
+ public ModuleEditCommand(ModuleCode moduleCode, EditModuleDescriptor editModuleDescriptor) {
+ requireAllNonNull(moduleCode, editModuleDescriptor);
+
+ this.moduleCode = moduleCode;
+ this.editModuleDescriptor = new EditModuleDescriptor(editModuleDescriptor);
+ }
+
+ /**
+ * Creates and returns a {@code Module} with the details of {@code moduleToEdit}
+ * edited with {@code editModuleDescriptor}.
+ */
+ private static Module createEditedModule(Module moduleToEdit, EditModuleDescriptor editModuleDescriptor) {
+ assert moduleToEdit != null;
+
+ ModuleCode moduleCode = moduleToEdit.getModuleCode();
+
+ // All fields can be optionally updated
+ Title updatedTitle = editModuleDescriptor.getTitle().orElse(moduleToEdit.getTitle());
+ ModuleCode updatedModuleCode = editModuleDescriptor.getModuleCode().orElse(moduleToEdit.getModuleCode());
+ Credits updatedCredits = editModuleDescriptor.getCredits().orElse(moduleToEdit.getCredits());
+ Optional updatedMemo = editModuleDescriptor.getMemo().orElse(moduleToEdit.getMemo());
+ Optional updatedSemester = editModuleDescriptor.getSemester().orElse(moduleToEdit.getSemester());
+ Optional updatedDescription = editModuleDescriptor.getDescription()
+ .orElse(moduleToEdit.getDescription());
+ Set updatedTags = editModuleDescriptor.getTags().orElse(moduleToEdit.getTags());
+
+ /*
+ * (Note): Grade cannot be edited here (using the edit command), have to do so using the module done
+ * command instead. Hence, we're just setting it to whatever value it originally was in the Model classes.
+ */
+ Optional updatedGrade = moduleToEdit.getGrade();
+
+ return new Module(updatedTitle, updatedModuleCode, updatedCredits, updatedMemo, updatedSemester,
+ updatedDescription, updatedGrade, updatedTags);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredModuleList();
+
+ /*
+ * TODO: For this i still prefer how yijie does it, using streams to query
+ * the list, it looks much neater, please take a look at RequirementEditCommand.java.
+ * However, over there I've also recommended her to have a method in the Model interface
+ * which gets a requirement/module (in your case), by RequirementName/ModuleCode
+ * ~ nathanael
+ */
+ Optional moduleToEditOpt = Optional.empty();
+
+ for (Module module : lastShownList) {
+ if (module.getModuleCode().equals(moduleCode)) {
+ moduleToEditOpt = Optional.of(module);
+ }
+ }
+
+ if (moduleToEditOpt.isEmpty()) {
+ throw new CommandException(MESSAGE_MODULE_NON_EXISTENT);
+ }
+
+ Module moduleToEdit = moduleToEditOpt.get();
+ Module editedModule = createEditedModule(moduleToEdit, editModuleDescriptor);
+
+ if (!moduleToEdit.isSameModule(editedModule) && model.hasModule(editedModule)) {
+ throw new CommandException(MESSAGE_DUPLICATE_MODULE);
+ }
+
+ model.setModule(moduleToEdit, editedModule);
+ // model.updateFilteredModuleList(Model.PREDICATE_SHOW_ALL_MODULES);
+
+ List requirementsToUpdate = model.getRequirementsWithModule(editedModule);
+
+ /*
+ * Given that this module has been updated in the modules list, there are two things we need
+ * to do, first is to delete the copies of this modules existing in the modules list of all
+ * requirements containing that module. And the second is that we need to update the
+ * creditsFulfilled of all requirements (which consists of that module).
+ *
+ * The code below does both of these, for each related Requirement.
+ */
+ requirementsToUpdate.stream()
+ .forEach(requirementToEdit -> {
+ // Copy over all the old values of requirementToEdit
+ igrad.model.requirement.RequirementCode requirementCode = requirementToEdit.getRequirementCode();
+ igrad.model.requirement.Title title = requirementToEdit.getTitle();
+
+ /*
+ * Now given that we've edited a module from a requirement, we've to update (recompute)
+ * creditsFulfilled in the relevant Requirements, but since Requirement constructor already does
+ * it for us, based on the module list passed in, we don't have to do anything here, just
+ * propagate the old credits value.
+ */
+ igrad.model.requirement.Credits credits = requirementToEdit.getCredits();
+
+ // Updates the existing requirement; requirementToEdit with the editedModule
+ requirementToEdit.setModule(moduleToEdit, editedModule);
+
+ // Get the most update module list (now with the new module replaced)
+ List modules = requirementToEdit.getModuleList();
+
+ // Finally, create a new Requirement with all the updated information (details).
+ Requirement editedRequirement = new Requirement(requirementCode, title, credits, modules);
+
+ // Update the current Requirement in the model (coursebook) with this latest version.
+ model.setRequirement(requirementToEdit, editedRequirement);
+ });
+
+ /*
+ * Also, due to the module being edited, we need to update CourseInfo, specifically its creditsFulfilled
+ * and cap property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(String.format(MESSAGE_MODULE_EDIT_SUCCESS, editedModule));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ModuleEditCommand)) {
+ return false;
+ }
+
+ // state check
+ ModuleEditCommand e = (ModuleEditCommand) other;
+ return moduleCode.equals(e.moduleCode)
+ && editModuleDescriptor.equals(e.editModuleDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the module with. Each non-empty field value will replace the
+ * corresponding field value of the module.
+ */
+ public static class EditModuleDescriptor {
+ private Title title;
+ private ModuleCode moduleCode;
+ private Credits credits;
+ private Optional memo;
+ private Optional description;
+ private Optional semester;
+ private Set tags;
+
+ public EditModuleDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditModuleDescriptor(EditModuleDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setModuleCode(toCopy.moduleCode);
+ setCredits(toCopy.credits);
+ setMemo(toCopy.memo);
+ setSemester(toCopy.semester);
+ setDescription(toCopy.description);
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, moduleCode, credits, memo, description, semester, tags);
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(title);
+ }
+
+ public void setTitle(Title title) {
+ this.title = title;
+ }
+
+ public Optional getModuleCode() {
+ return Optional.ofNullable(moduleCode);
+ }
+
+ public void setModuleCode(ModuleCode moduleCode) {
+ this.moduleCode = moduleCode;
+ }
+
+ public Optional getCredits() {
+ return Optional.ofNullable(credits);
+ }
+
+ public void setCredits(Credits credits) {
+ this.credits = credits;
+ }
+
+ public Optional> getMemo() {
+ return Optional.ofNullable(memo);
+ }
+
+ public void setMemo(Optional memo) {
+ this.memo = memo;
+ }
+
+ public Optional> getSemester() {
+ return Optional.ofNullable(semester);
+ }
+
+ public void setSemester(Optional semester) {
+ this.semester = semester;
+ }
+
+ public Optional> getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setDescription(Optional description) {
+ this.description = description;
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditModuleDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditModuleDescriptor e = (EditModuleDescriptor) other;
+
+ return getTitle().equals(e.getTitle())
+ && getModuleCode().equals(e.getModuleCode())
+ && getCredits().equals(e.getCredits())
+ && getMemo().equals(e.getMemo())
+ && getDescription().equals(e.getDescription())
+ && getSemester().equals(e.getSemester())
+ && getTags().equals(e.getTags());
+ }
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/requirement/RequirementAddCommand.java b/src/main/java/igrad/logic/commands/requirement/RequirementAddCommand.java
new file mode 100644
index 00000000000..c50fc9a23ce
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/requirement/RequirementAddCommand.java
@@ -0,0 +1,101 @@
+package igrad.logic.commands.requirement;
+
+import static igrad.logic.parser.CliSyntax.PREFIX_CREDITS;
+import static igrad.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.requirement.Requirement;
+import igrad.model.requirement.RequirementCode;
+
+/**
+ * Adds a requirement to the course.
+ */
+public class RequirementAddCommand extends RequirementCommand {
+ public static final String REQUIREMENT_ADD_COMMAND_WORD = REQUIREMENT_COMMAND_WORD + SPACE + "add";
+
+ public static final String MESSAGE_DETAILS = REQUIREMENT_ADD_COMMAND_WORD + ": Adds a requirement.\n";
+
+ public static final String MESSAGE_USAGE = "Parameter(s): "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_CREDITS + "CREDITS_TO_FULFIL\n"
+ + "Example: " + REQUIREMENT_ADD_COMMAND_WORD + " "
+ + PREFIX_TITLE + "Unrestricted Electives "
+ + PREFIX_CREDITS + "24\n";
+
+ public static final String MESSAGE_REQUIREMENT_ADD_HELP = MESSAGE_DETAILS + MESSAGE_USAGE;
+
+ public static final String MESSAGE_REQUIREMENT_ADD_SUCCESS = "New requirement added:\n%1$s";
+ public static final String MESSAGE_REQUIREMENT_NOT_ADDED = "Added requirement must be provided with arguments "
+ + PREFIX_TITLE + "TITLE " + PREFIX_CREDITS + "CREDITS ";
+ public static final String MESSAGE_REQUIREMENT_DUPLICATE = "This requirement already exists in the course book.";
+
+ private final Requirement requirementToAdd;
+
+ public RequirementAddCommand(Requirement requirementToAdd) {
+ requireNonNull(requirementToAdd);
+
+ this.requirementToAdd = requirementToAdd;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // if the name of the requirement has already been used
+ if (model.hasRequirement(requirementToAdd)) {
+ throw new CommandException(MESSAGE_REQUIREMENT_DUPLICATE);
+ }
+
+ RequirementCode codeWithoutNumber = requirementToAdd.getRequirementCode();
+ RequirementCode codeWithNumber = new RequirementCode(generateRequirementCode(model, codeWithoutNumber));
+
+ Requirement requirement = new Requirement(codeWithNumber,
+ requirementToAdd.getTitle(),
+ requirementToAdd.getCredits());
+
+ model.addRequirement(requirement);
+
+ /*
+ * Now that we've added a new Requirement to the system, we need to update CourseInfo, specifically its
+ * creditsRequired property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(String.format(MESSAGE_REQUIREMENT_ADD_SUCCESS, requirement));
+ }
+
+ /**
+ * Generates the requirement code based on the number of previous requirements that hold the same
+ * alphabetical part of the code.
+ */
+ private String generateRequirementCode(Model model, RequirementCode codeWithoutNumber) {
+ List requirementList = model.getRequirementList();
+
+ int lastUsedNumber = 0;
+ for (Requirement requirement : requirementList) {
+ RequirementCode requirementCode = requirement.getRequirementCode();
+
+ if (requirementCode.hasSameAlphabets(codeWithoutNumber)) {
+ int requirementNumber = requirementCode.getNumber();
+ if (lastUsedNumber <= requirementNumber) {
+ lastUsedNumber = requirementNumber + 1;
+ }
+ }
+ }
+
+ return codeWithoutNumber.getAlphabets() + lastUsedNumber;
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/requirement/RequirementAssignCommand.java b/src/main/java/igrad/logic/commands/requirement/RequirementAssignCommand.java
new file mode 100644
index 00000000000..93a043b49d8
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/requirement/RequirementAssignCommand.java
@@ -0,0 +1,117 @@
+package igrad.logic.commands.requirement;
+
+import static igrad.commons.util.CollectionUtil.requireAllNonNull;
+import static igrad.logic.parser.CliSyntax.PREFIX_MODULE_CODE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.module.Module;
+import igrad.model.module.ModuleCode;
+import igrad.model.requirement.Requirement;
+import igrad.model.requirement.RequirementCode;
+import igrad.model.requirement.Title;
+
+/**
+ * Assigns modules under a particular requirement.
+ */
+public class RequirementAssignCommand extends RequirementCommand {
+ public static final String REQUIREMENT_ASSIGN_COMMAND_WORD = REQUIREMENT_COMMAND_WORD + SPACE + "assign";
+
+ public static final String REQUIREMENT_ASSIGN_MESSAGE_DETAILS = REQUIREMENT_ASSIGN_COMMAND_WORD
+ + ": Assigns the requirement identified with modules "
+ + "by its requirement code. Existing requirement will be overwritten by the input values\n";
+
+ public static final String REQUIREMENT_ASSIGN_MESSAGE_USAGE = "Parameter(s): REQUIREMENT_CODE "
+ + PREFIX_MODULE_CODE + "MODULE_CODE]...\n";
+
+ public static final String REQUIREMENT_ASSIGN_MESSAGE_HELP = REQUIREMENT_ASSIGN_MESSAGE_DETAILS
+ + REQUIREMENT_ASSIGN_MESSAGE_USAGE;
+
+ public static final String MESSAGE_REQUIREMENT_NO_MODULES = "There must be at least one modules assigned.";
+
+ public static final String MESSAGE_MODULES_NON_EXISTENT =
+ "Not all Modules exist in the system. Please try other modules.";
+
+ public static final String MESSAGE_MODULES_ALREADY_EXIST_IN_REQUIREMENT =
+ "Some Modules already exists in this requirement. Please try other modules.";
+ public static final String MESSAGE_SUCCESS = "Modules assigned under Requirement:\n%1$s";
+
+ private RequirementCode requirementCode;
+ private List moduleCodes;
+
+ public RequirementAssignCommand(RequirementCode requirementCode, List moduleCodes) {
+ requireAllNonNull(requirementCode, moduleCodes);
+
+ this.requirementCode = requirementCode;
+ this.moduleCodes = moduleCodes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // Retrieve the requirement in question that we want to assign modules under..
+
+ // First check if the requirement exists in the course book
+ Requirement requirementToAssign = model.getRequirement(requirementCode)
+ .orElseThrow(() -> new CommandException(MESSAGE_REQUIREMENT_NON_EXISTENT));
+
+
+ final List modulesToAssign = model.getModulesByModuleCode(moduleCodes);
+
+ // First check, if all modules (codes) are existent modules in the course book (they should all be)
+ if (modulesToAssign.size() < moduleCodes.size()) {
+ throw new CommandException(MESSAGE_MODULES_NON_EXISTENT);
+ }
+
+ // Now check, if any modules specified are existent in the requirement (they should not)
+ if (requirementToAssign.hasModule(modulesToAssign)) {
+ throw new CommandException(MESSAGE_MODULES_ALREADY_EXIST_IN_REQUIREMENT);
+ }
+
+ // Finally if everything alright, we can actually then assign/add the specified modules under this requirement
+ requirementToAssign.addModules(modulesToAssign);
+
+ // First, we copy over all the old values of requirementToAssign
+ RequirementCode requirementCode = requirementToAssign.getRequirementCode();
+ Title title = requirementToAssign.getTitle();
+
+ /*
+ * Now given that we've added this list of new modules to requirement, we've to update (recompute)
+ * creditsFulfilled, but since Requirement constructor already does it for us, based
+ * on the module list passed in, we don't have to do anything here, just propage
+ * the old credits value.
+ */
+ igrad.model.requirement.Credits credits = requirementToAssign.getCredits();
+
+ // Get the most update module list (now with the new modules assigned/added)
+ List modules = requirementToAssign.getModuleList();
+
+ // Finally, create a new Requirement with all the updated information (details).
+ Requirement editedRequirement = new Requirement(requirementCode, title, credits, modules);
+
+ model.setRequirement(requirementToAssign, editedRequirement);
+
+ /*
+ * Now that we've assigned some modules under a particular Requirement to the system, we need to update
+ * CourseInfo, specifically its creditsFulfilled property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(
+ String.format(MESSAGE_SUCCESS, editedRequirement));
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/requirement/RequirementCommand.java b/src/main/java/igrad/logic/commands/requirement/RequirementCommand.java
new file mode 100644
index 00000000000..f4d6d2de93d
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/requirement/RequirementCommand.java
@@ -0,0 +1,13 @@
+package igrad.logic.commands.requirement;
+
+import igrad.logic.commands.Command;
+
+/**
+ * A generic Requirement command class.
+ */
+public abstract class RequirementCommand extends Command {
+ public static final String REQUIREMENT_COMMAND_WORD = "requirement";
+
+ public static final String MESSAGE_REQUIREMENT_NON_EXISTENT = "The requirement code provided is invalid.";
+
+}
diff --git a/src/main/java/igrad/logic/commands/requirement/RequirementDeleteCommand.java b/src/main/java/igrad/logic/commands/requirement/RequirementDeleteCommand.java
new file mode 100644
index 00000000000..da04ac8f874
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/requirement/RequirementDeleteCommand.java
@@ -0,0 +1,73 @@
+package igrad.logic.commands.requirement;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Optional;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.requirement.Requirement;
+import igrad.model.requirement.RequirementCode;
+
+/**
+ * Deletes an existing requirement from the course book.
+ */
+public class RequirementDeleteCommand extends RequirementCommand {
+
+ public static final String REQUIREMENT_DELETE_COMMAND_WORD = REQUIREMENT_COMMAND_WORD + SPACE + "delete";
+
+ public static final String MESSAGE_DETAILS = REQUIREMENT_DELETE_COMMAND_WORD + ": Deletes the requirement "
+ + "identified by its requirement code.\n";
+
+ public static final String MESSAGE_USAGE = "Parameter(s): REQUIREMENT_CODE\n"
+ + "Example: " + REQUIREMENT_DELETE_COMMAND_WORD + " UE0";
+
+ public static final String MESSAGE_REQUIREMENT_DELETE_HELP = MESSAGE_DETAILS + MESSAGE_USAGE;
+
+ public static final String MESSAGE_REQUIREMENT_DELETE_SUCCESS = "Deleted Requirement:\n%1$s";
+
+ private final RequirementCode requirementCode;
+
+ public RequirementDeleteCommand(RequirementCode requirementCode) {
+ requireNonNull(requirementCode);
+
+ this.requirementCode = requirementCode;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List requirements = model.getRequirementList();
+
+ // check if requirement exists in course book
+ Optional requirementToDelete = requirements.stream()
+ .filter(requirement -> requirement.getRequirementCode().equals(requirementCode)).findFirst();
+
+ if (requirementToDelete.isEmpty()) {
+ throw new CommandException(MESSAGE_REQUIREMENT_NON_EXISTENT);
+ }
+
+ Requirement toDelete = requirementToDelete.get();
+
+ model.deleteRequirement(toDelete);
+
+ /*
+ * Now that we've deleted a new Requirement in the system, we need to update CourseInfo, specifically its
+ * creditsRequired and creditsFulfilled property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ return new CommandResult(
+ String.format(MESSAGE_REQUIREMENT_DELETE_SUCCESS, toDelete));
+ }
+
+}
diff --git a/src/main/java/igrad/logic/commands/requirement/RequirementEditCommand.java b/src/main/java/igrad/logic/commands/requirement/RequirementEditCommand.java
new file mode 100644
index 00000000000..a46add86081
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/requirement/RequirementEditCommand.java
@@ -0,0 +1,147 @@
+package igrad.logic.commands.requirement;
+
+import static igrad.commons.util.CollectionUtil.requireAllNonNull;
+import static igrad.logic.parser.CliSyntax.PREFIX_CREDITS;
+import static igrad.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Optional;
+
+import igrad.commons.util.CollectionUtil;
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.module.Module;
+import igrad.model.requirement.Credits;
+import igrad.model.requirement.Requirement;
+import igrad.model.requirement.RequirementCode;
+import igrad.model.requirement.Title;
+
+/**
+ * Modifies an existing requirement in the course book.
+ */
+public class RequirementEditCommand extends RequirementCommand {
+ public static final String REQUIREMENT_EDIT_COMMAND_WORD = REQUIREMENT_COMMAND_WORD + SPACE + "edit";
+
+ public static final String MESSAGE_DETAILS = REQUIREMENT_EDIT_COMMAND_WORD + ": Edits the requirement identified "
+ + "by its requirement code. Existing requirement will be overwritten by the input values.\n";
+
+ public static final String MESSAGE_USAGE = "Parameter(s): REQUIREMENT_CODE "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_CREDITS + "CREDITS]\n"
+ + "Example: " + REQUIREMENT_EDIT_COMMAND_WORD + " UE0 "
+ + PREFIX_TITLE + "Unrestricted Electives";
+
+ public static final String MESSAGE_REQUIREMENT_EDIT_HELP = MESSAGE_DETAILS + MESSAGE_USAGE;
+
+ public static final String MESSAGE_REQUIREMENT_EDIT_SUCCESS = "Edited Requirement:\n%1$s";
+ public static final String MESSAGE_REQUIREMENT_NOT_EDITED = "At least one field must be modified.";
+
+ private final RequirementCode requirementCode;
+
+ private final EditRequirementDescriptor requirementDescriptor;
+
+ public RequirementEditCommand(RequirementCode requirementCode,
+ EditRequirementDescriptor requirementDescriptor) {
+ requireAllNonNull(requirementCode, requirementDescriptor);
+
+ this.requirementCode = requirementCode;
+ this.requirementDescriptor = new EditRequirementDescriptor(requirementDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Requirement requirementToEdit = model.getRequirement(requirementCode)
+ .orElseThrow(() -> new CommandException(MESSAGE_REQUIREMENT_NON_EXISTENT));
+
+ Requirement editedRequirement = createEditedRequirement(requirementToEdit, requirementDescriptor);
+
+ // If none of the parameters have been modified
+ if (editedRequirement.equals(requirementToEdit)) {
+ throw new CommandException(MESSAGE_REQUIREMENT_NOT_EDITED);
+ }
+
+ model.setRequirement(requirementToEdit, editedRequirement);
+ //model.updateRequirementList(Model.PREDICATE_SHOW_ALL_REQUIREMENTS);
+
+ /*
+ * Now that we've edited a new Requirement in the system, we need to update CourseInfo, specifically its
+ * creditsRequired property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(String.format(MESSAGE_REQUIREMENT_EDIT_SUCCESS, editedRequirement));
+ }
+
+ /**
+ * Creates and returns a {@code Requirement} with the details of {@code requirementToEdit}
+ * edited with {@code editRequirementDescriptor}.
+ */
+ private static Requirement createEditedRequirement(Requirement requirementToEdit,
+ EditRequirementDescriptor editRequirementDescriptor) {
+ assert requirementToEdit != null;
+ assert editRequirementDescriptor != null;
+
+ Title updatedTitle = editRequirementDescriptor.getTitle().orElse(requirementToEdit.getTitle());
+ Credits updatedCredits = editRequirementDescriptor.getCredits().orElse(requirementToEdit.getCredits());
+ RequirementCode requirementCode = requirementToEdit.getRequirementCode();
+ List moduleList = requirementToEdit.getModuleList();
+
+ return new Requirement(requirementCode, updatedTitle, updatedCredits, moduleList);
+ }
+
+ /**
+ * Stores the details to edit the requirement with. Each non-empty field value will replace the
+ * corresponding field value of the person.
+ */
+ public static class EditRequirementDescriptor {
+ private Title title;
+ private Credits credits;
+
+ public EditRequirementDescriptor() {
+ }
+
+ /**
+ * Makes a copy of a EditRequirementDescriptor.
+ */
+ public EditRequirementDescriptor(EditRequirementDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setCredits(toCopy.credits);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, credits);
+ }
+
+ public void setTitle(Title title) {
+ this.title = title;
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(title);
+ }
+
+ public void setCredits(Credits credits) {
+ this.credits = credits;
+ }
+
+ public Optional getCredits() {
+ return Optional.ofNullable(credits);
+ }
+ }
+}
diff --git a/src/main/java/igrad/logic/commands/requirement/RequirementUnassignCommand.java b/src/main/java/igrad/logic/commands/requirement/RequirementUnassignCommand.java
new file mode 100644
index 00000000000..0ce846eed34
--- /dev/null
+++ b/src/main/java/igrad/logic/commands/requirement/RequirementUnassignCommand.java
@@ -0,0 +1,121 @@
+package igrad.logic.commands.requirement;
+
+import static igrad.commons.util.CollectionUtil.requireAllNonNull;
+import static igrad.logic.parser.CliSyntax.PREFIX_MODULE_CODE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import igrad.logic.commands.CommandResult;
+import igrad.logic.commands.CommandUtil;
+import igrad.logic.commands.exceptions.CommandException;
+import igrad.model.Model;
+import igrad.model.course.CourseInfo;
+import igrad.model.module.Module;
+import igrad.model.module.ModuleCode;
+import igrad.model.requirement.Requirement;
+import igrad.model.requirement.RequirementCode;
+import igrad.model.requirement.Title;
+
+/**
+ * Unassigns modules under a particular requirement.
+ */
+public class RequirementUnassignCommand extends RequirementCommand {
+ public static final String REQUIREMENT_UNASSIGN_COMMAND_WORD = REQUIREMENT_COMMAND_WORD + SPACE
+ + "unassign";
+
+ public static final String REQUIREMENT_UNASSIGN_MESSAGE_DETAILS = REQUIREMENT_UNASSIGN_COMMAND_WORD
+ + ": Unassigns the requirement identified with modules "
+ + "by its requirement code. Existing requirement will be overwritten by the input values\n";
+
+ public static final String REQUIREMENT_UNASSIGN_MESSAGE_USAGE = "Parameter(s): REQUIREMENT_CODE "
+ + PREFIX_MODULE_CODE + "MODULE_CODE]...\n";
+
+ public static final String REQUIREMENT_UNASSIGN_MESSAGE_HELP = REQUIREMENT_UNASSIGN_MESSAGE_DETAILS
+ + REQUIREMENT_UNASSIGN_MESSAGE_USAGE;
+
+ public static final String MESSAGE_REQUIREMENT_NO_MODULES = "There must be at least one modules unassigned.";
+
+ public static final String MESSAGE_MODULES_NON_EXISTENT =
+ "Not all Modules exist in the system. Please try other modules.";
+
+ public static final String MESSAGE_MODULES_ALREADY_EXIST_IN_REQUIREMENT =
+ "Some Modules already exists in this requirement. Please try other modules.";
+ public static final String MESSAGE_SUCCESS = "Modules unassigned under Requirement:\n%1$s";
+
+ private RequirementCode requirementCode;
+ private List moduleCodes;
+
+ public RequirementUnassignCommand(RequirementCode requirementCode, List moduleCodes) {
+ requireAllNonNull(requirementCode, moduleCodes);
+
+ this.requirementCode = requirementCode;
+ this.moduleCodes = moduleCodes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // Retrieve the requirement in question that we want to unassign modules under..
+
+ // First check if the requirement exists in the course book
+ Requirement requirementToUnassign = model.getRequirement(requirementCode)
+ .orElseThrow(() -> new CommandException(MESSAGE_REQUIREMENT_NON_EXISTENT));
+
+
+ final List modulesToUnassign = model.getModulesByModuleCode(moduleCodes);
+
+ // First check, if all modules (codes) are existent modules in the course book (they should all be)
+ if (modulesToUnassign.size() < moduleCodes.size()) {
+ throw new CommandException(MESSAGE_MODULES_NON_EXISTENT);
+ }
+
+ // Now check, if all modules specified are existent in the requirement (they should be)
+ if (!requirementToUnassign.hasModule(modulesToUnassign)) {
+ throw new CommandException(MESSAGE_MODULES_ALREADY_EXIST_IN_REQUIREMENT);
+ }
+
+ /*
+ * Finally if everything alright, we can actually then unassign/'delete' the specified modules under
+ * this requirement
+ */
+ requirementToUnassign.removeModules(modulesToUnassign);
+
+ // First, we copy over all the old values of requirementToUnassign
+ RequirementCode requirementCode = requirementToUnassign.getRequirementCode();
+ Title title = requirementToUnassign.getTitle();
+
+ /*
+ * Now given that we've added this list of new modules to requirement, we've to update (recompute)
+ * creditsFulfilled, but since Requirement constructor already does it for us, based
+ * on the module list passed in, we don't have to do anything here, just propage
+ * the old credits value.
+ */
+ igrad.model.requirement.Credits credits = requirementToUnassign.getCredits();
+
+ // Get the most update module list (now with the new modules unassigned/'deleted')
+ List modules = requirementToUnassign.getModuleList();
+
+ // Finally, create a new Requirement with all the updated information (details).
+ Requirement editedRequirement = new Requirement(requirementCode, title, credits, modules);
+
+ model.setRequirement(requirementToUnassign, editedRequirement);
+
+ /*
+ * Now that we've assigned some modules under a particular Requirement to the system, we need to update
+ * CourseInfo, specifically its creditsFulfilled property.
+ *
+ * However, in the method below, we just recompute everything (field in course info).
+ */
+ CourseInfo courseToEdit = model.getCourseInfo();
+
+ CourseInfo editedCourseInfo = CommandUtil.retrieveLatestCourseInfo(courseToEdit, model);
+
+ // Updating the model with the latest course info
+ model.setCourseInfo(editedCourseInfo);
+
+ return new CommandResult(
+ String.format(MESSAGE_SUCCESS, editedRequirement));
+ }
+}
diff --git a/src/main/java/igrad/logic/parser/ArgumentMultimap.java b/src/main/java/igrad/logic/parser/ArgumentMultimap.java
new file mode 100644
index 00000000000..aeddccd803e
--- /dev/null
+++ b/src/main/java/igrad/logic/parser/ArgumentMultimap.java
@@ -0,0 +1,112 @@
+package igrad.logic.parser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Stores mapping of prefixes to their respective arguments.
+ * Each key may be associated with multiple argument values.
+ * Values for a given key are stored in a list, and the insertion ordering is maintained.
+ * Keys are unique, but the list of argument values may contain duplicate argument values, i.e. the same argument value
+ * can be inserted multiple times for the same prefix.
+ */
+public class ArgumentMultimap {
+ private static final Prefix PREFIX_PREAMBLE = new Prefix("");
+
+ /**
+ * Prefixes mapped to their respective arguments
+ **/
+ private final Map> argMultimap = new HashMap<>();
+
+ /**
+ * Associates the specified argument value with {@code prefix} key in this map.
+ * If the map previously contained a mapping for the key, the new value is appended to the list of existing values.
+ *
+ * @param prefix Prefix key with which the specified argument value is to be associated
+ * @param argValue Argument value to be associated with the specified prefix key
+ */
+ public void put(Prefix prefix, String argValue) {
+ List argValues = getAllValues(prefix);
+ argValues.add(argValue);
+ argMultimap.put(prefix, argValues);
+ }
+
+ /**
+ * Returns the last value of {@code prefix}.
+ */
+ public Optional getValue(Prefix prefix) {
+ List values = getAllValues(prefix);
+ return values.isEmpty() ? Optional.empty() : Optional.of(values.get(values.size() - 1));
+ }
+
+ /**
+ * Returns all values of {@code prefix}.
+ * If the prefix does not exist or has no values, this will return an empty list.
+ * Modifying the returned list will not affect the underlying data structure of the ArgumentMultimap.
+ */
+ public List getAllValues(Prefix prefix) {
+ if (!argMultimap.containsKey(prefix)) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(argMultimap.get(prefix));
+ }
+
+ /**
+ * Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces.
+ */
+ public String getPreamble() {
+ return getValue(new Prefix("")).orElse("");
+ }
+
+
+ /**
+ * Returns true if values of all key-value pairs in the {@code argMultimap} field (of this class),
+ * is empty. Also, if {@code checkPreamble} parameter is true, this method checks if those preambles
+ * are empty (empty string; ""), else any preamble is ignored.
+ *
+ * In other words, this method returns true if and only if there are no other arguments or specifiers
+ * entered after a command whose format have preambles (i.e, checkPreamble is true);
+ * e.g, 'module edit', 'requirement edit', etc.
+ * (Note: doesn't apply to all commands; e.g, module delete)
+ *
+ * For those command whose format have preambles (i.e, checkPreamble is false), this method ignores them
+ * and returns true if and only if there are no other arguments within it;
+ * e.g, 'module add BLAH' => returns true
+ */
+ public boolean isEmpty(boolean checkPreamble) {
+ /*
+ * If our hash-map of key-value pairs contain more than one key then, we are guaranteed that
+ * our command is not empty (although they may contain superfluous/missing specifiers, missing/invalid tags).
+ * But we can be sure that our command now probably contains the specifier and probably some arguments,
+ * and they will never be of the form; module add, module edit, etc. Hence we return false.
+ */
+ if (argMultimap.size() > 1) {
+ return false;
+ }
+
+ /*
+ * We know that regardless of the case, every command always will have the PREFIX_PREAMBLE key,
+ * which is the preamble text. Hence since argMultimap is of size 1 (i.e, only 1 key in the map),
+ * that key has to be the PREFIX_PREAMBLE key. Here there are 2 cases; checkPreamble true or false.
+ *
+ * Now, if checkPreamble is false, then we are not concerned of checking the preambles (whether they
+ * are empty or not). If they exists, which in this case, they have to (by the paragraph above) be the
+ * PREFIX_PREAMBLE key, then indeed we just ignore them, and conclude that the command is indeed
+ * 'empty', e.g,
+ * module add BLAH ==> isEmpty will yield true (since we're ignoring preambles)
+ *
+ * If instead checkPreamble is true, then we are concerned of checking preambles (whether they are empty
+ * or not). If they exists, which in this case, they have to (by the paragraph above) be the PREFIX_PREAMBLE
+ * key, and if they're empty then indeed we conclude that our command is 'empty', e.g,
+ * module edit
+ */
+ if (checkPreamble) {
+ return getPreamble().isEmpty();
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/igrad/logic/parser/ArgumentTokenizer.java
similarity index 76%
rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
rename to src/main/java/igrad/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..efb1e91a1ea 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/igrad/logic/parser/ArgumentTokenizer.java
@@ -1,17 +1,19 @@
-package seedu.address.logic.parser;
+package igrad.logic.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
* 2. Leading and trailing whitespaces of an argument value will be discarded.
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/}
- * in the above example.
+ * in the above example.
*/
public class ArgumentTokenizer {
@@ -21,7 +23,7 @@ public class ArgumentTokenizer {
*
* @param argsString Arguments string of the form: {@code preamble value value ...}
* @param prefixes Prefixes to tokenize the arguments string with
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @return ArgumentMultimap object that maps prefixes to their arguments
*/
public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
List positions = findAllPrefixPositions(argsString, prefixes);
@@ -33,12 +35,12 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
*
* @param argsString Arguments string of the form: {@code preamble value value ...}
* @param prefixes Prefixes to find in the arguments string
- * @return List of zero-based prefix positions in the given arguments string
+ * @return List of zero-based prefix positions in the given arguments string
*/
private static List findAllPrefixPositions(String argsString, Prefix... prefixes) {
return Arrays.stream(prefixes)
- .flatMap(prefix -> findPrefixPositions(argsString, prefix).stream())
- .collect(Collectors.toList());
+ .flatMap(prefix -> findPrefixPositions(argsString, prefix).stream())
+ .collect(Collectors.toList());
}
/**
@@ -62,7 +64,7 @@ private static List findPrefixPositions(String argsString, Prefi
* {@code argsString} starting from index {@code fromIndex}. An occurrence
* is valid if there is a whitespace before {@code prefix}. Returns -1 if no
* such occurrence can be found.
- *
+ *
* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and
* {@code fromIndex} = 0, this method returns -1 as there are no valid
* occurrences of "p/" with whitespace before it. However, if
@@ -72,7 +74,40 @@ private static List findPrefixPositions(String argsString, Prefi
private static int findPrefixPosition(String argsString, String prefix, int fromIndex) {
int prefixIndex = argsString.indexOf(" " + prefix, fromIndex);
return prefixIndex == -1 ? -1
- : prefixIndex + 1; // +1 as offset for whitespace
+ : prefixIndex + 1; // +1 as offset for whitespace
+ }
+
+ /**
+ * Returns a boolean variable specifying if the
+ * specified flag is present
+ *
+ * @param argsString e.g. "add n/CS2103T -a"
+ * @param flag the substring "-a" in the argsString
+ * @return
+ */
+ public static boolean isFlagPresent(String argsString, String flag) {
+ Pattern pattern = Pattern.compile(flag);
+ Matcher matcher = pattern.matcher(argsString);
+
+ return matcher.find();
+ }
+
+ /**
+ * Removes all flags from the argument string.
+ * Take note that flags are only specified at the end of a command
+ *
+ * @param argsString e.g. "add n/CS2103T -a"
+ * @return argsString without flags
+ */
+ public static String removeFlags(String argsString) {
+ int firstFlagIndex = argsString.indexOf("-");
+
+ if (firstFlagIndex < 0) {
+ return argsString;
+ } else {
+ return argsString.substring(0, firstFlagIndex);
+ }
+
}
/**
@@ -82,7 +117,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from
*
* @param argsString Arguments string of the form: {@code preamble value value ...}
* @param prefixPositions Zero-based positions of all prefixes in {@code argsString}
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @return ArgumentMultimap object that maps prefixes to their arguments
*/
private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) {
@@ -114,8 +149,8 @@ private static ArgumentMultimap extractArguments(String argsString, List[a-z]+(\\s[a-z]{3,})?)(?.*)");
+
+ /**
+ * Parses avatar name entered by user into {@code SelectAvatarCommand} for execution.
+ *
+ * @param avatarName full user input string (consisting the avatarName)
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SelectAvatarCommand parseAvatarName(String avatarName) throws ParseException {
+ SelectAvatarCommandParser selectAvatarCommandParser = new SelectAvatarCommandParser();
+ SelectAvatarCommand selectAvatarCommand = selectAvatarCommandParser.parse(avatarName);
+
+ return selectAvatarCommand;
+
+ }
+
+ /**
+ * Parses avatar name entered by user into {@code SelectAvatarCommand} for execution.
+ *
+ * @param userInput full user input string
+ * @return the {@code CourseAddCommand} command
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CourseAddCommand parseSetCourseName(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 argumentsWithFlags = matcher.group("arguments");
+ final String arguments = ArgumentTokenizer.removeFlags(argumentsWithFlags);
+
+ if (commandWord.equals(CourseAddCommand.COURSE_ADD_COMMAND_WORD)) {
+ return new CourseAddCommandParser().parse(arguments);
+ } else {
+ throw new ParseException(MESSAGE_COURSE_NOT_SET);
+ }
+ }
+
+ /**
+ * 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, IOException, ServiceException {
+ 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 argumentsWithFlags = matcher.group("arguments");
+ final String arguments = ArgumentTokenizer.removeFlags(argumentsWithFlags);
+
+ switch (commandWord) {
+
+ /*
+ * If there is only one command word provided instead of the supposed two-word commands, flag an error
+ * to feedback to the user.
+ */
+
+ case CourseCommand.COURSE_COMMAND_WORD:
+ throw new ParseException(MESSAGE_UNKNOWN_COURSE_COMMAND);
+
+ case RequirementCommand.REQUIREMENT_COMMAND_WORD:
+ throw new ParseException(MESSAGE_UNKNOWN_REQUIREMENT_COMMAND);
+
+ case ModuleCommand.MODULE_COMMAND_WORD:
+ throw new ParseException(MESSAGE_UNKNOWN_MODULE_COMMAND);
+
+ case CourseAddCommand.COURSE_ADD_COMMAND_WORD:
+ return new CourseAddCommandParser().parse(arguments);
+
+ case CourseEditCommand.COURSE_EDIT_COMMAND_WORD:
+ return new CourseEditCommandParser().parse(arguments);
+
+ case CourseDeleteCommand.COURSE_DELETE_COMMAND_WORD:
+ return new CourseDeleteCommand();
+
+ case CourseAchieveCommand.COURSE_ACHIEVE_COMMAND_WORD:
+ return new CourseAchieveCommandParser().parse(arguments);
+
+ case RequirementAddCommand.REQUIREMENT_ADD_COMMAND_WORD:
+ return new RequirementAddCommandParser().parse(arguments);
+
+ case RequirementEditCommand.REQUIREMENT_EDIT_COMMAND_WORD:
+ return new RequirementEditCommandParser().parse(arguments);
+
+ case RequirementDeleteCommand.REQUIREMENT_DELETE_COMMAND_WORD:
+ return new RequirementDeleteCommandParser().parse(arguments);
+
+ case RequirementAssignCommand.REQUIREMENT_ASSIGN_COMMAND_WORD:
+ return new RequirementAssignCommandParser().parse(arguments);
+
+ case RequirementUnassignCommand.REQUIREMENT_UNASSIGN_COMMAND_WORD:
+ return new RequirementUnassignCommandParser().parse(arguments);
+
+ case ModuleAddCommand.MODULE_ADD_COMMAND_WORD:
+
+ if (ArgumentTokenizer.isFlagPresent(argumentsWithFlags, FLAG_AUTO.getFlag())) {
+ return new ModuleAddAutoCommandParser().parse(arguments);
+ } else {
+ return new ModuleAddCommandParser().parse(arguments);
+ }
+
+ case ModuleEditCommand.MODULE_EDIT_COMMAND_WORD:
+ return new ModuleEditCommandParser().parse(arguments);
+
+ case ModuleDeleteCommand.MODULE_DELETE_COMMAND_WORD:
+ return new ModuleDeleteCommandParser().parse(arguments);
+
+ case ModuleDoneCommand.MODULE_DONE_COMMAND_WORD:
+ return new ModuleDoneCommandParser().parse(arguments);
+
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+
+ case ExportCommand.COMMAND_WORD:
+ return new ExportCommand();
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+
+}
diff --git a/src/main/java/igrad/logic/parser/CourseCommandParser.java b/src/main/java/igrad/logic/parser/CourseCommandParser.java
new file mode 100644
index 00000000000..cb6b28ae4bc
--- /dev/null
+++ b/src/main/java/igrad/logic/parser/CourseCommandParser.java
@@ -0,0 +1,28 @@
+package igrad.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import igrad.logic.parser.exceptions.ParseException;
+import igrad.model.course.Name;
+
+/**
+ * Represents a generic course command parser
+ */
+public class CourseCommandParser {
+ /**
+ * 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 Optional parseName(String name) throws ParseException {
+ requireNonNull(name);
+ String trimmedName = name.trim();
+ if (!Name.isValidName(trimmedName)) {
+ throw new ParseException(igrad.model.module.Title.MESSAGE_CONSTRAINTS);
+ }
+ return Optional.of(new Name(trimmedName));
+ }
+}
diff --git a/src/main/java/igrad/logic/parser/Flag.java b/src/main/java/igrad/logic/parser/Flag.java
new file mode 100644
index 00000000000..f178b7381b9
--- /dev/null
+++ b/src/main/java/igrad/logic/parser/Flag.java
@@ -0,0 +1,39 @@
+package igrad.logic.parser;
+
+/**
+ * A flag that, if present, specifies the system to include the option.
+ * E.g. '-a' in 'add n/CS2103T -a'.
+ */
+public class Flag {
+ private final String flag;
+
+ public Flag(String flag) {
+ this.flag = flag;
+ }
+
+ public String getFlag() {
+ return flag;
+ }
+
+ public String toString() {
+ return getFlag();
+ }
+
+ @Override
+ public int hashCode() {
+ return flag == null ? 0 : flag.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Flag)) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+
+ Flag otherFlag = (Flag) obj;
+ return otherFlag.getFlag().equals(getFlag());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/igrad/logic/parser/Parser.java
similarity index 52%
rename from src/main/java/seedu/address/logic/parser/Parser.java
rename to src/main/java/igrad/logic/parser/Parser.java
index d6551ad8e3f..6354e2971e8 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/igrad/logic/parser/Parser.java
@@ -1,7 +1,10 @@
-package seedu.address.logic.parser;
+package igrad.logic.parser;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.parser.exceptions.ParseException;
+import java.io.IOException;
+
+import igrad.logic.commands.Command;
+import igrad.logic.parser.exceptions.ParseException;
+import igrad.services.exceptions.ServiceException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
@@ -10,7 +13,8 @@ public interface Parser {
/**
* Parses {@code userInput} into a command and returns it.
+ *
* @throws ParseException if {@code userInput} does not conform the expected format
*/
- T parse(String userInput) throws ParseException;
+ T parse(String userInput) throws ParseException, IOException, ServiceException;
}
diff --git a/src/main/java/igrad/logic/parser/ParserUtil.java b/src/main/java/igrad/logic/parser/ParserUtil.java
new file mode 100644
index 00000000000..c02274efccd
--- /dev/null
+++ b/src/main/java/igrad/logic/parser/ParserUtil.java
@@ -0,0 +1,126 @@
+package igrad.logic.parser;
+
+import static igrad.commons.core.Messages.MESSAGE_SPECIFIER_INVALID;
+import static igrad.commons.core.Messages.MESSAGE_SPECIFIER_NOT_SPECIFIED;
+import static igrad.logic.parser.module.ModuleCommandParser.parseModuleCode;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import igrad.logic.parser.exceptions.ParseException;
+import igrad.model.avatar.Avatar;
+import igrad.model.module.ModuleCode;
+import igrad.model.requirement.RequirementCode;
+import igrad.model.tag.Tag;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserUtil {
+
+ public static final Function REQUIREMENT_CODE_SPECIFIER_RULE =
+ RequirementCode::isValidRequirementCode;
+
+ public static final Function MODULE_MODULE_CODE_SPECIFIER_RULE = ModuleCode::isValidModuleCode;
+
+ /**
+ * Parses a generic {@code String specifier} into a {@code Specifier}.
+ * The functional inteface {@code rule} should return true if valid and false otherwise.
+ * Also, {@code messageError} is the error message to show when a {@code ParserException} is thrown.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code specifier} is invalid.
+ */
+ public static Specifier parseSpecifier(String specifier, Function rule, String messageError)
+ throws ParseException {
+ requireNonNull(specifier);
+
+ String trimmedSpecifier = specifier.trim();
+
+ // We know that in any case, a specifier can never be empty (empty string "")
+ if (trimmedSpecifier.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_SPECIFIER_NOT_SPECIFIED, messageError));
+ }
+
+ /*
+ * Now apply other specifier specific semantic rule as according to {@code rule} parameter, and see if
+ * there is any other violation.
+ */
+ if (!rule.apply(trimmedSpecifier)) {
+ throw new ParseException(String.format(MESSAGE_SPECIFIER_INVALID, messageError));
+ }
+
+ return new Specifier(trimmedSpecifier);
+ }
+
+ /**
+ * Parses a {@code String name} into an {@code Avatar}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code avatarName} is invalid.
+ */
+ public static Avatar parseAvatarName(String name) throws ParseException {
+ requireNonNull(name);
+ String trimmedName = name.trim();
+ if (!Avatar.isValidName(trimmedName)) {
+ throw new ParseException(Avatar.MESSAGE_CONSTRAINTS);
+ }
+ return new Avatar(trimmedName);
+ }
+
+ /**
+ * 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 parseTag(Collection tags) throws ParseException {
+ requireNonNull(tags);
+ final Set tagsSet = new HashSet<>();
+ for (String tagName : tags) {
+ tagsSet.add(parseTag(tagName));
+ }
+ return tagsSet;
+ }
+
+ /**
+ * Parses {@code Collection moduleCodes} into a {@code List}.
+ */
+ public static List parseModuleCodes(Collection moduleCodes) throws ParseException {
+ requireNonNull(moduleCodes);
+
+ final List moduleCodesList = new ArrayList<>();
+
+ for (String moduleCode : moduleCodes) {
+ moduleCodesList.add(parseModuleCode(moduleCode));
+ }
+
+ return moduleCodesList;
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ public 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/Prefix.java b/src/main/java/igrad/logic/parser/Prefix.java
similarity index 88%
rename from src/main/java/seedu/address/logic/parser/Prefix.java
rename to src/main/java/igrad/logic/parser/Prefix.java
index c859d5fa5db..39c1c801066 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/igrad/logic/parser/Prefix.java
@@ -1,8 +1,8 @@
-package seedu.address.logic.parser;
+package igrad.logic.parser;
/**
* A prefix that marks the beginning of an argument in an arguments string.
- * E.g. 't/' in 'add James t/ friend'.
+ * E.g. 'n/' in 'module add n/CS1101S t/Programming Methodology I'.
*/
public class Prefix {
private final String prefix;
diff --git a/src/main/java/igrad/logic/parser/SelectAvatarCommandParser.java b/src/main/java/igrad/logic/parser/SelectAvatarCommandParser.java
new file mode 100644
index 00000000000..17f56e29f0c
--- /dev/null
+++ b/src/main/java/igrad/logic/parser/SelectAvatarCommandParser.java
@@ -0,0 +1,23 @@
+package igrad.logic.parser;
+
+import igrad.logic.commands.SelectAvatarCommand;
+import igrad.logic.parser.exceptions.ParseException;
+import igrad.model.avatar.Avatar;
+
+/**
+ * Parses input arguments and creates a new SelectAvatarCommand object
+ */
+public class SelectAvatarCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} representing the Avatar name in the context of the SelectAvatarCommand
+ * and returns an SelectAvatarCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SelectAvatarCommand parse(String name) throws ParseException {
+ Avatar avatar = ParserUtil.parseAvatarName(name);
+
+ return new SelectAvatarCommand(avatar);
+ }
+
+}
diff --git a/src/main/java/igrad/logic/parser/Specifier.java b/src/main/java/igrad/logic/parser/Specifier.java
new file mode 100644
index 00000000000..3088c0a9354
--- /dev/null
+++ b/src/main/java/igrad/logic/parser/Specifier.java
@@ -0,0 +1,40 @@
+package igrad.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a command specifier.
+ *