TwitterCLI Exercise

Introduction

This exercise is designed to help you compare the process of designing an application with Concourse versus other databases. You are provided with the Twitter interface and the fully functional TwitterCLI command line code.

Your Task

To complete this exercise, you need to:

  • Implement the Twitter interface using Concourse
  • Change line 22 of TwitterCLI.java to point to your implementation
  • Run TwitterCLI.java

The Twitter interface

The Twitter interface defines actions that mimic a subset of Twitter. You should implement this interface using Concourse.

Twitter.java
import java.util.Map;

/**
 * The interface for the back end of an command line application that mimics
 * some of the functionality of Twitter. The implementing class is expected to
 * keep track of the current user session.
 * 
 * @author jnelson
 */
public interface Twitter {
    /**
     * Follow {@code username}.
     * 
     * @param username
     * @return {@code true} if the current user starts following
     *         {@code username}.
     */
    public boolean follow(String username);
    /**
     * Authenticate the {@code username} and {@code password}.
     * 
     * @param username
     * @param password
     * @return {@code true} if the login is successful
     */
    public boolean login(String username, String password);
    /**
     * Register a user identified by the {@code username} and {@code password}
     * combination.
     * 
     * @param username
     * @param password
     * @return {@code true} if the new user is registered successfully
     */
    public boolean register(String username, String password);
    /**
     * Return the tweets where the current user is mentioned.
     * 
     * @return my mentions
     */
    public Map<Long, String> mentions();
    /**
     * Return the tweets on the current user's timeline.
     * 
     * @return my timeline
     */
    public Map<Long, String> timeline();
    /**
     * Post a tweet with {@code message}.
     * 
     * @param message
     */
    public void tweet(String message);
    /**
     * Unfollow {@code username}.
     * 
     * @param username
     * @return {@code true} if the current user starts unfollowing
     *         {@code username}
     */
    public boolean unfollow(String username);
}

The TwitterCLI Application

Once you have implemented the Twitter interface, change line 22 to point to your implementation and run this class.

TwitterCli.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.cinchapi.concourse.time.Time;

/**
 * The TwitterCLI is a command line application that mimics some of the
 * functionality of Twitter. This application is designed to run with a
 * {@link Twitter} back end that uses Concourse or some other database for data
 * storage.
 * 
 * @author jnelson
 */
public class TwitterCLI {
    /**
     * The {@link Twitter} back end that is used to manage the application logic
     * and data storage.
     */
    public static final Twitter twitter = null; //FIXME
    /**
     * Run the program...
     * 
     * @param args
     */
    public static void main(String... args) {
        String linebreak = System.getProperty("line.separator");
        boolean loggedIn = false;
        boolean running = true;
        String loggedOutInstructions = new StringBuilder()
                .append("Type 'login <username>' to login").append(linebreak)
                .append("Type 'register <username>' to create user")
                .append(linebreak).append("Type 'exit' to quit").toString();
        String loggedInInstructions = new StringBuilder()
                .append("Type 'tweet <message>' to tweet").append(linebreak)
                .append("Type 'follow <username>' to follow user")
                .append(linebreak)
                .append("Type 'unfollow <username>' to unfollow user")
                .append(linebreak)
                .append("Type 'timeline' to view your timeline")
                .append(linebreak)
                .append("Type 'mentions' to view your mentions")
                .append(linebreak).append("Type 'exit' to quit").toString();
        System.out.println("Welcome to the Twitter Demo application!");
        System.out.println("THIS APPLICATION READS PASSWORDS IN PLAIN TEXT SO "
                + "PLEASE DON'T USE SENSITIVE INFORMATION DURING TESTING");
        while (running) {
            try {
                if(!loggedIn) {
                    System.out.println("");
                    System.out.println(loggedOutInstructions);
                }
                else {
                    System.out.println("");
                    System.out.println(loggedInInstructions);
                }
                String input = getInput("twitter>");
                String[] toks = input.split(" ");
                if(toks.length < 1) {
                    System.err.println("Please specify an action");
                }
                else {
                    String action = toks[0];
                    if(action.equalsIgnoreCase("login") && !loggedIn) {
                        if(toks.length < 2) {
                            System.err.println("Please specify a username");
                        }
                        else {
                            String username = toks[1];
                            String password = getInput("password>");
                            if(twitter.login(username, password)) {
                                loggedIn = true;
                                System.out.println("Successfully logged in as "
                                        + username);
                            }
                            else {
                                System.err.println("Invalid username/password "
                                        + "combination");
                            }
                        }
                    }
                    else if(action.equalsIgnoreCase("register") && !loggedIn) {
                        if(toks.length < 2) {
                            System.err.println("Please specify a username");
                        }
                        else {
                            String username = toks[1];
                            String password = getInput("password>");
                            if(twitter.register(username, password)) {
                                System.out.println("Successfully registered "
                                        + username);
                            }
                            else {
                                System.err
                                        .println("Could not register user with the "
                                                + "specified username/password combination");
                            }
                        }
                    }
                    else if(action.equalsIgnoreCase("tweet") && loggedIn) {
                        if(toks.length < 2) {
                            System.err
                                    .println("Please specify a message for the tweet");
                        }
                        else {
                            StringBuilder message = new StringBuilder();
                            for (int i = 1; i < toks.length; i++) {
                                if(i != 1) {
                                    message.append(" ");
                                }
                                message.append(toks[i]);
                            }
                            twitter.tweet(message.toString());
                        }
                    }
                    else if(action.equalsIgnoreCase("follow") && loggedIn) {
                        if(toks.length < 2) {
                            System.err.println("Please specify a username");
                        }
                        else if(twitter.follow(toks[1])) {
                            System.out.println("You are now following "
                                    + toks[1]);
                        }
                        else {
                            System.err.println("Error trying to follow "
                                    + toks[1]);
                        }
                    }
                    else if(action.equalsIgnoreCase("unfollow") && loggedIn) {
                        if(toks.length < 2) {
                            System.err.println("Please specify a username");
                        }
                        else if(twitter.follow(toks[1])) {
                            System.out.println("You are no longer following "
                                    + toks[1]);
                        }
                        else {
                            System.err.println("Error trying to unfollow "
                                    + toks[1]);
                        }
                    }
                    else if(action.equalsIgnoreCase("timeline") && loggedIn) {
                        displayTweets(twitter.timeline());
                    }
                    else if(action.equalsIgnoreCase("mentions") && loggedIn) {
                        displayTweets(twitter.mentions());
                    }
                    else if(action.equalsIgnoreCase("exit")) {
                        running = false;
                    }
                    else {
                        System.err.println("Please specify a valid action");
                    }
                }
            }
            catch (Exception e) {
                System.err.println(e.getMessage());
            }
        }
        System.exit(0);
    }
    /**
     * Format and print {@code tweets} to the console.
     * 
     * @param tweets
     */
    private static void displayTweets(Map<Long, String> tweets) {
        for (Entry<Long, String> tweet : tweets.entrySet()) {
            String elapsed = getElapsedTimeString(tweet.getKey());
            String message = tweet.getValue();
            System.out.println(message + " (" + elapsed + " ago)");
        }
    }
    /**
     * Return a string that describes the time that has elapsed since the
     * timestamp specified in {@code microseconds}.
     * 
     * @param microseconds
     * @return the elapsed time string
     */
    private static String getElapsedTimeString(long microseconds) {
        long elapsed = TimeUnit.SECONDS.convert(Time.now() - microseconds,
                TimeUnit.MICROSECONDS);
        String unit = "";
        if(elapsed < 60) {
            unit = "seconds";
        }
        else if(elapsed >= 60 && elapsed < 3600) {
            elapsed = TimeUnit.MINUTES.convert(elapsed, TimeUnit.SECONDS);
            unit = elapsed > 1 ? "minutes" : "minute";
        }
        else if(elapsed >= 3600 && elapsed < 86400) {
            elapsed = TimeUnit.HOURS.convert(elapsed, TimeUnit.SECONDS);
            unit = elapsed > 1 ? "hours" : "hour";
        }
        else {
            elapsed = TimeUnit.DAYS.convert(elapsed, TimeUnit.SECONDS);
            unit = elapsed > 1 ? "days" : "day";
        }
        return elapsed + " " + unit;
    }
    /**
     * Get user input from the command line.
     * 
     * @param prompt
     * @return the input
     */
    private static String getInput(String prompt) {
        try {
            System.out.print(prompt + " ");
            return input.readLine();
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // Handler to read System.in
    private static final BufferedReader input = new BufferedReader(
            new InputStreamReader(System.in));
} 

Next Steps

Implement the Twitter interface using a relational or NoSQL database. How does the experience compare to using Concourse? Do you notice any performance differences in the TwitterCLI application?