Package edu.drexel.cs544.mcmuc.channels

Source Code of edu.drexel.cs544.mcmuc.channels.Controller

package edu.drexel.cs544.mcmuc.channels;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;

import edu.drexel.cs544.mcmuc.actions.Action;
import edu.drexel.cs544.mcmuc.actions.ListRooms;
import edu.drexel.cs544.mcmuc.actions.Message;
import edu.drexel.cs544.mcmuc.actions.PollPresence;
import edu.drexel.cs544.mcmuc.actions.Presence;
import edu.drexel.cs544.mcmuc.actions.Preserve;
import edu.drexel.cs544.mcmuc.actions.Timeout;
import edu.drexel.cs544.mcmuc.actions.UseRooms;
import edu.drexel.cs544.mcmuc.actions.Presence.Status;
import edu.drexel.cs544.mcmuc.ui.UI;
import edu.drexel.cs544.mcmuc.util.Certificate;
import edu.drexel.cs544.mcmuc.util.RoomDoesNotExistException;

/**
* Controller represents a control channel, which is a fixed port for the sending of
* messages, such as room creation and detection, that must reach all clients on the network.
* Controller exposes two sets of ports - all ports in use, and the ports in use that for which
* the client is actively using the room.
*/
public class Controller extends Channel {

    public static final int CONTROL_PORT = 31941;
    public final Map<String, Integer> roomNames = Collections.synchronizedMap(new HashMap<String, Integer>());
    public final Map<Integer, Channel> channels = Collections.synchronizedMap(new HashMap<Integer, Channel>());
    private UI ui;

    /**
     * Creates the controller channel on the given port and adds it to the set of Channels
     *
     * @param port int port to use (will always be CONTROL_PORT)
     */
    private Controller(int port) {
        super(port);
        channels.put(port, this);
        this.send(new ListRooms());
    }

    private static final Controller instance = new Controller(CONTROL_PORT);

    /**
     * Restricts the instantiation of the Controller class to one object (the Singleton
     * design pattern)
     *
     * @return Controller singleton
     */
    public static Controller getInstance() {
        return instance;
    }

    /**
     * Supports the processing of UseRooms, ListRooms, Timeout, and Preserve actions. Checks
     * the action key of the JSON received and uses that to pass processing to the
     * correct type. Errors are display if the message does not have an action or the
     * type is not supported (not list-rooms, use-rooms, timeout, and preserve).
     */
    @Override
    public void handleNewMessage(JSONObject jo) {
        Action action;
        String actionString = "";
        try {
            actionString = jo.getString("action");
        } catch (JSONException e) {
            System.err.println("Message does not have action.");
            e.printStackTrace();
        }
        if (actionString.equalsIgnoreCase(UseRooms.action)) {
            action = new UseRooms(jo);
            action.process(this);
        } else if (actionString.equalsIgnoreCase(ListRooms.action)) {
            action = new ListRooms(jo);
            action.process(this);
        } else if (actionString.equalsIgnoreCase(Timeout.action)) {
            action = new Timeout(jo);
            action.process(this);
        } else if (actionString.equalsIgnoreCase(Preserve.action)) {
            action = new Preserve(jo);
            action.process(this);
        } else {
            System.err.println("Message action type not supported: " + actionString);
        }
    }

    /**
     * Takes the given String and outputs it, either through a UI or System.out
     *
     * @param outputString String to display
     */
    public void output(String string) {
        if (this.ui != null) {
            this.ui.output(string);
        } else {
            // Poor man's UI
            System.out.println(string);
        }
    }

    /**
     * Takes the given String and outputs it, either through a UI or System.out, prefixed with a '*'
     *
     * @param string String to display
     */
    public void alert(String string) {
        if (this.ui != null) {
            this.ui.alert(string);
        } else {
            // Poor man's UI
            System.out.println("* " + string);
        }
    }

    public boolean hasForwarder(int port) {
        return (!roomNames.containsValue(port) && channels.containsKey(port));
    }

    /**
     * Resets the primary timer for the Room on roomPort
     *
     * @param roomPort int port of room to reset primary timer for
     */
    public void resetPrimaryTimer(int roomPort) {
        if (hasForwarder(roomPort)) {
            ((Forwarder) channels.get(roomPort)).resetPrimaryTimer();
        }
    }

    /**
     * If the set of Channels does not contain roomPort, create a new Forwarder on that port,
     * and add the port to the set of Channels.
     *
     * @param roomPort int
     */
    public synchronized void useRoom(int roomPort) {
        if (!channels.keySet().contains(roomPort) && roomPort != Controller.CONTROL_PORT) {
            startForwarder(roomPort);
        } else {
            // Do nothing, this port is already in use, either by a Room or Forwarder
        }
    }

    /**
     * Start a forwarder on the given port
     *
     * @param roomPort the port
     */
    public void startForwarder(int roomPort) {
        Forwarder fwd = new Forwarder(roomPort);
        channels.put(roomPort, fwd);
        fwd.send(new PollPresence());
    }

    /**
     * Shutdown the forwarder on a given port
     *
     * @param port the port
     */

    public void stopForwarder(int port) {
        Forwarder f = (Forwarder) channels.remove(port);
        f.shutdown();
    }

    /**
     * If the set of Channels contains roomPort, remove it. If the channel is a room, set the
     * client's presence to Offline. Then, start a Forwarder to replace the Room.
     *
     * @param roomPort int port to leave
     */
    public void leaveRoom(int roomPort) {
        Room r = (Room) channels.remove(roomPort);
        r.setStatus(Status.Offline);
        r.shutdown();
        startForwarder(roomPort);
    }

    /**
     * Remove the room that corresponds to the name given by roomName, set that client's status
     * to Offline, and remove the port from the set of Channels.
     *
     * @param roomName String name of room to leave
     * @throws RoomDoesNotExistException
     */
    public void leaveRoom(String roomName) throws RoomDoesNotExistException {
        Integer p = roomNames.remove(roomName);
        if (p != null) {
            leaveRoom(p);
            this.alert("Left room: \"" + roomName + "\"");
        } else {
            throw new RoomDoesNotExistException(roomName);
        }
    }

    /**
     * Create a new room on the port chosen by the hash algorithm given the roomName and portsInUse.
     * Set the user's name in the room to userName. Add the room and add the port to the set of Channels.
     *
     * @param roomName String name of room to use
     * @param userName String name to associate with user in the room
     */
    public void useRoom(String roomName, String userName) {
        Room room;
        if (roomNames.containsKey(roomName)) {
            room = (Room) channels.get(roomNames.get(roomName));
            if (room.getUserName().equals(userName)) {
                this.alert("Room \"" + roomName + "\" already exists with user \"" + userName + "\"");
            } else {
                room.setStatus(Presence.Status.Offline);
                room.setUserName(userName);
                room.setStatus(Presence.Status.Online);
                this.alert("Room \"" + roomName + "\" already exists, updating user to \"" + userName + "\"");
            }
        } else {
            room = new Room(roomName, new HashSet<Integer>(roomNames.values()), userName);
            Forwarder oldChannel = (Forwarder) channels.put(room.getPort(), room);
            if (oldChannel != null) {
                oldChannel.shutdown();
            }
            roomNames.put(roomName, room.getPort());
            this.send(new UseRooms(Arrays.asList(room.getPort())));
            this.alert("Created new room: \"" + roomName + "\" with user \"" + userName + "\"");
        }
    }

    /**
     * Gets the user's name in a given room (identified by the room name)
     *
     * @param roomName String room to return the user name for
     * @return String the user's name in that room
     */
    public String getUserName(String roomName) {
        Room room = (Room) channels.get(roomNames.get(roomName));
        if (room != null) {
            return room.getUserName();
        } else {
            return "";
        }
    }

    /**
     * Set that user's presence in the room associated with roomName to the given presence
     *
     * @param roomName String name of room to set presence for
     * @param presence Presence to set
     * @throws RoomDoesNotExistException
     * @see Status
     */
    public void setRoomStatus(String roomName, Status presence) throws RoomDoesNotExistException {
        Room room = (Room) channels.get(roomNames.get(roomName));
        if (room != null) {
            room.setStatus(presence);
            this.alert("Set presence for \"" + room.getUserName() + "@" + roomName + "\" to \"" + presence.toString().toLowerCase() + "\"");
        } else {
            throw new RoomDoesNotExistException(roomName);
        }
    }

    /**
     * Adds a public/private key pair to the room identified by roomName
     *
     * @param roomName String the room
     * @param publicKey Certificate public key
     * @param privateKey Certificate private key
     * @throws RoomDoesNotExistException
     * @see Room
     */
    public void addKeyPair(String roomName, Certificate publicKey, Certificate privateKey) throws RoomDoesNotExistException {
        Room room = (Room) channels.get(roomNames.get(roomName));
        if (room != null) {
            room.addKeyPair(publicKey, privateKey);
            this.alert("Added key pair for \"" + room.getUserName() + "@" + roomName + "\"");
        } else {
            throw new RoomDoesNotExistException(roomName);
        }
    }

    /**
     * Removes a public/private key pair from the room identified by roomName
     *
     * @param roomName String the room
     * @param publicKey Certificate public key
     * @throws RoomDoesNotExistException
     */
    public void removeKeyPair(String roomName, Certificate publicKey) throws RoomDoesNotExistException {
        Room room = (Room) channels.get(roomNames.get(roomName));
        if (room != null) {
            room.removeKeyPair(publicKey);
            this.alert("Removed key pair for \"" + room.getUserName() + "@" + roomName + "\"");
        } else {
            throw new RoomDoesNotExistException(roomName);
        }
    }

    /**
     * Send the given message to the room associated with roomName
     *
     * @param roomName String name of room to send action to
     * @param message Message to send
     * @throws RoomDoesNotExistException
     */
    public void messageRoom(String roomName, Message message) throws RoomDoesNotExistException {
        Room room = (Room) channels.get(roomNames.get(roomName));
        if (room != null) {
            room.send(message);
            this.output(message.getFrom() + "@" + roomName + ": " + (message.hasKey() ? "*encrypted*" : message.getBody())); // To create a new command prompt
        } else {
            throw new RoomDoesNotExistException(roomName);
        }
    }

    /**
     * Sets the Controller's UI
     *
     * @param ui UI to set
     */
    public void setUI(UI ui) {
        this.ui = ui;
    }

    /**
     * Return a list of the rooms in use. If a set of rooms is provided,
     * only respond about those rooms. Otherwise, return all rooms in use.
     *
     * @param list
     * @return
     */
    public Collection<Integer> getRoomsInUse(List<Integer> list) {
        Collection<Integer> roomsInUse = roomNames.values();
        if (list != null) {
            roomsInUse.retainAll(list);
        }
        return roomsInUse;
    }

    /**
     * Return a list of the channels in use. If a set of channels is provided,
     * only respond about those channels. Otherwise, return all channels in use.
     *
     * @param list
     * @return
     */
    public Collection<Integer> getChannelsInUse(List<Integer> list) {
        Collection<Integer> roomsInUse = channels.keySet();
        if (list != null) {
            roomsInUse.retainAll(list);
        }
        return roomsInUse;
    }

    /**
     * Shutdown the Controller.
     */
    public synchronized void shutdown() {
        for (Channel c : channels.values()) {
            if (c != this) {
                c.shutdown();
            }
        }
        super.mcc.close();
        super.runner.stop();
    }
}
TOP

Related Classes of edu.drexel.cs544.mcmuc.channels.Controller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.