package com.collibra.server; import java.io.PrintWriter; /** * A simple protocol for exchanging messages between a server and clients. * * @version 1.0 * @author Yordan Kirov * @since 21/11/2018 * */ class SimpleProtocol { /** * Sends a first message to every client that connects to the server. * @param out the PrintWriter with which the server sends messages to the client. * @param sessionId identification of the session with the current client. */ public static void sendFirstMessage(PrintWriter out, String sessionId) { out.println("HI, I'M " + sessionId); } /** * Extracts and validates the name of the connected client from its first message. * * The server expects a message starting with "HI, I'M " and followed by the name of the client. * If the client doesn't fulfil the server expectations, by sending the wrong command or sending * a clientName that is non alphanumeric plus the character "-", the server is awaiting for the client to give a * proper name and doesn't accept other commands before that. * * @param out the PrintWriter with which the sever sends messages to the client. * @param message received from a client. * * @return clientName - the name of the client if it is valid, otherwise returns null; */ public static String retrieveClientName(PrintWriter out, String message) { String clientName; if (message.contains("HI, I'M ")) { String unvalidatedClientName = message.replace("HI, I'M ", ""); if (isValidName(unvalidatedClientName)) { clientName = unvalidatedClientName; System.out.println("Connected client: " + clientName); out.println("HI " + clientName); return clientName; } } out.println(unsupportedCommand()); return null; } /** * Sends a message to the client before disconnecting. * The message includes the name of the client and the duration of the connection. * * @param out the PrintWriter with which the sever sends messages to the client. * @param clientName the name of the connected client. * @param startTime the time in milliseconds when the client connected. */ public static void sendGoodbyeMessage(PrintWriter out, String clientName, long startTime) { if (out != null) { System.out.println("GOODBYE MESSAGE!" + clientName); long stopTime = System.currentTimeMillis(); long duration = stopTime - startTime; out.println("BYE " + clientName + ", WE SPOKE FOR " + duration + " MS"); } } /** * Adds a node (as a string) to the GRAPH. * When the name of the node is only including alphanumeric character, * plus the character "-" and the node doesn't already exist. * * @param message received from a client. * @return returns an error message if the node already exists * or success message if the node has been successfully added. */ public static String addNode(String message) { String nodeName = message.replace("ADD NODE ", ""); if (!isValidName(nodeName)) { return unsupportedCommand(); } if (!Server.GRAPH.containsNode(nodeName)) { Server.GRAPH.addNode(nodeName); return "NODE ADDED"; } else { return "ERROR: NODE ALREADY EXISTS"; } } /** * Adds a directed edge to the GRAPH. * A directed edge has three parts, node from which it points, node to which it points * and weight which is a positive integer. * When the names of the nodes between the edge is are already existing and * the format of the command is valid and the weight is a positive integer. * * @param message received from a client. * @return returns an error message if the the edge can't be added * or success message if the edge has been successfully added. */ public static String addEdge(String message) { String edgeString = message.replace("ADD EDGE ", ""); String[] edgeParts = edgeString.split(" "); if (!isValidEdge(edgeParts)) { return unsupportedCommand(); } String fromName = edgeParts[0]; String toName = edgeParts[1]; if (Server.GRAPH.containsNode(fromName) && Server.GRAPH.containsNode(toName)) { int weight = Integer.valueOf(edgeParts[2]); Server.GRAPH.addEdge(fromName, toName, weight); return "EDGE ADDED"; } return "ERROR: NODE NOT FOUND"; } /** * Removes a node from the GRAPH, only when the node is already existing. * All edges that are linked to the removed node will also be removed. * @param message received from a client. * @return returns an error message if the node doesn't exist * or success message if the node has been successfully removed. */ public static String removeNode(String message) { String nodeName = message.replace("REMOVE NODE ", ""); if (Server.GRAPH.containsNode(nodeName)) { Server.GRAPH.removeNode(nodeName); return "NODE REMOVED"; } else { return "ERROR: NODE NOT FOUND"; } } /** * Removes all directed edges from the GRAPH when the command is valid * and the both nodes that form the graph are existing. * @param message received from a client. * @return returns an error message if the command is invalid or the nodes between the graph aren't existing. * Returns a success message if the edges have been successfully removed. */ public static String removeEdge(String message) { String edgeString = message.replace("REMOVE EDGE ", ""); String[] edgeParts = edgeString.split(" "); if (edgeParts.length != 2) { return unsupportedCommand(); } String fromName = edgeParts[0]; String toName = edgeParts[1]; if (Server.GRAPH.containsNode(fromName) && Server.GRAPH.containsNode(toName)) { Server.GRAPH.removeEdge(fromName, toName); return "EDGE REMOVED"; } return "ERROR: NODE NOT FOUND"; } /** * Calculates the sum of the weights of the shortest path between two nodes in the GRAPH, * when the input of the command is valid and the nodes exist in the GRAPH. * @param message received from a client. * @return the sum of the weights of the shortest path between two nodes. * Returns an error message if the command is invalid or the nodes of the GRAPH aren't existing. */ public static String getShortestPath(String message) { String fromAndTo = message.replace("SHORTEST PATH ", ""); String[] fromTo = fromAndTo.split(" "); if (fromTo.length != 2) { return unsupportedCommand(); } String from = fromTo[0]; String to = fromTo[1]; if (Server.GRAPH.containsNode(from) && Server.GRAPH.containsNode(to)) { return String.valueOf(Server.GRAPH.getShortestPathDistance(from, to)); } return "ERROR: NODE NOT FOUND"; } /** * Finds all the nodes that are closer to a node from the client message than the given weight. * * For example: * Simple graph: Mark -­ 5 -­> Michael -­ 2 -­> Madeleine -­ 8 -­> Mufasa * CLOSER THAN 8 Mark * Would return: Madeleine,Michael * Because Michael is at weight 5 from Mark and Madeleine is at weight 7 (5+2) from Mark. * * @param message received from a client. * @return a comma separated list(no spaces) of found nodes, that are closer to a node from the client message, * sorted alphabetically by name, not including the starting node. * Returns an error message if the command is invalid or the node of the GRAPH isn't existing. */ public static String getCloserThan(String message) { String weightAndNode = message.replace("CLOSER THAN ", ""); String[] weightNode = weightAndNode.split(" "); if (weightNode.length != 2 || !isValidWeightString(weightNode[0])) { return unsupportedCommand(); } int weight = Integer.valueOf(weightNode[0]); String nodeName = weightNode[1]; if (Server.GRAPH.containsNode(nodeName)) { String path = Server.GRAPH.getNodesCloserThan(weight, nodeName); return path; } return "ERROR: NODE NOT FOUND"; } /** * Returns string for unsupported command. * @return string for unsupported command. */ public static String unsupportedCommand() { return "SORRY, I DIDN'T UNDERSTAND THAT"; } private static boolean isValidEdge(String[] input) { if (input.length != 3) { return false; } String weightString = input[2]; boolean isValidWeightString = isValidWeightString(weightString); return isValidWeightString; } private static boolean isValidWeightString(String weightString) { return weightString.length() > 0 && weightString.chars().allMatch(Character::isDigit); } private static boolean isValidName(String input) { return input.matches("[A-Za-z0-9-]+"); } }