By: Team SGTravel
Since: Aug 2019
Licence: MIT
-
JDK
11
or above -
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 -
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.
-
Run the
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 SGTravel branding and refer to the AY1920S1-CS2113T-W13-3/main
repo.
If you plan to develop this fork as a separate product (i.e. instead of contributing to AY1920S1-CS2113T-W13-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, we recommend that you get some sense of the overall design by reading about SGTravel’s architecture.
The Architecture Diagram given above explains the high-level design of SGTravel. SGTravel adopts a n-tier style architecture where higher layers make use of services provided by lower layers. Here is a quick overview of each layer/component:
The UI
consists of a MainWindow
that is made up of parts e.g.SidePanel
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class.
The UI
component uses JavaFx UI framework. The layout of these UI
parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The UI
component,
-
Executes user commands using the
Logic
component. -
Recives commannd results from
Logic
component so that theUI
can be updated with the modified data.
Given below is the simplified activity diagram for the workflow of the UI
. Upon the start of the UI
, it would request for user input. Then, it would process the user input and execute the input. It would show the respective response after execution of the input. If the response requires calendar or map, it would show it to the user. Subsequently, it would loop back to request for user input again. Else, if the response is exit type, it would exit the app.
API :
Logic.java
-
Logic
uses theParser
andConversationManager
class to parse the user command. -
This results in a
Command
object which is executed by theLogicManager
. -
The command execution can affect the
Model
(e.g. adding an event). -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
. -
In addition, the
CommandResult
object can also instruct theUi
to perform certain actions, such as displaying calendar to the user. -
Furthermore, the
EditorManager
is allowed to "snatch" the user inputs from theParser
when it is activated.
Given below is the diagram illustrating the workflow of the Logic
component. The user input is passed to Logic
component. Then, it would determine the input is of single line type or complex multiple line type. If it is a multiple line type, it would start a Conversation
with the user to prompt for more input. Then, using the input, it would build it to become a single line input to be passed to create a Command
. In create command, figure at the bottom, the input is parsed to determine which command is to be built. Subsequently, the Command
would be executed to create CommandResult
which contains the response and result of the execution of the Command
.
API : Model.java
The Model
,
-
stores a
ProfileCard
object that represents the user’s profile. -
stores the SGTravel data.
-
the only class that is exposed to
Storage
component
The Storage
component,
-
can save
ProfileCard
objects in.txt
format and read it back. -
can save the SGTravel data in
.txt
format and read it back.
The figure below shows some parts of the activity of Storage
when it is initialised during the start of launching the SGTravel application. It will first read from event file path and parses it into EventList
for Storage
. Then it will read bus and train transport information and parses them into TransportationMap
for Storage
.
This section describes some noteworthy details on how certain features are implemented.
Given below is an example usage scenario of how user input is handled:
-
The user types in a command into the terminal, and clicks on the Enter button.
-
Upon button press, the text in the input is read, if it is non-empty, the application will echo back the user’s input.
-
The
MainWindow
will then calldukeResponse(input)
, which creates a thread using thePlatform
class. -
A
CommandResult
object is created, anddukeShow(result)
is called using this object. -
If the result is of
CommandResultExit
, calltryExitApp()
. Else if result is ofCommandResultCalender
, make a newCalenderWindow
object with the result. Else if the result is ofCommandResultMap
, make a newMapWindow
object.
Given below is an example usage scenario of how user input is handled:
-
If the input is identified as a single line command (e.g.
help
,list
), the appropriate command is returned. -
Else, call
getCommandFromConversationManager(userInput)
, which will cause theConversationManager
to callconverse(userInput)
. -
In
ConversationManager
,converse(userInput)
will check for the presence of aConversation
, and process the given user input to see if it is what theConversation
wants. For example, aisDateInput(userInput)
will check if the given user input is a date, whereas anisIntInput(userInput)
will check to see if it is anInteger
). If it matches, an appropriate prompt is returned as a message, and the appropriate fields of the conversation is updated. -
By checking if the
Conversation
inConversationManager
has ended,parseComplexCommand(userInput)
will be called to try to parse the entire user input into aCommand
. Whereas if theConversation
has not ended, aPromptCommand
is created by theConversationManager
using theConversation
and shown to the user. -
The
LogicManager
will then execute theCommand
and return aCommandResult
to theUi
.
-
Alternative 1 (current choice): Using
ConversationManager
to "accumulate" user input first before passing toLogicManager
as a single step command when the accumulation is ready.-
Pros: No need to create new command classes.
-
Pros: Can use the same key word to trigger both the single and multi-step commands based o the context.
-
Pros: Does not block out single-step command even when the multi-step command is ongoing.
-
Cons: Challenging to implement, exceptions need to be handled carefully.
-
-
Alternative 2: Single and Multi-step commands are considered as different commands.
-
Pros: Easy to implement.
-
Cons: We cannot use the same keywords to trigger both the multi-step and single-step command.
-
Cons: There will be many different commands with overlapping code which violates DRY principle.
-
Cons: Blocks out other commands while multi-step command is triggered.
-
The naive editing mechanism is facilitated by the QuickEditCommand
from the Logic
component.
Given below is an example usage scenario and how the editing mechanism behaves at each step:
-
The user calls the edit command with its relevant parameters.
e.g.e 1 Bishan 12/02/29 14/02/29
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
callsQuickEditParser
and returns anQuickEditCommand
Object. -
The
LogicManager
will callexecute()
on theQuickEditCommand
object which interacts with and updates theModel
component and return aCommandResult
Object containing a message. If noEvent
of the corresponding index is found, it would return a string of messageMESSAGE_INVALID_EVENT_INDEX
. -
The
CommandResult
Object will then be given back to theUi
component which displays the success message.
Alternatively, the editing mechanism can be facilitated by the EditorManager
and Selectors
in the Logic
Component.
Below, is a diagram describing the work flow of the editing mechanism.
The EditorManager
object oversees the edit operation with the help of Selectors
class which selects the Event
and the components to be updated. The EditorManager
contains an instance of LocationSelector
and an EventFieldSelector
which selects the index of the Event
and the components of the Event
respectively.
The EditorManager
object implements the following operations:
-
EditorManager+activate(EventList events, VenueList venues)
- Activates theEditorManager
-
EditorManager+isActive()
- Check if theEditorManager
is active -
EditorManager-deactivate()
- Deactivates theEditorManager
-
EditorManager+edit(String userInput)
- Updates the state of the editing process based on user input -
EditorManager+edit(KeyEvent keyInput)
- Updates the state of the editing process based on user key press -
EditorManager-selectEventField()
- Uses theEventFieldSelector
to select a field within theEvent
-
EditorManager-selectEvent()
- Uses theLocationSelector
to select anEvent
Given below is an example usage scenario and how the editing mechanism behaves at each step.
-
The user invokes the
EditorManager
by typingedit
followed by the enter key. -
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anEditorCommand
Object. -
The
LogicManager
will callexecute()
on theEditorCommand
object which return aCommandResult
Object containing a message and theEventList
in theModel
component. -
The
LogicManager
will then callactivate()
on theEditorManager
object inside theLogicManager
class. Upon activation, the user inputs will be passed to theEditorManager
object throughedit()
instead until it is deactivated. -
The
EditorManager
will deactivate itself once the user input:save
orclose
, which updates or discard the edits that were made respectively
Given below here is an example of how the EditorManager
manages which Event
and the corresponding field it should select:
Step 1. The user launches edit mode, the EditorManager
is in a clean state and will simply select index 0 of the Event
in the EventList
.
Step 2. User presses the up key. The EditorManager
calls feedKeyCode()
on the LocationSelector
which will then find the nearest Event
in the up direction and updates the index.
Step 3. User presses the enter key which locks in on the Event
. The EditorManager
will again call LocationSelector+feedKeyCode()
, and the LocationSelector
will now lock itself, meaning that the arrow keys will no longer change the index.
Step 4. User presses the down key. The EditorManager
will now call EventFieldSelector+feedKeyCode()
, and the EventFieldSelector
will return an index pointing to a field within the Event
.
Step 5. The user now inputs: 09/11/20
. The EditorManager
will now call Editor+edit()
which edits the event2’s startDate from 30/10/19
to 09/11/20
.
-
Alternative 1: The user inputs a specific command instruction and SGTravel executes it
-
Pros: Easy to implement
-
Cons: Difficult for user to learn, hard to make mass edits
-
-
Alternative 2: The user use arrow, enter keys to navigate around the events and edit directly
-
Pros: Straightforward to use, allow mass edits
-
Cons: Difficult to implement
-
-
Alternative 1: An
EditCommand
to edit-
Pros: No changes are required on Logic and easy for anyone with knowledge of OOP to understand
-
Cons: Does not allow for flexibility on the user’s end as it takes in only strict inputs that adhered to the format
-
-
Alternative 2: Using
EditorManager
to edit-
Pros: Does not violate separation of concerns and Single responsibility principle as it only deals with edit operation
-
Cons: Require many accessory classes to reduce coupling and increase cohesion within the
EditorManager
itself
-
-
Alternative 1:
EditCommand
-
Pros: Require minimal changes to the code
-
Cons: Does not demonstrate student’s understanding of software engineering
-
-
Alternative 2:
EditManager
-
Pros: Requires much more data structures, where SOLID principles can be demonstrated
-
Cons: Changes need to be made to entire architecture
-
SGTravel allows users to plan and customize routes to assist in travelling around. Multiple transportation options are covered, allowing for flexibility in planning. The Route
planning mechanism is facilitated by the RouteList
. This section will document the implementation of the Route
feature and its various components. Included below is the class diagram for RouteList
:
Route
objects are stored in the RouteList
. Each Route
object itself represents a list of RouteNode
objects that correspond to physical locations in an area. Below is the class diagram of a Route
object:
RouteNode
is an abstract class extending the Venue class. RouteNode
itself is extended by the BusStop
, TrainStation
and CustomNode
objects. Below is the class diagram of these classes:
Given below is an example usage scenario and how the Model
,RouteList
and Route
behaves at each step when trying to add a new Route
to SGTravel:
-
The user calls the
RouteAddCommand
with its relevant parameters.
e.g.routeAdd Go to Sentosa
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anRouteAddCommand
Object. -
The
LogicManager
will callexecute()
on theRouteAddCommand
object which return aCommandResultText
Object containing a message. -
During the
execute()
method, theRouteList
is obtained by calling thegetRoutes()
method in theModel
. -
A new
Route
is created with the relevant parameters and is added to theRouteList
via theadd()
method in theRouteList
. -
The method
save()
is invoked in theModel
, and aCommandResultText
object is created with an appropriate message to show that the command has been executed properly.
Given below is the sequence diagram of how the various components work in the execute()
method of the RouteNodeAdd
command:
Should the user want to edit a Route, the RouteEditCommand
can be used. Given below is an example usage scenario and how the Model
, RouteList
and Route
objects behave at each step:
-
The user calls the
RouteEditCommand
with its relevant parameters.
e.g.routeEdit 1 name Go to MBS
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anRouteEditCommand
Object. -
The
LogicManager
will callexecute()
on theRouteEditCommand
object which return aCommandResultText
Object containing a message. -
During the
execute()
method, theRouteList
is obtained by calling thegetRoutes()
method in theModel
. -
The
Route
that is going to be edited is obtained through calling theget()
method in theRouteList
with the given index parameter. -
The
editField()
method of theRouteEditCommand
is invoked, which will call eithersetName()
orsetDescription()
in theRoute
object, with the appropriate parameter. If an invalid field is given, anUnknownFieldException
is thrown, which will be caught by theMainWindow
object later on. -
The method
save()
is invoked in theModel
, and aCommandResultText
object is created with an appropriate message to show that the command has been executed properly.
Given below is the sequence diagram of how the various components work in the execute()
method of the RouteEditCommand
command:
The user can also add different RouteNode
objects to a Route
. This can be done with the RouteNodeAddCommand
. Given below is an example usage scenario and how the Model
, RouteList
, Route
and RouteNode
object behave at each step:
-
The user calls the
RouteNodeAddCommand
with its relevant parameters.
e.g.routeNodeAdd 1 1 at 17009 by bus
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anRouteNodeAddCommand
Object. -
The
RouteNodeAddCommand
object will contain a newRouteNode
object which is created from thecreateRouteNode()
method in theRouteNodeAddParser
object. ThecreateRouteNode()
method will parse through the given user input, and call thegetRouteNode()
method if the input is in the correct format. -
Depending on the user input, a
BusStop
,TrainStation
orCustomNode
object will be created, and theRouteNodeAddCommand
will contain this new object. -
The
LogicManager
will callexecute()
on theRouteNodeAddCommand
object which return aCommandResultImage
Object containing a message and an image of the map at that location. -
During the
execute()
method, thefetchNodeData()
method is invoked to try to get the full information about the newRouteNode
object. If this fails, aCommandResultImage
object with the appropriate error message will be returned. -
The
addToRoute()
method of theRouteNodeAddCommand
is invoked. TheRoute
that the newRouteNode
is being add to, is obtained through calling thegetRoute()
method in theModel
with the given index parameter. TheRouteNode
is added to theRoute
by invoking theaddNode()
method in theRoute
with the given index parameter for theRouteNode
. -
The method
save()
is invoked in theModel
, and aRouteNodeShowCommand
is created with the index parameters of thisRouteNode
. Theexecute()
method of thisRouteNodeShowCommand
is invoked, to create a newCommandResultImage
object, which contains a message showing that theRouteNode
has been added. ThisCommandResultImage
also contains the image of the map of theRouteNode
. TheRouteNodeShowCommand
will be explained later on.
The user can also show the map of a RouteNode
by using the RouteNodeShowCommand
. Given below is an example usage scenario and how the Model
, RouteList
, Route
and RouteNode
object behave at each step:
-
The user calls the
RouteNodeShowCommand
with its relevant parameters.
e.g.routeNodeShow 1 1
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anRouteNodeShowCommand
Object. -
During the
execute()
method, theRoute
that theRouteNode
that is being shown is obtained by calling thegetRoute()
method in theModel
. By callinggetNode()
on theRoute
, theRouteNode
being shown will be obtained. -
The
generateRouteNodeShow()
method is called in theApiParser
class. This method constructs a string from the parameters of theRouteNode
object, and obtains anImage
object from thegetStaticMap
method in theApiParser
. -
A
StaticMapUrlRequest
object is created, which will send a request to the OneMap API. The API returns anImage
object back to theStaticMapUrlRequest
, which will return the object back to theApiParser
. ThisImage
is returned back to theRouteNodeShowCommand
object, which will then return aComandResultImage
object with theImage
and the parameters of theRouteNode
.
The user can also show the map of a RouteNode
and nearby BusStop
and TrainStation
objects by using the RouteNodeNearbyCommand
. Given below is an example usage scenario and how the Model
, RouteList
, Route
and RouteNode
object behave at each step:
-
The user calls the
RouteNodeNearbyCommand
with its relevant parameters.
e.g.routeNodeNearby 1 1
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anRouteNodeShowCommand
Object. -
During the
execute()
method, theRoute
that theRouteNode
that is being shown is obtained by calling thegetRoute()
method in theModel
. By callinggetNode()
on theRoute
, theRouteNode
being shown will be obtained. -
The list of nearby
RouteNode
(BusStop
andTrainStation
) are obtained by calling thegetNeighbour()
method in theApiParser
class. -
The
generateStaticMapNeighbours()
method is called in theApiParser
class. This method constructs a string from the parameters of theRouteNode
object and the list of nearbyRouteNodes
, and obtains anImage
object from thegetStaticMap
method in theApiParser
. -
A
StaticMapUrlRequest
object is created, which will send a request to the OneMap API. The API returns anImage
object back to theStaticMapUrlRequest
, which will return the object back to theApiParser
. ThisImage
is returned back to theRouteNodeNearbyCommand
object, which will then return aComandResultImage
object with theImage
and list of nearbyRouteNodes
.
The user can also automatically generate a new Route
by using the RouteGenerateCommand
. Given below is an exmample usage scenario and how the Model
, RouteList
and PathFinder
object behave at each step.
-
The user calls the
RouteGenerateCommand
with its relevant parameters.
e.g.routeGenerate amk hub to nus by bus
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
breakdowns the user input into and returns anRouteGenerateCommand
Object. -
The
generateRoute()
method is called in theRouteGenerateCommand
. It invokes thegetLocationSearch()
method in theApiParser
class for the 2 given location parameters. This creates 2Venue
objects corresponding to the starting and ending points of thisRoute
. These 2 objects are passed into theexecute()
method in thePathFinder
object that is constructed in this method. Thisexecute()
method will return a list ofVenue
objects corresponding to the transfer points that the Route will have. -
A new
Route
object is created, and all theVenue
objects are iterated through. For each pair ofVenue
objects in the list,addInbetweenNodes()
is invoked from theRouteGenerateCommand
, which creates all theRouteNode
objects that exist between these 2 transfer points and inserts them into the newRoute
.addEndingNode()
is then invoked to add theVenue
objects asRouteNode
objects to theRoute
. -
The
RouteList
is obtained by invokinggetRoutes()
in theModel
, and the newRoute
is added via theadd()
method in theRouteList
. -
The method
save()
is invoked in theModel
, and aCommandResultText
object is created and returned.
-
Alternative 1:
RouteNode
is stored as an object.-
Pros: Easy to edit and interact with.
-
Cons: Requires making a
BusStop
,TrainStation
andCustomNode
object, as all 3 items have different properties.
-
-
Alternative 2:
RouteNode
would be stored as a String instead, and is broken up into its individual parts when manipulated.-
Pros: Easy to implement.
-
Cons: Prone to errors in breaking up the String because of its contents, and hard to maintain.
-
-
Alternative 1: A
RouteList
object holds several Routes in an ArrayList.-
Pros: Easy to interact with and modify a
Route
. Keeps theModel
simple. -
Cons: Will require more work to implement.
-
-
Alternative 2:
Routes
would be located in an ArrayList in the SGTravel’s model.-
Pros: Quick and easy to implement.
-
Cons: Limited ability to manipulate a
Route
. Additional manipulation commands will bloat up the model.
-
The completed Route feature would include a RouteManager
class in v2.0. The RouteManager
will be similar to the EditorManager
class, and will be activated by entering a specific command. With the RouteManager
turned on, ther user interacts in multi-step commands instead. In order to simplify the user experience, the Route
and RouteNode
that the user wants to interact with, are stored in the RouteManager
. Commands entered will directly affect the Route
and RouteNode
stored in the RouteManager
, and the user can easily change the Route
and RouteNode
that are selected by commands too. A saving feature as seen in the EditorManager
class will also be implemented, allowing the user to easily save or discard changes.
SGTravel facilitates the user to create itineraries for their Singapore trip either from scratch or based on recommendations given. The user can then store these itineraries and access them at any time leading to effective trip planning and scheduling. This section will document the implementation of the Itinerary
feature and its various components. The Itinerary
feature is facilitated by the Itinerary
class as well as the commands outlined below. Below is the class diagram for Itinerary
:
As can be seen from the diagram above. The main Itinerary
class has different fields such as the startDate
and endDates
(to calculate the number of days) and a list of Agenda
. Here the Agenda
class models the activities and locations to be seen in one day. Hence, it contains a list of Todos
and extends VenueList
.
Given below is an example usage scenario and how the newItinerary
mechanism behaves at each step:
-
The user calls the
NewItineraryCommand
with its relevant parameters.
e.g.newItinerary 23/04/20 24/04/20 NewItinerary 1 /venue MBS /do sightseeing /and eating.
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
callsCreateNewItineraryParser
and returns anNewItineraryCommand
object and passes the created itinerary in its constructor. -
The
LogicManager
will callexecute()
on theNewItineraryCommand
object which interacts with and stores the new itinerary in theModel
. -
The method
save()
is invoked in theModel
, and aCommandResultText
object is created with an appropriate message to show that the command has been executed properly and the itinerary has been created. -
If no
Itinerary
is created, it would return a message stringMESSAGE_ITINERARY_FAIL_CREATION
.
The activity diagram given below shows how the NewItineraryCommand
interacts with the Model
interface to save the new Itinerary
.
Alternatively, the creation of itineraries can be facilitated by the RecommendationsCommand
and the AddListCommand
.
Given below is an example usage scenario and how the Recommendation
mechanism behaves and interacts with the Model
:
-
The user calls the
RecommendationsCommand
with its relevant parameters.
e.g.recommend itinerary between 23/04/20 and 24/04/20
-
The
LogicManager
parses the user input using theRecommendationsCommandParser
. -
The
RecommendationsCommandParser
returns aRecommendationsCommand
object and passes the relevant array of date details. -
The
LogicManager
will callexecute()
on theRecommendationsCommand
object which returns aCommandResultText
object containing the recommended itinerary. -
During the
execute()
method a recommendation object containing all of SGTravel’s possible locations is created. -
The recommendation object then calls on the
makeItinerary()
function. This then returns an itinerary object contains details of a recommended trip of the specified length. If more than 9 days are entered aRecommendationsFailException
is passed. -
This returned recommended itinerary is then stored into the
Model
component via thesetRecentItinerary()
method. This makes the recommendation easily accessible to theaddThisList
command. -
A
CommandResultText
object is created with an appropriate message to show that the command has been executed properly.
Given below is the sequence diagram of how the various components work in the execute()
method of the RecommendationsCommand
command:
Given below is an example usage scenario and how the AddList
mechanism behaves and interacts in the Model
after a Recommendation
has been added :
-
The user calls the
AddThisListCommand
with its relevant parameters.
e.g.addThisList MyNewVacation
-
The
LogicManager
parses the user input using theParser
. -
The
Parser
returns aAddThisListCommand
object and passes the relevant newName with it. -
The
LogicManager
will callexecute()
on theAddThisListCommand
object which returns aCommandResultText
object containing the newly added recommendation in full. -
During the
execute()
method the recommendation given most recently is retrieved from storage. If no recent recommendation is present aNoRecentItineraryException
is returned. -
The
confirmRecentItinerary()
method is called. This saves the current recommendation with the new name entered. -
The
setRecentItinerary()
sets the recentItinerary (recommendation) to null so that the same recommendation cannot be added twice. -
The method
save()
is invoked in theModel
, and aCommandResultText
object is created with an appropriate message to show the stored itinerary has succeeded
Given below is the sequence diagram of how the various components work in the execute()
method of the AddThisListCommand
command:
Other helper commands such as:
listItinerary
, showItinerary
and doneItinerary
have not been shown here as they are simple text displaying commands. These commands offer an important interface to the user so that they can manage and create itineraries with greater ease.
-
Alternative 1: Currently,
newItinerary
command offers a one-shot command alternative.-
Pros: Easy to use and learn. Easy to implement.
-
Cons: Parsing function is complicated and error prone.
-
-
Alternative 2: Through a GUI Interface.
-
Pros: Much easier for the user to use. Less error prone.
-
Cons: Difficult to implement. Does not add to showcasing OOP skill.
-
-
Alternative 1: Currently, SGTravel rewrites the hashmap every time a command is entered.
-
Pros: Easy to implement with little lines of code. Easy to understand.
-
Cons: Decreases efficiency of code and reduces performance.
-
-
Alternative 2: Option to only update the recently added list.
-
Pros: Increases program efficiency.
-
Cons: Difficult to implement.
-
The completed itinerary feature would allow the user to create an itinerary as if they were engaging in a conversation with SGTravel. This process would also allow the user to edit the itinerary before confirming and storing it. The app would use an algorithm to find the nearest attractions to the user’s hotel stay. The activity diagram for this feature is given below:
There are two main additions coming in v2.0 are :
-
Recommendations based on
hotel location
-
Edit
feature during and after itinerary creation.
SGTravel enables the user to create a personal profile and save basic information. The profile mechanism is facilitated by the ProfileCard
. It stores information internally as Preferences
, Person
and favourite
as depicted in the figure below.
💡
|
Detailed information of itinerary could be found in Section 3.5 Itinerary |
Profile mechanism implements the following feature:
-
profile
- set the profile of the person -
profileSet
- set preference of the person -
profileShow
- shows the user the current profile -
addFav
- add favourite itinerary to favourite list -
deleteFav
- delete itinerary from favourite list -
showFav
- shows the details of favourite itinerary -
listFav
- list all the favourite itinerary.
The figure above shows how profile
mechanism works. Given below is an example usage scenario and how the profile
mechanism behaves at each step:
-
User input command
profile
with relevant information.
E.gprofile James 01/01/1999
-
LogicManager
passes the input toParser
. -
Parser
constructs relevant parser, which isProfileParser
in this case. -
ProfileParser
to break down the input intoString
andLocalDateTime
, which is the required parameter to constructProfileAddCommand
. -
ProfileAddCommand
is returned back toLogicManager
. -
LogicManager
callsexecute()
function ofProfileAddCommand
. -
ProfileAddCommand
callssetPerson()
with the parsed input to setPerson
inProfileCard
to the name and birthday of user. -
ProfileAddCommand
calls save onmodel
to save theProfileCard
with new information.
ℹ️
|
If user did not set profile, |
profileSet
have similar mechanism as profile
. Given below is an example usage scenario and how the profileSet
mechanism behaves at each step:
-
User input command
profileSet
with relevant information.
E.gprofile sports true
-
LogicManager
passes the input toParser
. -
Parser
constructs relevant parser, which isProfileSetPreferenceParser
in this case. -
ProfileSetPreferenceParser
to break down the input intoString
andBoolean
, which is the required parameter to constructProfileSetPreferenceCommand
. -
ProfileSetPreferenceCommand
is returned back toLogicManager
. -
LogicManager
callsexecute()
function of ProfileSetPreferenceCommand. -
ProfileSetPreferenceCommand
callsgetProfileCard()
ofModelManager
to get theProfileCard
and then callssetPreference()
with the parsed input to setpreference
inProfileCard
to the category to the setting. -
ProfileSetPreferenceCommand
calls save onModelManager
to save theProfileCard
with new information.
💡
|
The only valid preference currently in V 1.4 are sports,lifestyle,arts and entertainment. Additional preferences could be added by just adding additional field in |
The figure above shows the mechanism of addFav
. Given below is an example usage scenario and how the addFav
mechanism behaves at each step:
-
User input command addFav with relevant information.
E.gaddFav SundayVacay
-
LogicManager
passes the input toParser
. -
Parser
constructs relevant parser, which isProfileAddFavParser
in this case. -
ProfileAddFavParser
to break down the input intoString
, which is the required parameter to constructProfileAddFavCommand
. -
ProfileAddFavCommand
is returned back toLogicManager
-
LogicManager
callsexecute()
function ofProfileAddFavCommand
. -
ProfileAddFavCommand
callsgetItinerary()
ofModelManager
to get the Itinerary of given name and then callsaddToFavourite()
ofModelManager
. -
ModelManger
will calladdToFavourite()
ofProfileCard
to add the itinerary tofavourite
inProfileCard
. -
ProfileAddFavCommand
calls save onModelManager
to save the ProfileCard with new information.
deleteFav
have similar mechanism as addFav
. Given below is an example usage scenario and how the deleteFav
mechanism behaves at each step:
-
User input command deleteFav with relevant information.
E.gdeleteFav SundayVacay
-
LogicManager
passes the input toParser
. -
Parser
constructs relevant parser, which isProfileDeleteFavParser
in this case. -
ProfileDeleteFavParser
to break down the input intoString
, which is the required parameter to constructProfileDeleteFavCommand
. -
ProfileDeleteFavCommand
is returned back toLogicManager
. -
LogicManager
callsexecute()
function ofProfileDeleteFavCommand
. -
ProfileDeleteFavCommand
callsdeleteFavourite()
function ofModelManager
. -
ModelManger
will calldeleteFavourite()
ofProfileCard
to delete theItinerary
fromfavourite
inProfileCard
. -
ProfileDeleteFavCommand
calls save onModelManager
to save theProfileCard
with new information.
Other helper commands such as: profileShow
, listFav
and showfav
have not been shown here as they are simple text displaying commands.
-
Alternative 1: Create another
itinerary
list as a favourite list.-
Pros: Easy to implement.
-
Cons: Easy to mix up
itinerary
list withfavourite
list.
-
-
Alternative 2: Create
itinerary
list in profile as favourite list.-
Pros: Easier and more intuitive for user to view favourite list.
-
Cons: More difficult to implement as some classes can’t be reused.
-
We are using java.util.logging
package for logging.
-
The
Logger
for a class can be obtained usingLogger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)
-
Log level can be indicated using
logger.log(Level.INFO, MESSAGE)
which logs messages according to the log level
Logging Levels
-
SEVERE
: Critical problem detected which may possibly cause the termination of the application -
WARNING
: Can continue, but with caution -
INFO
: Information showing the noteworthy actions by the App -
FINE
: Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size
Refer to the guide here.
Refer to the guide here.
Refer to the guide here.
Our application, SGTravel, is for tourists and citizens who are looking to travel within Singapore. It allows the user to compare travel timings and provides information on attractions, amenities and costs of travel.
Target user profile:
-
Our application is for those users who are comfortable using CLI apps and prefer desktop apps rather than using phone applications.
-
Our application focuses on users who want to ease their travelling process.
-
Our application takes in the users constraints and plans their trip accordingly.
-
Our application suggests excursion destinations, routes and provides guidance for tourists as well.
Value proposition:
-
By using this application, users gain access to all relevant travel information within Singapore (costs, time taken, attractions) without the need to download other desktop applications.
-
Our application simplifies the process of trip planning by showing the user the shortest path between their starting place and their ending destination.
-
Our application provides supporting information to the user such as the currency exchange rate, the weather forecast and flight information to other countries out of Singapore.
-
Our application will also provide travel itineraries for tourists in Singapore with limited days stay.
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 when I forget how to use the application |
|
User |
See a dashboard with all of my travels itineraries, destinations and suggestions |
Be updated of my schedule |
|
User |
To create new destination request |
Be presented with the shortest time to reach my destination |
|
User |
Be able to filter travel paths based on different constraints |
Plan an efficient trip based on my needs |
|
User |
Be able to see the cost to get to destination depending on mode of transportation |
Plan the mode of transportation |
|
User |
To update destination requests |
Update my trips |
|
User |
To delete destination requests |
Mark my trips as complete |
|
Tourist |
Be able to see at least 3 different itineraries when I enter number of days I am in Singapore |
Choose one according to my liking |
|
user |
Notification alert |
Reminded of my flights and travel plans |
|
user |
Be able to put reminders for events |
Remember my reservations/plans |
|
User |
Be able to search for a destination by using a search bar |
Can search for destinations before making a new request |
|
User |
Be able to see taxis nearby my location |
Quickly locate a ride |
|
Local |
Be able to find information on parking spaces and ERP |
Travel by car in the most cost effective way |
|
Tourist |
Be able to see famous tourist destinations on a map |
Plan where to travel |
|
User |
Be able to learn about the weather forecast of the day/week |
Be prepared while commuting |
|
Tourist |
Be able to convert my home currency into Singaporean dollar / other currencies |
I can view Singapore’s currency value and other countries I may be connecting to |
|
Local |
Be able to see the PSI index |
Plan my activities to be indoors or outdoors |
|
Tourist |
Be able to see a list of hotels |
Choose the best hotel |
|
User |
Be able to choose my preferred mode of transportation |
Have it as my default option. While displaying destination request put preferred mode first |
|
Tourist |
Be able to get a list of attractions along a specific travel path |
See the attractions while on the way / during layovers |
|
User |
Be able to see a list of closest / recommended restaurant to my location |
Choose places to eat with ease |
|
Exchange Student |
Be able to have access to information about student prices for various attractions |
Be aware of discounts |
|
User |
Be able to access more websites / tourist booking sites |
Make bookings with the relevant authorities in the country |
|
User |
Be able to gain information about events in and around Singapore |
Visit time specific events |
|
User |
Be able to interact with the app through a graphical user interface |
Interact with the application more easily |
|
New User (Tourist) |
Have recommendations for attraction and travel tips for tourists (getting around, food culture etc) |
Read about Singapore before going there |
|
User |
Inform me of road hazards and delays along the way |
Avoid traffic congestion and be careful while driving/travelling |
|
Tourist |
Rate my favourite attractions and write reviews of my experience |
Record a brief summary of my travels |
|
Tourist |
Be able to get flight information to and out of Singapore |
Choose the best flight option |
|
New user |
Have a natural language-like CLI |
So that I can use the app with greater ease |
|
Exchange students |
Be able to see destinations around my hostel |
Plan weekend trips around my station |
|
User |
Able to access reviews about different destinations by giving relevant links |
Pick my destination of choice with second opinions of people who went there |
|
User |
Have different view mode (night/day) |
To customise my App to my liking |
|
Elderly User |
Be able to increase font size |
See the content more easily |
{More to be added in v2.0}
(For all use cases below, the System is the SGTravel
and the Actor is the user
, unless specified otherwise)
MSS
-
User requests to see instruction manual with the command
help
-
SGTravel shows a list of possible commands
Use case ends.
Extensions
-
1a. User input invalid.
-
1a1. SGTravel shows an error message “Enter help to see all possible commands”.
Use case resumes at step 1.
-
MSS
-
User selects new destination option.
-
SGTravel asks for start and end location.
-
User inputs start and end location.
-
SGTravel shows the shortest path between 2 locations on a map.
Use case ends.
Extensions
-
3a. The user enters an invalid location
-
3a1. SGTravel shows an error message.
Use case resumes at step 2.
-
MSS
-
User enters the
choose destination
mode -
SGTravel asks for start and end location.
-
User inputs start and end location.
-
User requests to filter travel paths with
filter:<constraint>
. -
SGTravel shows the filtered list.
-
User chooses the desired travel path based on the constraints
-
SGTravel shows the travel path between 2 locations on a map.
Use case ends.
Extensions
-
3a. The given constraint is not valid
-
3a1. SGTravel shows an error message containing the valid constraint options.
Use case resumes at step 2.
-
-
*a. User request to exit choose destination mode
-
SGTravel exit choose destination mode and goes back to home page.
-
-
*b. User request to analysis the cost of current travel path
-
SGTravel shows the cost of current travel path
-
-
*c. User request to change the mode of transportation
-
*c1. SGTravel shows the list of available transportation
-
*c2. User input the choice of transportation
-
*c3. SGTravel shows the path based on chosen transportation
-
MSS
-
User enters the create itinerary mode
-
SGTravel request start and end date of the itinerary
-
User Enters start and end date.
-
SGTravel shows different options of itineraries
-
SGTravel requests user to enter their choice
-
User enters their choice of itinerary
-
SGTravel saves this to the itineraries list to display on the dashboard
Use case ends.
Extensions
-
3a. The input is not valid
-
3a1. SGTravel shows invaild input error message.
Use case resumes at step 2.
-
-
6a. The input is not valid
-
6a1. SGTravel shows invaild input error message.
Use case resumes at step 5.
-
-
*a. User request to exit create itinerary mode
-
SGTravel exit’s create itinerary mode and goes back to home page.
-
MSS
-
User enters the convert currency command.
-
SGTravel shows all of the possible currencies to convert.
-
SGTravel requests user to enter home currency and foreign currency.
-
User enters home currency and foreign currency.
-
SGTravel requests to enter home currency amount.
-
User enters home currency amount.
-
SGTravel shows the converted currency amount.
Use case ends.
Extensions
-
4a. The input currency is not valid
-
4a1. SGTravel shows an error message.
Use case resumes at step 2.
-
-
6a. The user input is invaild
-
6a1. SGTravel shows invaild error message.
Use case resumes at step 5.
-
-
*a. User request to exit convert currency
-
SGTravel goes back to home page.
-
MSS
-
User enters the flight option mode.
-
SGTravel requests the user to enter the destination country.
-
User enters destination country.
-
SGTravel shows the user all of the flights sorted according to costs.
-
User enters command to filter flights with “filter:<constraint> ” 6. (constraints based on for example : Airline type, Number of destination etc.)
-
SGTravel shows the filtered list of flight options.
-
User enters preferred flight plan.
-
SGTravel stores flight plan in memory to display on dashboard and notification center.
Use case ends.
Extensions
-
3a. The input destination is not valid
-
3a1. SGTravel shows an error message containing the valid country.
Use case resumes at step 2.
-
-
5a. The given constraint is not valid
-
5a1. SGTravel shows an error message containing the valid constraint options.
Use case resumes at step 4.
-
Use case: Check PSI level
MSS
MSS
-
User requests for weather forecast
-
SGTravel shows weather forecast for the week
Use case ends.
Extensions
-
*a. User requests for weather forecast with specific date
-
SGTravel shows weather forecast for specific date
-
{More to be added in v2.0}
-
Should work on any mainstream OS as long as it has Java
11
or above installed. -
Should be able to hold up to 1000 travel plans without a noticeable sluggishness in performance for typical usage.
-
A user with above average typing (65 wpm) speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most tasks faster using commands than using a mouse.
-
Should come with automated unit tests and open source code.
-
Should work on both 32-bit and 64-bit environments.
-
Should not exceed 200MB in size.
-
Should not use any words deemed offensive to English speakers.
{More to be added in v2.0}
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. |
ℹ️
|
We also recommend testers to have a stable internet connection throughout the tests. |
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-
-
Loading sample data
-
Put the jar file into an empty directory
-
Re-launch the app by double-clicking the jar file.
Expected: Sample data should be displayed in the application on start up.
-
-
Shutting down
-
Prerequisites: Ensure that you are not in editing mode. Type
close
followed by the enter key to exit edit mode without saving. -
Type
bye
followed by the enter key -
Expected: Application should shut down.
-
-
Set up or change your current profile
-
Test case:
profile Tom 25/12/1997
Expected: Profile is updated. Name of user is nowTom
and date of birth is now25/12/1997
. -
Test case:
profile Moo 21/12/9999
Expected: Profile is not updated. Error status is shown by SGTravel as date has yet to happen. -
Type
profile
followed by the enter key
Expected: Profile is not updated. SGTravel prompts user to input his/her name. -
Type
Alexander the Great
followed by the enter key
Expected: Profile is not updated SGTravel prompts for user to input date of birth. -
Type
09/09/1997
followed by the enter key
Expected: Profile is updated. Name of the user is nowAlexander the Great
and date of birth is09/09/1997
.
-
-
Setting your hobbies
-
Test case:
profileSet arts true
Expected: Profile is updated and the user now likes arts. -
Test case:
profileSet dance true
Expected: Pofile is not updated. Error invalid format status is shown by SGTravel as there are only 4 options to set currently:arts, lifestyle, sports, entertainment
.
-
-
Searching for the route of a bus service
-
Test case:
busRoute 96
Expected: Bus route of the bus service is shown by SGTravel. -
Test case:
busRoute CS3243
Expected: No bus route is shown. Error details shown by SGTravel as there is no such bus service. -
Other incorrect bus route commands to try:
busRoute boomboom
,busRoute 1231231231231
Expected: Similar to previous.
-
-
Adding an event
-
Prerequisites: List all events using the
list
command. There should be noBishan
within the list. -
Test case:
event Bishan between 24/02/24 and 26/02/24
Expected: Event is added to the list. Details of added event is shown by SGTravel. -
Test case:
event Mxwglht between 05/04/28 and 07/04/28
Expected: Event is not added. Error details shown by SGTravel as location does not exist. -
Test case:
event Prison between 30/04/18 and 06/05/19
Expected: Event is not added. Error details shown by SGTravel as dates are already in the past. -
Other incorrect add commands to try:
event
,event moo
,event orchard between 20/09/29 and 25/09/28
,event between and
,event park between and
,event park between Mon and
,event park between and Mon
Expected: Similar to the previous.
-
-
Deleting an event while all events are listed
-
Prerequisites: List all events using the
list
command. Multiple events in the list. -
Test case:
delete 1
Expected: First event is deleted from the list. Details of the deleted event is shown by SGTravel. -
Test case:
delete 0
Expected: No event is deleted. Error details shown by SGTravel. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size),delete 999999999999999999999
(where 999999999999999999999 is larger than anInteger
)
Expected: Similar to previous.
-
-
Editing and changing the information of the events.
-
Prerequisites: List all events using the
list
command. Multiple events in the list. -
Test case:
e 1 Changi 20/09/25 25/09/25
Expected: First event is edited from the list. Details of the new event is shown by SGTravel. -
Test case:
e 1 xxxxzwyx 09/08/28 10/08/28
Expected: No event is edited. Error details shown by SGTravel as there is no such location. -
Test case:
e 0 Changi 27/08/25 14/10/25
Expected: No event is edited. Error details shown by SGTravel. -
Other incorrect delete commands to try:
e
,e x Changi 20/09/25 25/09/25
(where x is larger than the list size),e Changi Mon Wed
,e 1 09/08/28 10/08/28
Expected: Similar to previous.
-
-
Recommend new itineraries for users to use as a travel plan
-
Test case:
recommend itinerary between 21/04/20 and 25/04/20
Expected: A recommended holiday plan is shown by SGTravel. -
Test case:
recommend itinerary between 19/06/19 and 24/06/19
Expected: No recommended holiday plan is shown. Error details shown by SGTravel as dates are already in the past. -
Test case:
recommend itinerary between 21/04/20 and 25/09/20
Expected: No recommended holiday plan is shown. Error details shown by SGTravel as SGTravel is unable to come up with such a long travel plan. -
Other incorrect itinerary commands to try:
recommend between 21/04/20 and 25/09/20
,recommend
,recommend between and
,recommend between
,recommend and
,recommend between and 12/12/21
,recommend between 12/12/21 and
Expected: Similar to previous. -
Prerequisites: Have used the recommend command at least once successfully upon application start up. And must not have used the
addThisList
command previously. -
Test case:
addThisList myHolidayTrip
Expected: MyHolidayTrip is added to the list of itineraries. The details of the trip is shown by SGTravel. -
Test case:
addThisList
Expected: No itineraries is added. Error details is shown by SGTravel as there is no name provided for the new itinerary.
-
-
Add travelling routes for user to use whilst travelling
-
Prerequisites: Must not add routes that has the same name as an existing one.
-
Test case:
routeAdd NUS to NTU
Expected: Adds a route to the list of routes. The details of the route is shown by SGTravel. -
Test case:
routeAdd
Exepcted: No routes is added. Error details is show by SGTravel as there is no name provided for the new route. -
Prerequisites: List all routes using the
routeListAll
command. Multiples routes in the list. Must not add route node that is already existing in the route. -
Test case:
routeNodeAdd 1 1 at 17009 by bus
Expected: Route node is successfully added to the first route in the list. The location of the node is shown by SGTravel. -
Test case:
routeNodeAdd 0 0 at 17009 by bus
Expected: No route node is added. Error details is shown by SGTravel. -
Other incorrect add route commands to try:
routeNodeAdd x 1 at 17009 by bus
(where x is larger than the list size),routeNodeAdd 1 1 at
,routeNodeAdd 0
,routeNodeAdd 1 1
,routeNodeAdd 1 1 17009
,routeNodeAdd 1 1 at 17009 by moo
,routeNodeAdd 1 1 at by
Expected: Similar to previous.
-
-
Delete travelling routes while all routes are listed
-
Prerequisites: List all routes using the routeListAll command. Multiples routes in the list.
-
Test case:
routeDelete 1
Expected: First route is deleted from the list. Details of the deleted routes is shown by SGTravel. -
Test case:
routeDelete 0
Expected: No route is deleted. Error details shown by SGTravel. -
Other incorrect delete commands to try:
routeDelete mopi
,routeDelete x
(where x is larger than the list size),routeDelete 999999999999999999999
(where 999999999999999999999 is larger than anInteger
)
Expected: Similar to previous. -
Prerequisites: List all routes using the
routeListAll
command. Multiples routes in the list. List all route nodes usingrouteShow x
(where x is the index of the route) Listed routes must consist of multiple nodes. -
Test case:
routeNodeDelete 1 1
Expected: First route node of the first route node is deleted from the list. Details of the deleted node is shown by SGTravel. -
Test case:
routeNodeDelete 0 1
Expected: No route is deleted. Error details shown by SGTravel. -
Other incorrect delete commands to try:
routeNodeDelete vrift
,routeNodeDelete 1 x
(where x is larger than the list size),routeNodeDelete 1 999999999999999999999
(where 999999999999999999999 is larger than anInteger
)
Expected: Similar to previous.
-
-
Dealing with missing/corrupted data files
-
Removing all
.txt
files in the same directory -
Re-launch the app by double-clicking the jar file.
Expected: All sample data should be in the application on start up. -
Removing or corrupting only the
profile.txt
files in the same directory -
Re-launch the app by double-clicking the jar file.
Expected: Sample profile, favourite and itinerary data should be loaded and the other contents still remain intact. -
Removing or corrupting only the
events.txt
files in the same directory -
Re-launch the app by double-clicking the jar file.
Expected: All sample data should be in the application on start up. -
Removing or corrupting only the
routes.txt
files in the same directory -
Re-launch the app by double-clicking the jar file.
Expected: Sample route, profile, favourite and itinerary should be loaded and the other contents still remain intact. -
Removing or corrupting only the
favourite.txt
files in the same directory -
Re-launch the app by double-clicking the jar file.
Expected: Sample favourite data should be loaded and the other contents still remain intact. -
Removing or corrupting only the
itineraries.txt
files in the same directory -
Re-launch the app by double-clicking the jar file.
Expected: Sample itinerary data should be loaded and the other contents still remain intact.
-