{ list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well }
Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main (consisting of classes Main and MainApp) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI: The UI of the App.Logic: The command executor.Model: Holds the data of the App in memory.Storage: Reads data from, and writes data to, the hard disk.Commons represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1.
Each of the four main components (also shown in the diagram above),
interface with the same name as the Component.{Component Name}Manager class (which follows the corresponding API interface mentioned in the previous point.For example, the Logic component defines its API in the Logic.java interface and implements its functionality using the LogicManager.java class which follows the Logic interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class which captures the commonalities between classes that represent parts of the visible GUI.
The UI component uses the 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,
Logic component.Model data so that the UI ca be updated with the modified data.Logic component, because the UI relies on the Logic to execute commands.Model component, as it displays Person object residing in the Model.API : Logic.java
Here's a (partial) class diagram of the Logic component:
The sequence diagram below illustrates the interactions within the Logic component, taking execute("delete 1") API call as a example.
Note: The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the Logic component works:
Logic is called upon to execute a command, it is passed to a MapletParser object which in turn creates a parser that matches the command (e.g., DeleteCommandParser) and uses it to parse the command.Command object (more precisely, a object of one of its subclasses e.g., DeleteCommand) which is executed by the LogicManager.Model when it is executed (e.g. to delete a person).Model) to achieve.CommandResult object which is returned back from Logic.Here are the other classes in Logic (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
MapletParser class creates a XYZCommandParser (XYZ is a placeholder for the specific command name e.g., AddCommandParser) which uses the other classes shown above to parse the user command and create a XYZCommand object (e.g., AddCommand) which the MapletParser returns back as a Command object.XYZCommandParser classes (e.g., AddCommandParser, DeleteCommandParser, ...) inherit from the Parser interface so that they ca be treated similarly where possible e.g, during testing.API : Model.java
The Model component,
Attraction, Itinerary, and Location objects.ObservableLists for attractions, itineraries, and locations so that the UI automatically updates when the data changes.UserPrefs object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPrefs object.Model represents data entities of the domain, they should make sense on their own without depending on other components).Note: Location references: A Location stores the Name objects of the attractions it groups instead of duplicating full Attraction instances. This keeps locations lightweight while ensuring they stay consistent with the attraction list.

API : Storage.java
The Storage component,
MapletStorage and UserPrefStorage, which means it ca be treated as either one (if only the functionality of only one is needed).Model component (because the Storage component's job is to save/retrieve objects that belong to the Model)Classes used by multiple components are in the seedu.address.commons package.
This section describes some noteworthy details on how certain features are implemented.
The location management feature lets users group attractions under named locations so that related places stay together.
AddLocationCommand, EditLocationCommand, and DeleteLocationCommand handle the respective commands. Their parsers enforce the ln/ prefix, the presence of at least one attraction index where needed, and a valid action/ value for edits.Location, LocationName, and UniqueLocationList represent the domain model. A Location wraps a LocationName and a set of attraction Names.Model#addLocation, Model#setLocation, Model#deleteLocation, and Model#hasLocationName delegate to Maplet, which maintains the UniqueLocationList. Model#deleteLocation, and Model#hasLocationName delegate to Maplet, which maintains the UniqueLocationList.JsonSerializableMaplet and JsonAdaptedLocation, which validate that every referenced attraction exists when reading from disk.LocationListPanel and LocationCard render the list of locations in the UI.When a user executes addlocation ln/Singapore i/1 i/2:
MapletParser chooses AddLocationCommandParser, which tokenises the prefixes and converts each i/ value into an Index.Model#getFilteredAttractionList() and collects their Name values, rejecting out-of-range indexes or duplicates.Location is created. Location enforces that at least one attraction is present and throws if the set is empty.Model#hasLocationName prevents duplicate location names before calling Model#addLocation, which stores the entry in the UniqueLocationList.When a user executes editlocation ln/Singapore action/ADD i/3:
MapletParser chooses EditLocationCommandParser, which tokenises the prefixes, checks that each is present exactly once, and converts i/ into an Index.EditLocationCommand obtains the attraction at index 3 from Model#getFilteredAttractionList() to avoid editing attractions that are currently hidden from the user.Location by name, clones its attraction Name set, and either adds or removes the selected attraction depending on the requested action. Attempting to add a duplicate, remove a non-existent attraction, or leave the location empty raises a CommandException.Location is constructed with the updated attraction set and persisted via Model#setLocation, ensuring that list observers are notified of the change.The deletelocation command follows a similar flow but calls Model#deleteLocation(LocationName) to remove the entry while leaving the attractions untouched.
Names instead of full attraction objects, locations stay synchronized with the attraction list without duplicating data.JsonAdaptedLocation#toModelType) so invalid data never reaches the model.Location instances during edits keeps them immutable, which simplifies UI updates and equality checks while preventing shared mutable state between locations and attractions.The itinerary management feature allows users to create travel plans by grouping attractions into named itineraries.
AddItineraryCommand and DeleteItineraryCommand handle the respective commands. Their parsers enforce the n/ prefix for itinerary names and ai/ prefix for the attraction indexes.Itinerary, ItineraryName, and UniqueItineraryList represent the domain model. An Itinerary wraps an ItineraryName, a creation timestamp (LocalDateTime), and a UniqueAttractionList of attractions.Model#addItinerary, Model#deleteItinerary, Model#hasItinerary, and Model#setItinerary delegate to Maplet, which maintains the UniqueItineraryList.JsonSerializableMaplet and JsonAdaptedItinerary, which store attractions as names and validate that every referenced attraction exists.ItineraryListPanel and ItineraryCard render the list of itineraries in the UI. When an itinerary is selected, its associated attractions are displayed in a separate panel managed by MainWindow#showItineraryDetails.When a user executes additinerary n/Singapore Trip ai/1 ai/2:
MapletParser chooses AddItineraryCommandParser, which tokenizes the prefixes and converts each ai/ value into an Index.Model#getFilteredAttractionList() and collects the Name values, rejecting out-of-range indexes or duplicate references.Itinerary is created with the current timestamp (LocalDateTime.now()). The Itinerary constructor validates that attractions are unique and stores them in a UniqueAttractionList.Model#hasItinerary prevents duplicate itinerary names before calling Model#addItinerary, which stores the entry in the UniqueItineraryList.The deleteitinerary command follows a similar flow but uses an index-based approach. It retrieves the itinerary from Model#getFilteredItineraryList() and calls Model#deleteItinerary(Itinerary) to remove it while leaving the referenced attractions intact.
Attraction objects rather than just names, allowing direct access to attraction details without additional lookups.UniqueAttractionList, enforcing that an attraction can only appear once per itinerary.The proposed undo/redo mechanism is facilitated by VersionedMaplet. It extends Maplet with an undo/redo history, stored internally as a mapletStateList and currentStatePointer. Additionally, it implements the following operations:
VersionedMaplet#commit() — Saves the current Maplet state in its history.VersionedMaplet#undo() — Restores the previous Maplet state from its history.VersionedMaplet#redo() — Restores a previously undone Maplet state from its history.These operations are exposed in the Model interface as Model#commitMaplet(), Model#undoMaplet() and Model#redoMaplet() respectively.
Given below is a 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 VersionedMaplet will be initialized with the initial Maplet state, and the currentStatePointer pointing to that single Maplet state.
Step 2. The user executes delete 5 command to delete the 5th person in the Maplet. The delete command calls Model#commitMaplet(), causing the modified state of the Maplet after the delete 5 command executes to be saved in the mapletStateList, and the currentStatePointer is shifted to the newly inserted Maplet state.
Step 3. The user executes add n/David … to add a new person. The add command also calls Model#commitMaplet(), causing another modified Maplet state to be saved into the mapletStateList.
Note: If a command fails its execution, it will not call Model#commitMaplet(), so the Maplet state will not be saved into the mapletStateList.
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoMaplet(), which will shift the currentStatePointer once to the left, pointing it to the previous Maplet state, and restores the Maplet to that state.
Note: If the currentStatePointer is at index 0, pointing to the initial Maplet state, then there are no previous Maplet states to restore. The undo command uses Model#canUndoMaplet() to check if this is the case. If so, it will return a error to the user rather
tha attempting to perform the undo.
The following sequence diagram shows how a undo operation goes through the Logic component:
Note: The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Similarly, how a undo operation goes through the Model component is shown below:
The redo command does the opposite — it calls Model#redoMaplet(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the Maplet to that state.
Note: If the currentStatePointer is at index mapletStateList.size() - 1, pointing to the latest Maplet state, then there are no undone Maplet states to restore. The redo command uses Model#canRedoMaplet() 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 Maplet, such as list, will usually not call Model#commitMaplet(), Model#undoMaplet() or Model#redoMaplet(). Thus, the mapletStateList remains unchanged.
Step 6. The user executes clear, which calls Model#commitMaplet(). Since the currentStatePointer is not pointing at the end of the mapletStateList, all Maplet states after the currentStatePointer will be purged. Reason: It no longer makes sense to redo the add n/David … command. This is the behavior that most modern desktop applications follow.
The following activities diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire Maplet.
Alternative 2: Individual command knows how to undo/redo by itself.
delete, just save the person being deleted).{more aspects and alternatives to be added}
{Explain here how the data archiving feature will be implemented}
Editing of itinerary
Currently there is no way to conveniently edit an itinerary. The use will have to create a new itinerary using the additinerary command with the desired attractions and delete the old itinerary.
This can become tedious especially when the itinerary contains many attractions. We plan to add an edititinerary command that takes in a ITINERARY_NAME, action/ACTION and ai/ATTRACTION_INDEX parameter.
This works similarly to editlocation where you can add/remove attractions from a corresponding location.
Viewing comments
Currently you can view comments by clicking on the "View Comments" button with a mouse, or using the tab key to select the button and pressing enter. This can be rather inconvenient especially when the
attraction list gets very large. To resolve we plan to add a viewcomment command which takes in an INDEX parameter and opens the comment window for the attraction corresponding to INDEX.
Switch tabs
Currently you can switch between attraction, location and itinerary tabs by either clicking on the tab buttons with a mouse, or using the tab key to select the buttons and navigating with arrows keys.
This is inconvenient and unintuitive for users and can be improved on by adding a switch command which takes in a KEYWORD parameter. This KEYWORD parameter can take on three values: "attraction", "location" and "itinerary"
and allow the user to switch between the different tabs through commands.
Target user profile:
This product is aimed at individuals planning to go overseas/frequent travellers.
Value proposition: manage locations of interest faster tha typical travel management sites
Maplet ca store useful information on locations and attractions:
For attractions (within the location):
Attractions are grouped by locations and have activities associated with them.
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
* * * | User | add a attraction | record information about an attraction |
* * * | User | delete a attraction | remove unneeded / outdated information |
* * * | User | list attractions | locate details on that attraction without going through the entire list |
* * * | User | list all attractions | keep track of all attractions of interest |
* * | User | edit attraction details | correct a error, or to add new information about an existing entry |
* * | User | filter attractions by distance | find the closest attraction to me |
* * | User | filter attractions | find attractions that fit my specifications |
* * | User | add attraction to location | group attractions by location |
* * | User | delete attraction | remove unneeded / outdated locations |
* * | User | compare locations | see which location better fits my needs |
* | Financially aware user | add cost (if applicable) | see the cost related to each attraction for planning |
* | Trip planner | add opening hours | track when the location opens for future reference |
* | Trip planner | edit opening hours | update opening hours on new information / errors |
* | Trip planner | check opening hours | check if the attraction is open at the time of my intended visit |
* | Trip planner | add activities to attractions | record the associated activities of an attraction |
* | Trip planner | sort attractions by activities | find available attractions for the activities to be planned |
* | Trip planner | create itinerary | plan a list of activities and attractions for the day, verified by opening hours |
* | Trip planner | edit itinerary | edit a list of activities and attractions for the day, verified by opening hours |
* | Trip planner | delete itinerary | remove a list of activities and attractions for the day |
* | Experienced User | user single letter commands | more quickly perform operations |
* | Experienced User | export trip details | access my trip details without opening the program |
{More to be added}
(For all use cases below, the System is the Maplet and the Actor is the user, unless specified otherwise)
Use case: UC1 - List attractions
MSS
User chooses to list attractions at a location
Maplet shows a list of attractions at the location
Use case ends.
Extensions
1a. User does not specify location
1a1. Maplet shows a list of all attractions
Use case ends.
2a. The list is empty.
Use case ends.
Use case: UC2 - Add attraction
MSS
User chooses to add attraction
User enters the required attraction details
Maplet adds new attraction to list
Use case ends.
Extensions
2a. Details are in the wrong format
2a1. Maplet prompts the user for correct format
2a2. User enters correct attraction details
Steps 2a1-2a2 are repeated until the details entered are correct.
Use case resumes at step 3.
Use case: UC3 - Delete attraction
MSS
User chooses to list attractions (UC1) to see index of attractions
User chooses to delete an attraction in the list
User enters index of attraction to delete
Maplet deletes the attraction
Use case ends.
Extensions
3a. The given index is invalid.
3a1. Maplet prompts the user for valid index
3a2. User enters valid index
Steps 3a1-3a2 are repeated until index entered is valid.
Use case resumes at step 4.
Use case: UC4 - Find attraction
MSS
User chooses to find a attraction with a keyword
User enters keyword
Maplet shows a list of attractions related to keyword
Use case ends.
Extensions
2a. No keyword is given.
2a1. Maplet prompts the user for valid keyword
2a2. User enters valid keyword
Steps 2a1-2a2 are repeated until keyword entered is valid.
Use case resumes at step 3.
3a. The list is empty
Use case ends.
Use case: UC5 - Edit attraction
MSS
User chooses to list attractions (UC1) to see index of attractions
User chooses to edit an attraction in the list
User enters index and details of attraction to edit
Maplet updates the attraction details
Use case ends.
Extensions
3a. The given index is invalid.
3a1. Maplet prompts the user for valid index
3a2. User enters valid index
Steps 3a1-3a2 are repeated until index entered is valid.
Use case resumes at step 4.
3b. Details are in the wrong format
3b1. Maplet prompts the user for correct format
3b2. User enters correct attraction details
Steps 3b1-3b2 are repeated until the details entered are correct.
Use case resumes at step 4.
Use case: UC6 - Sort attractions
MSS
User chooses to sort attractions by a criteria
User enters criteria
Maplet shows a list of attractions sorted by criteria
Use case ends.
Extensions
2a. Wrong criteria format is given.
2a1. Maplet prompts the user for valid criteria
2a2. User enters valid criteria
Steps 2a1-2a2 are repeated until criteria entered is valid.
Use case resumes at step 3.
3a. The list is empty
Use case ends.
17 or above installed.{More to be added}
Given below are instructions to test the app manually.
Note: 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 a empty folder
Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
Saving window preferences
Resize the window to a optimum size. Move the window to a different location. Close the window.
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
{ more test cases … }
Deleting a person while all persons are being shown
Prerequisites: List all persons using the list command. Multiple persons in the list.
Test case: delete 1
Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
Test case: delete 0
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
Other incorrect delete commands to try: delete, delete x, ... (where x is larger tha the list size)
Expected: Similar to previous.
{ more test cases … }
Dealing with missing/corrupted data files
{ more test cases … }