Develop a baseline for Assignment 2, to include a simplified version of pacemaker application developed so far
Create a new folder to contain the project. To might call it 'pacemaker-skeleton'
In this folder, create the following directory structure:
pacemaker-skeleton
│
└── src
│
├── main
│ │
│ └──java
│
└── test
│
└──java
In the root of the folder, create the pom.xml file:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pacemaker</groupId>
<artifactId>pacemaker-skeleton</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>pacemaker-skeleton</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
<dependency>
<groupId>asg-cliche</groupId>
<artifactId>asg-cliche</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>java-ascii-table</groupId>
<artifactId>java-ascii-table</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
if you are using git, you might wish to use this .gitignore:
.idea
target
*.iml
.settings
.classpath
.project
We can now bring this project into Eclipse. Select File-Import, and locate Maven->Existing Maven Project:
The project should look like this:
In Eclipse, create following package in the main/java source folder:
Here are revised and simplified models for this package:
package models;
import static com.google.common.base.MoreObjects.toStringHelper;
import java.io.Serializable;
import java.util.UUID;
import com.google.common.base.Objects;
public class Location implements Serializable {
public String id;
public double longitude;
public double latitude;
public Location() {
}
public String getId() {
return id;
}
public double getLongitude() {
return longitude;
}
public double getLatitude() {
return latitude;
}
public Location(double latitude, double longitude) {
this.id = UUID.randomUUID().toString();
this.latitude = latitude;
this.longitude = longitude;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof Location) {
final Location other = (Location) obj;
return Objects.equal(latitude, other.latitude)
&& Objects.equal(longitude, other.longitude);
} else {
return false;
}
}
@Override
public String toString() {
return toStringHelper(this).addValue(id)
.addValue(latitude)
.addValue(longitude)
.toString();
}
@Override
public int hashCode() {
return Objects.hashCode(this.id, this.latitude, this.longitude);
}
}
package models;
import static com.google.common.base.MoreObjects.toStringHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.google.common.base.Objects;
public class Activity implements Serializable {
public String id;
public String type;
public String location;
public double distance;
public List<Location> route = new ArrayList<>();
public Activity() {
}
public Activity(String type, String location, double distance) {
this.id = UUID.randomUUID().toString();
this.type = type;
this.location = location;
this.distance = distance;
}
public String getId() {
return id;
}
public String getType() {
return type;
}
public String getLocation() {
return location;
}
public String getDistance() {
return Double.toString(distance);
}
public String getRoute() {
return route.toString();
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof Activity) {
final Activity other = (Activity) obj;
return Objects.equal(type, other.type)
&& Objects.equal(location, other.location)
&& Objects.equal(distance, other.distance)
&& Objects.equal(route, other.route);
} else {
return false;
}
}
@Override
public String toString() {
return toStringHelper(this).addValue(id)
.addValue(type)
.addValue(location)
.addValue(distance)
.addValue(route)
.toString();
}
@Override
public int hashCode() {
return Objects.hashCode(this.id, this.type, this.location, this.distance);
}
}
package models;
import static com.google.common.base.MoreObjects.toStringHelper;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.google.common.base.Objects;
public class User implements Serializable {
public String id;
public String firstName;
public String lastName;
public String email;
public String password;
public Map<String, Activity> activities = new HashMap<>();
public User() {
}
public String getId() {
return id;
}
public String getFirstname() {
return firstName;
}
public String getLastname() {
return lastName;
}
public String getEmail() {
return email;
}
public User(String firstName, String lastName, String email, String password) {
this.id = UUID.randomUUID().toString();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof User) {
final User other = (User) obj;
return Objects.equal(firstName, other.firstName)
&& Objects.equal(lastName, other.lastName)
&& Objects.equal(email, other.email)
&& Objects.equal(password, other.password)
&& Objects.equal(activities, other.activities);
} else {
return false;
}
}
@Override
public String toString() {
return toStringHelper(this).addValue(id)
.addValue(firstName)
.addValue(lastName)
.addValue(password)
.addValue(email)
.addValue(activities)
.toString();
}
@Override
public int hashCode() {
return Objects.hashCode(this.id, this.lastName, this.firstName, this.email, this.password);
}
}
In Eclipse, create following package in the main/java source folder:
Here are class for this package:
package parsers;
import java.util.Collection;
import java.util.List;
import models.Activity;
import models.Location;
import models.User;
public class Parser {
public void println(String s) {
System.out.println(s);
}
public void renderUser(User user) {
System.out.println(user.toString());
}
public void renderUsers(Collection<User> users) {
System.out.println(users.toString());
}
public void renderActivity(Activity activities) {
System.out.println(activities.toString());
}
public void renderActivities(Collection<Activity> activities) {
System.out.println(activities.toString());
}
public void renderLocations(List<Location> locations) {
System.out.println(locations.toString());
}
}
package parsers;
import com.bethecoder.ascii_table.ASCIITable;
import com.bethecoder.ascii_table.impl.CollectionASCIITableAware;
import com.bethecoder.ascii_table.spec.IASCIITableAware;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import models.Activity;
import models.Location;
import models.User;
public class AsciiTableParser extends Parser {
public void renderUser(User user) {
if (user != null) {
renderUsers(Arrays.asList(user));
System.out.println("ok");
} else {
System.out.println("not found");
}
}
public void renderUsers(Collection<User> users) {
if (users != null) {
if (!users.isEmpty()) {
List<User> userList = new ArrayList<User>(users);
IASCIITableAware asciiTableAware = new CollectionASCIITableAware<User>(userList, "id",
"firstname",
"lastname", "email");
System.out.println(ASCIITable.getInstance().getTable(asciiTableAware));
}
System.out.println("ok");
} else {
System.out.println("not found");
}
}
public void renderActivity(Activity activity) {
if (activity != null) {
renderActivities(Arrays.asList(activity));
System.out.println("ok");
} else {
System.out.println("not found");
}
}
public void renderActivities(Collection<Activity> activities) {
if (activities != null) {
if (!activities.isEmpty()) {
List<Activity> activityList = new ArrayList(activities);
IASCIITableAware asciiTableAware = new CollectionASCIITableAware<Activity>(activityList,
"id",
"type", "location", "distance", "starttime", "duration");
System.out.println(ASCIITable.getInstance().getTable(asciiTableAware));
}
System.out.println("ok");
} else {
System.out.println("not found");
}
}
public void renderLocations(List<Location> locations) {
if (locations != null) {
if (!locations.isEmpty()) {
IASCIITableAware asciiTableAware = new CollectionASCIITableAware<Location>(locations,
"id",
"latitude", "longitude");
System.out.println(ASCIITable.getInstance().getTable(asciiTableAware));
}
System.out.println("ok");
} else {
System.out.println("not found");
}
}
}
In Eclipse, create following package in the main/java source folder:
Here are revised and simplified models for this package:
package controllers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Optional;
import models.Activity;
import models.Location;
import models.User;
public class PacemakerAPI {
private Map<String, User> emailIndex = new HashMap<>();
private Map<String, User> userIndex = new HashMap<>();
private Map<String, Activity> activitiesIndex = new HashMap<>();
public PacemakerAPI() {
}
public Collection<User> getUsers() {
return userIndex.values();
}
public void deleteUsers() {
userIndex.clear();
emailIndex.clear();
}
public User createUser(String firstName, String lastName, String email, String password) {
User user = new User(firstName, lastName, email, password);
emailIndex.put(email, user);
userIndex.put(user.id, user);
return user;
}
public Activity createActivity(String id, String type, String location, double distance) {
Activity activity = null;
Optional<User> user = Optional.fromNullable(userIndex.get(id));
if (user.isPresent()) {
activity = new Activity(type, location, distance);
user.get().activities.put(activity.id, activity);
activitiesIndex.put(activity.id, activity);
}
return activity;
}
public Activity getActivity(String id) {
return activitiesIndex.get(id);
}
public Collection<Activity> getActivities(String id) {
Collection<Activity> activities = null;
Optional<User> user = Optional.fromNullable(userIndex.get(id));
if (user.isPresent()) {
activities = user.get().activities.values();
}
return activities;
}
public List<Activity> listActivities(String userId, String sortBy) {
List<Activity> activities = new ArrayList<>();
activities.addAll(userIndex.get(userId).activities.values());
switch (sortBy) {
case "type":
activities.sort((a1, a2) -> a1.type.compareTo(a2.type));
break;
case "location":
activities.sort((a1, a2) -> a1.location.compareTo(a2.location));
break;
case "distance":
activities.sort((a1, a2) -> Double.compare(a1.distance, a2.distance));
break;
}
return activities;
}
public void addLocation(String id, double latitude, double longitude) {
Optional<Activity> activity = Optional.fromNullable(activitiesIndex.get(id));
if (activity.isPresent()) {
activity.get().route.add(new Location(latitude, longitude));
}
}
public User getUserByEmail(String email) {
return emailIndex.get(email);
}
public User getUser(String id) {
return userIndex.get(id);
}
public User deleteUser(String id) {
User user = userIndex.remove(id);
return emailIndex.remove(user.email);
}
}
package controllers;
import com.google.common.base.Optional;
import asg.cliche.Command;
import asg.cliche.Param;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import models.Activity;
import models.User;
import parsers.AsciiTableParser;
import parsers.Parser;
public class PacemakerConsoleService {
private PacemakerAPI paceApi = new PacemakerAPI();;
private Parser console = new AsciiTableParser();
private User loggedInUser = null;
public PacemakerConsoleService() {
}
// Starter Commands
@Command(description = "Register: Create an account for a new user")
public void register(@Param(name = "first name") String firstName,
@Param(name = "last name") String lastName,
@Param(name = "email") String email, @Param(name = "password") String password) {
}
@Command(description = "List Users: List all users emails, first and last names")
public void listUsers() {
}
@Command(description = "Login: Log in a registered user in to pacemaker")
public void login(@Param(name = "email") String email,
@Param(name = "password") String password) {
}
@Command(description = "Logout: Logout current user")
public void logout() {
}
@Command(description = "Add activity: create and add an activity for the logged in user")
public void addActivity(
@Param(name = "type") String type,
@Param(name = "location") String location,
@Param(name = "distance") double distance) {
}
@Command(description = "List Activities: List all activities for logged in user")
public void listActivities() {
}
// Baseline Commands
@Command(description = "Add location: Append location to an activity")
public void addLocation(@Param(name = "activity-id") String id,
@Param(name = "longitude") double longitude,
@Param(name = "latitude") double latitude) {
}
@Command(description = "ActivityReport: List all activities for logged in user, sorted alphabetically by type")
public void activityReport() {
}
@Command(description = "Activity Report: List all activities for logged in user by type. Sorted longest to shortest distance")
public void activityReport(@Param(name = "byType: type") String sortBy) {
}
@Command(description = "List all locations for a specific activity")
public void listActivityLocations(@Param(name = "activity-id") String id) {
}
@Command(description = "Follow Friend: Follow a specific friend")
public void follow(@Param(name = "email") String email) {
}
@Command(description = "List Friends: List all of the friends of the logged in user")
public void listFriends() {
}
@Command(description = "Friend Activity Report: List all activities of specific friend, sorted alphabetically by type)")
public void friendActivityReport(@Param(name = "email") String email) {
}
// Good Commands
@Command(description = "Unfollow Friends: Stop following a friend")
public void unfollowFriend() {
}
@Command(description = "Message Friend: send a message to a friend")
public void messageFriend(@Param(name = "email") String email,
@Param(name = "message") String message) {
}
@Command(description = "List Messages: List all messages for the logged in user")
public void listMessages() {
}
@Command(description = "Distance Leader Board: list summary distances of all friends, sorted longest to shortest")
public void distanceLeaderBoard() {
}
// Excellent Commands
@Command(description = "Distance Leader Board: distance leader board refined by type")
public void distanceLeaderBoardByType(@Param(name = "byType: type") String type) {
}
@Command(description = "Message All Friends: send a message to all friends")
public void messageAllFriends(@Param(name = "message") String message) {
}
@Command(description = "Location Leader Board: list sorted summary distances of all friends in named location")
public void locationLeaderBoard(@Param(name = "location") String message) {
}
// Outstanding Commands
// Todo
}
package controllers;
import asg.cliche.Shell;
import asg.cliche.ShellFactory;
public class Main {
public static void main(String[] args) throws Exception {
PacemakerConsoleService main = new PacemakerConsoleService();
Shell shell = ShellFactory
.createConsoleShell("pm", "Welcome to pacemaker-console - ?help for instructions", main);
shell.commandLoop();
}
}
If you run Main - and list all commands, you should see this report in the console:
Welcome to pacemaker-console - ?help for instructions
pm> ?la
abbrev name params
...
... build in commands
...
r register (first name, last name, email, password)
l login (email, password)
f follow (email)
l logout ()
lu list-users ()
aa add-activity (type, location, distance)
la list-activities ()
al add-location (activity-id, longitude, latitude)
ar activity-report ()
ar activity-report (byType: type)
lal list-activity-locations (activity-id)
lf list-friends ()
far friend-activity-report (email)
uf unfollow-friend ()
mf message-friend (email, message)
lm list-messages ()
dlb distance-leader-board ()
dlbbt distance-leader-board-by-type (byType: type)
maf message-all-friends (message)
llb location-leader-board (location)
These are the commands for Assignment 2 - and are implemented as stubbs in PacemakerConsoleService class.
A scaled down implementation of the API is implemented in PacemakerAPI. It includes the primary features of the sample solution, simplified to exclude serialization.
The models are similar - but starttime and duration have been removed from the Activity class.
With the API implementation in place, we can make a start on some of the commands:
@Command(description = "Register: Create an account for a new user")
public void register(@Param(name = "first name") String firstName,
@Param(name = "last name") String lastName,
@Param(name = "email") String email, @Param(name = "password") String password) {
console.renderUser(paceApi.createUser(firstName, lastName, email, password));
}
@Command(description = "List Users: List all users emails, first and last names")
public void listUsers() {
console.renderUsers(paceApi.getUsers());
}
@Command(description = "Login: Log in a registered user in to pacemaker")
public void login(@Param(name = "email") String email,
@Param(name = "password") String password) {
Optional<User> user = Optional.fromNullable(paceApi.getUserByEmail(email));
if (user.isPresent()) {
if (user.get().password.equals(password)) {
loggedInUser = user.get();
console.println("Logged in " + loggedInUser.email);
console.println("ok");
} else {
console.println("Error on login");
}
}
}
@Command(description = "Logout: Logout current user")
public void logout() {
console.println("Logging out " + loggedInUser.email);
console.println("ok");
loggedInUser = null;
}
The above should permit the following interaction:
Welcome to pacemaker-console - ?help for instructions
pm> r homer simpson homer@simpson.com secret
+--------------------------------------+-----------+----------+-------------------+
| ID | FIRSTNAME | LASTNAME | EMAIL |
+--------------------------------------+-----------+----------+-------------------+
| 73cc563c-40b2-47a3-9acd-5a2471c4d7f9 | homer | simpson | homer@simpson.com |
+--------------------------------------+-----------+----------+-------------------+
ok
ok
pm> l homer@simpson.com secret
Logged in homer@simpson.com
ok
pm> l
Logging out homer@simpson.com
ok
pm>
Try it now to see if it works.
We can implement the add and list activities commands:
@Command(description = "Add activity: create and add an activity for the logged in user")
public void addActivity(
@Param(name = "type") String type,
@Param(name = "location") String location,
@Param(name = "distance") double distance) {
Optional<User> user = Optional.fromNullable(loggedInUser);
if (user.isPresent()) {
console
.renderActivity(paceApi.createActivity(user.get().id, type, location, distance));
}
}
@Command(description = "List Activities: List all activities for logged in user")
public void listActivities() {
Optional<User> user = Optional.fromNullable(loggedInUser);
if (user.isPresent()) {
console
.renderActivities(paceApi.getActivities(user.get().id));
}
}
These commands should allow us to interact as follows (having logged in successfully):
pm> aa walk fridge 23
+--------------------------------------+------+----------+----------+-----------+----------+
| ID | TYPE | LOCATION | DISTANCE | STARTTIME | DURATION |
+--------------------------------------+------+----------+----------+-----------+----------+
| 2cc9b97d-346f-4d3f-96a7-ccfcf2351025 | walk | fridge | 23 | null | null |
+--------------------------------------+------+----------+----------+-----------+----------+
ok
pm>aa walk tv 4
+--------------------------------------+------+----------+----------+-----------+----------+
| ID | TYPE | LOCATION | DISTANCE | STARTTIME | DURATION |
+--------------------------------------+------+----------+----------+-----------+----------+
| bfa408d8-be3e-4c88-99b7-0f50f3aa3408 | walk | tv | 4 | null | null |
+--------------------------------------+------+----------+----------+-----------+----------+
ok
pm> la
+--------------------------------------+------+----------+----------+-----------+----------+
| ID | TYPE | LOCATION | DISTANCE | STARTTIME | DURATION |
+--------------------------------------+------+----------+----------+-----------+----------+
| 2cc9b97d-346f-4d3f-96a7-ccfcf2351025 | walk | fridge | 23 | null | null |
| bfa408d8-be3e-4c88-99b7-0f50f3aa3408 | walt | tv | 4 | null | null |
+--------------------------------------+------+----------+----------+-----------+----------+
ok
pm>
Archive of the lab so far:
implement the Add Location Command
implement the List Location Command
Implement the Activity Report Command. There are two of these commands:
(a) taking no parameters - which sorts the activities by type
(b) talking a single parameter - the activity type. This command only lists activities of the specified type. However, they are to be sorted by distance, longest to shortest.
These exercises are solved in the next lab. Exercise 1 & 2 are relatively straightforward, and can be solved by looking at the sample solution to the assignment.
Exercise 3 (a) is also fairly straightforward, but exercise (b) might take a little more consideration.