By: T08-3
Since: Jan 2019
Licence: MIT
- 1. Introduction
- 2. About this Developer Guide
- 3. Setting up
- 4. Design
- 5. Implementation
- 6. Documentation
- 7. Testing
- 8. Dev Ops
- Appendix A: Product Scope
- Appendix B: User Stories
- Appendix C: Use Cases
- Appendix D: Non Functional Requirements
- Appendix E: Glossary
- Appendix F: Instructions for Manual Testing
Welcome to PlanMySem!
PlanMySem is a text-based (Command Line Interface) scheduling/calendar application that caters to NUS students and staff who prefer to use a desktop application for managing their schedule/calendar. PlanMySem automatically creates a planner that is synchronised according to the NUS academic calendar for the current semester and enables easy creation, editing and deleting of items. Special weeks such as recess week and reading week are taken into account within our unique recursion system. Items can then be efficiently managed via the intuitive tagging system.
PlanMySem is optimized for those who prefer to work with a Command Line Interface (CLI) and/or are learning to work more efficiently with CLI tools. Additionally, unlike traditional calendar/scheduling applications, PlanMySem utilizes minimal resources on the user’s machine while still allowing the user to view their schedules swiftly and efficiently.
This developer guide provides a detailed documentation on the implementation of all the various features PlanMySem offers. To navigate between the different sections, you could use the table of contents above.
For ease of communication, this document will refer to lessons/activities/events/appointments that you might add into the planner as slots.
Additionally, throughout this developer guide, there will be various icons used as described below.
💡
|
This is a tip. Follow these suggested tips to make your life much simpler when using PlanMySem! |
ℹ️
|
This is a note. These are things for you to take note of when using PlanMySem. |
❗
|
This is a sign-post dictating important information. These are information that you will surely need to know to use PlanMySem efficiently. |
🔥
|
This is a sign-post informing caution. Please take note of these items and exercise some care. |
|
This is a rule. Ensure that you follow these rules to ensure proper usage of PlanMySem. |
-
JDK
9
or later⚠️ JDK 10
on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK9
. -
IntelliJ IDE
ℹ️IntelliJ by default has Gradle and JavaFx plugins installed.
Do not disable them. If you have disabled them, go toFile
>Settings
>Plugins
to re-enable them.
-
Fork this repo, and clone the fork to your computer
-
Open IntelliJ (if you are not in the welcome screen, click
File
>Close Project
to close the existing project dialog first) -
Set up the correct JDK version for Gradle
-
Click
Configure
>Project Defaults
>Project Structure
-
Click
New…
and find the directory of the JDK
-
-
Click
Import Project
-
Locate the
build.gradle
file and select it. ClickOK
-
Click
Open as Project
-
Click
OK
to accept the default settings -
Run the
PlanMySem.Main
class (right-click theMain
class and clickRun Main.main()
) and try executing a few commands -
Run all the tests (right-click the
test
folder, and clickRun 'All Tests'
) and ensure that they pass -
Open the
StorageFile
file and check for any code errors -
Open a console and run the command
gradlew processResources
(Mac/Linux:./gradlew processResources
). It should finish with theBUILD SUCCESSFUL
message.
This will generate all resources required by the application and tests. -
Open
MainWindow.java
and check for any code errors-
Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully
-
To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select
Add '--add-modules=…' to module compiler options
for each error
-
-
Run the
PlanMySem.Main
and try a few commands -
Run the tests to ensure they all pass.
This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,
-
Go to
File
>Settings…
(Windows/Linux), orIntelliJ IDEA
>Preferences…
(macOS) -
Select
Editor
>Code Style
>Java
-
Click on the
Imports
tab to set the order-
For
Class count to use import with '*'
andNames count to use static import with '*'
: Set to999
to prevent IntelliJ from contracting the import statements -
For
Import Layout
: The order isimport static all other imports
,import java.*
,import javax.*
,import org.*
,import com.*
,import all other imports
. Add a<blank line>
between eachimport
-
Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.
After forking the repo, the documentation will still have the PlanMySem branding and refer to the https://github.com/CS2113-AY1819S2-T08-3/main
repo.
If you plan to develop this fork as a separate product (i.e. instead of contributing to https://github.com/CS2113-AY1819S2-T08-3/main
), you should do the following:
-
Configure the site-wide documentation settings in
build.gradle
, such as thesite-name
, to suit your own project. -
Replace the URL in the attribute
repoURL
inDeveloperGuide.adoc
andUserGuide.adoc
with the URL of your fork.
Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.
After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).
ℹ️
|
Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork. |
Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).
ℹ️
|
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based) |
When you are ready to start coding,
-
Get some sense of the overall design by reading Section 4.1, “Architecture”.
-
Take a look at [GetStartedProgramming].
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
💡
|
The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture .
|
Main
has only one class called Main
. 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.
Common
represents a collection of classes used by multiple other components.
The following class plays an important role at the architecture level, the App consists of four components:
Each of the four components
-
Defines its API in an
interface
with the same name as the Component. -
Exposes its functionality using a
{Component Name}Manager
class.
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
The sections below give more details of each component.
API : Ui.java
The UI consists of a MainWindow
that is made up of just a TextField commandInput
and a TextArea outputConsole
.
This application is mainly a text-based application, hence there are not many components here.
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 MainWindow
is specified in MainWindow.fxml
The UI
component,
-
Executes user commands read from
commandInput
, using theLogic
component. -
Displays
commandResult
to the user viaoutputConsole
.
API :
Logic.java
the Logic
component,
-
Uses the
parser
class to parse the user command.-
This results in a
Command
object which is executed.
-
-
The command execution can affect the
Model
(e.g. adding a Slot). -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back toUi
. -
In addition, the
CommandResult
object can also instruct theUi
to display results, 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.
API : Model.java
The Model
component
-
stores the Planner data.
-
does not depend on any of the other three components.
API : Storage.java
The Storage
component saves the Planner data in XML format and read it back.
This functionality is provided via Java Architecture for XML Binding (JAXB), a software framework that maps Java classes to XML representations. In summary, JAXB allows storing and retrieving data in memory in any XML format. More information about JAXB can be obtain from Oracle.
To represents XML content as Java objects, "adapted" classes are used to represent and allow conversion of un-mappable objects into mappable objects.
This section describes some noteworthy details on how certain features are implemented.
The Planner
and its Semester
has to be initialized for PlanMySem to work as all other features of PlanMySem would
interact with this Semester
object. The initialization is automated and dynamic to ensure sustainability.
Upon launching PlanMySem, the initialization of the Planner
and its Semester
would be implemented via two steps:
-
Automatically generate the academic calendar from the current date.
-
Setup current
Semester
from the academic calendar.
The academic calendar is dynamically generated by invoking the function generateSemester
in the Semester
class.
The function will first retrieve the current date from the system clock to determine which academic year it is.
As a new academic year starts from August, it can be determined from the month of the current date.
-
If the current date is before August, the current academic year is "the previous year / current year".
e.g. If the date is 25/3/2019, the academic year is "2018 / 2019". -
If the current date is after August, the current academic year is "the current year / next year".
e.g. If the date is 25/8/2019, the academic year is "2019 / 2020".
After determining the academic year, the details of the semesters will be generated. All the weeks of the academic year can be calculated from the first day of semester 1 since each semester has a fixed amount of weeks.
ℹ️
|
Semester 1 of the academic year starts with an orientation week and will always begin from the first Monday of August. |
-
Semester 1 has 18 weeks (inclusive of orientation week) and semester 2 has 17 weeks.
-
The vacation between semester 1 and 2 has 5 weeks.
-
The vacation between academic years will have 12 or 13 weeks depending on the starting week of the next academic year.
Each week of the year will correspond to an academic week and this information will be stored in a HashMap
for efficient retrieval. This
HashMap
can be used to determine the academic week given a date (by finding out the week of the year for that date).
The tables below shows an example of the relation between academic week and the week of the year for academic year 2018/2019.
Academic Week | Example (Week of the year) | Example (Period) |
---|---|---|
Orientation Week |
32 |
6 Aug 2018 (First Monday of Aug 2018) - 12 Aug 2018 |
Week 1 - 6 |
33 - 38 |
13 Aug 2018 - 23 Sep 2018 |
Recess Week |
39 |
24 Sep 2018 - 30 Sep 2018 |
Week 7 - 13 |
40 - 46 |
1 Oct 2018 - 18 Nov 2018 |
Reading Week |
47 |
19 Nov 2018 - 25 Nov 2018 |
Examination Week |
48 - 49 |
26 Nov 2018 - 9 Dec 2018 |
Vacation |
50 - 52 |
10 Dec 2018 - 30 Dec 2018 |
Vacation |
1 - 2 |
31 Dec 2018 - 13 Jan 2019 |
Academic Week | Example (Week of the year) | Example (Period) |
---|---|---|
Week 1 - 6 |
3 - 8 |
14 Jan 2019 - 24 Feb 2019 |
Recess Week |
9 |
25 Feb 2019 - 3 Mar 2019 |
Week 7 - 13 |
10 - 16 |
4 Mar 2019 - 21 Apr 2019 |
Reading Week |
17 |
22 Apr 2019 - 28 Apr 2019 |
Examination Week |
18 - 19 |
29 Apr 2019 - 12 May 2019 |
Vacation |
20 - 31 |
12 weeks duration |
Hence, the information listed below can be determined from the current date.
-
Current academic week
-
Current academic semester
-
Current academic year
-
Number of weeks in current academic semester
-
Start date of current academic semester
-
End date of current academic semester
These information would be assigned to the Semester
object upon initialization of the Planner
.
This section details an aspect which we carefully considered for the initialization of the Planner
and its Semester
.
-
Alternative 1 (current choice): Generate academic calendar by performing calculations from the current date.
-
Pros: Generation of academic calendar is dynamic and will work for future dates.
-
Cons: Computationally expensive as many operations have to be performed.
-
-
Alternative 2: Retrieve academic calendar from a pre-generated file.
-
Pros: Generation of academic calendar is efficient and not prone to calculation errors.
-
Cons: Requires the pre-generated file which may be accidentally edited or deleted by the user.
-
We chose alternative 1 as we wanted PlanMySem to be sustainable and continue working for future dates. The cons for alternative 2 also outweighs the cons for alternative 1 as editing or deleting the pre-generated file could potentially break the application. Hence, we implemented alternative 1 as it is a more suitable choice.
Due to the flexibility and huge variation of the envisioned command format and structures, it was decided that it was more appropriate to create a new Parser instead of relying on the existing regex implementation in AB3 for heavy parsing.
The AB3 parser was heavily modified to serve unordered command parameters as well as to allow more flexibility such that mistakes in commands will still be interpreted as valid as long as the "minimal" set of parameters are present. Regex is currently only used to retrieve the command keywords and arguments. Arguments are then parsed via 2 different methods/techniques according to the format and structure of the command keyword.
-
Ordering of parameters are ignored when possible.
-
Repeated parameters are ignored. The first parameter of the same "type" are taken as valid, the rest are discarded.
-
Alternate formats of commands are implemented to give freedom of choice and cater to different types of users with different personalities and comfort levels.
-
Shortened versions of command keywords are implemented to give ways for users to shorten commands and be more efficient.
Hence, parameters in PlanMySem can be categorised into 2 categories:
-
Prefixed parameters such as
n/NAME
,st/START_TIME
,des/DESCRIPTION
, etc. -
Non-Prefixed parameters, A.K.A. keywords, such as
INDEX
,TYPE_OF_VIEW
. etc.
To retrieve parameters, the function private static HashMap<String, Set<String>> getParametersWithArguments(String args)
can be called.
The keys of the returned HashMap
represent prefixes while the values represent the prefix’s parameters, held in a set.
This allows for easy, quick and efficient access to specific prefixes and its parameters; O(1) access, insertion and removal.
The results of getParametersWithArguments
can be interpreted in these manners:
-
When the returned set of parameters, to a specific prefix, is
null
, then both the prefix and parameters was not keyed in at all. -
When the returned set of parameters, to a specific prefix, is not
null
but contains emptystrings
such thatstring.isEmpty()
returns true, then the prefix was keyed in but the parameter was left blank.
❗
|
The values of the returned HashMap is a Set , hence, there is no need to handle repeated parameters of a specific prefix as they will be automatically discarded.
|
To retrieve keywords, the function private String getStartingArgument(String args)
can be called.
Here, keywords are thought of as parameters that are not prefixed.
In PlanMySem, keywords are utilized in command structures when they are to be used alone or when order of parameters are important.
In such cases, there is no logical need for prefixing as the meaning of these parameters can be identified.
The results of getStartingArgument
can be interpreted in these manners:
-
When the keyword is null, then the parameter was not keyed in.
-
When the keyword data type does not match the intended, then the parameter was keyed in wrongly or is mis-ordered.
ℹ️
|
Additional keywords are not handled and ignored to provide ease of use and cater to user mistakes. |
Here are the considerations that led to the new parsing system. In both cases, choices were made were largely due to the fact that they provide a better user experience and ease of use.
-
Alternative 1 (current choice): When possible, accept repeated parameters.
-
Pros: Less computationally expensive and allow users to make minor mistakes.
-
Cons: User errors may be misinterpreted and hence wrong actions may be executed.
-
-
Alternative 2: Always accept and handle repeated parameters.
-
Pros: Errors are shown to the user so that the invalid command may be fixed.
-
Cons: Force user to rewrite commands, even in the event of simple/minor mistakes, and thus may hinder user experience and ease of use.
-
-
Alternative 1 (current choice): Parse parameters without regards to order.
-
Pros: Greater user experience due to greater ease of use.
-
Cons: More computationally expensive and tougher development process due to more cases to care for, requires manual parsing.
-
-
Alternative 2: Accept only a specific ordering of parameters.
-
Pros: Less computationally expensive and short development process, able to use existing regex solutions in AB3.
-
Cons: Greatly hinder user experience as order of parameters have no relation to meaning of commands.
-
Though the current implementation has much flexibility, there is more that can be done to elevate user experience to the next level. These are some possible enhancements:
-
Parse a larger variety of date and time formats.
-
Parse time as a single parameter instead of two.
-
Enhance function calls to retrieve prepended parameters and keywords to handle trivial cases that should invoke
ParseException
.
Slot Management involves mainly the interaction between the users and their slots.
The section below will describe in detail the Current Implementation, Design Considerations and Future Implementation of the Slot Management.
Users are able to perform three actions (or commands), though a small variety of methods, involving slots:
-
Add
-
Add multiple slots via the recursion system.
-
Add a single slot via omitting the recursion system.
-
-
Edit
-
Edit multiple slots via tags.
-
Edit a single slot via index.
-
-
Delete
-
Delete slots via tags.
-
Delete a single slot via index.
-
The Add
command heavily relies on the recursion system to select multiple dates in which to add the same slot to multiple days.
Additionally, the Add
command also allows users to input tags to tag slots.
The Edit
and Delete
command then makes use of the tagging system to then select multiple slots for editing/deleting.
Here are the considerations regarding slot management.
The topic of whether to wrap all primitives and Strings
, in Java, is contentious.
However, in this case of PlanMySem, there are no possible invalid values for any of the data that Slot
holds, other than the /
character that would have already been handled by parser
.
For instance, any String
is a valid name
and the same goes for location
, description
and etc; wrapping these data will not achieve any narrowing of possible valid inputs.
Hence, data in Slot
are not wrapped. This is in accordance to the You aren’t gonna need it (YAGNI) principle.
-
Alternative 1 (current implementation): Use of
Map
, such asHashMap
to storeDays
that storeSlots
.-
Pros:
HashMap
allows for easier and faster, O(1) access time, access of particularDay
according to date. -
Cons: This requires splitting of the calendar into days, as such there is no easy way to account for
Slots
that occur across days.
-
-
Alternative 2: Store
Slots
in a huge list.-
Pros: Allows for easier access by "index" and offers flexibility, for example, in the time of slots.
-
Cons: Expensive to access, add and remove items. Furthermore, it is extremely expensive to collect slots that occur in a day, a very important and most likely to be a commonly used feature.
-
Alternative 1 was chosen as the benefits of quick and easy access to days outweigh the disadvantages involve with forbidding slots than span over a day. After all, there are few cases of slots crossing the boundaries of a day, over midnight.
Currently, Days
are held in a HashMap of key LocalDate
and value Day
. While this works without any loss in performance, this causes duplication of code and removes some key concepts of abstraction.
For example, there are code blocks dedicated to retrieving days or slots that could have been placed into this new class. This is an issue as these code have nothing to do with for instance, Semester
but they are placed there.
Therefore, this needs to be implemented in the future to achieve less coupling, more cohesion and respect the Single Responsibility Principle (SRP), Open-Closed Principle (OCP) and Separation of Concerns Principle (SoC).
While PlanMySem now allow users to work on the current semester, it is unable to cater to future semesters. For instance when a semester is about to end, users are not able to plan ahead for the coming semester.
This is an issue that plagues user experience and is a significant problem. To solve this issue, Planner
needs to hold multiple semesters in a List
and more features need to be included to allow saving, loading and switching of semesters and etc.
The find function supports searching using a single keyword.
ℹ️
|
The name/tag of the Slot MUST contain the specified keyword in order for a match to occur. Completely different keywords
do not constitute a match. |
The matching Slots
are then weighted based on their name/tag’s Levenshtein Distance from the keyword.
💡
|
A low Levenshtein Distance is attributed to a high level of similarity between the name/tag and the keyword.
(A value of 0 constitutes an exact match.) The maximum Levenshtein Distance set in PlanMySem is 20.
|
The weighted Slots
are inserted into a PriorityQueue
and the closest matching Slots
will be polled into the output list.
Upon executing the find
command with valid parameters,
a sequence of events is executed. The sequence of events illustrated in the Sequence Diagram below will be in reference to the execution
of a find n/keyword
command. The sequence of events are as follows:
-
Upon calling the
execute
method of theLogic
component, theLogic
component would then parse thefind n/keyword
command. -
LogicManager
then invokes theparseCommand
function ofParserManager
. -
ParserManager
in turn invokes theparse
function of the appropriate parser for thefind
command which in this case, isFindCommandParser
. -
After parsing is done,
FindCommandParser
would instantiate theFindCommand
object which would be returned to theLogicManager
. -
LogicManager
is then able to invoke theexecute
function of the returnedFindCommand
object. -
The command execution will call the
getDays
method of theFindCommand
object which retrieves data from theModel
component (i.e. retrieving data from the currentSemester
). -
FindCommand
will execute thegetDiscoveredNames
method to find the closely matchingSlots
with names containing 'keyword'. -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back toUi
. -
In addition, the
CommandResult
object can also instruct theUi
to display results, such as displaying help to the user.
To give a graphical summary of the above process, a Sequence Diagram detailing the execution of the find n/keyword
command is provided below.
The Communication Diagram below summarises the data links between the various objects in the find
command.
No. |
Alternatives |
Pros |
Cons |
Past Implementation 1 |
Positive search result by strictly matching the entered keyword |
Easy to implement. |
Search must be exact, typos or an incomplete keyword will yield incorrect results. Nothing different from |
Past Implementation 2 |
Positive search result as long as name/tag contains the keyword. |
Searches will detect names/tags similar to the keyword. |
Output list will be longer. May become excessively long if short keyword is provided. |
Current Implementation |
Store the search results in a |
Searches are ordered by a degree of similarity, instead of the random order of names/tags in Past Implementation 2. |
Adds complexities in finding and searching. |
This feature presents the planner in different formats to the user. The available formats are the month view, week view, and the day view. This section will detail how this feature is implemented.
Upon invoking the view
command with valid parameters (refer to UserGuide.adoc for view
usage),
a sequence of events is executed. For clarity, the sequence of events will be in reference to the execution
of a view month
command. A graphical representation is also included in the Sequence Diagram below for your reference
when following through the sequence of events. The sequence of events are as follows:
-
Firstly, the
view month
command is passed into theexecute
function ofLogicManager
to be parsed. -
LogicManager
then invokes theparseCommand
function ofParserManager
. -
ParserManager
in turn invokes theparse
function of the appropriate parser for theview
command which in this case, isViewCommandParser
.ℹ️The view
command can be parsed into only 3 general types of views which are the month, week or day view as specified in the command parameter. -
After parsing is done,
ViewCommandParser
would instantiate theViewCommand
object which would be returned to theLogicManager
. -
LogicManager
is then able to invoke theexecute
function of the returnedViewCommand
object. -
In the
execute
function of theViewCommand
object, data will be retrieved from theModel
component (i.e. retrieving data from the currentSemester
). -
Now that the
ViewCommand
object has the data of the current semester, it is able to invoke thedisplayMonthView
method. -
With the output returned from the
displayMonthView
, theCommandResult
object will be instantiated. -
This
CommandResult
object would be returned to theLogicManager
which then returns the sameCommandResult
object back to theUI
component. -
Finally, the
UI
component would display the contents of theCommandResult
object to the user. For thisview month
command, the displayed result would be the monthly calendar view of all months in the current semester.
The 3 general types of view (month, week, day) are generated by the methods displayMonthView
, displayWeekView
,
displayDetailedWeekView
, displayDayView
from the ViewCommand
class and the implementation of these methods is
explained below.
displayMonthView
method displays all the months of the current semester in a monthly calendar format. Each academic week
of the semester is also indicated in the display. The implementation of this method can be
broken down into 3 parts:
-
Print month header (e.g. January 2019) and calculate required amount of whitespace before the 1st of the month.
-
Print all days of the month using a loop.
-
Append academic week after each Saturday or last day of month.
-
-
Repeat parts 1 and 2 for every month in the semester.
displayWeekView
method displays the weekly calendar format of a specified week. The implementation of this method can be
broken down into the following steps:
-
Print academic week header (e.g. Week 13 of Sem 2).
-
Retrieve all days of the week and for each day, retrieve its slots into an
ArrayList
. -
For each day, print the slot details (only start time, end time and a shortened title) and remove the slot from the
ArrayList
. -
Repeat step 3 until the
ArrayList
of slots for each day is empty.
displayDayView
method displays the details of all slots of a specified day. The implementation of this method can be
broken down into 2 parts:
-
Retrieve all slots for the specified day.
-
Print all details of each slot found.
displayDetailedWeekView
method displays the details of all slots of a specified week since displayWeekView
only shows
a formatted and summarised week view. The implementation of this method can be broken down into the following steps:
-
Print academic week header (e.g. Week 13 of Sem 2).
-
Retrieve all days of the week.
-
For each day, print all details of all slots via the
displayDayView
method.
This section details our considerations for the implementation of the view
feature.
-
Alternative 1 (current choice): Option for user to display a formatted summarised week view or a detailed week view.
-
Pros: The formatted summarised week view is uncluttered. User given the choice and flexibility for the week view.
-
Cons: User is required to spend a little more time to specify an additional parameter in the
view week
command.
-
-
Alternative 2: Only a single formatted week view which displays details of all slots in the specified week.
-
Pros: Efficient for the user as user is only required to enter a single command to view all details of all slots.
-
Cons: The formatted week view will be too cluttered as there are too many slots and lots of details. Formatting is an issue as well as details of each slot can be of varying lengths.
-
Alternative 1 was chosen to be implemented as it gives the user freedom of choice to select the degree of details to be
displayed in the output of the view week
command. The output of alternative 1 is also less cluttered than alternative 2
and thus enhances the presentability of PlanMySem.
The undo/redo mechanism is facilitated by VersionedPlanner
.
ℹ️
|
Only Add , Edit and Delete commands can be undone/redone. |
It extends Planner
with an undo/redo history, stored internally as an plannerStateList
and currentStatePointer
.
Additionally, it implements the following operations:
-
VersionedPlanner#commit()
— Saves the current planner state in its history. -
VersionedPlanner#undo()
— Restores the previous planner state from its history. -
VersionedPlanner#redo()
— Restores a previously undone planner state from its history.
These operations are exposed in the Model
interface as Model#commitPlanner()
, Model#undoPlanner()
and Model#redoPlanner()
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 VersionedPlanner
will be initialized with the initial planner state, and the currentStatePointer
pointing to that single planner state.
Step 2. The user executes delete 5
command to delete the 5th Slot
in the planner. The delete
command calls Model#commitPlanner()
, causing the modified state of the planner after the delete 5
command executes to be saved in the plannerStateList
, and the currentStatePointer
is shifted to the newly inserted planner state.
Step 3. The user executes add n/CS2113T …
to add a new slot. The add
command also calls Model#commitPlanner()
, causing another modified planner state to be saved into the plannerStateList
.
ℹ️
|
If a command fails its execution, it will not call Model#commitPlanner() , so the planner state will not be saved into the plannerStateList .
|
Step 4. The user now decides that adding the Slot
was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#undo()
, which will shift the currentStatePointer
once to the left, pointing it to the previous planner state, and restores the planner to that state.
ℹ️
|
If the currentStatePointer is at index 0, pointing to the initial planner state, then there are no previous planner states to restore. The undo command uses Model#canUndo() 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:
The redo
command does the opposite — it calls Model#redoPlanner()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the planner to that state.
ℹ️
|
If the currentStatePointer is at index plannerStateList.size() - 1 , pointing to the latest planner state, then there are no undone planner states to restore. The redo command uses Model#canRedo() 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 planner, such as list
, will usually not call Model#commitPlanner()
, Model#undoPlanner()
or Model#redoPlanner()
. Thus, the plannerStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitPlanner()
. Since the currentStatePointer
is not pointing at the end of the plannerStateList
, all planner 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.
The following activity diagram summarizes what happens when a user executes a new command:
-
Alternative 1 (current choice): Saves the entire planner.
-
Pros: Easy to implement.
-
Cons: May have performance issues in terms of memory usage.
-
-
Alternative 2: Individual command knows how to undo/redo by itself.
-
Pros: Will use less memory (e.g. for
delete
, just save the person being deleted). -
Cons: We must ensure that the implementation of each individual command are correct.
-
-
Alternative 1 (current choice): Use a list to store the history of planner 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
andVersionedPlanner
.
-
-
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.
-
This feature exports the Planner into a .ics file. This section will detail how this feature is implemented.
Upon entering the export
command with valid parameters (refer to UserGuide.adoc for export
usage), the
following sequence of events is executed:
-
The
ParserManager
parses theexport
command and calls theparse
method inExportCommandParser
. -
The
ExportCommandParser
then constructs aExportCommand
object with a filename. -
The Command object is returned and execution will get the current
Semester
fromModel
-
The
IcsSemester
is then constructed usingSemester
and converted to aString
. -
The
String
is then written to a file with the filename parsed. -
The result of the command execution,
CommandResult
, will then returned toUi
.
Given below is the Sequence Diagram upon executing the export
command.
The ExportCommandParser
will check whether the optional filename parameter was input. If this parameter is included, the input filename is used. Else, if no other characters have been input (e.g. "export"), the default "PlanMySem" is used as the filename.
This process can be seen from the activity diagram in the figure below.
This portion explains alternative implementations as well as the rationale behind my chosen method.
-
Alternative 1 (current choice): Writing my own .ics file.
-
Pros: No need to include and understand how to use the external library.
-
Cons: Difficult to read and work with .ics formatting.
-
-
Alternative 2: Using iCal4j library to read and write .ics files.
-
Pros: No need to manually format data into .ics format.
-
Cons: Difficult to translate our recursion system to the .ics RRULE system.
-
Reason for current choice: Using the library will allow PlanMySem
to easily import non-native .ics files. However, this would require changes to Model
as currently the recurrence for slots is not saved.
In addition, as our application is a specially designed planner for NUS matters, I felt that it was unnecessary to have the same slots on multiple applications.
Hence, I chose to code the reading and writing of .ics files and add a disclaimer that importing of non-native .ics files is likely to cause errors.
The storage file "PlanMySem.txt" is encrypted to prevent easy access of the user’s calendar.
We are encrypting and decrypting the data using the Java Cipher
.
This feature is implemented through the Encryptor
that contains the encrypt and decrypt methods. The encrypt method takes a String
as an argument and returns a encrypted String object. The decrypt method takes in a String object as an argument and returns the decrypted message as a String object.
The encryption is done using AES/CBC/PKCS5Padding. The key used for encryption/decryption is generated through various device parameters such as username, operating system (OS) and java runtime version. The secret key generated is stored in a file named "KeyStorage.jceks". No password is required from the user to retrieve this key, but a password input can be added to KeyStorage
to improve security.
A initialization vector (IV) is required for the Cipher Block Chain (CBC) mode of encryption. A random IV is generated and appended at the beginning of the data before being stored. The IV is then retrieved from the same file to decrypt the data.
Encryption of the data is done automatically before the file is saved. In the implementation, the AdaptedPlanner is first marshaled into a StringWriter
before being encrypted and written into the file. This is to ensure that the data is JAXB formatted and the save algorithm is unaffected.
Similarly, decryption of the data is done automatically before it is loaded. In the implementation, the file is read and decrypted and parsed into a StringReader
. The StringReader
is then un-marshaled and loaded. This is to ensure that the file is converted back into a JAXB object before being loaded and the load algorithm is unaffected.
The files generated by PlanMySem are also named "PlanMySem" and are saved in user’s PlanMySem folder by default. This default filename and file path can be changed via the the configuration file (default: config.json
).
There is no need for manual configuration of the Semester
as it is initialized dynamically as mentioned in
Section 5.1, “Initialization of the Planner and its Semester”.
We use asciidoc for writing documentation.
ℹ️
|
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. |
See UsingGradle.adoc to learn how to render .adoc
files locally to preview the end result of your edits.
Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc
files in real-time.
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.
Here are the steps to convert the project documentation files to PDF format.
-
Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the
docs/
directory to HTML format. -
Go to your generated HTML files in the
build/docs
folder, right click on them and selectOpen with
→Google Chrome
. -
Within Chrome, click on the
Print
option in Chrome’s menu. -
Set the destination to
Save as PDF
, then clickSave
to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.
The build.gradle
file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.
💡
|
Attributes left unset in the build.gradle file will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
The name of the website. If set, the name will be displayed near the top of the page. |
not set |
|
URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar. |
not set |
|
Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. |
not set |
Each .adoc
file may also specify some file-specific asciidoc attributes which affects how the file is rendered.
Asciidoctor’s built-in attributes may be specified and used as well.
💡
|
Attributes left unset in .adoc files will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
Site section that the document belongs to.
This will cause the associated item in the navigation bar to be highlighted.
One of: * Official SE-EDU projects only |
not set |
|
Set this attribute to remove the site navigation bar. |
not set |
The files in docs/stylesheets
are the CSS stylesheets of the site.
You can modify them to change some properties of the site’s design.
The files in docs/templates
controls the rendering of .adoc
files into HTML5.
These template files are written in a mixture of Ruby and Slim.
|
Modifying the template files in |
There are three ways to run tests.
💡
|
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. |
Method 1: Using IntelliJ JUnit test runner
-
To run all tests, right-click on the
src/test/java
folder and chooseRun 'All Tests'
-
To run a subset of tests, you can right-click on a test package, test class, or a test and choose
Run 'ABC'
Method 2: Using Gradle
-
Open a console and run the command
gradlew clean allTests
(Mac/Linux:./gradlew clean allTests
)
ℹ️
|
See UsingGradle.adoc for more info on how to run tests using Gradle. |
Method 3: Using Gradle (headless)
Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.
To run tests in headless mode, open a console and run the command gradlew clean headless allTests
(Mac/Linux: ./gradlew clean headless allTests
)
-
Unit tests targeting the lowest level methods/classes.
e.g.PlanMySem.commons.UtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.PlanMySem.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.PlanMySem.logicManager.LogicTest
,PlanMySem.parse,ParserTest
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.
When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.
Here are the steps to create a new release.
-
Update the version number in
Main.java
. -
Generate a JAR file using Gradle.
-
Tag the repo with the version number. e.g.
v0.1
-
Create a new release using GitHub and upload the JAR file you created.
Projects often depends on third-party libraries. For example, PlanMySem depends on the 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:
-
Include those libraries in the repo (this bloats the repo size)
-
Require developers to download those libraries manually (this creates extra work for developers)
Target user profile:
-
NUS students and staff
-
has a need to manage a significant number of categories, activites, timeslots, tags in a calendar
-
prefer desktop apps over other types
-
prefers having a completely offline calendar
-
can type fast
-
prefers typing over mouse input
-
is reasonably comfortable using CLI apps
Value proposition: manage personal planner faster than a typical mouse/GUI driven app and caters to users who prefer an offline solution due to the current technology climate where information privacy/data privacy/data protection has become an uncertainty
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
new user |
see usage instructions |
refer to instructions if I do not know how to use the app |
|
new user |
initialize the calendar by year and semester |
align the planner with the school’s academic calendar |
|
user |
add a slot |
store all my slots in the calendar |
|
user |
delete a slot |
remove all slots from my calendar that have been cancelled |
|
user |
edit a slot |
edit slots from my calendar that have been postponed/ brought forward/ changed |
|
user |
list all slots |
view all slots on the planner which I have activities on |
|
user |
recurse a slot |
easily create all the relevant time slots for a module to recur every week |
|
user |
view all slots on a certain day |
conveniently view my planner for the day |
|
user |
view all slots on a certain week |
conveniently view my planner for the week |
|
user |
view all slots on a certain month |
conveniently view my planner for the month |
|
user |
add details to a slot |
record information related to the slot |
|
user |
undo a command |
easily revert my changes and restore a previous state |
|
user |
redo a command |
easily revert my |
|
user |
view the planner in a graphic calendar format |
easily view my schedule for the day/week/month/semester |
|
user |
view a slot |
view the details of a specific activity I am looking for |
|
user |
remove tags on a time slot |
remove unused/ unnecessary tags from an activity |
|
user |
edit tags |
rename tags |
|
user |
list all tags |
view all existing tags |
|
user |
view color coded categories |
easily view the different types of categories |
|
user |
encrypt my planner data |
ensure the privacy of my planner |
|
user |
decrypt an encrypted planner data |
securely transfer the planner data to be operated on another device |
|
user |
import semester timetable (.ics files) |
transfer my existing activities into the new timetable |
|
user |
export semester timetable (.ics files) |
view my timetable on another platform |
|
user |
receive notifications of upcoming activities |
be reminded of important upcoming activities |
|
user |
view recess week and exam week |
view specifically the weeks to rest |
|
user |
view vacations |
plan my schedule on vacation days or special semesters |
|
user |
favourite an activity |
prioritise important activities |
|
user |
view public holidays |
be aware of upcoming public holidays |
|
user |
compare my timetable with someone else’s |
find a common time slot for a meeting |
|
user |
generate summary reports |
view how much time I spent attending training / tutorials |
This section describes the Use Cases for some of our implemented features.
(For all use cases below, the System is PlanMySem
and the Actor is the user
, unless specified otherwise)
-
MSS:
-
1. User inputs add command followed by all the mandatory parameters.
-
2. System reflects the additions to the planner.
Use case ends.
-
-
Extensions:
-
1a. System detects an error in the entered data.
-
1a1. System outputs error message.
Use case ends.
-
-
1b. System detects insufficient parameters in the entered data.
-
1b1. System outputs error message.
Use case ends.
-
-
-
MSS:
-
1. User inputs the command to list slots followed by the tag or name of the slot.
-
2. System displays all slots with the specified name or tag with their indexes.
Use case ends.
-
-
Extensions:
-
1a. Planner is empty.
-
1a1. System outputs error message.
Use case ends.
-
-
1b. Tag or name does not exist in the planner.
-
1b1. System outputs error message.
Use case ends.
-
-
-
MSS:
-
1. User inputs the delete command followed by the index or tag of the intended slot.
-
2. System deletes the intended slot from the planner and outputs confirmation message.
Use case ends.
-
-
Extensions:
-
1a. Tag or index does not exist in the planner.
-
1a1. System outputs error message.
Use case ends.
-
-
-
MSS:
-
1. User inputs command to edit a slot along with the tag or index, followed by the parameters to be changed.
-
2. System changes the specified parameters for the slot.
-
3. System reflects the slots as well as the perimeters changed.
Use case ends.
-
-
Extensions:
-
1a. Tag or index does not exist in the planner.
-
1a1. System outputs error message.
Use case ends.
-
-
1b. System detects an error in the entered data.
-
1b1. System outputs error message.
Use case ends.
-
-
-
MSS:
-
1. User inputs the command to view all slots along with the specific day or date in the current semester.
-
2. System displays all the slots for that specified day or date.
Use case ends.
-
-
Extensions:
-
1a. Specified day or date does not exist in the current semester.
-
1a1. System outputs error message.
Use case ends.
-
-
1b. System detects an error in the entered data.
-
1b1. System outputs error message.
Use case ends.
-
-
-
MSS:
-
1. User inputs command to export the planner.
-
2. System converts planner to .ics format.
-
3. System saves .ics file in the main directory as "PlanMySem.ics".
-
4. System displays confirmation message.
Use case ends.
-
-
Extensions: :: 1a. A filename is included in the command.
- :
-
1ai. The filename is valid.
- :
-
1ai.1. System converts planner to .ics format.
- :
-
1ai.2. System saves .ics file in the respective directory.
- :
-
1ai.3. System displays confirmation message.
Use case ends. ::: 1aii. The filename is invalid :::: 1aii.1 System outputs error message.
+ Use case ends.
-
Should work on any mainstream OS as long as it has Java 9 or higher installed.
-
Should be able to hold up a fully packed schedule, three times over, 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.
-
The system should respond relatively quickly to user commands so as to not make the user wait around; this is an advantage of using PlanMySem.
-
The system should take up relatively little space on the local machine so as to cater to all students and OS.
-
The system should be easy to use, intuitive and simple, such that any student regardless of past experience with calendar/scheduling software is able to use it.
-
The system should be flexible to allow all kinds of schedules that target users might have.
-
The data should be encrypted to prevent private data from being accessed.
- Mainstream OS
-
Windows, Linux, Unix, OS-X
- Levenshtein Distance
-
The Levenshtein distance is a string metric for measuring difference between two sequences.
Informally, the Levenshtein distance between two words is the minimum number of single-character edits (i.e. insertions, deletions or substitutions) required to change one word into the other.
Given below are instructions to test the app manually.
ℹ️
|
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing. |
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows a window with a welcome message. 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.
-
-
Deleting a slot while all slots are listed
-
Prerequisites: Add a single or multiple slot(s) using the
add
command. -
Test case:
add n/CS2113T Lecture d/mon st/8:00 am et/9:00 am des/Topic: Software Engineering t/CS2113T t/Lecture r/normal r/past
Expected: Add a single slot, named "CS2113T Lecture" with description "Software Engineering" on all mondays, from 0800hrs to 0900hrs with the tags "CS2113T" and "Tutorial". Total of 13 slots are added to the planner. -
Test case:
add n/CS2113T Lecture d/mon st/8:00 am et/9:00 am des/Topic: Software Engineering t/CS2113T t/Lecture
Expected: Add a single slot, named "CS2113T Lecture" with description "Software Engineering" on the coming monday, from 0800hrs to 0900hrs with the tags "CS2113T" and "Tutorial". A single slot is added to the planner. -
Test case:
add n/ Lecture d/mon st/8:00 am et/9:00 am des/Topic: Software Engineering t/CS2113T t/Lecture
Expected: No Slot is added. Error details shown in the status message. -
Other incorrect add commands to try include invalid or unspecified dates, days and or time
Expected: Similar to previous.
-
-
Editing a slot while all slots are listed
-
Prerequisites: List all slot using the
list
command. Multiple slots in the list. -
Test case:
edit 1 nl/ICube
Expected: The first slot shown in the list is edited such that it’s location is replaced with "ICube". The selected slot and the edited details, in this case only "location", is displayed. -
Test case:
edit 0
Expected: No Slot is edited. Error details shown in the status message. -
Other incorrect edit commands to try:
edit
,edit x
(where x is larger than the list size) {give more}
Expected: Similar to previous.
-
-
Deleting a slot while all slots are listed
-
Prerequisites: List all slot using the
list
command. Multiple slots in the list. -
Test case:
delete 1
Expected: First slot is deleted from the Planner. Number of deleted slots is shown, asi
, and details of the slot is shown. -
Test case:
delete 0
Expected: No Slot is deleted. Error details shown in the status message. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size) {give more}
Expected: Similar to previous.
-
-
View all slots for the current week
-
Prerequisites: Add slots using the
add
command for any day of the current week. -
Test case:
view week
Expected: A weekly calendar view with all added slots for the current week with a summarised title. -
Test case:
view week details
Expected: A detailed view of all added slots for the current week. -
Other incorrect view commands to try:
view
,view week 0
Expected: Error message returned which shows proper command usage or indicating an invalid input.
-