+/**\r
+ *\r
+ */\r
+package de.hamatoma.jatty;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.DataOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.net.ServerSocket;\r
+import java.net.Socket;\r
+import java.net.SocketException;\r
+import java.util.ArrayList;\r
+import java.util.Calendar;\r
+import java.util.List;\r
+\r
+/**\r
+ * Stores the properties of a connection for one client.\r
+ *\r
+ * @author hm\r
+ *\r
+ */\r
+class Connection extends Thread {\r
+ Socket connectionSocket;\r
+ Scrambler scrambler;\r
+ Server server;\r
+ UpdateStatus updateStatus = new UpdateStatus();\r
+ String user;\r
+\r
+ public Connection(final Socket connectionSocket, final Server server) {\r
+ this.connectionSocket = connectionSocket;\r
+ this.user = this.connectionSocket.getInetAddress().getHostAddress();\r
+ this.scrambler = server.cloneScrambler();\r
+ log("new connection: " + this.user);\r
+ this.server = server;\r
+ start();\r
+ }\r
+\r
+ /**\r
+ * @return the user name of the connection\r
+ */\r
+ public String getUser() {\r
+ return this.user;\r
+ }\r
+\r
+ public void log(final String message) {\r
+ System.out.println(message);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ *\r
+ * @see java.lang.Thread#run()\r
+ */\r
+ @Override\r
+ public void run() {\r
+ boolean again = true;\r
+ while (again) {\r
+ try {\r
+ final BufferedReader inFromClient = new BufferedReader(\r
+ new InputStreamReader(\r
+ this.connectionSocket.getInputStream()));\r
+ final DataOutputStream outToClient = new DataOutputStream(\r
+ this.connectionSocket.getOutputStream());\r
+ try {\r
+ String clientSentence = inFromClient.readLine();\r
+ clientSentence = this.scrambler.decode(clientSentence);\r
+ String answer = null;\r
+ if (clientSentence != null) {\r
+ if (!clientSentence.startsWith("/UPDATE"))\r
+ log("> " + clientSentence);\r
+ if (clientSentence.startsWith("/EXIT")) {\r
+ again = false;\r
+ } else if (clientSentence.startsWith("/LOG")) {\r
+ final String[] args = clientSentence.split("\\s+");\r
+ if (args.length > 1) {\r
+\r
+ this.user = this.server.findUnusedUser(args[1]);\r
+ this.server.setUpdateNoUser();\r
+ answer = this.server.getUpdate(this.updateStatus,\r
+ "/STATUS " + String.format(\r
+ I18N.tr(I18N.WELCOME2, "welcome"),\r
+ this.user));\r
+ } else {\r
+ answer = "/ERROR "\r
+ + I18N.tr(I18N.USERNAME,\r
+ "missing username and password: e.g.")\r
+ + " /LOG jonny s!ekret\n";\r
+ }\r
+ } else if (clientSentence.startsWith("/UPDATE")) {\r
+ answer = this.server.getUpdate(this.updateStatus, null);\r
+ } else {\r
+ final Calendar cal = Calendar.getInstance();\r
+ final String line = String.format(\r
+ "%02d:%02d:%02d %s: %s",\r
+ cal.get(Calendar.HOUR_OF_DAY),\r
+ cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND),\r
+ this.user, clientSentence);\r
+ this.server.putTalkLine(line);\r
+ answer = this.server.getUpdate(this.updateStatus, null);\r
+ log("< " + answer.substring(0, answer.length()));\r
+ }\r
+ if (answer != null)\r
+ outToClient.writeBytes(answer);\r
+ }\r
+ } catch (final SocketException e2) {\r
+ log("+++ connection lost");\r
+ again = false;\r
+ }\r
+ } catch (final IOException e) {\r
+ e.printStackTrace();\r
+ again = false;\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+/**\r
+ * A chat server.\r
+ *\r
+ * @author wk\r
+ *\r
+ */\r
+public class Server {\r
+ List<Connection> connections = new ArrayList<Connection>();\r
+ int maxTalkLines = 1024;\r
+ int port = 58123;\r
+ Scrambler scrambler;\r
+ List<String> talkLines = new ArrayList<String>();\r
+ int talkLinesOffset = 0;\r
+ /// stores the update number when the last login has been done\r
+ int updateNoUser = 0;\r
+\r
+ /**\r
+ * Constructor.\r
+ *\r
+ * @param port\r
+ * the port listened by the server\r
+ */\r
+ public Server(final int port, final Scrambler scrambler) {\r
+ this.scrambler = scrambler;\r
+ this.port = port;\r
+ }\r
+\r
+ /**\r
+ * Search a name which is not used by another connection\r
+ *\r
+ * @param user\r
+ * the wanted user name\r
+ * @return <code>user</code> if this name is not used<br>\r
+ * otherwise: a modification of the username\r
+ */\r
+ public String findUnusedUser(final String user) {\r
+ String rc = user;\r
+ if (userExists(user)) {\r
+ int no = 1;\r
+ while (userExists(rc = user + no))\r
+ no++;\r
+ }\r
+ return rc;\r
+ }\r
+\r
+ /**\r
+ * @return a clone of the the scrambler\r
+ */\r
+ public Scrambler cloneScrambler() {\r
+ Scrambler rc = null;\r
+ try {\r
+ rc = (Scrambler) this.scrambler.clone();\r
+ } catch (final CloneNotSupportedException e) {\r
+ e.printStackTrace();\r
+ }\r
+ return rc;\r
+ }\r
+\r
+ /**\r
+ * Returns the data not yet reported to the client.\r
+ *\r
+ * @param updateStatus\r
+ * IN/OUT: the "ages" of the resources\r
+ * @param message\r
+ * <code>null</code> or an additional message to send\r
+ * @return the lines not yet sent as a string\r
+ */\r
+ public String getUpdate(final UpdateStatus updateStatus,\r
+ final String message) {\r
+ final StringBuilder buffer = new StringBuilder();\r
+ synchronized (this) {\r
+ final int count = this.talkLines.size();\r
+ final int start = updateStatus.getMessageNo()\r
+ - this.talkLinesOffset;\r
+ if (start < count) {\r
+ for (int ix = start; ix < count; ix++) {\r
+ buffer.append(this.scrambler.encode(this.talkLines.get(ix)))\r
+ .append('\n');\r
+ }\r
+ updateStatus.setMessageNo(this.talkLinesOffset + count);\r
+ }\r
+ if (updateStatus.getUserNo() < this.updateNoUser) {\r
+ final StringBuilder buffer2 = new StringBuilder();\r
+ buffer2.append("/USER");\r
+ for (final Connection connection : this.connections) {\r
+ buffer2.append(' ').append(connection.getUser());\r
+ }\r
+ buffer2.append('\n');\r
+ buffer.append(this.scrambler.encode(buffer2.toString()));\r
+ updateStatus.setUserNo(this.updateNoUser);\r
+ }\r
+ }\r
+ if (message != null) {\r
+ buffer.append(this.scrambler.encode(message)).append('\n');\r
+ }\r
+ return buffer.toString();\r
+ }\r
+\r
+ /**\r
+ * Writes a message to stdout.\r
+ *\r
+ * @param message\r
+ * the message to write\r
+ */\r
+ public void log(final String message) {\r
+ System.out.println(message);\r
+ }\r
+\r
+ /**\r
+ * Collects the answer string list.\r
+ *\r
+ * Only the missing information will be returned.\r
+ *\r
+ * @param updateNo\r
+ * the "age" of the message list since the last query of this\r
+ * connection\r
+ * @param message\r
+ * <code>null</code> or an additional line of the answer string\r
+ * list\r
+ * @return\r
+ */\r
+ public String prepareAnswer(final int updateNo, final String message) {\r
+ final StringBuilder buffer = new StringBuilder();\r
+ synchronized (this) {\r
+\r
+ }\r
+ return buffer.toString();\r
+ }\r
+\r
+ /**\r
+ * Stores a chat message into the message list.\r
+ *\r
+ * @param line\r
+ * the line to store\r
+ */\r
+ public void putTalkLine(final String line) {\r
+ synchronized (this) {\r
+ if (this.talkLines.size() >= this.maxTalkLines) {\r
+ this.talkLines.remove(0);\r
+ this.talkLinesOffset++;\r
+ }\r
+ this.talkLines.add(line);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Removes a connection from the connection list.\r
+ *\r
+ * @param connection\r
+ */\r
+ public void removeConnection(final Connection connection) {\r
+ this.connections.remove(connection);\r
+ }\r
+\r
+ /**\r
+ * Runs the chat server.\r
+ */\r
+ public void run() {\r
+ ServerSocket welcomeSocket = null;\r
+ log("jatty " + Jatty.VERSION);\r
+ log("0.0.0.0:" + this.port);\r
+ try {\r
+ welcomeSocket = new ServerSocket(this.port);\r
+ while (true) {\r
+ final Socket socket = welcomeSocket.accept();\r
+ final Connection connection = new Connection(socket, this);\r
+ this.connections.add(connection);\r
+ }\r
+ } catch (final Exception e) {\r
+ e.printStackTrace();\r
+ } finally {\r
+ if (welcomeSocket != null)\r
+ try {\r
+ welcomeSocket.close();\r
+ } catch (final IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ log(I18N.tr(I18N.SERVER1, "server stopped"));\r
+ }\r
+\r
+ /**\r
+ * Increments the "age" of user list.\r
+ */\r
+ public void setUpdateNoUser() {\r
+ synchronized (this) {\r
+ this.updateNoUser++;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Tests whether a given user exists.\r
+ *\r
+ * @param user\r
+ * username to test\r
+ * @return <code>true</code>: a user with the given name exists\r
+ */\r
+ public boolean userExists(final String user) {\r
+ boolean rc = false;\r
+ synchronized (this) {\r
+ for (final Connection connection : this.connections) {\r
+ if (connection.getUser().equals(user)) {\r
+ rc = true;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ return rc;\r
+ }\r
+}\r
+\r
+/**\r
+ * A utility class for storing "ages" of the changeable resources.\r
+ *\r
+ * @author hm\r
+ *\r
+ */\r
+class UpdateStatus {\r
+ int messageNo = 0;\r
+\r
+ int userNo = 0;\r
+\r
+ /**\r
+ * @return the messageNo\r
+ */\r
+ public int getMessageNo() {\r
+ return this.messageNo;\r
+ }\r
+\r
+ /**\r
+ * @return the userNo\r
+ */\r
+ public int getUserNo() {\r
+ return this.userNo;\r
+ }\r
+\r
+ /**\r
+ * @param messageNo\r
+ * the messageNo to set\r
+ */\r
+ public void setMessageNo(final int messageNo) {\r
+ this.messageNo = messageNo;\r
+ }\r
+\r
+ /**\r
+ * @param userNo\r
+ * the userNo to set\r
+ */\r
+ public void setUserNo(final int userNo) {\r
+ this.userNo = userNo;\r
+ }\r
+}
\ No newline at end of file