diff --git a/.gitignore b/.gitignore
index c7cf644..0969247 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
.idea
-SharifEngine.iml
+JGFramework.iml
*.class
@@ -7,9 +7,13 @@ SharifEngine.iml
.mtj.tmp/
# Package Files #
-*.jar
*.war
*.ear
+.DS_Store
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+# Jar libraries #
+libs/*.jar
+out/
\ No newline at end of file
diff --git a/JGFramework.iml b/JGFramework.iml
new file mode 100644
index 0000000..d3e9e47
--- /dev/null
+++ b/JGFramework.iml
@@ -0,0 +1,17 @@
+
+
+ * {@link server.Server Server} class is the main runner of the framework activities.
+ * Creation of the server class will assign the proper objects with the preferred configurations based on
+ * files within the config files.
+ * The class creates a terminal with connection through a terminal network.
+ * Also sets the {@link server.core.GameLogic GameLogic} subclass instance to the
+ * {@link server.core.GameHandler GameHandler} of the framework with the utility
+ * {@link server.core.Factory Factory} class.
+ * Any mistake in the config files causes rising a runtime exception.
+ *
+ * This class factory accepts a {@link server.core.Factory Factory} instance in order to set the
+ * user created subclass of {@link server.core.GameLogic GameLogic} class to the
+ * {@link server.core.GameHandler GameHandler} of the server.
+ * The configuration of {@link server.network.TerminalNetwork TerminalNetwork} and
+ * {@link server.network.UINetwork UINetwork} are inside a file in resources folder.
+ * ({@see https://github.com/JavaChallenge/JGFramework/wiki wiki})
+ * Occurring any error during parsing config json files, causes a runtime exception to be thrown.
+ *
+ * This method will check the token for a valid 32-character token and returns it as an + * {@link java.lang.String String}. + * In the case of an invalid token, raises a runtime exception. + *
+ * @return 32-character token + */ + public String getTerminalToken() { + if (token.matches("(.){32}") && token.length() == 32) { + return token; + } + throw new RuntimeException("Invalid terminal token in config file"); + } + + /** + * Getter for the terminal port. + *+ * This method checks the port for a valid number and returns it as an integer. + * In the case of an invalid port number (out of range numbers), raises a runtime exception. + *
+ * @return Integer port number + */ + public int getTerminalPort() { + if (port > 0 && port <= 65535) { + return port; + } else { + throw new RuntimeException("Invalid terminal port number in config file"); + } + } + } + + /** + * + */ + private static class UIConfig { + private String token; + private int port; + + public UIConfig(String token, int port) { + this.token = token; + this.port = port; + } + + /** + * Getter for the user interface token. + *+ * This method will check the token for a valid 32-character token and returns it as an + * {@link java.lang.String String}. + * In the case of an invalid token, raises a runtime exception. + *
+ * @return 32-character token + */ + public String getUIToken() { + if (token.matches("(.){32}") && token.length() == 32) { + return token; + } + throw new RuntimeException("Invalid UI token in config file"); + } + + /** + * Getter for the ui port. + *+ * This method checks the port for a valid number and returns it as an integer. + * In the case of an invalid port number (out of range numbers), raises a runtime exception. + *
+ * @return Integer port number + */ + public int getUIPort() { + if (port > 0 && port <= 65535) { + return port; + } else { + throw new RuntimeException("Invalid ui port number in config file"); + } + } + } + + /** + * + */ + private static class ClientConfig { + private int port; + + public ClientConfig(int port) { + this.port = port; + } + + /** + * Getter for the client server port. + *+ * This method checks the port for a valid number and returns it as an integer. + * In the case of an invalid port number (out of range numbers), raises a runtime exception. + *
+ * @return Integer port number + */ + public int getClientPort() { + if (port > 0 && port <= 65535) { + return port; + } else { + throw new RuntimeException("Invalid ui port number in config file"); + } + } + } } diff --git a/src/server/core/CommandHandler.java b/src/server/core/CommandHandler.java new file mode 100644 index 0000000..d474a81 --- /dev/null +++ b/src/server/core/CommandHandler.java @@ -0,0 +1,84 @@ +package server.core; + +import server.Server; +import server.core.model.Event; +import server.network.TerminalNetwork; +import server.network.data.Message; + +import java.util.HashMap; +import java.util.function.UnaryOperator; + +/** + * Created by alilotfi on 2/2/15. + */ +public class CommandHandler implements TerminalNetwork.TerminalInterface { + + public static final String REPORT_NAME = "report"; + + protected Server mServer; + private HashMap+ * The user must fill in this method to return the preferred subclass of the + * {@link server.core.GameLogic GameLogic} in order to be used in the + * {@link server.core.GameHandler GameHandler} class + *
+ */ +public interface Factory { + + /** + * The user fills in this abstract method to return the preferred subclass of the game logic. + * @return The preferred child of the {@link server.core.GameLogic GameLogic} class + */ + public GameLogic getGameLogic(String[] options); +} diff --git a/src/server/core/GameHandler.java b/src/server/core/GameHandler.java index 3a475da..0ebc20a 100644 --- a/src/server/core/GameHandler.java +++ b/src/server/core/GameHandler.java @@ -1,13 +1,344 @@ package server.core; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import server.core.model.ClientInfo; +import server.core.model.Event; import server.network.ClientNetwork; import server.network.UINetwork; +import server.network.data.Message; +import util.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.concurrent.*; /** - * Created by Razi on 12/6/2014. + * Core controller of the framework, controls the {@link server.core.GameLogic GameLogic}, main loop of the game and + * does the output controlling operations. + *+ * This class runs the main running thread of the framework. Class interacts with the clients, UI, and the + * GameLogic itself. + * Threads in this class, will gather the clients' events + * (See also {@link server.network.ClientNetwork ClientNetwork}), send them to the main Game + * (See also {@link server.core.GameLogic GameLogic}) + * The output will be manipulated and sent to the appropriate controller within a inner module of the class + * (OutputController). + * The sequence of the creation and running the operations of this class will be through the call of the following + * methods. + * {@link server.core.GameHandler#init() init()}, {@link server.core.GameHandler#start() start()} and then at the + * moment the external terminal user wants to shut down the games loop (except than waiting for the + * {@link server.core.GameLogic GameLogic} to flag the end of the game), the + * {@link server.core.GameHandler#shutdown() shutdown()} method would be called. + * Note that shutting down the {@link server.core.GameHandler GameHandler} will not immediately stop the threads, + * actually it will set a shut down request flag in the class, which will closes the thread in the aspect of + * accepting more inputs, and the terminate the threads as soon as the operation queue got empty. + *
*/ public class GameHandler { - private ClientNetwork clientNetwork; - private UINetwork uiNetwork; - private GameLogic gameLogic; + + private static final String RESOURCE_PATH_OUTPUT_HANDLER = "resources/game_handler/output_handler.conf"; + private static final String RESOURCE_PATH_TURN_TIMEOUT = "resources/game_handler/turn_timeout.conf"; + private final long GAME_LOGIC_SIMULATE_TIMEOUT; + private final long GAME_LOGIC_TURN_TIMEOUT = 1000; + private final long CLIENT_RESPONSE_TIME; + + private ClientNetwork mClientNetwork; + private UINetwork mUINetwork; + private GameLogic mGameLogic; + private OutputController mOutputController; + private ClientInfo[] mClientsInfo; + + private Loop mLoop; + BlockingQueue+ * The constructor accepts the instances of {@link server.core.GameHandler GameHandler} and + * {@link server.network.ClientNetwork ClientNetwork} classes. Then sets some configurations of the loops + * within the "turn_timeout.conf" file ({@see https://github.com/JavaChallenge/JGFramework/wiki wiki}). + *
+ * @param clientNetwork Network which the game will contact to the clients through + * @param uiNetwork Network which the game will contact to the UI through + */ + public GameHandler(ClientNetwork clientNetwork, UINetwork uiNetwork) { + mClientNetwork = clientNetwork; + mUINetwork = uiNetwork; + terminalEventsQueue = new LinkedBlockingQueue<>(); + + Gson gson = new Gson(); + try { + File file = new File(RESOURCE_PATH_TURN_TIMEOUT); + BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + TimeConfig timeConfig = gson.fromJson(bufferedReader, TimeConfig.class); + GAME_LOGIC_SIMULATE_TIMEOUT = timeConfig.getClientResponseTime(); + CLIENT_RESPONSE_TIME = timeConfig.getUIResponseTime(); + } catch (FileNotFoundException notFound) { + throw new RuntimeException("turn_timeout config file not found"); + } catch (JsonParseException e) { + throw new RuntimeException("turn_time config file does not meet expected syntax"); + } + } + + /** + * Initializes the game handler module based on the given game. + *+ * This method creates needed instance of module {@link server.core.OutputController OutputController} based on + * configurations located in the "output_handler.conf" file + * ({@see https://github.com/JavaChallenge/JGFramework/wiki wiki}). + * Any problem in loading or parsing the configuration JSON files, will result in rising a runtime exception. + *
+ */ + public void init() { + OutputHandlerConfig outputHandlerConfig; + Gson gson = new Gson(); + try { + File file = new File(RESOURCE_PATH_OUTPUT_HANDLER); + BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + outputHandlerConfig = gson.fromJson(bufferedReader, OutputHandlerConfig.class); + } catch (FileNotFoundException notFound) { + throw new RuntimeException("Output handler config file not found"); + } catch (JsonParseException e) { + throw new RuntimeException("Output handler config file does not meet expected syntax"); + } + mOutputController = new OutputController(outputHandlerConfig.sendToUI, + mUINetwork, + outputHandlerConfig.timeInterval, + outputHandlerConfig.sendToFile, + outputHandlerConfig.getFile(), + outputHandlerConfig.bufferSize); + } + + /** + * Setter of the game logic, will set the main game engine of the {@link server.core.GameHandler GameHandler} to + * the given instance of {@link server.core.GameLogic GameLogic}. + * @param gameLogic The given instance of the {@link server.core.GameLogic GameLogic} to set in the object. + */ + + public void setGameLogic(GameLogic gameLogic) { + mGameLogic = gameLogic; + } + + /** + * Getter of the {@link server.network.ClientNetwork ClientNetwork} class which is used to contact to the clients + * through it. + * @return The {@link server.network.ClientNetwork ClientNetwork} instance used to connect to clients + */ + public ClientNetwork getClientNetwork() { + return mClientNetwork; + } + + /** + * Getter of the {@link server.network.UINetwork UINetwork} class instance, which is used in order to connect to + * the UI. + * @return Instance of {@link server.network.UINetwork UINetwork} class, used to send messages to user interface + */ + public UINetwork getUINetwork() { + return mUINetwork; + } + + /** + * Setter of the {@link server.core.model.ClientInfo ClientInfo} instance stored in the class in order to recognize + * the clients from each other + * @param clientsInfo Array of information of the clients held in the class + */ + public void setClientsInfo(ClientInfo[] clientsInfo) { + mClientsInfo = clientsInfo; + } + + /** + * Starts the main game ({@link server.core.GameLogic GameLogic}) loop and the + * {@link server.core.OutputController OutputController} operations in two new {@link java.lang.Thread Thread}. + */ + public void start() { + mLoop = new Loop(); + new Thread(mLoop).start(); + new Thread(mOutputController).start(); + } + + /** + * Registers a shutdown request into the main loop and {@link server.core.OutputController OutputController} class + *+ * Note that the shutdown requests, will be responded as soon as the current queue of operations got freed. + *
+ */ + public void shutdown() { + mLoop.shutdown(); + mOutputController.shutdown(); + } + + /** + * Queues an event to be simulated in the next turn of the loop. + * + * @param event terminal event + */ + public void queueEvent(Event event) { + synchronized (mLoop.terminalEventsQueue) { + mLoop.terminalEventsQueue.add(event); + } + } + + /** + * In order to give the loop a thread to be ran beside of the main loop. + *+ * This inner class has a {@link java.util.concurrent.Callable Callable} part, which is wrote down as a + * runnable code template. This template is composed by the multiple steps in every turn of the game. + *
+ */ + private class Loop implements Runnable { + + private boolean shutdownRequest = false; + + private Event[] environmentEvents; + private Event[] terminalEvents; + private Event[][] clientEvents; + private final ArrayList+ * This class could be filled by a {@link com.google.gson.Gson Gson} object. The properties of class are + * normally accessed through {@link server.core.GameHandler#init() init()} method. + *
+ */ + private class OutputHandlerConfig { + private boolean sendToUI; + private int timeInterval; + private boolean sendToFile; + private String filePath; + private int bufferSize; + + public OutputHandlerConfig(boolean sendToUI, + int timeInterval, + boolean sendToFile, + String filePath, + int bufferSize) { + this.sendToUI = sendToUI; + this.timeInterval = timeInterval; + this.sendToFile = sendToFile; + this.filePath = filePath; + this.bufferSize = bufferSize; + } + private File getFile() { + return new File(filePath); + } + } + + /** + * The template of the "turn_timeout.conf" JSON file. + *+ * This class could be filled by a {@link com.google.gson.Gson Gson} object. The properties of class are + * normally accessed through {@link server.core.GameHandler#GameHandler GameHandler Constructor} method. + *
+ */ + private static class TimeConfig { + private long mClientResponseTime; + private long mSimulateTimeout; + + public TimeConfig(long clientResponseTime,long uiResponseTime) { + mClientResponseTime = clientResponseTime; + mSimulateTimeout = uiResponseTime; + } + + public long getClientResponseTime() { + return mClientResponseTime; + } + + public long getUIResponseTime() { + return mSimulateTimeout; + } + } } diff --git a/src/server/core/GameLogic.java b/src/server/core/GameLogic.java index f0fe94f..58ccab4 100644 --- a/src/server/core/GameLogic.java +++ b/src/server/core/GameLogic.java @@ -1,7 +1,74 @@ package server.core; +import server.core.model.ClientInfo; +import server.core.model.Event; +import server.network.data.Message; + +import java.util.HashMap; +import java.util.UUID; + +import java.util.HashMap; + /** - * Created by Razi on 12/6/2014. + * The abstract class representing the main game logic of the user's game. + *+ * This class will be the simulator engine of the game. + *
*/ -public abstract class GameLogic { +public interface GameLogic { + + //TODO: Saeed Rajab, change these... common baby... + /** + * This method must send initial and necessary values to UI and clients. + * @return A hashmap that hasToken as key and a Message as value.
+ * Token is used for specifying if the Message is for UI or Client.
+ */
+ public void init();
+
+ /**
+ *
+ * @return
+ */
+ public Message getUIInitialMessage();
+
+ /**
+ *
+ * @return
+ */
+ public Message[] getClientInitialMessages();
+
+ /**
+ *
+ * @return
+ */
+ public ClientInfo[] getClientInfo();
+
+ /**
+ * Simulate events based on the current turn event and calculate the changes in game.
+ * @param terminalEvent Events that user enters in terminal.
+ * @param environmentEvent Events that is related to environment. Suppose we want to develop a strategic game.
+ * Increasing/Decreasing a specific resource in map is an environment event.
+ * @param clientsEvent Events that is related to client e.g. moving the player.
+ */
+ public void simulateEvents(Event[] terminalEvent, Event[] environmentEvent, Event[][] clientsEvent);
+
+ /**
+ * This method generates the output based on the changes that were calculated in
+ * {@link #simulateEvents(server.core.model.Event[], server.core.model.Event[], java.util.HashMap)}.
+ * @return A hashmap that has Token as key and a Message as value.
+ * Token is used for specifying if the Message is for UI or Client.
+ */
+ public void generateOutputs();
+
+ public Message getUIMessage();
+
+ public Message[] getClientMessages();
+
+ /**
+ * This method is used for making the environment events.
+ * @return An array that is environment events.
+ */
+ public Event[] makeEnvironmentEvents();
+
+ public boolean isGameFinished();
}
diff --git a/src/server/core/OutputController.java b/src/server/core/OutputController.java
new file mode 100644
index 0000000..307f1ec
--- /dev/null
+++ b/src/server/core/OutputController.java
@@ -0,0 +1,380 @@
+package server.core;
+
+import server.exceptions.OutputControllerQueueOverflowException;
+import server.network.UINetwork;
+import server.network.data.Message;
+
+import java.io.*;
+import java.util.LinkedList;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.*;
+
+/**
+ * Class created as a part of {@link GameHandler GameHandler} class for controlling the output.
+ * + * This class gathers all output taken from "GameLogic" into a queue, and the process them as the user + * wishes. Currently passing to {@link server.network.UINetwork UINetwork} and saving in a + * local file is supported. + *
+ */ +public class OutputController implements Runnable { + + private static final int QUEUE_DEFAULT_SIZE = 100000; + + private boolean sendToUI = false; + private UINetwork uiNetwork; + private int timeInterval; + private Timer timer; + private boolean sendToFile = false; + private File outputFile; + private int bufferSize; + private FileWriter fileWriter; + private LinkedList+ * This constructor creates an instance of OutputController class with the given parameters. + * The two booleans indicate that how the class will handle the given outputs. + * In the case that sendToUI flag is set: + *
+ * Output handling will be assumed as a "Pre Runner" in order to a fast forward run based + * on the game logic, and storing data on a file for later use. + * Data would be held on memory until reaches the specified buffer size, and then will be + * written to the specified file (Using {@link server.core.OutputController.FileWriter FileWriter} + * runnable class). + *
+ * In the case that sendToFile flag is set: + *+ * This type of output processing, will store outputs in the object, and sends it in regular + * timer ticks. This time is specified in milliseconds as timeInterval + *
+ * If the sendToUI or sendToFile flags were set, then the two other values would be assigned to the + * appropriate instance values. + * Note that this will raise a runtime exception, in the case of invalid arguments. + * + * @param sendToUI Indicates that data will be sent to given {@link server.network.UINetwork UINetwork} instance + * or not + * @param uiNetwork The given instance of {@link server.network.UINetwork UINetwork} class to send data to + * @param timeInterval The indicated timer tick which triggers + * {@link server.core.OutputController.UINetworkSender#sendToUINetwork(server.network.data.Message) + * sendToUINetwork(Message)} method + * @param sendToFile Indicates that a log of output will be saved in the given {@link java.io.File java.io.File} or + * not + * @param outputFile The given {@link java.io.File java.io.File} to save data within + * @param bufferSize The preferred number of message elements to be held on memory before writing to file + */ + public OutputController(boolean sendToUI, UINetwork uiNetwork, int timeInterval + , boolean sendToFile, File outputFile, int bufferSize) { + messagesQueue = new LinkedList<>(); + this.sendToUI = sendToUI; + if (sendToUI) { + if (uiNetwork != null) { + this.uiNetwork = uiNetwork; + } else { + throw new RuntimeException("The given parameter as uiNetwork is null"); + } + + if (timeInterval > 0) { + this.timeInterval = timeInterval; + } else { + throw new RuntimeException("The given parameter as timeInterval is invalid"); + } + } + this.sendToFile = sendToFile; + if (sendToFile) { + this.outputFile = outputFile; + try { + this.outputFile.createNewFile(); + } catch (IOException e) { + throw new RuntimeException("Could not create log file"); + } + + if (bufferSize > 0 && bufferSize <= QUEUE_DEFAULT_SIZE) { + this.bufferSize = bufferSize; + } else if (bufferSize > 0) { + throw new RuntimeException("The given parameter as bufferSize is greater than max queue size"); + } else { + throw new RuntimeException("The given parameter as bufferSize is not valid"); + } + } + } + + /** + * Run method implemented from {@link Runnable java.lang.Runnable} class, which will set and run the two type + * of output handlers. + *+ * In this method, if the sendToUI mode got activated in the class, then a {@link java.util.Timer Timer} will + * be scheduled in the given period to send data to {@link server.network.UINetwork UINetwork}. + * And also if the sendToFile mode got activated, then a thread of + * {@link server.core.OutputController.FileWriter FileWriter} class will be run. + *
+ */ + @Override + public void run() { + if (sendToUI) { + timer = new Timer(); + timer.scheduleAtFixedRate(new UINetworkSender(), 0, timeInterval); + } + + if (sendToFile) { + fileWriter = new FileWriter(outputFile); + } + new Thread(fileWriter).start(); + } + + /** + * Accepts an instance of {@link server.network.data.Message Message} class as an argument, and places it + * on the queue. + *+ * In this method, the given message will be putted in the message queue, only if there's a place on the + * queue. Otherwise the cleaning and caching processes will be done (through the + * {@link #hanldeOverflow() hanldeOverflow} method). + * Also if the buffer size condition is met, then the file writer method will be called with an + * alternative thread, to save the contents on the file. + *
+ * @param message The given message (as output) to put in the message queue. + */ + public synchronized void putMessage(Message message) { + if (messagesQueue.size() <= QUEUE_DEFAULT_SIZE) { + messagesQueue.addLast(message); + if (messagesQueue.size() == bufferSize && sendToFile) { + fileWriter.putMessages(messagesQueue); + messagesQueue = new LinkedList<>(); + } + } else { + if (hanldeOverflow()) { + messagesQueue.addLast(message); + } else { + throw new OutputControllerQueueOverflowException("Could not handle message queue overflow"); + } + } + synchronized (messagesQueue) { + messagesQueue.notifyAll(); + } + } + + /** + * Method created to handle the possible overflows occurance in the message queue. + *+ * TODO: INCOMPLETE - Must be implemented to cache the queue to file. + *
+ * @return True if the queue unblocked, false if any error occur during this operation + */ + private boolean hanldeOverflow() { + messagesQueue.clear(); + return true; + } + + /** + * Tries to shutdown all the threads ran in this class so far. + *+ * This method calls the close on the {@link server.core.OutputController.FileWriter FileWriter} instance of + * the object, causing it to interrupt as soon as all files wrote down on the file. + * Also cancels the timer to stop automatically invocation of + * {@link server.core.OutputController.UINetworkSender UINetworkSender}. + * Other threads would be closed automatically ({@link server.core.OutputController.UINetworkSender + * UINetworkSenders} after the timeout). + *
+ */ + public void shutdown() { + fileWriter.close(); + timer.cancel(); + } + + /** + * This inner class is used to do processes needed during the file saving operations, as an alternative + * thread. + *
+ * This class will be run by the "OutputController" class, as a file save operation is needed.
+ * This Runnable implemented class, could do the long term file saving operations, as an alternative
+ * thread.
+ * This class uses a {@link java.util.concurrent.BlockingQueue BlockingQueue
+ * This method makes the class enable to have a local copy of message queue, during the file + * processing operations (The outer copy could be destroyed). + *
+ * @param messages The given message linked list, in order to save as a local copy in + * {@link java.util.concurrent.BlockingQueue BlockingQueue} + */ + public void putMessages(LinkedList+ * This method uses a while loop to save the whole contents of + * {@link java.util.concurrent.BlockingQueue BlockingQueue} of + * {@link server.network.data.Message Messages} to the given file. + * This will stop working only if the close order were sent and there's no more + * {@link server.network.data.Message Messages} on the queue. + *
+ */ + @Override + public void run() { + open = true; + while (open || messagesQueue.size() > 0) { + writeToFile(); + } + } + + /** + * Stores the contents of the message queue as an appendix to the FileWriter file. + *+ * This method will write the whole contents of the saved message queue in the file writer object + * on the given file object by object. As this operation is completed, the file writer tries to write the + * next file in the {@link java.util.concurrent.BlockingQueue BlockingQueue} + *
+ */ + private void writeToFile() { + try { + try { + OutputStream outputStream = new FileOutputStream(file); + OutputStream buffer = new BufferedOutputStream(outputStream); + ObjectOutput outputFile = new ObjectOutputStream(buffer); + while (!messagesQueue.isEmpty()) { + for (Message message : messagesQueue.take()) { + outputFile.writeObject(message); + } + } + } catch (FileNotFoundException fileNotFound) { + throw new RuntimeException("Could not find the log file"); + } catch (IOException ioException) { + throw new RuntimeException("Could not write on the log file"); + } + synchronized (messagesQueue) { + notifyAll(); + } + } catch (InterruptedException e) { + throw new RuntimeException("Taking from FileWriter message queue interrupted."); + } + } + + /** + * This will send to the class a request to close connection to the file. + *+ * This method sets the open flag of class to false and so as soon as + * {@link java.util.concurrent.BlockingQueue BlockingQueue} is empty, the writing operation will be + * finished and the thread will be killed. + *
+ */ + public void close() { + this.open = false; + } + } + + /** + * Is responsible for sending the first {@link server.network.data.Message Message} in the queue to the + * {@link server.network.UINetwork UINetwork}. + *+ * As the failure in connection and some other issues leaves the + * {@link server.network.UINetwork#sendBlocking(server.network.data.Message) UINetwork.send(Message)} method + * blocked, and so causes a thread block, this class runs as an alternative thread to send the messages + * in the queue, to the {@link server.network.UINetwork UINetwrok} instance without causing the main + * thread of OutputController to sleep. + *
+ */ + private class UINetworkSender extends TimerTask { + + private final static int CONNECTION_TIMEOUT = 1000; + + private boolean sent = false; + + /** + * Implemented run method from {@link java.lang.Runnable Runnable} class which sends the first message in the + * queue to the {@link server.network.UINetwork UINetwork}. + *+ * This method will call the {@link #sendToUINetwork(server.network.data.Message) sendToUINetwork(Message)} + * method with the appropriate message instance. + * Every connection made in a separate thread and with a specified timeout. + * If the last connection could send the message within the timeout, then this method will take another + * message from the queue and send to the network, otherwise, the last message will be sent again. + * The caller timer will be canceled if the shutdown request was sent to the class. + *
+ */ + @Override + public void run() { + Message message; + synchronized (messagesQueue) { + while (messagesQueue.size() <= 0) { + try { + messagesQueue.wait(); + } catch (InterruptedException e) { + throw new RuntimeException("Wait on UINetworkSender interrupted"); + } + } + message = messagesQueue.getFirst(); + if (sent) { + messagesQueue.removeFirst(); + } + } + sent = false; + sendToUINetwork(message); + } + + /** + * This method serves the instance of {@link server.network.UINetwork UINetwork} class with messages. + *+ * This method calls {@link server.network.UINetwork#sendBlocking(server.network.data.Message) send(message)} + * method on {@link server.network.UINetwork UINetwork} instance in order to show up on UI. + * Calling this while the message queue is empty, will put it in the wait mode. + * Basically this method will be called by the timer scheduled according to user preferred + * time interval. + * As the time of sending message to the {@link server.network.UINetwork UINetworks} exceeds the + * timeout limit, then the execution will be cancelled. + *
+ */ + private void sendToUINetwork(Message message) { + Callable+ * Parts of this class members are required for creating and connecting a client to the + * {@link server.network.ClientNetwork ClientNetwork}, these parts have to be wrote down to the config file in + * "Resources" folder ({@see https://github.com/JavaChallenge/JGFramework/wiki wiki}). + *
+ */ +public class ClientInfo { + + private String mToken; + private String mName; + private int mID; + + /** + * Setter for the ID of the client. + *+ * ID of every client would be generated inside the {@link server.network.ClientNetwork ClientNetwor} class. + * Then the assigned unique ID will be set to the client through this method. + *
+ * @param ID Assigned ID by the {@link server.network.ClientNetwork ClientNetwork} class. + */ + public void setID(int ID) { + mID = ID; + } + + /** + * Getter of the Token filed of the object. + *+ * Returns the 32-character token assigned to the client (normally by user) as an String + *
+ * @return String token of the client + */ + public String getToken() { + return mToken; + } + + + //TODO DOC + public void setmName(String mName) { + this.mName = mName; + } + + //TODO DOC + public void setmToken(String mToken) { + this.mToken = mToken; + } +} diff --git a/src/server/core/model/Event.java b/src/server/core/model/Event.java new file mode 100644 index 0000000..8b09db7 --- /dev/null +++ b/src/server/core/model/Event.java @@ -0,0 +1,9 @@ +package server.core.model; + +/** + * Created by Razi on 12/22/2014. + */ +public class Event { + public static final String EVENT = "event"; + +} diff --git a/src/server/exceptions/OutputControllerQueueOverflowException.java b/src/server/exceptions/OutputControllerQueueOverflowException.java new file mode 100644 index 0000000..1dfc891 --- /dev/null +++ b/src/server/exceptions/OutputControllerQueueOverflowException.java @@ -0,0 +1,14 @@ +package server.exceptions; + +/** + * Created by alilotfi on 1/23/15. + */ +public class OutputControllerQueueOverflowException extends RuntimeException { + public OutputControllerQueueOverflowException() { + super(); + } + + public OutputControllerQueueOverflowException(String s) { + super(s); + } +} diff --git a/src/server/network/ClientHandler.java b/src/server/network/ClientHandler.java new file mode 100644 index 0000000..ea23df1 --- /dev/null +++ b/src/server/network/ClientHandler.java @@ -0,0 +1,250 @@ +package server.network; + +import server.network.data.Message; +import util.Log; + +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.function.Supplier; + +/** + * {@link server.network.ClientHandler} handles a client, i.e. it is responsible + * for sending/receiving messages to/from the client. + *+ * After that a client is verified by the server, server assigns a + * {@link server.network.ClientHandler} to that client. + *
+ * Messages should arrive in a predefined interval of time (which is determined
+ * by the server). Every message arrived out of this interval is discarded
+ * immediately. If two or more messages arrived in the valid time the last one
+ * is stored as the "last validated message".
+ */
+public class ClientHandler {
+
+ /**
+ * Logging tag.
+ */
+ private static String TAG = "ClientHandler";
+
+ /**
+ * Socket of the client.
+ */
+ private JsonSocket client;
+
+ /**
+ * Lock for {@link #client}.
+ */
+ private final Object clientLock;
+
+ /**
+ * Termination flag.
+ */
+ private boolean terminateFlag;
+
+ /**
+ * Last valid message which is arrived on time.
+ */
+ private Message lastValidatedMessage;
+
+ /**
+ * Last message received from client.
+ */
+ private Message lastReceivedMessage;
+
+ /**
+ * This object is notified when a message is received.
+ */
+ private final Object messageNotifier;
+
+ /**
+ * Message queue. These messages will be sent to the client asap.
+ */
+ private final LinkedBlockingDeque
+ * First of all clients (players) must be defined to the server via method
+ * {@link #defineClient}. Server assigns an ID to each client so further calls
+ * get ID of the client instead of its token.
+ *
+ * When a client (player) is connected to the server, it sends a token and waits
+ * for the initial message which contains necessary data of beginning of the
+ * game.
+ *
+ * Each turn server sends some data to the clients, these data are first queued
+ * by {@link #queue} and then sent by send method. Server does it best to send
+ * messages simultaneously to clients, i.e. sending procedure is fair.
+ *
+ * The communications are one sided, i.e. everything which is sent by client is
+ * ignored by the server.
*/
-public class ClientNetwork {
+public class ClientNetwork extends NetServer {
+
+ /**
+ * Logging tag.
+ */
+ private static String TAG = "ClientNetwork";
+
+ /**
+ * Indicates that receive time is valid or not.
+ */
+ private volatile boolean receiveTimeFlag;
+
+ /**
+ * Tokens of clients.
+ */
+ private HashMap
+ * A
+ * To create a
+ * After a
+ * Note that the other endpoint of the communication must use a
+ *
+ * This class is a member of {@link server.network}.
+ *
+ * @see com.google.gson.Gson
+ * @see java.net.Socket
+ *
+ */
+public class JsonSocket {
+
+ /**
+ * The
+ * When the method {@link #listen} is called, a
+ * {@link server.network.NetServerThread} is assigned to listen on the specified
+ * port. When a client attempts to connect to that port, the {@link #accept}
+ * method of the class is called, with a reference to the
+ *
+ * It is better to do all operations for a client in a separated thread. So if
+ * the client is fake it doesn't affect the behavior of the server.
+ *
+ * @see server.network.NetServerThread
+ * @see server.network.TerminalNetwork
+ * @see server.network.ClientNetwork
+ * @see server.network.UINetwork
+ */
+public abstract class NetServer {
+
+ /**
+ * The thread which is assigned to listen on the port.
+ */
+ private NetServerThread listener;
+
+ /**
+ * A thread is created and assigned to listen on the specified port.
+ * {@link #terminate} must be called between two consecutive calls of this
+ * function.
+ *
+ * @param port server port
+ * @see #terminate
+ * @see server.network.NetServerThread
+ */
+ public synchronized void listen(int port) {
+ if (listener != null)
+ throw new IllegalStateException("NetServer is currently listening.");
+ listener = new NetServerThread(port, this::accept);
+ listener.start();
+ }
+
+ /**
+ * Terminates operations of the current listener. {@link #listen} must be
+ * called between two consecutive calls of this function.
+ *
+ * @see #listen
+ * @see server.network.NetServerThread
+ */
+ public synchronized void terminate() {
+ if (listener == null)
+ throw new IllegalStateException("NetServer is not started or has been terminated.");
+ listener.terminate();
+ listener = null;
+ }
+
+ /**
+ * Returns the state of the server.
+ *
+ * @return true if listening is terminated.
+ */
+ public boolean isTerminated() {
+ return listener == null;
+ }
+
+ /**
+ * A method to accept new clients. When a client connects to the port of
+ * this
+ * This method is abstract and so it is not implemented in this class.
+ * Each server has a different behavior on accepting new clients, e.g.
+ * it may take a token from client or send some information at initial point.
+ * To implement a server, one can extend this class and implement this method.
+ *
+ * Note that this method must take care of everything that could be happen
+ * during communications between client and server.
+ *
+ * @param client a {@link server.network.JsonSocket} which is connected
+ * to the client's socket.
+ */
+ protected abstract void accept(JsonSocket client);
+
+}
diff --git a/src/server/network/NetServerThread.java b/src/server/network/NetServerThread.java
new file mode 100644
index 0000000..7e906f5
--- /dev/null
+++ b/src/server/network/NetServerThread.java
@@ -0,0 +1,114 @@
+package server.network;
+
+import util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.function.Consumer;
+
+/**
+ * This thread is used by {@link server.network.NetServer} to listen on
+ * some port for the new clients.
+ *
+ * The actual work of the {@link server.network.NetServerThread} is done by a
+ * {@link java.net.ServerSocket}. When a
+ * The consumer should accept the client and perform necessary operations.
+ *
+ * In the {@link server.network.NetServer} the consumer is the abstract method
+ * {@link server.network.NetServer#accept}.
+ *
+ * When the server socket is disconnected from the port, e.g. when it is closed
+ * due to an unknown reason, a new server socket will be created.
+ * So it is guaranteed that server is always listening on the port.
+ *
+ * @see server.network.NetServer
+ * @see java.net.ServerSocket
+ */
+public class NetServerThread extends Thread {
+
+ /**
+ * Logging tag.
+ */
+ private static String TAG = "NetServerThread";
+
+ /**
+ * The port to listen on.
+ */
+ private final int port;
+ /**
+ * Terminate flag!
+ */
+ private boolean terminateFlag;
+ /**
+ * The underlying server socket which actually listens on the port.
+ */
+ private ServerSocket serverSocket;
+ /**
+ * This consumer specifies the behavior of the server when a new client is
+ * connected.
+ * Actually,
+ * A new object from this class will be created when a
+ * All of the required efforts for connecting a client to that server is done by the parent class called {@link server.network.NetServer}
+ * so in this class we just need to handle the events that are occur after user successfully connects with
+ *
+ * @see server.network.TerminalNetwork.TerminalNetworkThread
+ * @see server.network.NetServer
*/
-public class TerminalNetwork {
+public class TerminalNetwork extends NetServer {
+
+ public static final int MAX_RECEIVE_EXCEPTIONS = 20;
+
+ /**
+ * used to identify the users to connect to Terminal.
+ */
+ private String token;
+
+ /**
+ * This interface is used for transfer
+ * {@link server.network.TerminalNetwork.TerminalNetworkThread} uses a {@link server.network.JsonSocket}
+ * to get the inputs of a client. The first input is a
+ * Then it converts every input to a {@link server.network.data.Message} class
+ * and if the input was a valid one it checks whether this input is command or event.
+ * Then it creates the appropriate object and by means of {@link server.network.TerminalNetwork.TerminalInterface}
+ * sends it to {@link server.core.model.Event}.
+ *
+ * @see server.network.NetServer
+ * @see server.network.JsonSocket
+ */
+ class TerminalNetworkThread extends Thread {
+
+ /**
+ * Use for get the user input.
+ */
+ private JsonSocket client;
+
+ /**
+ * For making an infinite loop. there's no need to make it false because when the terminal actually
+ * terminated, the thread will be stop.
+ */
+ private boolean userHasCommand = true;
+
+ /**
+ * Constructor.
+ * @param client use to get the input from user
+ */
+ public TerminalNetworkThread (JsonSocket client) {
+ this.client = client;
+ }
+
+ /**
+ * Check tokens and analyze the inputs of the user.
+ *
+ * @see #TerminalNetworkThread(JsonSocket)
+ * @see #checkInput()
+ */
+ @Override
+ public void run() {
+ try {
+ Message msg = client.get(Message.class);
+
+ if (!msg.name.equals("token") || msg.args == null || msg.args.length < 1 || !msg.args[0].equals(token)) {
+ client.close();
+ return;
+ }
+ Object [] args = new Object[1];
+ args[0] = new ArrayList
+ * When a client is connected to the server, it sends a token and waits for the
+ * initial message which contains necessary data of beginning of the game.
+ *
+ * Messages are sent using methods {@link #sendBlocking} or {@link #sendNonBlocking}.
+ *
+ * The communications are one sided, i.e. everything which is sent by client is
+ * ignored by the server.
*/
-public class UINetwork {
-}
+public final class UINetwork extends NetServer {
+
+ /**
+ * Logging tag
+ */
+ private static String TAG = "UINetwork";
+
+ /**
+ * token of the server
+ */
+ private final String mToken;
+
+ /**
+ * current client of the server
+ */
+ private JsonSocket mClient;
+
+ /**
+ * Lock for {@link #mClient}
+ */
+ private final Lock mClientLock;
+
+ /**
+ * Notifies waiters when a new client is connected.
+ */
+ private final Object clientNotifier;
+
+ /**
+ * thread executor which is used to accept clients
+ */
+ private ExecutorService executor;
+
+ /**
+ * Thread executor which is used to send messages.
+ */
+ private ExecutorService sendExecutor;
+
+ /**
+ * Initializes the class and starts sending messages to clients.
+ * If there is no client at the time of sending, the message will be
+ * thrown away.
+ *
+ * @param token token of the server
+ * @see #sendBlocking
+ * @see #sendNonBlocking
+ * @see #hasClient
+ * @see #waitForClient
+ * @see #waitForNewClient
+ */
+ public UINetwork(String token) {
+ mToken = token;
+ clientNotifier = new Object();
+ mClientLock = new ReentrantLock(true);
+ }
+
+ /**
+ * Sends a message to the client.
+ * Caller method will be blocked until the message is sent.
+ *
+ * @param msg message to send
+ */
+ public void sendBlocking(Message msg) {
+ try {
+ mClient.send(msg);
+ } catch (IOException e) {
+ Log.d(TAG, "Message sending failure.", e);
+ }
+ }
+
+ /**
+ * Sends a message to the client.
+ * Caller method wont be blocked.
+ *
+ * @param msg message to send
+ * @see #sendBlocking
+ */
+ public void sendNonBlocking(Message msg) {
+ sendExecutor.submit(() -> sendBlocking(msg));
+ }
+
+ /**
+ * Creates a new thread to verify the client by taking a token.
+ *
+ * @param client a {@link server.network.JsonSocket} which is connected
+ * @see server.network.NetServer#accept
+ */
+ @Override
+ protected void accept(JsonSocket client) {
+ executor.submit(() -> {
+ try {
+ verifyClient(client);
+ } catch (Exception e) {
+ // if anything was wrong close the client!
+ Log.i(TAG, "Client rejected.", e);
+ try {
+ client.close();
+ } catch (Exception ignored) {}
+ }
+ });
+ }
+
+ /**
+ * Verifies the client by taking a token.
+ *
+ * @param client client
+ * @throws Exception if verification is failed
+ * @see #accept
+ */
+ private void verifyClient(JsonSocket client) throws Exception {
+ // get the token, timeout is 10 seconds
+ FuturetimeValidator)
+ * stores it in {@link #lastValidatedMessage}.
+ *
+ * @param timeValidator get method of this object returns
+ * true if and only if it is called in a valid time.
+ * (valid time is the time when messages can be
+ * arrived from clients, e.g. half a second after
+ * each turn)
+ * @return a runnable which is used by server to receive new messages of client
+ */
+ public Runnable getReceiver(Suppliertimeout milliseconds until
+ * the client send a message.
+ *
+ * @throws InterruptedException if current thread is interrupted.
+ */
+ public void waitForClientMessage(long timeout) throws InterruptedException {
+ synchronized (messageNotifier) {
+ messageNotifier.wait(timeout);
+ }
+ }
+
+ /**
+ * Blocks caller method until a client is connected to this handler.
+ *
+ * @throws InterruptedException if current thread is interrupted.
+ */
+ public void waitForClient() throws InterruptedException {
+ if (client == null || client.isClosed())
+ synchronized (clientLock) {
+ clientLock.wait();
+ }
+ }
+
+ /**
+ * Blocks caller method at most timeout milliseconds until a
+ * client is connected to this handler.
+ *
+ * @param timeout timeout in milliseconds
+ * @throws InterruptedException if current thread is interrupted.
+ */
+ public void waitForClient(long timeout) throws InterruptedException {
+ if (client == null || client.isClosed())
+ synchronized (clientLock) {
+ clientLock.wait(timeout);
+ }
+ }
+
+ /**
+ * Terminates operations of the handler. It actually closes the socket and
+ * changes a flag.
+ */
+ public void terminate() {
+ try {
+ terminateFlag = true;
+ if (client != null)
+ client.close();
+ } catch (IOException e) {
+ Log.i(TAG, "Socket closing failure.", e);
+ }
+ }
+
+}
diff --git a/src/server/network/ClientNetwork.java b/src/server/network/ClientNetwork.java
index 00b8687..f33e121 100644
--- a/src/server/network/ClientNetwork.java
+++ b/src/server/network/ClientNetwork.java
@@ -1,7 +1,377 @@
package server.network;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import server.core.model.Event;
+import server.network.data.Message;
+import util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.*;
+
/**
- * Created by Razi on 12/6/2014.
+ * {@link server.network.ClientNetwork} is a server which is responsible for
+ * sending messages from server to players
+ * and receiving players' requests (messages).
+ * null if there is no valid msg
+ * @see {@link #defineClient}
+ */
+ public Message getReceivedMessage(int clientID) {
+ return mClients.get(clientID).getLastValidatedMessage();
+ }
+
+ /**
+ * Returns last valid event which is received from a client.
+ *
+ * @param clientID ID of the client
+ * @return last valid event or null if there is no valid event
+ * @see {@link #getReceivedMessage}
+ */
+ public Event[] getReceivedEvent(int clientID) {
+ Message msg = getReceivedMessage(clientID);
+ Event[] events = null;
+ try {
+ JsonArray eventArray = ((JsonElement)msg.args[0]).getAsJsonArray();
+ events = new Event[eventArray.size()];
+ for (int i = 0; i < events.length; i++)
+ events[i] = gson.fromJson(eventArray.get(i), Event.class);
+ } catch (Exception e) {
+ Log.i(TAG, "Error getting received messages.", e);
+ }
+ return events;
+ }
+
+ @Override
+ protected void accept(JsonSocket client) {
+ acceptExecutor.submit(() -> {
+ try {
+ verifyClient(client);
+ } catch (Exception e) {
+ // if anything was wrong close the client!
+ Log.i(TAG, "client rejected", e);
+ try {
+ client.close();
+ } catch (Exception ignored) {}
+ }
+ });
+ }
+
+ private void verifyClient(JsonSocket client) throws Exception {
+ // get the token, timeout is 10 seconds
+ Futuretimeout milliseconds until
+ * the specified client send a message.
+ *
+ * @param clientID ID of the client
+ * @throws InterruptedException if current thread is interrupted.
+ * @see {@link #defineClient}
+ */
+ public void waitForClientMessage(int clientID, long timeout) throws InterruptedException {
+ mClients.get(clientID).waitForClientMessage(timeout);
+ }
+
+ /**
+ * Blocks caller method until the specified client is connected to the server.
+ *
+ * @param clientID ID of the client
+ * @throws InterruptedException if current thread is interrupted.
+ * @see {@link #defineClient}
+ */
+ public void waitForClient(int clientID) throws InterruptedException {
+ mClients.get(clientID).waitForClient();
+ }
+
+ /**
+ * Blocks caller method at most timeout milliseconds until
+ * the specified client is connected to the server.
+ *
+ * @param clientID ID of the client
+ * @param timeout timeout in milliseconds
+ * @throws InterruptedException if current thread is interrupted.
+ * @see {@link #defineClient}
+ */
+ public void waitForClient(int clientID, long timeout) throws InterruptedException {
+ mClients.get(clientID).waitForClient(timeout);
+ }
+
+ /**
+ * Blocks caller method until all clients are connected to the server.
+ *
+ * @throws InterruptedException if current thread is interrupted.
+ */
+ public void waitForAllClients() throws InterruptedException {
+ for (ClientHandler client : mClients)
+ client.waitForClient();
+ }
+
+ /**
+ * Blocks caller method at most timeout milliseconds until all
+ * clients are connected to the server.
+ *
+ * @param timeout timeout in milliseconds
+ * @throws InterruptedException if current thread is interrupted.
+ */
+ public void waitForAllClients(long timeout) throws InterruptedException {
+ for (ClientHandler client : mClients) {
+ long start = System.currentTimeMillis();
+ client.waitForClient(timeout);
+ long end = System.currentTimeMillis();
+ timeout -= end - start;
+ if (timeout <= 0)
+ return;
+ }
+ }
+
+ /**
+ * Returns number of connected clients.
+ *
+ * @return num of connected clients.
+ */
+ public int getNumberOfConnected() {
+ int noc = 0;
+ for (ClientHandler client : mClients)
+ if (client.isConnected())
+ noc++;
+ return noc;
+ }
+
+ /**
+ * States that a client is connected or not.
+ *
+ * @param clientID ID of the client
+ * @return true if the client is connected
+ * @see {@link #defineClient}
+ */
+ public boolean isConnected(int clientID) {
+ return mClients.get(clientID).isConnected();
+ }
+
+ @Override
+ public void listen(int port) {
+ super.listen(port);
+ acceptExecutor = Executors.newCachedThreadPool();
+ }
+
+ @Override
+ public void terminate() {
+ super.terminate();
+ acceptExecutor.shutdownNow();
+ }
+
}
diff --git a/src/server/network/JsonSocket.java b/src/server/network/JsonSocket.java
new file mode 100644
index 0000000..b946a05
--- /dev/null
+++ b/src/server/network/JsonSocket.java
@@ -0,0 +1,162 @@
+package server.network;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+import java.io.*;
+import java.net.Socket;
+
+/**
+ * This class is a wrapper for java's Socket that uses json strings
+ * to transmit objects.
+ * JsonSocket holds a Gson object to convert
+ * objects to json string and vice-versa, and a Socket to
+ * send and receive json strings.
+ * JsonSocket, one can pass an existing socket
+ * to the constructor of this class.
+ * JsonSocket was successfully created, one can send and
+ * receive objects with methods send and get.
+ * JsonSocket to send and receive objects correctly.
+ * Gson object is used to convert between java and json.
+ */
+ private Gson mGson;
+ /**
+ * The underlying Socket.
+ */
+ private Socket mSocket;
+ /**
+ * Input stream of the socket.
+ */
+ private InputStream mIn;
+ /**
+ * Output stream of the socket.
+ */
+ private OutputStream mOut;
+
+ /**
+ * Initiates new socket with specified host and port and uses
+ * that socket to transmit json strings.
+ *
+ * @param host the host name.
+ * @param port the port number.
+ * @throws IOException if an I/O error occurs when creating the socket or
+ * its input or output stream.
+ * @see java.net.Socket#Socket(java.lang.String, int)
+ * @see #JsonSocket(java.net.Socket)
+ */
+ public JsonSocket(String host, int port) throws IOException {
+ this(new Socket(host, port));
+ }
+
+ /**
+ * Uses an existing socket to transmit json strings.
+ *
+ * @param socket the socket object.
+ * @throws IOException if an I/O error occurs when creating the
+ * input or output stream of the socket.
+ */
+ public JsonSocket(Socket socket) throws IOException {
+ mGson = new Gson();
+ mSocket = socket;
+ mIn = mSocket.getInputStream();
+ mOut = mSocket.getOutputStream();
+ }
+
+ /**
+ * Closes the underlying socket.
+ *
+ * @throws IOException if an I/O error occurs when closing the socket.
+ * @see java.net.Socket#close
+ * @see #isClosed
+ */
+ public void close() throws IOException {
+ mSocket.close();
+ }
+
+ /**
+ * Returns the closed state of the socket.
+ *
+ * @return true if the socket has been closed
+ * @see java.net.Socket#isClosed
+ * @see #close
+ */
+ public boolean isClosed() {
+ return mSocket.isClosed();
+ }
+
+ /**
+ * Converts obj to json string and sends it via the socket's
+ * output stream.
+ *
+ * @param obj the object to send.
+ * @throws IOException if an I/O error occurs, e.g. when the socket is closed.
+ * @see com.google.gson.Gson#toJson(java.lang.Object)
+ */
+ public void send(Object obj) throws IOException {
+ String json = mGson.toJson(obj);
+ byte buffer[] = json.getBytes();
+ mOut.write(buffer, 0, buffer.length);
+ mOut.write('\0');
+ }
+
+ /**
+ * Reads a json string from socket's input stream, and converts it to
+ * a JsonObject.
+ *
+ * @return the received JsonObject
+ * @throws IOException if an I/O error occurs, e.g. when the socket is closed.
+ * @see com.google.gson.Gson#fromJson(String,java.lang.Class)
+ * @see #get(java.lang.Class)
+ */
+ public JsonObject get() throws IOException {
+ return get(JsonObject.class);
+ }
+
+ /**
+ * Reads a json string from socket's input stream, and converts it to
+ * a an object of the specified class.
+ *
+ * @param classOfInput object's class
+ * @param NetServer runs a
+ * server on the specified port and waits for the clients to be connected,
+ * and then based on the implementation of the accept method,
+ * performs some operation for each client,
+ * e.g. performs some operation based on the request,
+ * returns some information to the requester,
+ * or offers a terminal for the client.
+ * JsonSocket which is connected to the client. This is the initial
+ * point of the connection and one can send initial messages to start
+ * communication with the client.
+ * NetServer, this method is called, with a reference to
+ * a {@link server.network.JsonSocket} which is connected to the client side
+ * socket. A new thread may be assigned to handle requests of the client.
+ * NetServerThread is ran,
+ * it creates a new server socket to listen on that port. When a new client is
+ * connected to that port, it passes the client's
+ * {@link server.network.JsonSocket} to the specified consumer.
+ * clientAcceptor.accept(JsonSocket) is called when
+ * a client is connected to the port.
+ */
+ private ConsumerterminateFlag is set to
+ * true.
+ *
+ * @see #runServer
+ * @see #terminate
+ */
+ @Override
+ public void run() {
+ while (!terminateFlag)
+ try {
+ runServer();
+ } catch (Exception e) {
+ Log.i(TAG, "Server socket closed", e);
+ }
+ }
+
+ /**
+ * Runs the server and handles new clients. The method can not be completed
+ * without throwing an exception.
+ *
+ * @throws IOException if an I/O error occurs,
+ * e.g. when the server socket is closed.
+ * @see #terminate
+ */
+ private void runServer() throws IOException {
+ if (serverSocket != null && !serverSocket.isClosed())
+ serverSocket.close();
+ serverSocket = new ServerSocket(port);
+ while (!terminateFlag)
+ clientAcceptor.accept(new JsonSocket(serverSocket.accept()));
+ }
+
+ /**
+ * Terminates operations of the current thread. Sets the terminate flag
+ * to true and closes the server socket.
+ */
+ public synchronized void terminate() {
+ terminateFlag = true;
+ try {
+ serverSocket.close();
+ } catch (Exception e) {
+ Log.i(TAG, "Socket closing failure.", e);
+ }
+ }
+
+}
diff --git a/src/server/network/TerminalNetwork.java b/src/server/network/TerminalNetwork.java
index 015299d..1c78c91 100644
--- a/src/server/network/TerminalNetwork.java
+++ b/src/server/network/TerminalNetwork.java
@@ -1,7 +1,161 @@
package server.network;
+import server.core.model.Event;
+import server.network.data.Message;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
/**
- * Created by Razi on 12/6/2014.
+ * This class is responsible for create and handling the communication between users and the Terminal
+ * Terminal wants to start.
+ * Commands and Events.
+ * This interface is implemented in {@link server.core.GameHandler}.
+ * TerminalInterface has two methods called putEvent and runCommand
+ * that will be used for sending essential data to {@link server.core.GameHandler}.
+ */
+ public interface TerminalInterface {
+ void putEvent(Event event);
+ Message runCommand(Message command);
+ }
+
+ /**
+ * An instance of {@link server.network.TerminalNetwork} used to
+ */
+ private TerminalInterface handler;
+
+ @Override
+ protected void accept(JsonSocket client) {
+ new TerminalNetworkThread(client).start();
+ }
+
+ public TerminalNetwork(String token) {
+ this.token = token;
+ }
+
+ public void setHandler(TerminalInterface handler) {
+ this.handler = handler;
+ }
+
+ /**
+ * This thread is used by {@link server.network.TerminalNetwork}
+ * to handle multiple clients that want to start a terminal.
+ * Token.
+ * If user enter a correct Token then terminal actually starts.
+ * node.js client.
+ *