By: F14-B1
Since: Mar 2018
Licence: MIT
- 1. Overview
- 2. Setting up
- 3. Design
- 4. Implementation
- 5. Documentation
- 6. Testing
- 7. 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
My Team Manager (MTM) is a team managing application for football coaches. MTM is designed to be quick and easy for you to manage a team of players without the need of a mouse.
This developer’s guide provides information that will not only show the design principles of the project, but allow you to understand the different implementations, and give you the opportunity to get started with being a contributor or as a reference for experienced developer.
This developer’s guide consist of the following sections:
The following pointers teach you how to get started with developing on our application. You’ll learn how to prepare yourself with the project on IntelliJ and run it. Then, you’ll update configurations in IntelliJ to easily match the coding standards and you’ll be ready to start coding.
But before you start, there are two fundamental prerequisites you should know about MTM:
MTM is written in Java 8 and with the use of an integrated development environment (IDE) such as IntelliJ, it provides you as a developer with a better environment suited for developing MTM to maintain coding standards and provide continuous integration.
-
You are required to have JDK
1.8.0_60
or later,ℹ️Having any Java 8 version is not enough.
This app will not work with earlier versions of Java 8.
You can download Java 8 from here. -
and 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.
You can download IntelliJ from here.
Before you begin setting up the project, please ensure that you’ve done the following on our GitHub repository (repo).
-
Fork this repo, and clone the fork to your computer
Upon completion of forking from our GitHub repo, you can proceed to set up your project on IntelliJ.
-
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 which will generate all resources required by the application and tests.
Now that you’ve properly setup your project, you can verify that you have done everything properly.
-
Run the
seedu.address.MainApp
and try a few commands -
Run the tests to ensure they all pass.
You have verified the setup and you’re all ready to dive into the code, but before you do, check out the following configurations that will help you make your coding more integrated and accurate.
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, links in the documentation will still point to the se-edu/addressbook-level4
repo. If you plan to develop this as a separate product (i.e. instead of contributing to the se-edu/addressbook-level4
) , you should replace the URL in the variable repoURL
in DeveloperGuide.adoc
and UserGuide.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 3.1, “Software Architecture”.
-
Dive right in and get started with programming.
After you have successfully configured MTM, you are encouraged to read through the following section to understand the design of the software. The design section covers a high level overview the architecture and components of the software, as well as how common classes, configuration and logging throughout development are being used.
The Architecture Diagram given below (Figure 1) 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 MainApp
. It is responsible for,
-
At app launch: Initializes the components in the correct sequence, and connects them up with each other.
-
At shut down: Shuts down the components and invokes cleanup method where necessary.
Commons
represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.
-
EventsCenter
: This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design) -
LogsCenter
: Used by many classes to write log messages to the App’s log file.
The rest of 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.
For example, the Logic
component (see the class diagram given below) defines it’s API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class.
The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1
.
ℹ️
|
Note how the Model simply raises a AddressBookChangedEvent when the Address Book data are changed, instead of asking the Storage to save the updates to the hard disk.
|
The diagram below shows how the EventsCenter
reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.
ℹ️
|
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.
|
The sections below give more details of each component.
The UI Component contains a MainWindow that consists of various parts e.g. CommandBox, ResultDisplayer, TeamDetails, PersonListPanel, PlayerDetails etc. The UI classes are all child classes of the abstract UiPart class.
The structure of the UI component is shown in the figure below:
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
, PlayerDetails
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. -
Binds itself to some data in the
Model
so that the UI can auto-update when data in theModel
change. -
Responds to events raised from various parts of the App and updates the UI accordingly.
The Logic Component handles how each command would be parsed and executed. The class diagrams below illustrates the structure of the Logic Component and the structure of each individual commands.
XYZCommand
and Command
in Figure 6, “Structure of the Logic Component”API :
Logic.java
-
Logic
uses theAddressBookParser
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 a person) and/or raise events. -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API call.
The Model Component handles the players and teams data structures in My Team Manager. These structures also provide APIs to create, read, update and delete the details of these objects. The class diagram below shows the structure of the Model Component.
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores the Address Book data.
-
exposes an unmodifiable
ObservableList<Person>
that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -
does not depend on any of the other three components.
Any changes made to the data is retained and handled by the Storage Component. The structure of this component is shown in Figure 10 below.
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. -
can save the Address Book data in xml format and read it back.
All classes used by the different components can be found in the seedu.addressbook.commons
package.
Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json
).
We are using java.util.logging
package for logging. The LogsCenter
class is used to manage the logging levels and logging destinations.
-
The logging level can be controlled using the
logLevel
setting in the configuration file (See Configuration) -
The
Logger
for a class can be obtained usingLogsCenter.getLogger(Class)
which will log messages according to the specified logging level -
Currently log messages are output through:
Console
and to a.log
file.
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
This section describes some noteworthy details on how certain features are implemented.
The undo/redo mechanism is facilitated by an UndoRedoStack
, which resides inside LogicManager
. It supports undoing and redoing of commands that modifies the state of the address book (e.g. add
, edit
). Such commands will inherit from UndoableCommand
.
UndoRedoStack
only deals with UndoableCommands
. Commands that cannot be undone will inherit from Command
instead. The following diagram shows the inheritance diagram for commands:
As you can see from the diagram, UndoableCommand
adds an extra layer between the abstract Command
class and concrete commands that can be undone, such as the DeleteCommand
. Note that extra tasks need to be done when executing a command in an undoable way, such as saving the state of the address book before execution. UndoableCommand
contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the template pattern.
Commands that are not undoable are implemented this way:
public class ListCommand extends Command {
@Override
public CommandResult execute() {
// ... list logic ...
}
}
With the extra layer, the commands that are undoable are implemented this way:
public abstract class UndoableCommand extends Command {
@Override
public CommandResult execute() {
// ... undo logic ...
executeUndoableCommand();
}
}
public class DeleteCommand extends UndoableCommand {
@Override
public CommandResult executeUndoableCommand() {
// ... delete logic ...
}
}
Suppose that the user has just launched the application. The UndoRedoStack
will be empty at the beginning.
The user executes a new UndoableCommand
, delete 5
, to delete the 5th person in the address book. The current state of the address book is saved before the delete 5
command executes. The delete 5
command will then be pushed onto the undoStack
(the current state is saved together with the command).
As the user continues to use the program, more commands are added into the undoStack
. For example, the user may execute add n/David …
to add a new person.
ℹ️
|
If a command fails its execution, it will not be pushed to the UndoRedoStack at all.
|
The user now decides that adding the person was a mistake, and decides to undo that action using undo
.
We will pop the most recent command out of the undoStack
and push it back to the redoStack
. We will restore the address book to the state before the add
command executed.
ℹ️
|
If the undoStack is empty, then there are no other commands left to be undone, and an Exception will be thrown when popping the undoStack .
|
The following sequence diagram shows how the undo operation works:
The redo does the exact opposite (pops from redoStack
, push to undoStack
, and restores the address book to the state after the command is executed).
ℹ️
|
If the redoStack is empty, then there are no other commands left to be redone, and an Exception will be thrown when popping the redoStack .
|
The user now decides to execute a new command, clear
. As before, clear
will be pushed into the undoStack
. This time the redoStack
is no longer empty. It will be purged as it no longer make sense to redo the add n/David
command (this is the behavior that most modern desktop applications follow).
Commands that are not undoable are not added into the undoStack
. For example, list
, which inherits from Command
rather than UndoableCommand
, will not be added after execution:
The following activity diagram summarize what happens inside the UndoRedoStack
when a user executes a new command:
-
Alternative 1 (current choice): Add a new abstract method
executeUndoableCommand()
.-
Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with
Command
do not have to know thatexecuteUndoableCommand()
exist. -
Cons: Hard for new developers to understand the template pattern.
-
-
Alternative 2: Just override
execute()
.-
Pros: Does not involve the template pattern, easier for new developers to understand.
-
Cons: Classes that inherit from
UndoableCommand
must remember to callsuper.execute()
, or lose the ability to undo/redo.
-
-
Alternative 1 (current choice): Saves the entire address book.
-
Pros: Easy to implement.
-
Cons: May have performance issues in terms of memory usage.
-
-
Alternative 2: Individual command knows how to undo/redo by itself.
-
Pros: Will use less memory (e.g. for
delete
, just save the person being deleted). -
Cons: We must ensure that the implementation of each individual command are correct.
-
-
Alternative 1 (current choice): Only include commands that modifies the address book (
add
,clear
,edit
).-
Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost).
-
Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing
undo
.
-
-
Alternative 2: Include all commands.
-
Pros: Might be more intuitive for the user.
-
Cons: User have no way of skipping such commands if he or she just want to reset the state of the address * book and not the view. Additional Info: See our discussion here.
-
-
Alternative 1 (current choice): Use separate stack for undo and redo.
-
Pros: Easy to understand 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
andUndoRedoStack
.
-
-
Alternative 2: Use
HistoryManager
for undo/redo.-
Pros: We do not need to maintain a separate stack, 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.
-
Player
is one of the core components of MTM. To implement it, we used the same Person
object and added additional attributes to it. Shown below is the
new constructor for the Person object followed by the class diagram.
public Person(Name name, Phone phone, Email email, Address address, Remark remark, TeamName teamName,
Set<Tag> tags, Rating rating, Position position, JerseyNumber jerseyNumber, Avatar avatar) {
...
this.remark = remark;
this.teamName = teamName;
this.rating = rating;
this.position = position;
this.jerseyNumber = jerseyNumber;
this.avatar = avatar;
}
The add and edit command has also been updated to include teamName,Rating, Position, Jersey Number and Avatar.
One thing to note is that in executeUndoableCommand()
in both add and edit commands now does two additional things, setting the file path for players avatar, and assigning a player to a team
and adding person to current filtered list. Shown below is the code snippet of the new executeUndoableCommand()
and sequence diagram
of the new add command as an example.
@Override
public CommandResult executeUndoableCommand() throws CommandException {
requireNonNull(model);
try {
if (!toAdd.getAvatar().toString().equals(UNSPECIFIED_FIELD)) {
toAdd.getAvatar().setFilePath(toAdd.getName().fullName);
}
model.addPerson(toAdd);
if (!toAdd.getTeamName().toString().equals(UNSPECIFIED_FIELD)) {
model.assignPersonToTeam(toAdd, toAdd.getTeamName());
model.updateFilteredPersonList(toAdd.getTeamName());
EventsCenter.getInstance().post(new HighlightSelectedTeamEvent(toAdd.getTeamName().toString()));
} else { ...
The Rating
class contains a String value
. value
holds the players rating, an integer from 0 to 5. It uses a validation regex [0-5]
to ensure valid input.
The prefix for Rating
is ra/
The`Position` class contains a String value
. value
here holds the players position, an integer from 1 to 4. It uses a validation regex [1-4]
to ensure valid input.
The prefix for Position
is po/
.
A static hashmap is used to store and retrieve the corresponding position names of the position values. The position name can be called using the method getPositionName()
which returns myMap.get(value)
.
The code snippet for the hashmap
and getPositionName()
is shown below.
private static final Map<String, String> myMap;
static {
Map<String, String> aMap = new HashMap<>();
aMap.put("1", "Striker");
aMap.put("2", "Midfielder");
aMap.put("3", "Defender");
aMap.put("4", "Goalkeeper");
myMap = Collections.unmodifiableMap(aMap);
}
public String getPositionName() {
return myMap.get(value);
}
The`JerseyNumber` class contains a String value
. value
here holds the players jersey number, an integer from 0 to 99. It uses a validation regex [0-9]|[1-8][0-9]|9[0-9]
to ensure valid input.
The prefix for Position
is j/
.
The`Avatar` class contains a String value
. value
here holds the absolute filepath to the players avatar image file, a png of jpeg file. It uses a validation regex ([^\s]+(\.(?i)(jpg|png))$)
to ensure valid input.
The prefix for Avatar
is av/
.
When the class is created, the value
stores the original location of the players avatar image file eg. C:\image.png
or if not specified, stores "<UNSPECIFIED>".
Thereafter, if the avatar is specified when adding or editing a player, setFilePath()
is called which will attempt to copy the image file to a /image
folder that is in the same directory as the MTM.jar
file.
value
is then updated to the new file path.
The code snippet for setFilePath() is shown below.
public void setFilePath(String player) throws IOException {
if (value.equals("<UNSPECIFIED>")) {
return;
}
final File file = new File(value);
Path dest = new File("images/" + player.replaceAll("\\s+", "") + ".png").toPath();
Files.createDirectories(Paths.get("images")); // Creates missing directories if any
Files.copy(file.toPath(), dest, StandardCopyOption.REPLACE_EXISTING);
this.value = dest.toString();
}
Leaving a remark would be ideal for the user to note down any important detail of a player that is useful in the future. Remarks given can be in any format and therefore would not look good if it was done using tags instead, as tags are used with minimal words.
The remark mechanism is facilitated by RemarkCommand
and it inherits from UndoableCommand
, making it undoable. The mechanism allows user to perform the adding, editing, and deleting of a single remark to a specified Person
via the INDEX
from the list shown in the UI
. The field remark
is similar to the other fields of Person
, hence some of its logic in AddCommand.java
and EditCommand.java
are updated.
The mechanism uses the command remark
and a r/
prefix to add, edit, and delete a single remark of a Person
. When the user leaves the remark as empty after the r/
prefix, it is an indication to delete the remark, and when it is valid, it either create a new remark for that Person
or overwrites the current existing remark. Only one remark
is saved at a time. If the user remark
on the same person, it will be overwritten.
The field remark
is found in Person
, and it is not modified via add
or edit
commands. All functions related to remark
is done strictly via the remark
command.
The sequence diagram below illustrates the operation of the remark
command:
The remark
field is not required when adding or editing a Person
, and it will be initialized to an empty string or retrieved to fit the implementation of the other fields.
Code snippet from AddCommandParser.java
that shows how remark is initialized as empty.
public AddCommand parse(String args) throws ParseException {
//...AddCommandParser code...
Remark remark = new Remark("");
Person person = new Person(name, phone, email, address, remark, teamName, tagList, rating,
position, jerseyNumber, avatar);
//...AddCommandParser code...
}
Code snippet from EditCommand.java
that shows how remark is being retrieved.
private static Person createEditedPerson(Person personToEdit,
EditPersonDescriptor editPersonDescriptor) {
//...EditCommand code...
Remark updatedRemark = (personToEdit.getRemark().isPrivate()) ? personToEdit.getRemark()
: personToEdit.getRemark();
return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark,
updatedTeamName, updatedTags, updatedRating, updatedPosition, updatedJerseyNumber,
updatedAvatar);
}
-
Alternative 1 (current choice): Creates an additional
remark
field inPerson
.-
Pros: Follows the existing style of similar fields, making implementation easier.
-
Cons: Could only have 1 remark per person, unless
remark
mechanism follows thetag
mechanism to have multiple remarks.
-
-
Alternative 2: Have a list of remarks that associates with the specified
Person
.-
Pros: Can have multiple remarks associating with a specific
Person
. -
Cons: Harder to maintain, more complicated.
-
-
Alternative 1 (current choice): Create a specific command for remarking
Person
.-
Pros: Optional for user to enter remark during add, and have an isolated command to give remarks to a player that works as an add and edit command for remark.
-
Cons: Increases the number of commands available for the user, and could mistaken that remark can be done using add/edit command.
-
-
Alternative 2: Make it similar to how other fields are implemented in
Person
.-
Pros: Quick and easy implementation as it uses the same format as other fields, and can be added and modified through
add
andedit
commands. -
Cons: An additional field that can be input when adding a player, making the process longer to execute the command.
-
Phone
, Email
, Remark
, Rating
and Address
of a player has an additional boolean attribute
isPrivate
which tracks the privacy of the particular field. When a player is added into MTM, isPrivate
of these
fields are set to 'false' by default.
Each of these fields have a different toString
method which would return <Private 'FIELD'>
if privacy of
the field is set to 'true', as shown in the following code snippet:
@Override
public String toString() {
if (isPrivate) {
return "<Private Address>";
}
return value;
}
XmlAdaptedPerson
under the storage portion has been edited to save the isPrivate
value for the fields:
@XmlElement(required = true)
private Boolean phonePrivacy;
Toggling of privacy works in a similar way to Edit
. New fields will be created with privacy settings of EditPersonPrivacy
based on user input. This is done by first detecting if a particular prefix is present when command is
entered in TogglePrivacyCommandParser
:
if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
epp.setPrivatePhone(false);
}
If prefix of a field is not present, the privacy setting of that particular field in EditPersonPrivacy
would be null.
In TogglePrivacyCommand
, the new fields are created with this:
private static Phone createPhonePrivacy(Person person, EditPersonPrivacy epp) {
Phone phone;
try {
if (person.getPhone().isPrivate()) {
person.getPhone().togglePrivacy();
phone = new Phone(person.getPhone().toString());
person.getPhone().togglePrivacy();
} else {
phone = new Phone(person.getPhone().toString());
}
} catch (Exception e) {
throw new AssertionError("Invalid Phone");
}
if (epp.getPrivatePhone() != null) {
phone.setPrivate(person.getPhone().isPrivate());
phone.togglePrivacy();
} else {
phone.setPrivate(person.getPhone().isPrivate());
}
return phone;
}
Note that in the second portion of the code, if epp.getPrivatePhone()
is null, this would mean that it’s privacy
setting was not toggled and thus is set to the same as it was before. Else, it would be toggled:
public void togglePrivacy() {
this.isPrivate = isPrivate ? false : true;
}
A new person object is then created:
private static Person createEditedPrivacyPerson(Person personToEdit, EditPersonPrivacy epp)
throws IllegalValueException {
assert personToEdit != null;
Name updatedName = personToEdit.getName();
Phone updatedPhone = createPhonePrivacy(personToEdit, epp);
Email updatedEmail = createEmailPrivacy(personToEdit, epp);
Address updatedAddress = createAddressPrivacy(personToEdit, epp);
Remark updatedRemark = createRemarkPrivacy(personToEdit, epp);
TeamName updatedTeamName = personToEdit.getTeamName();
Set<Tag> updatedTags = personToEdit.getTags();
Rating updatedRating = createRatingPrivacy(personToEdit, epp);
Position updatedPosition = personToEdit.getPosition();
JerseyNumber updatedJerseyNumber = personToEdit.getJerseyNumber();
Avatar updatedAvatar = personToEdit.getAvatar();
return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark,
updatedTeamName, updatedTags, updatedRating, updatedPosition, updatedJerseyNumber, updatedAvatar);
}
and is used to update current person:
model.updatePerson(personToEdit, editedPerson);
-
Alternative 1 (current choice): Boolean
isPrivate
added to field classes.-
Pros: Privacy settings can be obtained straight from class by calling getter method.
-
Cons: Adddtional methods are needed to set and get value of
isPrivate
.
-
-
Alternative 2: Field privacy settings stored in
Person
in a HashMap.-
Pros: Field privacy can be accessed and modified easily
-
Cons:
Person
has to be accessed every time field privacy needs to be checked
-
-
Alternative 1 (current choice): All newly added players' fields are not private by default.
-
Pros:
Add
command does not need to be tweaked to allow adding player with private fields. -
Cons: After adding players, user has to do additional command to toggle privacy of fields.
-
-
Alternative 2: Include implementation of prefixes that denotes private field during
Add
.-
Pros: Newly added players can have private fields right away.
-
Cons: Addtional changes has to be made to
Add
command.
-
The sort
command is currently able to sort players by name, email, address, rating, jersey and postition in either ascending or descending order. Support for more fields will be added in subsequent updates.
-
The
sort
command is parsed throughSortCommandParser
which hands control to theSortCommand
class. -
Java Collections Sort API is used together with a custom
Comparator
in this implementation
Sorting is facilitated by the SortCommand
which uses method sortPlayers
to ultimately call method sortBy
in UniquePersonList for the actual sorting as shown in this code snippet:
switch (field) {
case "name":
comparator = nameComparator;
break;
//... code for other switch cases...
default:
throw new AssertionError("Invalid field parameter entered...\n");
}
switch (order) {
case "asc":
Collections.sort(internalList, comparator);
break;
case "desc":
Collections.sort(internalList, Collections.reverseOrder(comparator));
break;
default:
throw new AssertionError("Invalid field parameter entered...\n");
}
}
The code above utilises a custom Comparator
defined in the sample code below:
Comparator<Person> nameComparator = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getName().fullName.compareTo(p2.getName().fullName);
}
};
The following sequence diagram shows the program flow when sort
is used:
ℹ️
|
If address book is empty, a NoPlayerException will be thrown by sortBy method in UniquePlayerList.
|
-
Alternative 1 (current choice): Sorting is done in
UniquePersonList
.-
Pros: Sorting methods resides in class that handles most operations done to list. Future changes to implementation would be easier.
-
Cons: Complicated flow of control passed between classes.
-
-
Alternative 2: Sorting is done in
SortCommand
.-
Pros: Easier to trace flow of control as lesser passing between classes.
-
Cons: Unnecessary coupling if
UniquePersonList
has to rely onSortCommand
.
-
-
Alternative 1 (current choice): Sort can only be done by 1 field.
-
Pros: Easy to implement and input is straightforward.
-
Cons: Unable to fine tune to great detail how teams are sorted and displayed.
-
-
Alternative 2: Sort can be done by multiple fields.
-
Pros: Able to fine tune to great detail how teams are sorted and displayed.
-
Cons: Unnecessary as team managers would not need to sort players by multiple fields.
-
-
Alternative 1 (current choice): Address book is saved after sorting.
-
Pros: Easy to implement. Allows team managers to use preferred sort pattern in every session.
-
Cons: Previous order of players will be lost.
-
-
Alternative 2: Address book is not saved after sorting.
-
Pros: Good if team manager wants to sort players for current session only.
-
Cons: Sort order is lost when program exits. More memory is used to sort a list and reverse it after.
-
One of the core components to MTM is Team
, and it is the base to creating more commands for the user to access capability on team management. The Team
component provides user with better organizational methods for organizing their players and interact with multiple teams to manage them effectively.
We achieved the implementation of Team
component by introducing a UniqueTeamList
into the application which consists of a list of Team
objects. UniqueTeamList
is similar in context to UniquePersonList
in which it keeps a unique list of all the Team
objects. Team
objects consist of TeamName
object, which is used to uniquely identify the team. Team
inherits UniquePersonList
object which stores the a list of unique Person
objects, containing information of the player.
The class diagrams below shoes the relationship between the related classes. The Team Class Diagrams are an extension to the current existing Designs of Model
, Storage
, and UI
.
Code snippet from Team.java
that shows the constructor for Team
.
public class Team {
private final TeamName teamName;
public Team(TeamName teamName) {
this.teamName = teamName;
}
}
For the application to store the information of Team
into a list, we will need to introduce a new UniqueTeamList
variable into the AddressBook.java
, so that it will have a container for all the new Team
objects that is going to be created in MTM.
Code snippet from AddressBook.java
that shows the declaration and initialization of UniqueTeamList
.
public class AddressBook implements ReadOnlyAddressBook {
//...AddressBook code...
private final UniqueTeamList teams;
{
teams = new UniqueTeamList;
}
public void setTeams(List<Team> teams) throws DuplicateTeamException {
this.teams.setTeams(teams);
}
@Override
public ObservableList<Team> getTeamList() {
return teams.asObservableList();
}
//...AddressBook code...
}
Since Team
inherits the UniquePersonList
class, we are able to store Person
object into Team
by using the super class method add
in UniquePersonList
. By storing a list of players inside Team
object, future enhancements can easily make use of the data to perform functions that requires quick access to all players in a team. However, this have created a coupling in which data made to the changes made to `AddressBook.persons
needs to be updated in Team
to maintain synchronicity.
Code snippet from AddressBook.java
that shows the propagation of details updated in Person
from the full player list to the Person
objects in Team
.
public void updatePerson(Person target, Person editedPerson)
throws DuplicatePersonException, PersonNotFoundException {
requireNonNull(editedPerson);
//...syncEditedPerson initialisation...
if (!editedPerson.getTeamName().toString().equals(UNSPECIFIED_FIELD)) {
teams.getTeam(editedPerson.getTeamName()).setPerson(target, editedPerson);
}
persons.setPerson(target, syncedEditedPerson);
}
An additional TeamName
field is also added to Person
so that the user can quickly determine the team the player is in through the use of PREDICATE
.
Code snippet from Person.java
that shows the TeamName
field.
public class Person {
private final TeamName teamName;
public Person(Name name, Phone phone, Email email, Address address, Remark remark,
TeamName teamName, Set<Tag> tags, Rating rating, Position position,
JerseyNumber jerseyNumber, Avatar avatar) {
//...attributes initialisation...
this.teamName = teamName;
}
public TeamName getTeamName() {
return teamName;
}
}
Code snippet from ModelManager.java
that shows the use of filtering the person list using the TeamName
field in Person
.
@Override
public void updateFilteredPersonList(TeamName targetTeam) throws TeamNotFoundException {
requireNonNull(targetTeam);
List<Team> teamList = addressBook.getTeamList();
if (teamList.stream().anyMatch(target -> target.getTeamName().equals(targetTeam))) {
filteredPersons.setPredicate(t -> t.getTeamName().equals(targetTeam));
} else {
throw new TeamNotFoundException();
}
}
Additional exception classes are also created so that these new exceptions can be thrown during the program, such that when these exceptions are thrown, the code is easily comprehensible.
TeamNotFoundException.java
public class TeamNotFoundException extends Exception {
//...TeamNotFoundException...
}
DupliecateTeamException.java
public class DuplicateTeamException extends DuplicateDataException {
//...DuplicateTeamException...
}
Implementing a new component into MTM, the data consisting of Team
is also stored into storage using XmlAdaptedTeam.java
to format the output.
Code snippet from XmlAdaptedTeam.java
that shows the elements that will be stored into storage.
public class XmlAdaptedTeam {
@XmlElement(required = true)
private String teamName;
@XmlElement
private List<XmlAdaptedPerson> players = new ArrayList<>();
}
-
Alternative 1 (current choice):
Team
object stores aPerson
object that is also inUniquePersonList
andTeamName
is also an attribute ofPerson
.-
Pros: Provides an easier lookup of person belonging in which team without the need of iterating through the
UniqueTeamList
for a specificPerson
, and provide ease of access to data for future enhancements. -
Cons: Checks to ensure synchronicity need to be done thoroughly to ensure that player information is in sync between Team and full player list
-
-
Alternative 2:
Team
object is stored inPerson
object as an attribute.-
Pros: Logical thought process to include
Team
as an attribute. -
Cons: Excess storage wasted due to duplicated data of
Team
in everyPerson
.
-
To assist the user with manging teams, we have decided to use the design concept of Create-Read-Update-Delete (CRUD) to implement commands related to Team
.
This set of commands provide the basic necessities for the user to efficiently use our application.
Based on the principles of CRUD, we have created the commands that corresponds to each principles, namely, create
, view
, rename
and assign
, remove
.
With these commands, the application is ready to support the features that the application is designed to be used.
Team-related commands that modifies the data in the application, such as create
, rename
, assign
, and remove
, inherits from UndoableCommand
class which makes all these commands undoable. Each command has its own Parser
to uniquely parse user input arguments for the commands, so that it is validated and the command understand what it should be doing.
For each feature, new functions are created in AddressBook.java
and ModelManager.java
so that the commands executed are able to manipulate the data accordingly.
After the creation of a new team, the user will then be able to perform a new set of commands that performs team management functionality. Moreover, the add
command can be used with the prefix tm/
to immediately add the user into MTM and into the team specified.
The sequence diagram below illustrates the operation of the create
command:
The command uses the filteredPersons
list, and an overloaded updateFilteredPersonList
method that sets a predicate that filters the list based on a TeamName
, in ModelManager.java
so that it can be displayed via the UI.
The sequence diagram below illustrates the operation of the view
command:
The user can rename a specified team to a new team name only if the current team name does not existing in MTM. On a side note, it prevents the user from renaming the team to the same name that it currently have.
This command will update the name of the teams that are in teams
in AddressBook.java
and will update all the players TeamName
field in persons
and Team
with the new team name.
The sequence diagram below illustrates the operation of the rename
command:
The core feature that organizes the player into teams so that the user can easily view the desired team players quickly. The command have 2 functions, firstly it is able to assign an individual or a set of players to a specified team, and secondly, it is able to unassign an individual or a set of players from any team.
The command takes in a set of indexes that corresponds to the player in the current list, and will perform the assign operation in ascending index order. If the command is unable to process an index given, it will process all the valid index until the index that causes an issue.
ℹ️
|
Typical issues that the command handles are,
|
This command will update and synchronise all affected players in persons
and in the specified Team
object, such that their TeamName
field in Person
are updated with the new assigned or unassigned team. At the same time, the list of players in Team
gets update if new player are assigned or unassigned.
The sequence diagram below illustrates the operation of the assign
command:
The final step in completing the CRUD design is the removal of team. It will delete the Team
from teams
list in AddressBook.java
and will update all affected players in the team to be unassigned from the team by having their TeamName
field updated with an unspecifed field.
The sequence diagram below illustrates the operation of the remove
command:
-
Alternative 1 (current choice): Uses CRUD design concept to create new commands.
-
Pros: Systematic approach in dealing with what functions should be created to ensure that the product has the features required to perform team management, and provides a platform to work on ideal features that target the needs of the audience.
-
Cons: Simple and only captures the basic requirements of the product.
-
-
Alternative 2: Implement commands based on suggested features.
-
Pros: End product will have features that targets the need of the audiences.
-
Cons: Possible to miss out basic and core features of a team management application.
-
-
Alternative 1 (current choice): Make all functions that modifies data to be undoable.
-
Pros: Follows the current implementation of
undo
such that if data are modified in MTM, it will be an undoable command. -
Cons: Need to ensure that regression bugs are squashed when the new commands are executed, and time consuming in identifying regression.
-
-
Alternative 2: Make all functions not undoable.
-
Pros: Easy to implement and will produce lesser regression bugs.
-
Cons: Does not align with the current implementation of
undo
&redo
, and the user would require more steps to revert any changes made.
-
Locking mechanism of MTM can be toggled on and off using the Key
command. It utilises the Model to access
user preferences of MTM. Current lock state and password is stored in UserPrefs
in the Storage component.
This activity diagram shows the logic behind the Key
command:
Password checking done in Key
command:
private boolean correctPassword() {
UserPrefs up = model.getUserPrefs();
String hash = Hashing.sha256().hashString(password, StandardCharsets.UTF_8).toString();
return hash.equals(up.getAddressBookHashedPass());
}
When password check is done, the lock on MTM is toggled to the state opposite of the current:
if (correctPassword()) {
if (model.getLockState()) {
model.unlockAddressBookModel();
} else {
model.lockAddressBookModel();
}
logger.info("Lock state is now: " + Boolean.toString(model.getLockState()));
return new CommandResult(MESSAGE_SUCCESS);
}
In a locked state, only certain functions of MTM can be used. This is to prevent unauthorised tampering with the details stored on MTM.
This is done in AddressBookParser
. Commands are split into low level or not. When a command is
being executed, it is checked if it falls under the low level category. If so, it would execute. When adding new features,
add them to this list should you feel it is 'low level':
private Command lowLevelCommand(String commandWord, String arguments) throws ParseException {
switch(commandWord) {
case ChangeThemeCommand.COMMAND_WORD:
case ChangeThemeCommand.COMMAND_ALIAS:
return new ChangeThemeCommandParser().parse(arguments);
case FindCommand.COMMAND_WORD:
case FindCommand.COMMAND_ALIAS:
return new FindCommandParser().parse(arguments);
case ListCommand.COMMAND_WORD:
case ListCommand.COMMAND_ALIAS:
return new ListCommand();
case KeyCommand.COMMAND_WORD:
case KeyCommand.COMMAND_ALIAS:
return new KeyCommandParser().parse(arguments);
case ViewCommand.COMMAND_WORD:
case ViewCommand.COMMAND_ALIAS:
return new ViewCommandParser().parse(arguments);
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
case SortCommand.COMMAND_WORD:
case SortCommand.COMMAND_ALIAS:
return new SortCommandParser().parse(arguments);
default:
return null;
}
}
If command being executed does not fall into the category of 'low level', a check on the lock state of MTM is done before allowing or restricting access:
if (lockState) {
throw new ParseException(MESSAGE_RESTRICTED);
}
-
Alternative 1 (current choice): Lock is toggled with only a single command
Key
.-
Pros: Easy to implement, just check current lock state and switch it.
-
Cons: With a toggle, user might unlock MTM thinking he/she is locking it.
-
-
Alternative 2: Locking and unlocking of MTM is done with two separate commands.
-
Pros: Ensures that when a lock is done, MTM is truly locked.
-
Cons: Addtional command needs to be created, along with its command parser and implementations.
-
The tab autocomplete feature works by handling the Tab key pressed event, searching for any commands with matching prefix and returns one if found.
The command strings are stored in a Trie data structure, named CommandTrie
, for optimal search.
public class CommandTrie {
String attemptAutoComplete (String input);
void insert (String input);
Set<String> getCommandSet();
}
The trie is made up of TrieNode
objects which contains three objects. The TrieNode sibling
represents a character on the same level as the current one. The TrieNode child
represents a next possible letter.
For example in the words edit
and exit
, the node for d
would have a child i
and a sibling x
. x
would have a child i
.
public class TrieNode {
private TrieNode sibling;
private TrieNode child;
private char key;
...
}
Upon the pressing of the tab key, the command box calls attemptAutoComplete
, in the commandTrie
class. With the content of the command box as the query, attemptAutoComplete searches for the query using the standard Trie search algortihm.
If the query prefix itself is not present in the trie, then the attempt fails. If the query reaches a TrieNode
with a sibling
or child
TrieNode
,
it will provide the user with possible commands for a given input. In this case, a dropdown box will appear in the UI that lists the possible commands for the user.
Upon finding a matching command, attemptAutoComplete
returns it and replaces the text in the Command Line. If the no matching command is found, the text is turned red.
MTM is designed to manage a multitude of players and the main function that would be used repetitively is the add
command, hence it would alleviate the cumbersomeness of entering every detail of the players when you want the command to be executed quickly.
Having the option to only entering the mandatory fields and leaving the optional ones empty will make the process of adding players more efficient.
This mechanism enhances the original implementation of the add
command.
The required fields that are mandatory to be filled by the user are n/NAME
and e/EMAIL
.
This implementation of making fields optional for AddCommand
allows user to add players without their full information, and can be updated accordingly when the user retrieves their full information.
Code snippet from ParserUtil.java
that shows the implementation of when a field is left empty.
"<UNSPECIFIED>" string will be return when the value passed in is empty.
public class ParserUtil {
public static final String UNSPECIFIED_FIELD = "<UNSPECIFIED>";
//...ParserUtil code...
public static Optional<String> parseValue(Optional<String> value, String messageConstraints)
throws IllegalValueException {
if (value.isPresent() && value.get().equals(UNSPECIFIED_FIELD)) {
throw new IllegalValueException(messageConstraints);
} else {
return Optional.of(value.orElse(UNSPECIFIED_FIELD));
}
//...ParserUtil code ...
}
Code snippet from AddCommandParser.java
that shows the usage of parsing fields that are set as optional.
public AddCommand parse(String args) throws ParseException {
//...tokenize arguments...
if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_EMAIL)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
AddCommand.MESSAGE_USAGE));
}
//...get person details from arguments...
Address address = ParserUtil.parseAddress(ParserUtil.parseValue(argMultimap
.getValue(PREFIX_ADDRESS), Address.MESSAGE_ADDRESS_CONSTRAINTS)).get();
return new AddCommand(person);
//...AddCommandParser code...
}
Code snippet from Address.java
that shows an example of an optional field being valid when not specified.
public class Address {
//...Address code...
public static boolean isValidAddress(String test) {
return test.matches(ADDRESS_VALIDATION_REGEX) || test.equals(UNSPECIFIED_FIELD);
}
}
-
Alternative 1 (current choice): Set a default value for unspecified fields and parse fields that are mandatory.
-
Pros: Easy to implement as fields are still filled with information even though user did not specify.
-
Cons: Unattractive display of fields when it is unspecified.
-
-
Alternative 2: Require users to input all fields.
-
Pros: The current implementation is used, hence there is no additional code to ensure validity of unspecified input.
-
Cons: Less flexibility is given to the user when adding a player into the application.
-
The Team Display Bar is implemented as TeamDisplay
in the UI Component and renders TeamDisplay.fxml
.
It is called from MainWindow
and highlights
the current team that has been selected in the Command Line Interface by the user.
It calls the Team
model and displays the Player
cards associated with that Team
.
It contains event handler methods such as handleShowNewTeamEvent(), handleHighlightSelectedTeamEvent(),
and handleDeselectTeamEvent(), which update the UI accordingly.
The component interactions can be seen in the high level sequence diagram for TeamDisplay
below, using the example of a create
command:
Code snippet from 'TeamDisplay' to show initialisation of UI component and event handlers:
public class TeamDisplay extends UiPart<Region> {
private static final String FXML = "TeamDisplay.fxml";
public TeamDisplay() {
super(FXML);
//...initialise Teams code...
registerAsAnEventHandler(this);
}
@Subscribe
private void handleShowNewTeamEvent(ShowNewTeamNameEvent event) {
//...handleShowNewTeamEvent code...
}
@Subscribe
private void handleHighlightSelectedTeamEvent(HighlightSelectedTeamEvent event) {
//...handleHighlightSelectedTeamEvent code...
}
@Subscribe
private void handleDeselectTeamEvent(DeselectTeamEvent event) {
//...handleDeselectTeamEvent code...
}
}
-
Alternative 1 (current choice):
TeamDisplay
is placed underneath theResultDisplay
and above thePersonListPanel
.-
Pros: Located at an obvious location for the user to view the team currently selected.
-
Cons: There will be less space for the
ResultDisplay
andCommandBox
.
-
-
Alternative 2:
TeamDisplay
is placed on the left of thePersonListPanel
as a vertical bar.-
Pros: There will be more space for the
ResultDisplay
andCommandBox
. -
Cons: It is less obvious to the user as it is at the side.
-
The Player Details
pane is implemented as PlayerDetails
in the UI Component.
It is called from PlayerListPanel
. It renders PlayerDetails.fxml
and displays the selected PersonCard
.
It calls the Person
model and displays the fields in the Person
model that are not displayed in the left panel.
It contains the event handler method handlePersonDetailsChangedEvent(), which updates the UI component when the edit `
or `remark
commands are entered.
The component interactions can be seen in the high level sequence diagram for PlayerDetails
below, using the example of a remark
command:
Code snippet from 'PlayerDetails' to show initialisation of UI component:
public class PlayerDetails extends UiPart<Region> {
private static final String FXML = "PlayerDetails.fxml";
public final Person person;
public PlayerDetails(Person person) {
super(FXML);
this.person = person;
//....player details code...
}
@Subscribe
private void handlePersonDetailsChangedEvent(PersonDetailsChangedEvent event) {
...handlePersonDetailsChangedEvent code...
}
}
-
Alternative 1 (current choice):
PlayerDetails
is placed on the right ofPerson List Panel
, replacing theBrowserPanel
.-
Pros: It is the only large unused space in the software left and is right beside the
Person List Panel
, thus is the logical place to look at after selection of a person card. -
Cons: This is a lot of whitespace in the pane as are not many fields.
-
-
Alternative 2:
PlayerDetails
pane size is reduced and the extra space is repurposed.-
Pros: There will be more space for another new feature e.g. calendar.
-
Cons: It can only be implemented in v2.0 due to time constraints in development.
-
The ChangeThemeCommand
is a new feature that allows user to change the current theme to another theme. A new css class is implemented to accommodate the new theme, LightTheme.
The MainWindow
class is also changed to contain a handleChangeThemeRequestEvent() method which is an event handler to setAddressBookTheme
,
which is a method in UserPrefs
.
Below is the sequence diagram for how the ChangeThemeCommand
works:
changeTheme
CommandCode snippet from 'ChangeThemeCommand':
public class ChangeThemeCommand extends Command {
public ChangeThemeCommand(String theme) {
this.theme = theme.trim();
}
@Override
public CommandResult execute() throws CommandException {
//...check for valid theme code...
EventsCenter.getInstance().post(new ChangeThemeEvent(this.theme));
return new CommandResult(String.format(MESSAGE_THEME_SUCCESS, this.theme));
}
}
-
Alternative 1 (current choice): The command syntax is in the form "changeTheme Dark" or "changeTheme Light".
-
Pros: This supports future implementation of more themes, so that the developer can easily add the new themes without having to change the execution.
-
Cons: The command is longer than it could be. (see alternative 2)
-
-
Alternative 2: The command syntax in the form "changeTheme", which would automatically toggle the theme.
-
Pros: User does not have to type anything to change the theme, so it might be more user friendly.
-
Cons: Future implementation of more themes would be harder for the developer as the toggle function would have to be changed quite drastically to become a command for selecting a theme out of multiple themes.
-
-
Alternative 1 (current choice):
ChangeThemeCommand
is implemented as a CLI command.-
Pros: Consistent with the rest of the application, of which all changes are made by the CLI.
-
Cons: User has yet another command to remember the syntax of.
-
-
Alternative 2: Change of theme is implemented as a button to change onClick.
-
Pros: User does not have to type anything to change the theme, so it might be more user friendly.
-
Cons: Inconsistent with the rest of the application, which is CLI-based.
-
The Set
Command is an entirely new command that allows the user to assign a colour to a specific tag.
This mechanism is facilitated by the SetCommandParser
, which creates and returns a new SetCommand
.
In SetCommandParser
, which implements the Parser
interface, it parses the arguments inputted into the CLI, and checks whether the arguments are valid.
SetCommandParser
is implemented as such:
public class SetCommandParser implements Parser<SetCommand> {
public SetCommand parse(String args) throws ParseException {
// ...parse arguments and check for invalid arguments...
}
}
SetCommand
inherits the abstract Command
class. After execute()
is called in SetCommand
, the tag colour is set through the logic portions of ModelManager
and AddressBook
, then
lastly changes tagColour
attribute within the Tag
object itself. It also posts an event in SetCommand
, to which
its handler in PersonCard
responds and performs the UI update.
SetCommand
is implemented in this way:
public class SetCommand extends Command {
private final Tag tagToSet;
private final String tagColour;
public SetCommand(Tag tag, String colour) {
requireNonNull(tag);
tagToSet = tag;
tagColour = colour;
}
@Override
public CommandResult execute() {
requireNonNull(model);
boolean isTagValid = model.setTagColour(tagToSet, tagColour);
//...check for valid tagName code....
EventsCenter.getInstance().post(new ChangeTagColourEvent(tagToSet.getTagName(), tagColour));
return new CommandResult(String.format(MESSAGE_SUCCESS, tagToSet.toString(), tagColour));
}
}
The following sequence diagram shows how the set command operation works:
-
Alternative 1 (current choice): Inherit from
Command
.-
Pros: Does not involve complicated undo/redo tests, simple and quicker implementation, lessen chances of mistakes made in implementation.
-
Cons: User cannot use the
undo/redo
command.
-
-
Alternative 2 : Inherit from
UndoableCommand
.-
Pros: User can utilise the
undo/redo
command. -
Cons: Hard for developers to implement extra tests, not very necessary as users can just as easily type out the colour they would like to change their tag to; it is a short command, especially with the
stc
alias.
-
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.
If you are intending to develop our software further, it is highly recommended that you run tests in the ways listed below.
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
)
We have two types of tests:
-
GUI Tests - These are tests involving the GUI. They include,
-
System Tests that test the entire App by simulating user actions on the GUI. These are in the
systemtests
package. -
Unit tests that test the individual components. These are in
seedu.address.ui
package.
-
-
Non-GUI Tests - These are tests not involving the GUI. They include,
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.address.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.address.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g.seedu.address.logic.LogicManagerTest
-
This section covers resources for you to develop this software with good practices and prepare it for release.
See UsingGradle.adoc to learn how to use Gradle for build automation.
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
MainApp.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.
A project often depends on third-party libraries. For example, Address Book depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)
This section tells you more details of our software, our target users, the user stories, and gives you a sneak peak into our development process.
-
football team managers
-
has a need to manage a significant number of contacts
-
prefer desktop apps over other types
-
can type fast
-
prefers typing over mouse input
-
is reasonably comfortable using CLI apps
Exclusive application for management of footballers and football teams that provides an enhanced listing of footballers and convenient lookup on updated information of players.
-
Codee
-
Major - Revamp MTM’s whole GUI for ease of viewing teams and players’ details.
-
e.g. remove browser panel, add player details panel, change person card.
-
-
Minor - Command to set the colour of tags to colour of choice.
-
-
Jordan
-
Major - Implementation of Team Component and Related Functions.
-
e.g. creation of team, assignment of players, viewing of teams, removal of team, renaming of team.
-
-
Minor
-
Introduce
remark
command andremark
field. -
Enhanced add functionality to allow optional fields.
-
-
-
Syafiq
-
Major - Create a new player class that contains more information about the players.
-
e.g. Position, Rating (0 - 5 Star), Remarks
-
Update add, edit ,list, sort to include these fields accordingly.
-
-
Minor - Autocomplete command
-
-
Tianwei
-
Major - privacy
-
Set private field and passwords
-
Make accounts
-
-
Minor - Sorting players by different fields
-
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
First Time User Stories |
|||
|
new user |
see usage instructions |
refer to instructions when I forget how to use the App |
|
new user |
access the user guide |
find out how to use fancy features of the application |
|
new user |
see a list of instructions available |
navigate and use the application easily |
Player Related User Stories |
|||
|
team manager |
add a new player |
|
|
team manager |
add player’s address |
mail him important documents |
|
team manager |
add player’s contact number |
contact him when needed |
|
team manager |
add player’s email |
email him when needed |
|
team manager |
add player’s jersey number |
easily identify them during the game |
|
team manager |
add player’s position |
easily pick my lineup for match |
|
team manager |
add player’s ratings |
easily identify the better players |
|
team manager |
delete a player |
remove him if I kick him out from the team or he quits |
|
team manager |
find a player by name |
locate details of players without having to go through the entire list |
|
team manager |
edit player’s contact number |
|
|
team manager |
edit player’s email |
|
|
team manager |
edit player’s jersey number |
|
|
team manager |
add player’s match stats (e.g. goals scored) |
decide the lineup, give award |
|
team manager |
add player remarks |
for self note |
|
team manager |
add player’s avatar |
for facial recognition |
|
team manager |
edit player’s position |
easily pick my lineup for match |
|
team manager |
edit player’s address |
|
|
team manager |
edit player’s avatar |
|
|
team manager |
edit player remarks |
for self note |
|
team manager |
hide private contact details by default |
minimize chance of someone else seeing them by accident |
|
team manager |
edit player’s name |
|
|
team manager with many players in the team managing application |
sort by player name |
locate a person easily |
Team Related User Stories |
|||
|
team manager |
create teams |
organize and manage my players through their respective team |
|
team manager |
assign player to teams |
identify the team that the player is playing for |
|
team manager |
view players in specified team |
identify the lineup of the team and which player belongs to which team |
|
team manager |
remove teams |
remove teams that I no longer managed |
|
team manager |
set match with competing team |
acknowledge and plan training session for the team |
|
team manager |
view upcoming match |
keep track of the upcoming matches with opponent teams |
|
team manager |
edit player’s allocated team |
move players between teams |
|
team manager |
schedule training programs for team |
I do not have any conflicting schedule between different teams |
|
team manager |
send reminder to team of schedule |
my players does not forget about training session |
|
team manager |
sort by team name |
identify group lineup easily |
Additional User Stories |
|||
|
team manager who remembers better with visual |
set colours to tags |
easily identify the tag that I have set to players |
|
team manager |
autofill command |
perform task quickly |
|
team manager |
password login |
if team manager wants to protect certain information |
|
team manager |
submit feedback to developers |
developers can improve the application constantly |
(For all use cases below, the System is the MTM
and the Team Manager is the user
, unless specified otherwise)
MSS
-
User enter command to add
-
MTM prompt user of format to enter player’s name and details
-
User enter player’s name and details in required format
-
User press enter to insert person into storage
Use case ends.
Extensions
-
4a. User did not enter any details.
-
4a1. MTM inform user that it is an invalid add command.
Use case ends.
-
MSS
-
User enter command to find
-
User enter player’s name
-
MTM display list of players found
-
User enter command to edit player’s team using index of displayed list
Use case ends.
Extensions
-
2a. Player name does not exist.
-
2a1. MTM inform user that player does not exist.
Use case ends.
-
MSS
-
User enter command to find team
-
User enter team name
-
MTM display list of players in team
Use case ends.
Extensions
-
2a. Team name does not exist.
-
2a1. MTM informs user that team name does not exist
Use case ends.
-
MSS
-
User enter command to find
-
User enter player’s name
-
MTM display list of players found
-
User enter remove command and index associated with player
-
MTM displays player that is removed and updated list of players
Use case ends.
Extensions
-
4a. Invalid index entered.
-
4a1. MTM informs user that index is invalid.
-
4a2. User enters valid index.
Use case resumes at step 5.
-
-
4b. User removes wrong index unintentionally.
-
4b1. User undo remove by entering command to undo.
Use case ends.
-
{More to be added}
-
Should work on any mainstream OS as long as it has Java 1.8.0_60 or higher installed.
-
Should be able to hold up to 150 players 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.
-
Works on both 32-bit and 64-bit machines
-
Should respond within 1 second of query
-
Should be intuitive and easy to use for a first-time user.
-
Should be able to handle any sort of input, i.e. should recover from invalid input.
-
Should have audience-focused user guides and developer guides.
-
Should have command names that concisely describe their function.
-
Should be an open-source project.
-
Development be cost effective or free.
-
App should be able to work offline.
-
Should save and backup the state of the team managing application regularly.
-
Current versions must be backward compatible with older versions to support undo.
-
The user interface should be simple and minimise distractions so that user can continue with their work in a focused manner.
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 the GUI with a set of sample contacts. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
{ more test cases … }
-
Deleting a player while all players are listed
-
Prerequisites: List all players using the
list
command. Multiple players in the list. -
Test case:
delete 1
Expected: First player is deleted from the list. Details of the deleted player 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 than the list size) {give more}
Expected: Similar to previous.
-
{ more test cases … }
-
Dealing with missing/corrupted data files
-
{explain how to simulate a missing/corrupted file and the expected behavior}
-
{ more test cases … }
-
Remarking a player while all players are listed
-
Prerequisites: List all players using the
list
command. Multiple players in the list. -
Test case:
remark 1 r/This is my remark to you.
Expected: First player is remarked from the list. Details of the remarked player shown in the status message. Timestamp in the status bar is updated. -
Test case:
remark 0
Expected: No person is remarked. Error details shown in the status message. Status bar remains the same. -
Other incorrect delete commands to try:
remark
,remark x
(where x is larger than the list size),remark -1
,remark 2 x/
(where x is not the prefix for remark)
Expected: Similar to previous. -
Test case:
remark 1
Expected: First player has its remark removed. Details of the remarked player shown in the status message. Timestamp in the status bar is updated. -
Test case:
remark 1 r/I want to delete this.
→remark 1 r/
Expected: Similar to previous.
-
-
Adding a player where player is unique
-
Prerequisites: Newly added player must not exist in MTM.
-
Test case:
add n/Tumeric Turner e/[email protected]
Expected: Player added into MTM and will be on the list. Details of the newly added player shown in the status message. Fields that are not input during addition are replaced with<UNSPECIFIED>
Timestamp in the status bar is updated. -
Test case:
add n/Guavi Hollae
Expected: Player is not added into MTM. Errors details shown in status message. Name and email are compulsory fields that need to be entered foradd
to work.
-
-
Creating a Team with current existing data in MTM
-
Prerequisites: Team currently does not exist in MTM.
-
Test case:
create NUSTeam
Expected: Team is created and displayed in Team Display Panel. Team name will be shown in the status message. Timestamp in the status bar is updated. -
Test case:
create NUSTeam
Expected: Team is not created due to team already existing in MTM. Team Display Panel remains the same. Error details shown in the status message. Status bar remains the same. -
Test case:
create &-Team-&
Expected: Team does not allow names with special characters, only alphanumeric, can consist of all numbers and can contain space in name. Team Display Panel remains the same. Error details shown in the status message. Status bar remains the same.
-
-
Viewing a Team with current existing data in MTM
-
Prerequisites: Team currently exist in MTM.
-
Test case:
view Arsenal
Expected: Team in Team Display Panel will be selected. Player list will be updated with list of players that are in the Team. Team name will be shown in the status message. -
Test case:
view NonExistingTeam
Expected: Team is not viewed as it does not exist in MTM. Error details shown in the status message. -
Test case:
view chelsea
Expected: Team is not viewed as it does not exist in MTM, as team names are case sensitive. Error details shown in the status message.
-
-
Prerequisites: The command
create NUSTeam
was executed right before executing this test case.-
Test case:
view NUSTeam
Expected: Team in Team Display Panel will be selected. Player list will be empty as no players are in the team yet. Team name will be shown in the status message.
-
-
-
Assigning players to a Team after executing
create NUSTeam
-
Test case:
list
→assign NUSTeam i/1 3 5
Expected: Team in Team Display panel will be selected. Player list will be updated with list of players that are in the Team with the newly assigned players. Each individual player assign will be shown in the status message. Timestamp in the status bar is updated. -
Test case:
list
→assign NUSTeam i/3
Expected: No player is assigned as player already exist in the team. Error details shown in the status message. Status bar remains the same. -
Test case:
list
→assign NUSTeam i/2 3 4
Expected: Only the player at index 2 is assigned. Assign command will stop once it detects that a player cannot be assigned. Team Display Panel will not be updated. Person Card of index 2 will be updated with assigned team. Timestamp in the status bar is updated. -
Test case:
list
→assign NUSTeam i/9 6 4
Expected: All players will be assigned by ascending index order. Team in Team Display panel will be selected. Player list will be updated with list of players that are in the Team with the newly assigned players. Each individual player assign will be shown in the status message. Timestamp in the status bar is updated. -
Test case:
list
→assign NUSTeam i/7 40
Expected: Index exceeding the number of players listed in the Player List Panel will be ignored. Team in Team Display panel will be selected. Player list will be updated with list of players that are in the Team with the newly assigned players. Each individual player assign will be shown in the status message. Timestamp in the status bar is updated. -
Test case:
list
→assign NUSTeam i/-1
Expected: Invalid index will have error details shown in the status message. Team is not viewed. -
Test case:
list
→assign NUSTeam
Expected: Error details shown in the status message. Invalid command format as no index is given. Team is not viewed. -
Test case:
assign NoTeam
Expected: Error deatils shown in the status message. No such team found in MTM. Team is not viewed, player list not updated. -
Test case:
add n/Barry Putter e/[email protected] tm/NUSTeam
Expected: New person is added into MTM. Team in Team Display Panel will be selected. Player list will be updated with the new added person. Timestamp in the status bar is updated. -
Test case:
add n/Himonie Branger e/[email protected] tm/MissingTeam
Expected: Person is not added into MTM. Team does not exist in MTM. Error details shown in the status message.
-
-
Unassigning players from a Team
-
Prerequisites: Players are already existing in the team
-
Test case:
view NUSTeam
→assign i/1 2 3
Expected: Player list will be updated as the specified players to be unassigned will be removed from the team. Each individual player unassign will be shown in the status message. Timestamp in the status bar is updated. -
Test case:
list
→assign i/10 11
Expected: Player list will be updated with the specified players to be unassigned will have their team name updated to<UNSPECIFIED>
. Each individual player unassign will be shown in the status message. Timestamp in the status bar is updated. -
Test case:
assign i/NUS
Expected: Invalid index will have error details shown in the status message. -
Test case:
list
→assign i/1
Expected: Error details shown in the status message. Player already has no team and can’t be unassigned.
-
-
Renaming a Team after assigning players above
-
Prerequisites: Ensure that the team to be renamed into does not exist
-
Test case:
rename NUSTeam tm/SUNTeam
Expected: Team in Team Display Panel will be renamed to the new team name. New team name will be selected in the Team Display Panel. Player list of all the players in the new team name will be displayed. All players in the team will have their team name updated to the new team name. Team rename will be shown in the result status message. Timestamp in the status bar is updated. -
Test case:
rename NewTeam tm/MooTeam
Expected: Error details shown in the status message. Team to be rename does not exist in MTM. -
Test case:
rename SUNTeam tm/&-Team
Expected: Error details shown in the status message. Team to be rename into is invalid and should only contains alphanumeric characters. -
Test case:
rename tm/SmoovTeam
Expected: Error details shown in the status message. Did not specify which team to rename and will indicate invalid command format.
-
-
Removing a Team after renaming the team above
-
Prerequisites: Team currently exist in MTM.
-
Test case:
view SUNTeam
→remove SUNTeam
Expected: Team is removed and Team Display Panel is updated to reflect that the team has been removed. Team name that is being removed will be shown in the status message. All players in the team will have their team name updated in the Player List Panel. Player List Panel will show the full list of players in MTM. Timestamp in the status bar is updated. -
Test case:
list
→remove Arsenal
Expected: Similar to above, will be displaying full list of players before and afterremove
command. -
Test case:
remove SUNTeam
Expected: Error details shown in the status message. Team does not exist in MTM.
-
-
Changing the theme that MTM is currently on
-
Test case:
cte Dark
(if current theme is Light) orcte Light
(if current theme is Dark)
Expected: MTM colour scheme will change to the respective themes as shown below:
-